Git Flow, GitHub Flow and trunk-based
Every team agrees, out loud or not, on how work flows into main. Meet the three common models, what each one optimises for, and run two weeks of a feature to feel why long-lived branches hurt.
Branches cost nothing in git, so a team can arrange them any way it likes. That freedom is the problem: someone has to decide which branches live for years, which live for hours, and how work gets from one to another. That decision is a branching strategy, and the three you’ll meet most are Git Flow, GitHub Flow and trunk-based development.
None of them is “correct”. Each optimises for something different.
Git Flow: release lines for versioned software
In 2010 Vincent Driessen published a model that became known as git-flow. It keeps two branches forever: master (now usually main), where the code “always reflects a production-ready state”, and develop, the integration branch that “reflects a state with the latest delivered development changes for the next release”. Short-lived supporting branches feed them:
- feature branches start from
developand merge back into it. - release branches “support preparation of a new production release”. They start from
developand merge into bothmainanddevelop. - hotfix branches “arise from the necessity to act immediately upon an undesired state of a live production version”. They start from
mainand merge into both.
- Two branches live forever: main, always production-ready, and develop, where the next release comes together.
- Each feature gets its own branch from develop and merges back into it when done.
- To ship, cut a release branch from develop, finish it there, then merge it into main - and back into develop.
- A production emergency gets a hotfix branch from main, merged back into main and into develop.
Driessen merges with --no-ff, which “causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward”, so the history keeps the fact that a feature branch existed.
Ten years later he added a note of reflection that matters as much as the model. Web apps, he wrote, “are typically continuously delivered, not rolled back, and you don’t have to support multiple versions of the software running in the wild” - not the kind of software he had in mind. “If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team.” For software that “is explicitly versioned”, or where you support several versions at once, git-flow “may still be as good of a fit”. His last word: “Consider your own context.”
GitHub Flow: one main, short branches, pull requests
GitHub describes its flow as “a lightweight, branch-based workflow” with six steps:
- Create a branch
- Make changes
- Create a pull request
- Address review comments
- Merge your pull request
- Delete your branch
There is no develop and no release branch: work branches from main and comes back to main through a pull request, with your pipeline as the required checks.
Trunk-based development: integrate all the time
trunkbaseddevelopment.com defines it as a model “where developers collaborate on code in a single branch called ‘trunk’” and “resist any pressure to create other long-lived development branches”. Short-lived feature branches are still used “for code-review and build checking (CI)”.
DORA’s research is the reason many teams moved this way. Its capability page says branches in trunk-based development “typically last no more than a few hours”, and that the higher-performing teams in its 2016 and 2017 data:
- “Have three or fewer active branches in the application’s code repository.”
- “Merge branches to trunk at least once a day.”
- “Don’t have code freezes and don’t have integration phases.”
The obvious objection: how do you merge a feature to trunk every day when it takes two weeks to build? With a feature flag (Pete Hodgson calls them feature toggles). A release toggle lets “incomplete and un-tested codepaths” ship “to production as latent code” - in trunk, built and tested with everything else, but switched off.
- A change lives on its own branch for hours, not weeks.
- Review and CI run on the pull request, then it merges into trunk - at least once a day.
- Trunk stays releasable and deploys to users.
- Half-built features merge too, behind a flag. They are integrated and tested every day, but users cannot reach them until the flag goes on.
Why long-lived branches hurt
Pro Git calls a topic branch “a short-lived branch that you create and use for a single particular feature or related work”. The longer one lives, the more the rest of the team changes underneath it, and every file both sides touched has to be reconciled when you finally merge - usually long after you remember why you changed it.
Try it. In this model, you and the team each change a few files every day for two weeks. A merge has to reconcile every file both sides changed since you last integrated. Merging less often doesn’t just batch that work, it adds to it: changes that would have gone in one after another now collide.
Choosing
| Model | Optimises for | Fits when |
|---|---|---|
| Git Flow | Separate, stabilised release lines | Software that “is explicitly versioned”, or several supported versions in the wild (Driessen) |
| GitHub Flow | Simplicity: one main, short branches, review |
Continuously delivered software - Driessen’s own suggestion for it |
| Trunk-based | Integrating constantly, so merges stay small | Teams that merge at least daily and can hide unfinished work behind flags (DORA) |
Your team deploys a web app to production several times a day, and only one version is ever live. What did git-flow's own author suggest?
Driessen's 2020 note: for continuously delivered software, adopt a much simpler workflow (like GitHub flow) instead of shoehorning git-flow in. Git-flow fits explicitly versioned software with several supported versions.
A feature takes two weeks. How does a trunk-based team still merge it to trunk every day?
A release toggle lets incomplete code ship as latent code: in trunk, integrated and tested with everyone else's changes, but switched off until the feature is done.
In the two-week model, why did merging every day mean less pain than merging once at the end?
Daily merges had almost nothing to reconcile - one file in two weeks. One big merge reconciles eight at once, two weeks later, when nobody remembers why.
Everything above ran in your browser. Now try it in a real terminal: any Linux machine, a Mac's Terminal app, or Windows with WSL. Nothing to install beyond that.
Find out what strategy your own repository actually follows - by how long branches live, not by what the wiki says. In any clone with a remote:
git fetch --prune
# remote branches, most recently updated last, with their age
# (origin/HEAD is a pointer to the default branch, not a branch - skip it)
git for-each-ref --sort=committerdate --format='%(committerdate:relative)%09%(refname:lstrip=2)' refs/remotes | grep -v '/HEAD$'
# how many branches exist at all
git for-each-ref --format='%(refname:lstrip=2)' refs/remotes | grep -vc '/HEAD$'Branches that are weeks or months old are long-lived, whatever they’re called. DORA’s high performers had three or fewer active branches - how does yours compare?
Sources
- Vincent Driessen - A successful Git branching model (2010, with a note of reflection, 2020)
- GitHub Docs - GitHub flow
- trunkbaseddevelopment.com
- DORA - Capabilities - Trunk-based development
- Pro Git, chapter 3.4 - Branching Workflows
- Pete Hodgson - Feature Toggles (aka Feature Flags), martinfowler.com (2017)
Missions and quizzes above earn their own XP. This marks the lesson done.