A branching strategy is a team's agreement about how branches are created, used and merged. Without one, a shared repository fills up with half-finished work and painful merges. With the right one, main stays releasable and everyone knows where their changes go.
Why a team needs a strategy
Git makes branches cheap: creating one takes a moment and costs almost nothing. That is exactly why a team needs rules for them.
If everyone commits straight to main with no review and no tests, one broken commit breaks the build for everyone, and there is never a point where main is known to be safe to ship. At the other extreme, if every developer keeps their own branch for weeks, those branches drift apart. When they finally merge, the conflicts are large, hard to understand and easy to get wrong.
A branching strategy sits between those extremes. It answers a few practical questions:
- Where does new work start, and where does it end up?
- How long should a branch live?
- What has to happen before code reaches
main: review, tests, or both? - How is a release cut, and how does an urgent fix get out?
The three strategies below answer those questions differently. None is right for every team: each trades structure against speed.
Git Flow: a branch for every job
Git Flow was popularised by Vincent Driessen's post 'A successful Git branching model'. It uses two long-lived branches and three kinds of short-lived ones.
The five branch types
mainholds released code only (the original post called itmaster). Every commit on it is a version that went to production, usually tagged, such asv1.1.developis where finished features collect before a release. It is the integration branch.- Feature branches start from
developand merge back intodevelopwhen the feature is done. They never touchmaindirectly. - Release branches start from
developwhen the team has enough for a release. Only bug fixes, version bumps and release notes go on them. When the release is ready, the branch merges intomain, which is tagged, and back intodevelop, so the fixes are not lost. - Hotfix branches start from
mainwhen production is broken. The fix merges intomainfor an immediate patch release, and intodeveloptoo, so the bug doesn't return in the next release.
Here is one feature travelling through Git Flow to a tagged release:
What Git Flow costs
The structure helps when releases are events: a mobile app that goes through app store review, desktop software with version numbers, or a product that supports several versions at once. A release branch gives the team a quiet place to stabilise one version while new work carries on in develop.
The cost is overhead. There are two long-lived branches to keep in step, every release and hotfix has to be merged twice, and forgetting the merge back into develop is a classic way to bring back a bug that was already fixed. Feature branches off develop can also live a long time, which brings back the merge pain the strategy was meant to prevent. Driessen later added a note to his own post suggesting that teams delivering a web app continuously are better served by a simpler workflow.
Feature branch workflow
The feature branch workflow keeps one long-lived branch, main, and gives every task its own short branch. Names describe the work: feature-login, fix-cart. When the work is ready, its author opens a pull request, a teammate reviews it, automated tests run, and the branch merges into main. GitHub flow is essentially this workflow under another name.
A worked example
Say you are adding a login form. You start from an up-to-date main:
git switch main
git pull
git switch -c feature-loginYou make your changes, commit them in small steps, then push the branch:
git add login.py
git commit -m "Add login form"
git push -u origin feature-loginNow you open a pull request from feature-login into main. A reviewer notices the password field has no length limit, so you add a commit and push again, and the pull request updates itself. When the tests pass and the review is approved, the branch merges and you delete it.
Meanwhile a teammate has merged fix-cart into main. If you both changed the same lines, Git asks you to resolve a conflict before your merge can go through. The shorter your branch lives, the fewer changes pile up on main in the meantime, and the smaller that conflict tends to be.
Why it suits most teams
It is easy to explain, every change is reviewed before it lands, and main stays working because nothing reaches it without passing the tests. The common hosting platforms, GitHub, GitLab and Bitbucket, are built around this loop, with branch protection rules to enforce it.
The main risk is a branch that grows too big. A branch open for three weeks with forty files changed is hard to review and hard to merge, however good the process around it.
Trunk-based development
Trunk-based development takes 'keep branches short' as far as it goes. Everyone integrates into one branch, the trunk (usually main), at least once a day. Some teams commit to it directly. Others use tiny branches that live for hours, or a day or two at most, just long enough for a quick review.
Because changes are small and frequent, conflicts stay small too, and problems show up within hours rather than at the end of a sprint. The trunk is expected to be releasable at all times, which is what makes deploying many times a day possible.
Feature flags hide unfinished work
The obvious question: how do you merge a feature that takes two weeks to build if you merge every day? The answer is a feature flag, a switch in the code that decides at run time whether the new behaviour is on.
def checkout(cart, user):
if flags.is_enabled("new-checkout", user):
return new_checkout(cart)
return old_checkout(cart)The new checkout ships to production switched off. The team merges pieces of it every day, tries it with the flag on for themselves, then turns it on for everyone when it is ready. Once it is fully on, they delete the flag and the old code path.
The discipline it demands
Trunk-based development only works with safeguards. A fast automated test suite has to run on every change, because a broken trunk blocks everyone. Developers have to split work into small, safe steps, which is a skill in itself. And flags need cleaning up, or the code fills with old if-statements that nobody dares remove.
Choosing a strategy
The right choice depends mostly on how you release:
| How you ship | Good fit | Why |
|---|---|---|
| Planned, versioned releases | Git Flow | Release branches stabilise a version |
| Steadily, reviewing every change | Feature branches | Simple, and main stays working |
| Many times a day | Trunk-based | Tiny changes, little merge pain |
These are not locked boxes. Plenty of teams run a feature branch workflow where branches last a day, which is close to trunk-based development in practice. Start with the simplest strategy that fits your release process, and add structure only when a real problem calls for it.
Common mistakes
- Long-lived feature branches. Whatever the strategy, a branch that lives for weeks turns into a large, risky merge. Merge smaller pieces more often.
- Git Flow for a web app that deploys daily. With only one version ever in production,
developand release branches add merges without adding safety. - Forgetting the merge back. In Git Flow, a hotfix that merges into
mainbut notdevelopbrings the bug back in the next release. - Trunk-based without tests. Merging to
mainoften is only safe when automated tests catch breakage within minutes. - Never removing feature flags. Old flags pile up and make the code hard to follow.
Key takeaways
- A branching strategy is a team's agreement on how branches are created, used and merged.
- Git Flow uses
main,develop, feature, release and hotfix branches, and suits planned, versioned releases. - The feature branch workflow gives each task its own branch and merges it through a reviewed pull request. It suits most teams.
- Trunk-based development merges small changes into
mainoften, with unfinished work hidden behind feature flags. - Whatever you pick, short-lived branches mean fewer conflicts and a healthier
main.