Branching strategies
A team’s rules for how changes flow into the shared code and out to releases: which branches exist, how long they live, and how they get merged.
How it works
Every team that shares a repository needs an agreement about branches, and the main variable is how long work stays apart before it is integrated. In trunk-based development everyone merges small changes into one branch (trunk or main) at least once a day; branches, if used at all, last hours, and unfinished features hide behind feature flags rather than living on a branch. GitHub flow is a light version for teams that deploy continuously: main is always deployable, each change gets a short branch and a pull request, and a merged pull request is deployed straight away. Git flow, published by Vincent Driessen in 2010, keeps two permanent branches, main (originally master) for released code and develop for the next release, plus feature, release and hotfix branches; it suits software shipped in numbered versions. Any of these can add release branches: a branch cut from the mainline to stabilize one version while new work continues, with fixes made on the mainline and cherry-picked across. The longer a branch lives, the further it drifts and the bigger and riskier its merge. The last choice is how a branch lands: a merge commit keeps its commits and records the join, a rebase replays its commits on top of main for a straight-line history, and a squash merge folds the whole branch into one commit.
Writers on a shared document. One team types into the live draft all day in small edits; another writes chapters in private copies for weeks and then faces a painful afternoon pasting them back in.
Reach for it when
- Trunk-based or GitHub flow: a web service deployed many times a day, with fast automated tests
- Git flow or release branches: installed software, libraries or firmware where several versions are supported at once
- Feature flags: you want to merge unfinished work daily without exposing it to users
Think twice when
- Long-lived feature branches that take weeks to merge; split the work or put it behind a flag
- Git flow for a continuously deployed web app; its own author now recommends something simpler
- Rebasing commits other people have already pulled; it rewrites history they have built on
A real example
A team of eight runs a SaaS product on Git flow, with a release branch every two weeks and a two-day merge-and-stabilize ritual before each one. They switch to trunk-based development: each change is a branch of a few hours with a pull request, CI runs on every push, and a half-built billing page ships to production switched off behind a feature flag until it is ready. They delete the develop branch, and production now gets several small deployments a day instead of one big one every fortnight.
The strategies side by side#
| Strategy | Long-lived branches | Typical branch life | How a release happens | Fits |
|---|---|---|---|---|
| Trunk-based development | Only trunk (release branches optional) | A few hours | Deploy from trunk, or cut a release branch when it’s time | Teams with fast tests and feature flags, at any size |
| GitHub flow | Only main | One pull request’s worth of work | Each merged pull request is deployed at once | Web apps and services deployed continuously |
| Git flow | main and develop | Feature branches last as long as the feature | A release branch from develop is stabilized, merged to main and tagged | Explicitly versioned software with several versions in use |
| Release branches | One per supported version | Until that version is retired, sometimes years | Only stabilizing fixes go in; new work stays on the mainline | Products with versions in the field that need patches |
- DORA’s guidance for trunk-based development: three or fewer active branches, merge to trunk at least once a day, and no code freezes or integration phases.
Merge, rebase or squash#
Hosts such as GitHub let a repository allow or enforce each method.
Merge commit
--no-ffEvery commit from the branch is kept, plus a merge commit that records when the branch joined. Nothing is rewritten, but the history branches and rejoins, which some find hard to read.
Rebase and merge
linearThe branch’s commits are replayed on top of main, so history reads as one straight line. The replayed commits get new hashes, so never rebase commits that others have already based work on.
Squash and merge
one commitThe whole branch becomes one commit on main, so work-in-progress commits disappear and main has one commit per change. The branch’s individual steps are lost from main’s history.
In code · bash
# Three ways to land the branch fix-cart on main
# 1. Merge commit: keeps every branch commit plus a commit that joins them
git switch main && git merge --no-ff fix-cart
# 2. Rebase: replay the branch on top of main, then fast-forward (straight history)
git switch fix-cart && git rebase main
git switch main && git merge --ff-only fix-cart
# 3. Squash: the whole branch becomes one new commit on main
git switch main && git merge --squash fix-cart
git commit -m "Fix rounding in cart totals"Often confused with
Where it came from1999–2020
Branching is as old as version control, and so is the pain of merging. Large merges of long-separated work often brought bugs and regressions, so teams held code freezes and integration phases before each release. In the 1990s Microsoft’s daily builds and then Extreme Programming’s continuous integration pushed the other way: merge everything into one mainline at least daily so merges stay small. When Google adopted Perforce it worked trunk-based from the outset, and Paul Hammant later collected the techniques at trunkbaseddevelopment.com.
Git made branches cheap and fast, and teams started using many of them. In January 2010 Vincent Driessen published “A successful Git branching model”, with permanent master and develop branches plus feature, release and hotfix branches, and git-flow became one of the most common policies around. In August 2011 Scott Chacon explained why GitHub didn’t use it: GitHub deployed to production every day, often several times, so it had no “releases” to manage. GitHub flow had one rule at its core: anything in master is deployable.
Research and hindsight then favored integrating often. Analysis of DORA’s 2016 and 2017 survey data found that teams with three or fewer active branches, daily merges to trunk and no code freezes achieved higher delivery speed, stability and availability. In 2017 Pete Hodgson described release toggles as the feature flags that make trunk-based development possible. In March 2020 Driessen added a note to his post: for continuously delivered software, adopt a simpler workflow like GitHub flow; git-flow still fits software that is explicitly versioned.
Why it’s built this way
- Integrate at least daily
- Large merges of long-lived branches often introduce bugs and lead to code freezes. Merging small batches into trunk every day keeps each merge small and the mainline always working.
- Hide unfinished work behind flags, not on branches
- A release toggle lets incomplete code ship to production switched off, so it can be merged daily. The cost is extra code paths to test and old flags to remove, which Hodgson calls the carrying cost of toggles.
- A separate
developbranch in Git flow - Driessen wanted every commit on
masterto be a production release by definition, so integration happened ondevelop. That makes sense when releases are planned events, and adds overhead when every merge is a release. - Fix on the mainline, then cherry-pick to the release
- Fixes made only on a release branch are easy to forget to copy back, and the regression reappears in the next version. Fowler notes that many teams make the fix on the mainline first and cherry-pick it into the release branch.
Timeline
- 1999
Extreme Programming popularizes continuous integration into one mainline
- 2010
Vincent Driessen publishes the git-flow branching model in January
- 2011
Scott Chacon describes GitHub flow in August
- 2017
Pete Hodgson’s “Feature Toggles” describes release toggles for trunk-based teams
- 2020
Driessen’s note of reflection (March); Martin Fowler’s “Patterns for Managing Source Code Branches” (May)
- Trunk Based Development: Game changers (Microsoft daily builds, XP, Google on Perforce)
- Vincent Driessen: A successful Git branching model (Jan 2010, note of reflection Mar 2020)
- Scott Chacon: GitHub Flow (Aug 31, 2011)
- DORA: Capabilities, trunk-based development
- Pete Hodgson: Feature Toggles (aka Feature Flags), martinfowler.com (Oct 2017)
- Martin Fowler: Patterns for Managing Source Code Branches (May 2020)
Sources9
- Trunk Based Development: Introduction (Paul Hammant)
- DORA: Capabilities, trunk-based development (branch life, three or fewer branches, no code freezes)
- GitHub Docs: GitHub flow
- Scott Chacon: GitHub Flow (deploy immediately after merge)
- Vincent Driessen: A successful Git branching model (main, develop, feature, release and hotfix branches)
- Martin Fowler: Patterns for Managing Source Code Branches (release branches, cherry-picks)
- Pete Hodgson: Feature Toggles (release toggles, carrying cost)
- GitHub Docs: About merge methods on GitHub (merge commit, squash, rebase)
- Pro Git: Rebasing (linear history, and don’t rebase shared commits)