open-source vs. saas: simplifying code differentiation with git
of the four common strategies — a private fork, an enterprise branch, private submodules, and per-tier feature flags in one repo — only the last two scale past a small team, and flags-in-one-repo is the only one where every fix lands in every edition automatically. forks and long-lived branches optimize for week one and tax every week after.
strategy 1: the private fork
fork the public repo, add paid features privately, merge upstream 'regularly'. in practice the merges decay from weekly to quarterly to never, because every divergence makes the next merge worse. the fork also poisons community trust subtly: contributors sense that the public repo is a mirror, not the product.
strategy 2: the enterprise branch
same repo, long-lived branch for the paid edition. better than a fork — at least the history is shared — but the branch still accumulates conflicts, and ci has to build and test two diverging lines forever. release day means rebasing the paid branch onto the release tag and praying.
strategy 3: private submodules / packages
keep genuinely proprietary code in a private package the commercial build imports. this is a sound pattern — gitlab and many open-core companies use a variant of it — but on its own it answers only 'where does secret code live', not 'which edition enables what'. you still need a packaging layer that says what each edition turns on.
strategy 4: one repo, tiers as flag config
the differentiation moves out of git topology entirely. main is the only long-lived line; tiers (open-source, saas, pro) are named configurations; each feature that differs is a flag with per-tier values; builds read generated config. git does what it's good at — one history — and packaging becomes a reviewable data file.
- every bugfix reaches every edition at the next build, by construction.
- the public repo is the real product, which keeps contributors and auditors honest.
- combine with strategy 3 for the rare truly-private module: flags decide activation, the private package holds the secret bits.
the comparison in one line
forks duplicate history, branches duplicate effort, submodules isolate secrets, and per-tier flags isolate decisions. you want decisions isolated — that's the part that changes with your pricing page. tier·dev manages exactly that layer: the tiers, the flags, and the generated per-tier config you commit.
see this on your own repo
tier·dev turns the open-source / saas difference into per-tier feature flags — one repo, generated config, no sdk.
faq
- doesn't gitlab famously use a separate ee codebase?
- they famously moved away from it — merging ce and ee into a single codebase in 2019 because maintaining two was, in their words, a drag on velocity. the single-repo direction is the lesson, not the exception.
- is a monorepo the same idea?
- related but orthogonal. a monorepo co-locates many projects; tier-flagging packages one project many ways. you can do either without the other.
- what belongs in a private package rather than behind a flag?
- code whose mere visibility is the problem: licensed third-party integrations, security-sensitive logic, partner work under nda. everything else ships dark behind a flag.