Git Branching Strategy
A Git branching strategy is the agreement on how a team splits its work into branches and rejoins them. The choice looks like a matter of taste and is not: the only variable that reliably predicts pain is how long a branch lives before it merges, and every strategy is really a position on that one number. This page covers the four models you will meet, what makes short branches possible, and when release branches genuinely earn their cost.
- What matters
- Branch lifetime
- Trunk-based
- Under 24 hours
- Correlates with
- Elite DORA scores
Branch lifetime is the whole story
A branch is a bet that the rest of the codebase will not change in a way that conflicts with your work. That bet is cheap for a day and expensive for a month, because conflict risk grows with both the size of your change and the number of changes everyone else lands while you are away.
The trap is that the cost is invisible while it accrues. A branch open for three weeks feels productive every one of those days; the price arrives all at once at merge, as a conflict resolution nobody estimated and a regression nobody predicted. This is why long-lived branches and painful integration are the same problem described twice.
The four models you will meet
Trunk-based
Everyone commits to one main branch, via short-lived branches that live hours, not days. Unfinished work hides behind feature flags rather than in a branch. Production is a tag or a deploy from main.
GitHub Flow
One main branch plus a branch per change, merged through a pull request, deployed from main. The pragmatic middle ground and the default for most product teams — trunk-based with review added.
GitLab Flow
GitHub Flow plus downstream branches that mirror environments (staging, production) or versions. Changes always flow one way, main → downstream, so an environment can never contain something main does not.
GitFlow
Long-lived develop, release and hotfix branches alongside main. Built for versioned software with several supported releases in the field, and priced accordingly.
A correction that matters, because GitFlow is still taught as the default: its author added a note to the original 2010 post saying it was designed for software with explicit versioned releases, and that teams building continuously delivered web applications should not adopt it. It was never wrong — it was answering a question most web teams no longer have.
GitLab Flow is worth knowing because it is what teams usually reach for when GitHub Flow stops fitting — when there is a staging environment that must be exactly one step behind production, or a customer on an older version. Its one rule prevents the classic disaster: a fix applied directly to the production branch and never merged back, which quietly reappears as a bug in the next release.
Choosing between them
| Model | Branch life | Fits | Needs |
|---|---|---|---|
| Trunk-based | < 1 day | Continuous delivery, one live version | Strong tests, feature flags |
| GitHub Flow | 1–3 days | Most product teams | Code review, CI on every PR |
| GitLab Flow | 1–3 days + env branches | Staged environments or a few versions | Discipline that changes only flow downstream |
| GitFlow | Weeks | Versioned or installed software | Release management, back-porting |
The honest decision rule is short. If you support exactly one version in production, choose the shortest-lived branches your test suite can carry. If you support several versions at once — an on-premise product, a mobile app with users on old builds, firmware — you genuinely need release branches, and GitFlow is a reasonable answer rather than a legacy habit.
It is worth knowing the price of that answer before agreeing to it. Every supported version is a branch that a security fix must be applied to separately, tested against separately and released separately. Three supported versions means every urgent patch is three cherry-picks, three test runs and three releases — and the fix that was correct on main sometimes does not apply cleanly to the two-year-old branch. That work is the real cost of release branches, and it is invisible in any diagram of the model.
What makes short branches possible
Teams rarely choose long branches on purpose. They end up with them because something makes merging early impossible, and the model is blamed for the symptom. Three things are usually the actual cause.
Review latency and change size
If a pull request waits a day and a half for a reviewer, nobody opens small ones — the round-trip cost is fixed, so the rational move is to batch more into each. That makes reviews larger, which makes them slower, which makes batching more attractive. The loop is self-reinforcing and it is the most common reason a team cannot get branch lifetime down.
The fixes are unglamorous: a stated expectation for review turnaround measured in hours, a size at which a reviewer is allowed to ask for a split, and pairing or ensemble work for the changes that would otherwise be too big to review at all. Note that trunk-based development does not skip review — it moves it, either to pairing in real time or to a review that happens fast enough not to bend the workflow around itself.
Big changes without big branches
The usual objection to short branches is the large refactor — replacing a payment provider, swapping an ORM, restructuring a module. Branch by abstraction is the technique that answers it: introduce an interface in front of the thing being replaced, move callers onto the interface one merge at a time, build the new implementation behind it, switch, then delete the old one. Every step is a small, safe, independently mergeable change, and the codebase is releasable at each of them.
Merge, rebase or squash
This argument consumes more team energy than it deserves, so here is the short version. A merge commit preserves exactly what happened, including the fact that two lines of work existed in parallel; on a busy repository with short branches it produces a history that is accurate and hard to read. Rebasing replays your commits on top of the current trunk, giving a linear history — at the cost of rewriting commits, which is fine on your own branch and harmful on a shared one. Squashing collapses a branch into a single commit, which makes the trunk history read as one change per unit of work and throws away the intermediate steps.
None of the three affects delivery outcomes measurably; pick one, write it down, and stop discussing it. What does matter is a rule most teams agree on without arguing: never rewrite history on a branch someone else has based work on. That one causes real damage, and unlike the aesthetic question it is not a matter of preference.
How this shows up in real delivery
Branching strategy is where team process meets engineering practice, and it exposes gaps in the latter mercilessly. A team cannot move to short-lived branches if its test suite takes forty minutes and is flaky, because nobody will merge six times a day into a signal they do not trust. The branching model is usually a symptom; the test suite is usually the cause.
Where it degrades
- A feature branch open for a month, so the merge is a project of its own.
- GitFlow adopted for a web app with one live version, adding ceremony that serves nothing.
- A per-developer branch, which turns integration into a periodic negotiation.
- A hotfix applied to the production branch and never merged back, so it returns as a bug.
- Short branches without feature flags, so half-built work is either merged broken or held back.
- Rewriting history on a shared branch, which breaks everyone who had based work on it.
When to use it
Use it when
- Choosing trunk-based when exactly one version runs in production and tests are fast and trustworthy.
- Choosing GitHub Flow as a sane default for a product team that reviews every change.
- Choosing GitLab Flow when environments or a small number of versions must be tracked as branches.
- Choosing GitFlow when several released versions must be supported and patched in parallel.
- Pairing short branches with feature flags so unfinished work can merge safely.
Avoid it when
- Moving to trunk-based while the test suite is slow or flaky — fix that first.
- Adopting GitFlow because it is familiar rather than because you ship versions.
- Adding branch types to solve a review or ownership problem — that is a process fix, not a git one.
- Supporting more released versions than the back-porting effort can actually carry.
- Changing the model without changing what made the old one painful.
Found this useful?
Share it with someone who is working on the same problem.