Curious Pandas

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.

16 min+40 XP for finishing

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 develop and merge back into it.
  • release branches “support preparation of a new production release”. They start from develop and merge into both main and develop.
  • hotfix branches “arise from the necessity to act immediately upon an undesired state of a live production version”. They start from main and merge into both.
Visual guideGit Flow's branches
Git Flow's branchesTwo long-lived branches, main and develop. Feature branches merge into develop. A release branch is cut from develop and merged into main and back into develop. A hotfix branch is cut from main and merged back into main, and into develop as well.mainproduction-readydevelopnext releasefeature/*from develophotfix/*from mainrelease/*prepare a releasemergebranchmergebranchGit Flow's branchesTwo long-lived branches, main and develop. Feature branches merge into develop. A release branch is cut from develop and merged into main and back into develop. A hotfix branch is cut from main and merged back into main, and into develop as well.mainproduction-readydevelopnext releasefeature/*from develophotfix/*from mainrelease/*prepare a releasemergebranchmergebranch
  1. Two branches live forever: main, always production-ready, and develop, where the next release comes together.
  2. Each feature gets its own branch from develop and merges back into it when done.
  3. To ship, cut a release branch from develop, finish it there, then merge it into main - and back into develop.
  4. 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:

  1. Create a branch
  2. Make changes
  3. Create a pull request
  4. Address review comments
  5. Merge your pull request
  6. 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.

Visual guideTrunk-based, with a flag
Trunk-based, with a flagWork lives on a branch for hours, goes through review and CI, and merges into trunk, which is always releasable. Trunk deploys to users. Unfinished work is merged too, but a feature flag keeps it switched off for users until it is done.short branchhours, not weeksreview + CIthe pull requesttrunkalways releasableuserssee finished workfeature flagoff until doneopen PRmergedeployhides WIPTrunk-based, with a flagWork lives on a branch for hours, goes through review and CI, and merges into trunk, which is always releasable. Trunk deploys to users. Unfinished work is merged too, but a feature flag keeps it switched off for users until it is done.short branchhours, not weeksreview + CIthe pull requesttrunkalways releasableuserssee finished workfeature flagoff until doneopen PRmergedeployhides WIP
  1. A change lives on its own branch for hours, not weeks.
  2. Review and CI run on the pull request, then it merges into trunk - at least once a day.
  3. Trunk stays releasable and deploys to users.
  4. 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.

Two weeks of a featurea model: files both sides changed

You are building search filters over ten working days while the rest of the team keeps merging to trunk. Choose how often you integrate, then run the two weeks.

    largest single merge-
    files reconciled in total-
    days users could reach it-

    Nothing run yet.

    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)
    Check yourself+10 XP

    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?

    Check yourself+10 XP

    A feature takes two weeks. How does a trunk-based team still merge it to trunk every day?

    Check yourself+10 XP

    In the two-week model, why did merging every day mean less pain than merging once at the end?

    Do it for real · your machine+50 XP bonus

    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?

    Self-reported. We can't see your machine, and we don't want to. This is a bonus, never required.

    Sources

    Finished reading?

    Missions and quizzes above earn their own XP. This marks the lesson done.