Sign in
← Git: workflows
11 min · medium · Lesson 3 of 3

git-flow vs trunk-based

Two branching models: heavyweight git-flow and trunk-based with feature flags. When to use which, and why.

Why a branching model

Branches in git are cheap, but without an agreement a team quickly drowns in chaos. Which branches live long, where do tasks merge, where does a release ship from, where does a hotfix go? A branching model is a team agreement that answers these questions.

There are many models, but the whole spectrum makes sense once you know the two extremes: git-flow and trunk-based. In between sits the simpler GitHub flow, with a few words on it below.

git-flow: two permanent branches and many temporary ones

Vincent Driessen described the model in 2010. It has two permanent branches:

The original article calls the production branch master, these days it's usually called main. The other branches are temporary and get deleted after merging:

Here's what shipping release 1.3.0 looks like:

git checkout -b release/1.3.0 develop
git checkout main
git merge --no-ff release/1.3.0
git tag -a v1.3.0 -m "release 1.3.0"
git checkout develop
git merge --no-ff release/1.3.0
git branch -d release/1.3.0

The --no-ff flag forces git to create a merge commit even when the branch could just be fast-forwarded. That way the history shows which commits came in as one branch. git-flow uses --no-ff for every merge.

The scheme is detailed and fits well with infrequent, versioned releases: desktop programs, libraries, products that support several versions at once. In 2020 the author himself added a note to the article: for continuously delivered web applications he recommends a simpler model, such as GitHub flow.

Why long-lived branches hurt

While a branch lives on its own, main keeps moving forward. The longer the gap, the more files have changed on both sides, and the heavier the merge. With several such branches, merging turns into days of separate work. That's what people call merge hell.

How to tell a branch has gone stale. The point where a branch split off from main is shown by merge-base:

git merge-base main feature/payments
42ea92042ed03244d20fc206c404fd6e17287ef6

You can look up that commit's date with git log -1 <hash>. The date of the branch's latest commit doesn't help here: a month-old branch could have gotten a commit yesterday.

The A..B notation means "commits that are in B and not in A". That gives two questions:

git log --oneline main..feature/payments
4e3452f pay
7d51986 pool 20

That's the branch's own work. And how far main has moved since the branch point:

git rev-list --count feature/payments..main
2

The easiest way to check for a conflict is a trial merge that you abort right away:

git merge feature/payments
Auto-merging app.conf
CONFLICT (content): Merge conflict in app.conf
Automatic merge failed; fix conflicts and then commit the result.
git merge --abort

git merge --abort puts the branch and the working folder back to how they were before the merge. If your Git is set to another language, the messages will be translated, the file name stays the same.

trunk-based: one main branch

trunk-based flips the approach. There's one main branch (main, also called the trunk), and everyone merges small changes into it often. The team doesn't keep any other long-lived branches.

Task branches exist, but they're short. They're opened for review and a CI run, and trunkbaseddevelopment.com recommends keeping such a branch to no more than a couple of days. Once merged, the branch gets deleted, including on the server:

git push origin --delete fix/typo
To ../origin.git
 - [deleted]         fix/typo
short-lived branch (a day or two) → review and CI → merge into main → deploy

Branches are short, so they barely drift from main and don't produce merge hell. main is always in a working state, and you can ship from it at any moment. This is the model for teams with CI/CD and frequent deploys.

A release in trunk-based is a tag on a main commit. Some teams cut a release/* branch off main before a release, but don't develop on it. A bug gets fixed in main first, and the finished commit gets carried into the release branch with git cherry-pick.

Unfinished features and feature flags

The main question for trunk-based: if everything merges into main quickly, what do you do with a feature that isn't ready yet? The answer is a feature flag: a switch that turns a piece of code on or off without a new release. The code merges into main, but it's hidden behind the flag and turned off.

Pseudocode:

if (featureEnabled("new-checkout")) {
    newCheckout();
} else {
    oldCheckout();
}

Where to store the flag is up to the team: a config file, an environment variable, a dedicated flag service. In the simplest case it's a line in a config:

new_checkout=off

The feature ships to production turned off. It gets turned on separately once it's ready. That splits two events: "ship the code" and "turn on the feature". Unfinished work doesn't sit in a separate long-lived branch and doesn't get cut out of main before a release, it just stays off.

Flags have a cost. Every flag adds a branch in the code, and forgotten flags pile up over years. Once a feature is on for everyone and working, the flag and the old code path get deleted. trunkbaseddevelopment.com recommends setting a review date about a month after the release.

GitHub flow: the middle ground

GitHub flow is simpler than git-flow: one permanent branch main, a branch per task, a pull request with review, a merge into main, and the branch gets deleted. There's no separate develop, release/*, or hotfix/*. If branches live a day or two, it's close to trunk-based. If they live for weeks, the problems of long-lived branches come back.

What to pick

Web and SaaS products usually go trunk-based: changes land faster, there are fewer conflicts. git-flow stays relevant where a release is an explicit version that's prepared and shipped on its own.

Common mistakes

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson git-workflow-03-flow-vs-trunk