Git flow for a team of one, without the ceremony

Searching for git flow produces a diagram from 2010 with five kinds of branch, two of which live forever, and a set of rules about which branch may be merged into which. For a team of twenty shipping a boxed product with three supported versions, that diagram earns every line. For two people deploying a web application several times a week, it produces a develop branch that is always one merge behind main and a release/1.4.2 branch that exists for forty minutes.

The interesting part is that the model's own author reached the same conclusion, in writing, on the page that ranks first for this search. Most articles about git flow do not mention it. Before deciding what to adopt, it is worth reading what the original actually says, what it explicitly excludes, and where the tooling has gone since, because all three have changed while the diagram has not.

What the model actually specifies

Vincent Driessen published the branching model on 5 January 2010. It defines two branches with infinite lifetime and three kinds of supporting branch with short ones.

The long-lived pair is master and develop. master holds production-ready state, and every commit on it is a release. develop holds the integration state for the next release. The supporting branches each have a defined origin and a defined destination. Feature branches start from develop and merge back into develop. Release branches start from develop, merge into both master and develop, and exist to stabilise a release without blocking new feature work. Hotfix branches start from master, merge into both master and develop, and exist so an urgent production fix does not have to wait for whatever is half-finished on develop.

That last pair of rules is the real content of the model. The reason for two long-lived branches is to make it possible to ship a fix without shipping everything else that has been merged since. Everything else in the diagram follows from that requirement, and a team that does not have that requirement is carrying the structure without the benefit.

The companion tool wrapped these rules in commands. git flow init prompted for the branch names and prefixes, and git flow feature start, git flow release start and git flow hotfix start, each with a matching finish, performed the branch creation and the pair of merges. The README is explicit that a feature branch's base must be a commit on develop, which is the model's constraint expressed as a tool constraint.

The note the author added ten years later

On 5 March 2020 Driessen added a note of reflection to the top of the original post. It is worth reading rather than summarising, but the substance is short.

The model was conceived in 2010, and in the decade that followed it became popular to the point where, in the author's words, people started treating it like a standard of sorts, but also as a dogma or panacea. During the same decade the most popular kind of software being built with Git shifted towards web applications, which are typically continuously delivered, not rolled back, and do not require supporting multiple versions running in the wild. The note states plainly that this is not the class of software the post was written about, and that a team doing continuous delivery would be better served adopting a much simpler workflow, naming GitHub flow, rather than trying to shoehorn git flow into the team.

The note then draws the line for when the model still applies: software that is explicitly versioned, or that has to support multiple versions in the wild.

That is a usable test with two questions. Does a version number mean something to the people using the software. Can somebody still be running last quarter's release. Two yes answers mean the release and hotfix branches have a job. Two no answers mean they are ritual, and the original author says so.

Where the tooling went, which matters more than it should

Anyone who follows an older tutorial will hit this within a minute, so it is worth stating before the workflow advice.

The original nvie/gitflow repository is archived on GitHub. Its README now opens with a notice that the repository is no longer maintained. The Homebrew formula named git-flow follows suit: it is marked deprecated, with the reason recorded as the repository being archived, and it still installs version 0.4.1. The widely recommended successor, petervanderdoes/gitflow-avh, is also archived, its last push dating to August 2023, and it is not present in Homebrew's core formulae.

What replaced both is git-flow-next, maintained by the team behind the Tower Git client. It is written in Go, the current version is 2.1.0, and it is in Homebrew core:

brew install git-flow-next

Its own documentation describes it as built on the original git-flow and gitflow-avh projects, both now discontinued, and it is backward compatible with commands from either. The design difference is that branch structure is configuration rather than assumption: it ships presets for Gitflow, GitHub flow and GitLab flow, and a from-scratch option, with a generic model of parent and child branches underneath instead of a fixed feature, release and hotfix vocabulary.

For a small team the practical reading of all this is not which tool to install. It is that the 2010 model is no longer the default anybody builds tooling around, and that a tool which can express a simpler workflow is worth more than one which can only express the elaborate one.

What a workflow has to do, and what it costs

Strip the names away and any branching model is buying three things. A state that is known to be deployable. A point at which somebody else can read the change before it lands. And a route to ship one fix without shipping the rest.

Workflow Long-lived branches Where a release comes from Cost to run
Git flow main and develop A release branch cut from develop Two merges per release, two per hotfix, constant drift between the pair
GitHub flow main only main, continuously A pull request per change
GitLab flow with environments main plus one branch per environment Promotion between environment branches One merge per promotion
Tag-based releases on main main only A tag on main, with a branch cut from the tag only when an old version needs a fix Nothing until a fix for an old version is actually needed

The last row is the one small teams usually want and rarely consider, because tutorials present release branches as something you maintain rather than something you create on demand. A tag is a name for a commit. If version 1.4 needs a patch eight months later, a branch from that tag can be created at that moment, with the same result as a release branch that had been sitting there since. Nothing is lost by waiting, and nothing has to be maintained meanwhile.

The cost column is where git flow gets small teams. Two long-lived branches means every change is merged twice and the two are never quite equal, so there is always a question about which one reflects production. With one person that question has no one to ask.

A workflow for one or two people

GitHub's documentation describes GitHub flow as a lightweight, branch-based workflow, and notes that GitHub uses it for its own site policy, documentation and roadmap rather than only for code. The documented steps are: create a branch, make changes, open a pull request, address review comments, merge the pull request, delete the branch.

For a solo developer, steps four and five collapse and the pull request stops being a review gate, but it is still worth opening, because it is where continuous integration reports and where the change gets a description that survives. For two people it is the whole point.

Three settings make the loop maintain itself rather than accumulating debris.

git config set --global fetch.prune true
git config set --global push.autoSetupRemote true
git config set --global branch.sort -committerdate

fetch.prune removes remote-tracking refs for branches that no longer exist on the server, so the branch list stays honest. push.autoSetupRemote assumes --set-upstream on a first push when no upstream tracking exists, which removes the one command everybody forgets. branch.sort puts recent work at the top of every listing.

On the server side, GitHub offers a repository setting labelled Automatically delete head branches, in Settings under General, in the Pull Requests section. Turning it on removes the branch when the pull request merges, and combined with fetch.prune it means finished branches disappear from your machine without anyone tidying up.

That is the whole workflow: main, short branches off it, a pull request each, a tag when something is released, and a branch cut from a tag only when an old version genuinely needs a fix. Reviewing two branches side by side rather than switching between them is easier with git worktree add, which gives a branch its own directory backed by the same repository, and easier still when the folder, the shell and the diff share one window. A branch that needs a look while away from the desk can also be reached on the Mac from a phone or tablet, which for a two person team is often the difference between a review today and a review tomorrow.

When the ceremony earns its place

None of the above is an argument that git flow was wrong. It is an argument about which problem it solves.

Keep the release branch when a release needs to sit still while somebody tests it, and feature work cannot stop for the duration. That is a genuine conflict, and a branch is the correct way to resolve it. Keep it when a release needs sign-off from outside the team, because the branch is what the sign-off refers to. Keep the hotfix branch when versions in the wild are still supported, which is the case the original author names as the model's remaining fit.

Two intermediate positions are worth knowing. Release branches without a permanent develop branch gives you the stabilisation window without the constant drift between two long-lived branches: cut a release branch from main when a release is being prepared, fix on it, tag, merge back, delete.

The other is the arrangement GitLab documents under the name GitLab flow. Its published description is specific about the difference: where git flow makes develop the default branch, GitLab flow works with main directly and adds pre-production branches for fixes before production, with as many of them as a team needs, giving a chain such as main to test, test to acceptance, acceptance to production. Commits flow downstream, so every change is exercised in each environment on the way. The same page notes that release branches still have a place in it, and gives the case of a public API where a v1 and a v2 branch are maintained separately, so a bug found in review can be fixed on the older line.

For a small team the useful idea there is not the number of environments. It is that the extra branches correspond to deployment targets that actually exist, rather than to a stage in a process. A branch named after a machine you can point at is a fact. A branch named after a phase is a description of intent, and descriptions of intent drift.

The test to apply is whether a branch in the diagram corresponds to something that exists in your situation. A develop branch is worth having when there is a meaningful difference between merged and released. If everything merged is deployed the same afternoon, that difference does not exist, and the branch is recording it anyway.

What to change first

Answer the author's two questions before touching any tooling: does a version number mean anything to the people using the software, and can somebody still be running an older release. Two no answers mean main with short branches and tags is enough, so set fetch.prune and push.autoSetupRemote, turn on automatic head branch deletion, and stop maintaining a develop branch. If you do install a tool, git-flow-next is the one still being maintained. A window where the branch list, the terminal and the diff sit together is the shape Atriens is built around.

Frequently asked questions

Is git flow still recommended in 2026?

Its author added a note to the original post in March 2020 saying that teams doing continuous delivery would be better off with a simpler workflow such as GitHub flow, and that the model still fits software which is explicitly versioned or has multiple versions running in the wild. That is the test to apply, rather than a general yes or no.

How do I install git flow on a Mac?

The original git-flow formula in Homebrew is marked deprecated because the upstream repository is archived, and the gitflow-avh fork is archived too. The maintained option is brew install git-flow-next, which is in Homebrew core at version 2.1.0 and accepts commands from both earlier projects.

What is the difference between git flow and GitHub flow?

Git flow keeps two permanent branches and cuts release and hotfix branches between them, so a release is a branch. GitHub flow keeps one permanent branch, and every change arrives through a short branch and a pull request, so a release is whatever is on main. The first buys the ability to ship a fix without shipping everything merged since; the second buys far less bookkeeping.

Do I need a develop branch if I work alone?

Only if there is a real difference between merged and released. A develop branch records the gap between the two, so it earns its place when releases are batched and tested before shipping. When everything merged goes out the same day, it is a second name for the same state and one more merge per change.

Can I use release branches without using all of git flow?

Yes, and for many small teams it is the right subset. Cut a release branch from main when a release needs a stabilisation window, fix on it, tag the result, merge back and delete the branch. That keeps the one part of the model that solves a real conflict without maintaining two long-lived branches permanently.

How do I fix a bug in an old version without a hotfix branch?

Create a branch from the tag for that version at the moment the fix is needed, commit the fix there, tag the patch release, and cherry-pick the same change onto main. A tag names a commit permanently, so nothing is lost by not having kept a branch open. The branch only needs to exist while the fix is being made.

Back to all posts