DEV Community

Mikhail Dorokhovich
Mikhail Dorokhovich

Posted on

Trunk-Based Development vs GitFlow: The Principle Is to Match Your Branches to Your Cadence

The problem in context

A lot of repositories are museums of good intentions. Feature branches live for weeks, sometimes months, drifting further from the mainline every day until merging one means a full day of conflict archaeology. main has no protection, so a direct push can — and eventually does — destabilize a release. Hotfixes go out and never get back-merged, so the same bug reappears two releases later. And because unrelated features get mixed into a single release branch, no release is ever truly reproducible.

The reflex is to blame Git, or to reach for whatever branching model a louder team swears by. But the underlying issue in the perennial trunk-based development vs GitFlow debate is subtler: a branch in Git is just a lightweight pointer to a commit, so creating, merging, and deleting branches is cheap — which means long-lived branches are a choice, not a necessity. The teams drowning in conflicts are almost always shipping often while branching as if they shipped quarterly. That mismatch, not the tool and not the model, is the problem.

The principle

The principle here is that branch lifetime should track shipping frequency: the more often you ship, the shorter your branches must live and the stricter your CI gates into the mainline must be. Pick the model to match the cadence, rather than inheriting one by accident.

That reframe dissolves the "which model is correct" argument, because branching models have a clear arc and each fits a different cadence. From 2008–2012 it was GitFlow with many long-lived branches; 2013–2018 simplified to GitHub Flow; from 2018 onward the industry moved to trunk-based development with short branches and feature flags. GitFlow is not wrong — its own author now notes it suits versioned software with multiple supported releases rather than continuously deployed web apps. If releases are infrequent and you run many parallel supported versions, its explicit release and hotfix branches are genuinely clearer. If you ship frequently, the answer is trunk-based plus feature flags plus strict CI. There is a detailed treatment of choosing and running a branching model that walks the full decision; the compressed rule is: cadence dictates model.

The second, deeper principle is to know what branches are actually for, because every merge-hell symptom is one of their benefits inverted. Branches buy exactly four things:

  • Risk isolation — unfinished work does not break the stable line. (Long-lived branches invert this by drifting.)
  • Parallelism — features, releases, and hotfixes proceed at once.
  • Quality control — PRs, mandatory reviews, green-CI gates. (An unprotected main inverts this.)
  • Traceability — commits, tags, and release notes document exactly what shipped. (Mixed release branches invert this.)

Reframing the goal as "preserve those four properties" is what makes the specific rules feel principled rather than arbitrary. It also fixes the vocabulary: main is the always-deployable, protected production source of truth; develop is an optional integration buffer for sprint cadences; feature/* is short-lived and one-PR-per-goal; release/* is stabilization only (fixes and version/CHANGELOG bumps, never new features); hotfix/* branches off main and integrates back into both main and develop.

Trade-offs

The choice is not binary good-vs-bad; it is a fit question. The honest comparison:

Dimension GitFlow (long-lived branches) Trunk-based (short branches + flags)
Best-fit cadence Infrequent, versioned releases; multiple supported versions Frequent / continuous deployment
Merge cost Grows with branch age — conflict archaeology Small and frequent
Release reproducibility Clear via explicit release branches Clear via trunk + tags + flags
Incomplete work Hidden in a long branch Merged behind a feature flag
CI strictness required Moderate High — the mainline gate is load-bearing
Main failure mode Parallel branches drift out of sync Undisciplined flags accumulate as debt

The trade-off worth stating plainly: trunk-based buys small merges and fast flow, but only if you pay for it with strict CI and feature-flag discipline — flags are what make merging incomplete work safe, and a flag with no owner or expiry is future debt. GitFlow buys clean parallel-version management, but the bill arrives as merge cost that scales with branch age. Choosing trunk-based while keeping weeks-long branches gives you the costs of both and the benefits of neither.

How to adopt

Do not adopt a whole new model overnight — that is the failure mode, and it invites the resistance you fear. Sequence by leverage.

  1. Protect main first. Require reviews and green CI; forbid direct pushes. This is the single highest-payoff change: the moment direct pushes are impossible, "unstable release" incidents essentially stop.
  2. Shorten feature branches. Aim for hours to a couple of days. Add a lightweight branch-age report to CI that flags any feature branch older than a few days, so drift becomes visible before it becomes painful. Making the invisible cost visible changes behaviour more than any policy memo.
  3. Adopt one release lifecycle and walk every release through it. Preparation branches off a clean mainline:
git switch develop && git pull --ff-only
git switch -c release/1.8.0
git commit -am "chore(release): bump to 1.8.0 & update CHANGELOG"
Enter fullscreen mode Exit fullscreen mode

The release merges into main with a no-fast-forward merge and an annotated tag, then — the step teams skip — back-integrates into develop so nothing is lost:

git switch main && git pull --ff-only
git merge --no-ff release/1.8.0 -m "release: 1.8.0"
git tag -a v1.8.0 -m "Release 1.8.0"
git push origin main --tags
Enter fullscreen mode Exit fullscreen mode

Hotfixes follow the same discipline in miniature: branch off main, fix, merge back into both main and develop. Skipping that final back-merge is the exact bug that resurrects old defects. This release/hotfix structure is the part worth keeping from Vincent Driessen's original model even as you shorten everything else toward trunk.

  1. Make the good path the quick path. Standardize a handful of commands and aliases so the right thing is also the easy thing:
git switch -c feature/login
git fetch --prune
git pull --ff-only
git switch -c release/2.0.0 develop
git switch main && git merge --no-ff release/2.0.0 && git tag -a v2.0.0 -m "Release 2.0.0"
Enter fullscreen mode Exit fullscreen mode

Drill one rule hardest: never rebase a published branch — rewriting shared history breaks teammates; for public branches prefer merge --no-ff. Pair that with Conventional Commits and auto-generated changelogs and semantic releases come almost for free.

The cultural surprise is that resistance rarely materializes once aliases and protected main are in place. Nobody enjoys conflict archaeology, so a workflow that quietly removes it sells itself after the first clean release. You do not mandate discipline so much as remove the friction that was rewarding the bad habits.

Where this goes next

The direction of travel is trunk-based plus feature flags, branch protection expressed as policy-as-code, and automated releases that compress the cycle to hours. Flags are the piece that makes short branches safe — you merge incomplete work behind a flag instead of hiding it in a long-lived branch — and combined with canary or blue-green delivery, branching stops being a source of risk and becomes bookkeeping.

The further horizon is that as more of the commit-to-release path gets handled by automation and AI-assisted tooling — bots that open, review, and land small changes; agents that assemble release notes from conventional commits; policy engines that gate merges on live quality signals — the value of a short, linear, well-tagged history compounds. Automated systems reason far more reliably over a clean trunk than over a thicket of drifting branches. The teams that match their branching to their cadence today are the ones whose history will be legible enough for the next generation of tooling to safely act on.

Sources & further reading

Top comments (0)