Articles/Git/Improve Your Git Workflow with Git Flow

Improve Your Git Workflow with Git Flow

Git Flow is a structured branching model built around versioned, scheduled releases. Here is how its branches fit together, a hands-on walkthrough of features, releases and hotfixes, and an honest take on when it is still the right call.

December 6, 2016·6 min read

Git Flow is a development workflow for keeping branching consistent across a team. It was the workflow everyone copied for years, and it is still a great fit for certain kinds of projects. In this article I will walk through the branches it uses, show a hands-on example of features, releases and hotfixes, and give you an honest read on when to reach for it.

Is Git Flow still the right choice?

Let's get the reality check out of the way first, because it changes how you should read the rest of this post.

Git Flow comes from Vincent Driessen's 2010 article "A successful Git branching model." In 2020 he added a note to the top of that same article saying that if your team practices continuous delivery, you should probably adopt something simpler like GitHub Flow, and that Git Flow still fits teams building explicitly versioned software or supporting multiple versions in the wild. Atlassian now describes Git Flow as a legacy workflow, one that has lost ground to trunk-based development for modern CI/CD.

None of that makes it wrong. It just means the extra branches are overhead you only want to pay for when versioned releases actually earn it. Think desktop apps, libraries, mobile releases, SDKs, anything where multiple versions live in the world at once and need separate maintenance. If you are shipping a web app that deploys continuously, skip ahead to GitHub Flow, or read my Git Flow vs GitHub Flow comparison for the full head to head.

Still here? Then you probably ship versions, and Git Flow's structure is going to serve you well. Let's get into it.

The branches

Git Flow uses two long-lived branches plus three kinds of supporting branches.

  • main is your currently released code, what you would find in production. (In the original 2010 model this branch was called master; modern repos default to main, and that is what I will use throughout.)
  • develop is the integration branch where finished work collects between releases.

On top of those two, you create short-lived supporting branches as you need them.

  • feature/* branches come off develop for each new piece of work.
  • release/* branches freeze develop so you can harden a version before shipping it.
  • hotfix/* branches come off main to patch production in a hurry.

The golden rule: you never commit directly to main or develop. Everything arrives through a supporting branch.

Setting up the project

The best way to learn this is by doing, so let's set up a fresh repository. Inside your project directory, create the first commit on main and push it up.

BASH
echo "# Git Flow Example" >> README.md
git init
git add README.md
git commit -m "Initial commit"
git remote add origin git@github.com:frankperez87/gitflow-example.git
git push -u origin main

That initial commit is the one exception to the never-touch-main rule; we are just bootstrapping the repo. From here on, main only changes through releases and hotfixes.

Next, create the develop branch and track it remotely. This is where features will land.

BASH
git branch develop
git push -u origin develop

Feature branches

Every new piece of work is a feature, unless it is an urgent production fix (that is a hotfix, which we will get to). Let's add a contributing guide. We branch off develop and prefix the branch with feature/.

BASH
git checkout -b feature/contributing develop

Now make the change. Keep it small and real.

BASH
echo "# Contributing" >> CONTRIBUTING.md
echo "Open a pull request against develop and keep changes focused." >> CONTRIBUTING.md
git add CONTRIBUTING.md
git commit -m "Add contributing guide"

You can also name the branch feature-contributing with a dash instead of a slash. Both are fine; just pick one convention and stick to it.

With the feature done, merge it back into develop and delete the branch, since it has served its purpose.

BASH
git checkout develop
git merge feature/contributing
git branch -d feature/contributing

Release branches

When develop has enough finished work to ship, you cut a release branch. Release branches are prefixed with release/ followed by the version number. This freezes a snapshot of develop so you can do final hardening (version bumps, last-minute fixes, changelog) without blocking new feature work.

BASH
git checkout -b release/0.1.0 develop
echo "Version 0.1.0" >> CHANGELOG.md
git add CHANGELOG.md
git commit -m "Prepare 0.1.0 release"

When the release is ready, merge it into main, then tag that commit with the version.

BASH
git checkout main
git merge release/0.1.0
git tag -a v0.1.0 -m "Release 0.1.0"
git push origin main v0.1.0

Here is the step people forget: you also merge the release back into develop. Any fixes or version bumps you made on the release branch happened off develop's line, so merging them back keeps develop from falling behind production.

BASH
git checkout develop
git merge release/0.1.0
git push
git branch -d release/0.1.0

Hotfix branches

Now suppose a user finds a bug in production that cannot wait for the next release. A hotfix branches straight off main (not develop), because you want to patch exactly what is live.

BASH
git checkout -b hotfix/login-typo main

Make the smallest change that fixes the problem and commit it.

BASH
git commit -am "Fix typo on login button"

Just like a release, a hotfix merges into both main and develop so the fix is not lost, and the new version gets tagged on main. A bug fix is a backward-compatible change, so it bumps the PATCH number: 0.1.0 becomes 0.1.1.

BASH
git checkout main
git merge hotfix/login-typo
git tag -a v0.1.1 -m "Hotfix: login button typo"
git push origin main v0.1.1

git checkout develop
git merge hotfix/login-typo
git push

git branch -d hotfix/login-typo

A quick word on versioning

Those tags are not arbitrary. Git Flow pairs naturally with semantic versioning, where a version reads MAJOR.MINOR.PATCH:

  • MAJOR for a breaking change.
  • MINOR for new, backward-compatible functionality (your next feature release would be v0.2.0).
  • PATCH for a backward-compatible bug fix (the hotfix above, v0.1.1).

Tag every release on main and your team, along with your users, can always check out exactly what shipped and when.

The git flow CLI

You do not have to type all of those merges and tags by hand. The git flow extension wraps the whole model into a handful of commands.

BASH
git flow init                          # set up main, develop and the branch prefixes
git flow feature start contributing    # branch off develop
git flow feature finish contributing   # merge back into develop, delete the branch
git flow release start 0.1.0           # branch off develop
git flow release finish 0.1.0          # merge to main and develop, tag main
git flow hotfix start 0.1.1            # branch off main
git flow hotfix finish 0.1.1           # merge to main and develop, tag main

It is doing exactly the merges and tags you just learned, which is why it is worth understanding the manual flow first. One heads-up for 2026: the original nvie/gitflow extension was archived in October 2025 (it now points to a successor called git-flow-next), and the long-popular gitflow-avh fork was archived back in 2023. The model is the same either way, so I lean on the plain git commands above and treat the CLI as optional sugar.

Wrapping up

That is Git Flow end to end: two long-lived branches, supporting branches for features, releases and hotfixes, and a tag on main for every version. The structure shines when you ship versioned software on a schedule and have to maintain more than one release at a time.

If that does not sound like your project, do not force it. The real win is that your whole team agrees on how branches are named, when things merge, and how releases get tagged. For a continuously deployed web app, that agreement usually looks like GitHub Flow instead. Either way, pick one, write it down, and get everyone using it.