Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Git Tip of the Week: Git Flow

Gtotw 2011 Git

This week's Git Tip of the Week is about git flow. You can subscribe to the feed if you want to receive new instalments automatically.


Following on from last week's post on merging, and specifically discussing git merge --no-ff, we will be looking at a popular Git strategy known as git flow.

Originally posted as Git Flow: a successful branching model, it has become popular in a number of larger projects which use branching as a way of introducing features.

Feature branches: the key point in git flow is the creation and use of feature branches. Their purpose is to allow parallel (or independent) feature development which can subsequently be merged back into a consolidated branch. Of course, Git is particularly well suited to this task since branches are free to create and merging can be done easily afterwards.

Feature branches are merged back onto the main development branch, known as develop in git flow. The key point with git merge --no-ff is that it gives you a merge node, even if the develop branch can otherwise be fast forwarded. This merge node allows you to determine on which branch you create the fix in the first place.

Although many consider “feature” to be a large term, in fact, it's quite possible to think of a micro feature as well. Some developers will create a new feature branch for each bug that gets filed, which then preserves a link with the bug that was filed in a bug tracker. (You can also use arbitrary commit messages as well to enable this linkage.)

Release branches: once the develop branch is ready for a release, it is branched onto a (pre)-release branch. This is then used for bugfixes for that release only (which are merged back into the develop branch) in preparation for the final tagging. Each branch is named for its version number; so release-1.0 would be the branch used for developing the code ahead of the 1.0 release itself. Once development of the 1.0 release is finalised, it will be tagged and work on the release-1.0 branch will be stopped. (Git Flow suggests the branch is deleted at this point to prevent accidental further work on that branch. Since it is tagged, it's easy to recreate if ever needed.)

Hotfix branches: there may be a need to spin up a new release quickly (sometimes also known as patch releases) for a production issue, but ahead of the next major (or minor) release. Hotfix branches are also named for the release (e.g. hotfix-1.0.1) and only contain the specific bugfixes for that release. Changes are merged back into the develop branch to ensure that the bug is also fixed in subsequent releases as well. Typically only a few commits exist on hotfix branches at a time, since they often merge back into the release.

What's in a name?

None of these names really change what you can achieve with Git; after all, one can just as easily create a branch from a tag as well as a branch. Indeed, the git flow model suggests work is done against the develop branch instead of the master branch (which is conventionally the main branch for Git based development) – in git flow, the master branch is reserved for the released tags only.

However, one key point is that the merge nodes store the name of the branch that they merged from. This means if the branch names are used, you can trace the flow of a feature from the feature branch through to the develop branch and ultimately a release branch. There's a great image (which I strongly encourage you to look at in the context of the original post).

To demonstrate how it works in practice, here's a shortened sequence of development in Git Flow and the resulting merge tree.


(master) $ git log --oneline
4f5da46 Initial Commit
(master) $ git checkout -b develop
Switched to a new branch 'develop'
(develop) $ …
(develop) $ git checkout -b feature1 develop
Switched to a new branch 'feature1'
(feature1) $ …
(feature1) $ git checkout -b feature2 develop
Switched to a new branch 'feature2'
(feature2) $ …
(feature2) $ git checkout develop; git merge --no-ff feature2
Merge made by recursive.
…
(develop) $ git checkout develop; git merge --no-ff feature1
Merge made by recursive.
…
(develop) $ git checkout -b release-1.0 develop # ready for 1.0
Switched to a new branch 'release-1.0'
(release-1.0) $ echo 1.0 > version; git add version; git commit -m "Version 1.0" version
[release-1.0 c51b802] Version 1.0
(release-1.0) $ git checkout master
Switched to branch 'master'
(master) $ git merge --no-ff release-1.0
Merge made by recursive.
(master) $ git tag -a 1.0 # tag the release once it's finished
(master) $ git checkout -b hotfix-1.0.1 master # create a hotfix for 1.0
Switched to a new branch 'hotfix-1.0.1'
(hotfix-1.0.1) $ echo 1.0.1 > version; git add version; git commit -m "Version 1.0.1"
…
(hotfix-1.0.1) $ git checkout master
Switched to branch 'master'
(master) $ git merge --no-ff hotfix-1.0.1
Merge made by recursive.
(master) $ git checkout develop # merge the hotfix back into 'develop'
Switched to branch 'develop'
(develop) $ git merge --no-ff hotfix-1.0.1
Merge made by recursive.
(develop) $ git branch -d hotfix-1.0.1
Deleted branch hotfix-1.0.1 (was f43bb33).

Although the edits above don't have any real work contained in them, the point of the merges allows us to generate this psuedo real-world looking merge tree:


(develop) $ git log --decorate --graph --oneline
*   17e4c5f (HEAD, develop) Merge branch 'hotfix-1.0.1' into develop
|\
| * f43bb33 (hotfix-1.0.1) Hotfix2
| * 50a102d Hotifx1
| * d2fe7d6 Version 1.0.1
| *   d534111 (tag: 1.0) Merge branch 'release-1.0'
| |\
* | \   77e7eac Merge branch 'release-1.0' into develop
|\ \ \
| | |/
| |/|
| * | 9261fad (release-1.0) Release10Fix2
| * | 2562cb3 Release10Fix1
| * | c51b802 Version 1.0
* | | fbdd638 Work6
* | | 7353285 Work5
|/ /
* | 538bb9a Work4
* |   cdef254 Merge branch 'feature1' into develop
|\ \
| * | 793b1bc (feature1) Feature1Work4
| * | 3f2be07 Feature1Work3
| * | 7879593 Feature1Work2
| * | 8e01f4d Feature1Work1
* | |   9239c56 Merge branch 'feature2' into develop
|\ \ \
| |_|/
|/| |
| * | 6752543 (feature2) Feature2Work3
| * | d065311 Feature2Work2
| * | 040fcaf Feature2Work1
| |/
| * a9a97be Work3
| * a4253c8 Work2
| * 163c835 Work1
|/
* 4f5da46 Initial Commit

This might seem noisy, but we can condense it down by using the --merges flag of git log, which restricts us to just the merge nodes:


(develop) $ git log --decorate --graph --oneline --merges
* 17e4c5f (HEAD, develop) Merge branch 'hotfix-1.0.1' into develop
| * d534111 (tag: 1.0) Merge branch 'release-1.0'
* 77e7eac Merge branch 'release-1.0' into develop
* cdef254 Merge branch 'feature1' into develop
* 9239c56 Merge branch 'feature2' into develop

At this point, the benefit of using the --no-ff becomes clear; it is a way of documenting the merges such that they can be filtered by the git log command. In addition, the naming conventions of Git Flow enable a well described set of feature and release branches that is identifiable from the graph.

Whether or not Git Flow is right for you now, it's worth being aware of what it is (and why it follows the path that it does). Even if it's not something that works for you now, as your repositories scale up and your use of git grows, it may be worth coming back to in the future. There's also a set of scripts available at the GitHub repository, which makes managing the individual branches easier.


Come back next week for another instalment in the Git Tip of the Week series.