Git Flow

Method

A branching convention that assigns fixed roles to development, release, and hotfix branches and allows movement only along set paths.

Why it exists

A branch, a line of work split off from the mainline, is far too easy to create. One command makes one, and you can name it whatever you like. On a team with no convention, that produces twenty of them floating around with names like new-login, fix2, and theo-test.

The names aren't really the problem. The paths are. When everyone has their own idea of where a branch should start and where it should return to, nobody can answer the question asked the night before a release: what exactly is on the mainline right now? A tested, signed-off feature sits next to experimental code pushed yesterday, and pulling that experiment back out means lifting off everything stacked on top of it.

For products with a fixed ship date, that situation is expensive. Software people install, or an app that has to pass store review, is hard to take back once it goes out, so you need somewhere to gather only what's shipping and check it over for a few days. Git Flow is that somewhere, built out of branches.

It started in 2010, when a developer published the scheme his team used along with one clear diagram. All it does is assign each branch a role and pin down which branch you split from and which you merge back into. The diagram was legible in a way that written conventions rarely are, and it spread fast — for most of the decade that followed, teams adopting it were often adopting a picture rather than a decision.

How to run it

Five kinds of branch each get a role, and the routes between them are fixed.

  1. main — only what has actually shipped. Since the record here is the release history, every merge gets a version tag like 2.4.1. Many teams pair it with semantic versioning, which assigns a meaning to each of the three digits.
  2. develop — where everything headed for the next release collects. This is the baseline for day-to-day work.
  3. feature/* — one branch per feature. It splits off develop and returns to develop when finished. It never touches main directly.
  4. release/* — the staging ground for a release. A copy is taken from develop, and inside it no new features go in, only bug fixes. The days that quality assurance (QA) spends verifying the build happen on this branch. When that's done it merges into both main and develop.
  5. hotfix/* — used when something urgent breaks in what already shipped. Because the only thing you may touch is the version currently in users' hands, it splits off main rather than develop, and once fixed it merges back into both.

The double destination on the last two is the heart of the convention. Merge a fix into main and forget develop, and the bug you just fixed comes back at the next release, usually weeks later and usually reported by the same customer. That is the single most common accident here, which is why many teams automate the second merge rather than trusting anyone to remember it.

The convention also settles how things combine. Merging a feature/* uses a merge, which brings two diverged lines back together and leaves a record that they were joined, and nothing gets in without an approval.

When it fits and when it doesn't

This works well for products whose versions are visible to the outside world and where several versions have to be supported at once — installed software, libraries other people build on, apps that go through store review. When you need to patch the 2.4 that users are running while separately building 3.0, having main and develop apart earns its keep directly, and a team with a scheduled verification window gets a container for those days in release/*.

In a web service that ships several times a day, the same structure turns expensive. Five kinds of branch means five places where things merge, and a team deploying twice a day that walks the full route each time spends more time on branch bookkeeping than on shipping.

The bigger cost is that branches live a long time. Let one feature/* sit for three weeks and develop moves the whole time. A merge conflict, where both sides changed the same place and the machine can't combine them, grows with the delay: merge after a day and two or three lines overlap, wait three weeks and ten files are out of alignment.

That is why teams that ship fast moved toward trunk-based development, which merges into the mainline several times a day. The trade is that half-finished code is always on the mainline, so without continuous integration checking every merge automatically, the mainline can sit broken for a day before anyone notices.

How it sounds at work

"This is a hotfix, so cut it from main, not develop."

The fix targets the version users are running, so it has to branch from the shipped line rather than the development one.

"The release branch is cut, so new features go into the next round."

Verification has started, so this branch accepts bug fixes only.

"Did you back-merge the hotfix into develop?"

If it only landed on main, the same bug will reappear at the next release.

At Pagedeck the web product and the mobile app run on different conventions, which surprises new engineers who expect one rule for the company. The web side ships several times a day and merges straight to the mainline; the app has to clear store review, so it follows something close to Git Flow.

The reason Theo gives new hires for the split is the cost of undoing. On the web, a problem can be rolled back to the previous version in ten minutes. With the app, a fixed build takes days to reach users' devices. The side where reversing is expensive gets the branch that holds a verification window.

What people get wrong

Plenty of people learn Git Flow as the default branching strategy. It comes up first in search results, the diagram is tidy, and that makes it look like a standard — tutorials explaining branching often reuse the same picture.

It is really one option among several, built for a particular situation. It assumes a product that bundles up a version and ships it on a date, and on a team without that assumption it adds steps without returning anything. Leaving it in place doesn't just slow releases down, either. Because the procedure is tedious, people start routing around it, and skipping develop to merge straight into main "because it's urgent" happens a few times a month. A convention that exists but isn't followed is worse than no convention at all.

Where to go next

Last updated 2026-09-16