Every branching strategy is an answer to one question: how long may a branch live before merging? Everything else — the diagrams, the branch names, the ceremony — follows from that number.
Topic 1: Three Strategies, One Axis
Trunk-based — branches live hours. Everyone integrates into main at least daily. Unfinished work hides behind feature flags. Fewest conflicts of any strategy, and the highest bar for supporting practice: you need fast, trustworthy CI and a real ability to deploy from main at any moment.
GitHub flow — branches live days. One long-lived branch (main), short feature branches, PR review, deploy from main. The pragmatic default for a service deployed continuously, and what most teams should use.
GitFlow — branches live weeks. main, develop, feature/*, release/*, hotfix/*. Designed for versioned software shipped on a schedule to customers who install it. On a web service deployed daily it adds a permanent merge tax and buys nothing.
| Trunk-based | GitHub flow | GitFlow | |
|---|---|---|---|
| Long-lived branches | 1 | 1 | 2 (main + develop) |
| Branch lifetime | Hours | Days | Weeks |
| Merge conflicts | Rare | Occasional | Frequent and large |
| Requires feature flags | Yes | Sometimes | No |
| Requires strong CI | Yes | Yes | Helps |
| Supports many live versions | Poorly | Poorly | Yes |
| Release cadence | Continuous | Continuous | Scheduled |
The honest recommendation: GitHub flow unless you have a specific reason. Trunk-based if you have the CI and flag discipline to support it and want the fastest integration. GitFlow only if you genuinely ship versioned releases and support more than one at a time — a mobile app with a review process, an on-premises product, firmware.
Topic 2: Why Long-Lived Branches Cost More Than They Look
The cost is not linear in time, which is why “just one more week” is such a reliable trap:
- Conflicts grow superlinearly. Two weeks of divergence is far more than twice the pain of one.
- Feedback is delayed. A design flaw found at merge time is a rewrite; found on day one it is a conversation.
- CI tests a fiction. The branch is green against a
mainthat no longer exists. - Review degrades. A 3,000-line diff receives approval, not review.
- The merge becomes an event — scheduled, feared, and requiring the author to be present.
The fix is almost never a better branching strategy. It is smaller changes: merge the refactor separately, merge behind a flag, merge the interface before the implementation.
Topic 3: Feature Flags — the Enabler for Short Branches
A feature flag decouples merging from releasing, which is what makes daily integration of unfinished work possible:
if flags.enabled("new-checkout", user):
return new_checkout(user)
return old_checkout(user)
What that buys: incomplete code merges to main safely, the feature turns on for 1% then 50% then everyone, and turning it off is a config change rather than a rollback.
What it costs, and this is the part teams underestimate: every flag is a branch in the code, and n flags mean 2ⁿ possible states, of which you test perhaps three. Flags that outlive their rollout become permanent complexity.
The discipline that keeps it manageable: an owner and an expiry date per flag, a periodic sweep that deletes flags fully rolled out, and a hard rule that a flag lives weeks, not quarters. A repository with forty flags has traded merge pain for a worse kind of complexity.
Topic 4: Release Branches and Hotfixes
Even trunk-based teams need a release branch when they must support a version they cannot immediately update.
git switch -c release/2026.08 main
git tag -a v2026.08.0 -m "Release 2026.08.0"
git push origin release/2026.08 --tags
The rule for fixes: always fix on main first, then cherry-pick to the release branch. Not the other way round.
# fix lands on main as 9f8e7d
git switch release/2026.08
git cherry-pick -x 9f8e7d # -x records the source commit
git tag -a v2026.08.1 -m "Fix token expiry"
git push origin release/2026.08 --tags
Fixing on the release branch first and merging back is how a bug gets re-introduced in the next release: somebody forgets the merge-back, and the fix exists only on a branch that is about to be abandoned. Fixing forward and cherry-picking backward means the fix is on main by construction.
Emergency hotfix, same principle, compressed:
1. Branch from the TAG that is in production, not from main
2. Minimal fix, tested
3. Tag, deploy
4. Cherry-pick or merge the fix into main immediately — before the incident review
Step 4 is the one skipped under pressure, and skipping it means the next deploy silently reverts the hotfix. Put it in the incident checklist.
Topic 5: Monorepo vs Many Repositories
The other structural decision, and it interacts with everything above.
| Monorepo | Multi-repo | |
|---|---|---|
| Atomic cross-project change | One commit | Coordinated PRs, ordering matters |
| Dependency versions | One version, always current | Pinned, and drift accumulates |
| CI | Needs change-detection to stay fast | Naturally scoped |
| Access control | All-or-nothing (Git’s limitation) | Per repository |
| Clone size | Grows with everything | Small |
| Tooling | Needs investment: sparse checkout, build graph | Works out of the box |
Neither is right in general. What is reliably wrong is a monorepo without the tooling investment — one that clones for eleven minutes and runs every test on every change — or a multi-repo estate where a single logical change requires five coordinated merges in a specific order.
The Git features that make a large monorepo bearable — sparse checkout, partial clone, worktrees, scheduled maintenance — are covered in the repository-scale lesson.
Topic 6: Writing the Convention Down
A strategy that lives in people’s heads is not a strategy. What the document should contain, and nothing more:
Branch names feature/*, fix/*, chore/*, release/*
Base branch always main
Merge strategy squash (one of the three, chosen)
Branch lifetime merge within 2 days, or explain in the PR
Required checks build, unit, lint — enforced by branch protection
Review 1 approval, CODEOWNERS for protected paths
Release tag from main, semver, annotated tags only
Hotfix branch from the tag, fix, tag, cherry-pick to main
Flags every flag has an owner and a removal date
Then enforce it in the forge, not in review comments. Branch protection rules, required status checks, an allowed-merge-strategy setting, automatic branch deletion. A convention enforced by a human is a convention that erodes on the first busy week.
Try it yourself: compute the median lifetime of your last fifty merged branches. Teams almost always guess a number well below the real one, and that gap — not the diagram on the wiki — is what actually determines how much merge pain the team experiences.
Common mistake: adopting GitFlow because it appears in every search result, on a service that deploys twelve times a day. The develop branch becomes a second main that must be kept in sync, every change is merged twice, release branches linger, and the team concludes that Git is complicated. The strategy is not wrong; it is answering a question — how do we support three shipped versions at once — that the team does not have.