Git makes branches cheap to create, but it does not say how to use them. Every team ends up setting rules, explicit or implicit: how many branches exist, how long they last, who merges them and when. A well-chosen branching strategy reduces conflicts and makes releases predictable; a poor one produces branches that live for weeks, painful merges and the feeling that version control is an obstacle rather than a help.
The common enemy: long-lived branches
Before comparing strategies, it is worth understanding the problem they all try to solve. The longer a branch stays apart from the main branch, the more the code diverges. The consequences are familiar to anyone who has worked in a team:
- numerous merge conflicts that are hard to resolve;
- integration defects that only surface at the end, when fixing them costs more;
- huge code reviews that nobody manages to read carefully.
The different strategies are, at heart, different answers to the question: how long can we afford to work apart?
Trunk-based development
In trunk-based development everyone integrates frequently into the main branch (the "trunk", often called main). Branches, if they exist, last a few hours or a couple of days at most.
How do you work on a feature that takes weeks? With a few techniques:
- feature flags: the new code is present but switched off by a configuration toggle until it is ready;
- incremental development: the feature is split into small steps, each of which can be integrated without breaking anything;
- branch by abstraction: you introduce an abstraction layer that lets you replace a component gradually.
The prerequisite is a good suite of automated tests run on every integration. Without tests, integrating often just means breaking things often.
GitHub flow
GitHub flow is a lightweight and very widespread variant:
mainis always in a releasable state;- for every change you create a branch with a descriptive name;
- you open a pull request, discuss it and review it;
- after approval and passing tests, you merge into
mainand release.
It suits projects with continuous delivery, such as web applications, where there is only one version in production. The key discipline is keeping branches small: a GitHub flow with three-thousand-line pull requests has all the flaws of long-lived branches.
Git flow
Git flow is a more structured model, with branches dedicated to different roles:
| Branch | Role |
|---|---|
main |
Contains only released versions |
develop |
Integrates features for the next release |
feature/* |
Development of individual features, branching from develop |
release/* |
Preparation and stabilisation of a version |
hotfix/* |
Urgent fixes starting from main |
This model makes sense when you maintain distinct numbered versions, for example software installed by customers, where version 2.3 must receive fixes while 2.4 is being developed. For a website with a single version online, however, Git flow adds steps without proportionate benefits: many teams have abandoned it for exactly this reason.
How to choose
A few practical questions guide the choice:
- How many versions of the software must you maintain at the same time? One: trunk-based or GitHub flow. Several: a model with release branches.
- How reliable are your automated tests? If they are poor, they need improving first; frequent integration without tests is risky.
- How large is the team? With many people, clear rules on branch lifetime and review become essential.
- How often do you release? Daily releases push towards trunk-based work; quarterly releases tolerate more structure.
In my work, for web projects run by small teams, GitHub flow with short branches and mandatory tests is almost always the best compromise.
Useful rules whatever the strategy
- Protect the main branch: no direct pushes, merges only after tests and review.
- Descriptive names:
fix/total-roundingsays more thanbranch-test-3. - Update your branch often against the main one, to discover conflicts early.
- Delete merged branches: a repository with hundreds of dead branches confuses everyone.
- Write the strategy down: two lines in the README prevent arguments and misunderstandings.
Merge, squash or rebase
At integration time you also choose how to bring the commits into the main branch. A merge keeps the branch's entire history with a merge commit. A squash compresses the branch into a single commit, producing a linear, compact history. A rebase reapplies the commits one by one on top of the main branch, keeping them separate but linear. No option is right in absolute terms: what matters is that the team chooses one and applies it consistently.
In short
- All strategies fight the same problem: branches that live too long.
- Trunk-based development integrates continuously and requires solid tests and feature flags.
- GitHub flow is simple and effective for software with a single version in production.
- Git flow makes sense when several released versions are maintained in parallel.
- Whichever model you choose, protect the main branch and write it down.