I'm a huge fan of gitflow. It's made my life much easier. However, I've noticed recently that, as the codebase grows, features can become disorganized or unwieldy. gitflow-avh solves this problem incredibly well by allowing features to be based off any branch.

Note

This is not a gitflow overview. Check out the original branching model and the gitflow repo for more information on those.

Code

You can view the code related to this post in its repo. To see how I generated everything, check out the scripts directory.

Problem

After reading the intro, you might be thinking that my features are too large. On the contrary, I'm a pedant with a penchant for pigeonholing things. Let's say you want to add feature foo:

  • foo-dependency must be built for foo
  • foo-dependency must be tested
  • foo must be built
  • foo must be tested

Assume foo-dependency is only used by foo.

Feature Per Item

Your flow could look like this:

$ git flow feature start add-foo-dependency
$ touch src/foo-dependency.ext
$ git add src/foo-dependency.ext
$ git commit -m 'Create foo-dependency'
$ git flow feature finish --no-ff add-foo-dependency
$ git flow feature start test-foo-dependency
$ touch tests/foo-dependency.ext
$ git add tests/foo-dependency.ext
$ git commit -m 'Test foo-dependency'
$ git flow feature finish --no-ff test-foo-dependency
$ git flow feature start add-foo
$ touch src/foo.ext
$ git add src/foo.ext
$ git commit -m 'Create foo'
$ git flow feature finish --no-ff add-foo
$ git flow feature start test-foo
$ touch tests/foo.ext
$ git add tests/foo.ext
$ git commit -m 'Test foo'
$ git flow feature finish --no-ff test-foo
$ git log --graph --all --topo-order --decorate --oneline --boundary --color=always
*   03d94f9 (HEAD -> dev) Merge branch 'feature/test-foo' into dev
|\  
| * 842ac3a Test foo
|/  
*   2a84d06 Merge branch 'feature/add-foo' into dev
|\  
| * 052adc7 Create foo
|/  
*   a9b2d78 Merge branch 'feature/test-foo-dependency' into dev
|\  
| * 7cdf67e Test foo-dependency
|/  
*   14cb44b Merge branch 'feature/add-foo-dependency' into dev
|\  
| * ba5c2c8 Create foo-dependency
|/  
* a2a682a (master) Initial commit

I'm not a huge fan of this approach, because add-foo-dependency and add-foo both merge untested code directly into the dev branch. While each feature does exactly one thing, the one thing sorta causes organizational problems. I don't like untested code floating around in a main branch when I can avoid it.

Two Features

It could also look like this:

$ git flow feature start add-foo-dependency
$ touch src/foo-dependency.ext
$ git add src/foo-dependency.ext
$ git commit -m 'Create foo-dependency'
$ touch tests/foo-dependency.ext
$ git add tests/foo-dependency.ext
$ git commit -m 'Test foo-dependency'
$ git flow feature finish --no-ff add-foo-dependency
$ git flow feature start add-foo
$ touch src/foo.ext
$ git add src/foo.ext
$ git commit -m 'Create foo'
$ touch tests/foo.ext
$ git add tests/foo.ext
$ git commit -m 'Test foo'
$ git flow feature finish --no-ff add-foo
$ git log --graph --all --topo-order --decorate --oneline --boundary --color=always
*   76dada7 (HEAD -> dev) Merge branch 'feature/add-foo' into dev
|\  
| * 54d82ae Test foo
| * 152b98c Create foo
|/  
*   1d841a7 Merge branch 'feature/add-foo-dependency' into dev
|\  
| * 0e062b5 Test foo-dependency
| * d730279 Create foo-dependency
|/  
* ac908e3 (master) Initial commit

While this avoids merging untested code, it merges totally useless code with foo-dependency. If there's any appreciable time between add-foo-dependency and add-foo, someone else might create a feature that removes foo-dependency by merit of its unnecessary inclusion.

One Feature

Or it could look like this:

$ git flow feature start add-foo
$ touch src/foo-dependency.ext
$ git add src/foo-dependency.ext
$ git commit -m 'Create foo-dependency'
$ touch tests/foo-dependency.ext
$ git add tests/foo-dependency.ext
$ git commit -m 'Test foo-dependency'
$ touch src/foo.ext
$ git add src/foo.ext
$ git commit -m 'Create foo'
$ touch tests/foo.ext
$ git add tests/foo.ext
$ git commit -m 'Test foo'
$ git flow feature finish --no-ff add-foo
$ git log --graph --all --topo-order --decorate --oneline --boundary --color=always
*   5f83929 (HEAD -> dev) Merge branch 'feature/add-foo' into dev
|\  
| * dc4edaa Test foo
| * 20e3801 Create foo
| * aea02da Test foo-dependency
| * 113e27f Create foo-dependency
|/  
* 8bff3c8 (master) Initial commit

If foo is a bit more complicated than touching a few files, this gets messy fast. foo-dependency and foo (as well as their associated tests) will probably have a ton of commits, which defeats the purpose of splitting out the branches.

Solution

The gitflow-avh project attempts to update and extend gitflow with some useful features. As far as I know, it's a drop-in replacement for gitflow, so you won't notice a difference.

Installation

See the official docs for detailed instructions. Depending on your OS, you might be able to find it via a package manager as gitflow-avh. If not, you can install manually without much trouble:

install-gitflow-avh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash
# This is basically the wiki's installation script except more complicated

if which wget >/dev/null; then
wget --no-check-certificate -q https://raw.githubusercontent.com/petervanderdoes/gitflow-avh/develop/contrib/gitflow-installer.sh
elif which curl >/dev/null; then
curl -fLO https://raw.githubusercontent.com/petervanderdoes/gitflow-avh/develop/contrib/gitflow-installer.sh
else
echo 'Unable to download installer'
exit 1
fi
sudo bash gitflow-installer.sh
rm -f gitflow-installer.sh

Using Subfeatures

Vanilla gitflow bases all features off the dev branch.

$ git flow feature start do-something
Switched to a new branch 'feature/do-something'

Summary of actions:
- A new branch 'feature/do-something' was created, based on 'dev'
- You are now on branch 'feature/do-something'

Now, start committing on your feature. When done, use:

git flow feature finish do-something
$ git flow feature finish do-something
Switched to branch 'dev'
Already up to date.
Deleted branch feature/do-something (was 6c3fe58).

Summary of actions:
- The feature branch 'feature/do-something' was merged into 'dev'
- Feature branch 'feature/do-something' has been locally deleted
- You are now on branch 'dev'

With gitflow-avh, you can base features off any branch, which means when you finish it, it's merged back into the source branch.

$ git flow feature start do-something
Switched to a new branch 'feature/do-something'

Summary of actions:
- A new branch 'feature/do-something' was created, based on 'dev'
- You are now on branch 'feature/do-something'

Now, start committing on your feature. When done, use:

git flow feature finish do-something
$ git flow feature start subtask feature/do-something
Switched to a new branch 'feature/subtask'

Summary of actions:
- A new branch 'feature/subtask' was created, based on 'feature/do-something'
- You are now on branch 'feature/subtask'

Now, start committing on your feature. When done, use:

git flow feature finish subtask
$ git flow feature finish subtask
Switched to branch 'feature/do-something'
Already up to date.
Deleted branch feature/subtask (was 6c3fe58).

Summary of actions:
- The feature branch 'feature/subtask' was merged into 'feature/do-something'
- Feature branch 'feature/subtask' has been locally deleted
- You are now on branch 'feature/do-something'
$ git flow feature finish do-something
Switched to branch 'dev'
Already up to date.
Deleted branch feature/do-something (was 6c3fe58).

Summary of actions:
- The feature branch 'feature/do-something' was merged into 'dev'
- Feature branch 'feature/do-something' has been locally deleted
- You are now on branch 'dev'

Problem Solved with Subfeatures

$ git flow feature start add-foo
$ git flow feature start add-foo-dependency feature/add-foo
$ git flow feature start create-foo-dependency feature/add-foo-dependency
$ touch src/foo-dependency.ext
$ git add src/foo-dependency.ext
$ git commit -m 'Create foo-dependency'
$ git flow feature finish --no-ff create-foo-dependency
$ git flow feature start test-foo-dependency feature/add-foo-dependency
$ touch test/foo-dependency.ext
$ git add tests/foo-dependency.ext
$ git commit -m 'Test foo-dependency'
$ git flow feature finish --no-ff test-foo-dependency
$ git flow feature finish --no-ff add-foo-dependency
$ git flow feature start create-foo feature/add-foo
$ touch src/foo.ext
$ git add src/foo.ext
$ git commit -m 'Create foo'
$ git flow feature finish --no-ff create-foo
$ git flow feature start test-foo feature/add-foo
$ touch tests/foo.ext
$ git add tests/foo.ext
$ git commit -m 'Test foo'
$ git flow feature finish --no-ff test-foo
$ git flow feature finish --no-ff add-foo
$ git log --graph --all --topo-order --decorate --oneline --boundary --color=always
*   bd73c92 (HEAD -> dev) Merge branch 'feature/add-foo' into dev
|\  
| *   4ee685f Merge branch 'feature/test-foo' into feature/add-foo
| |\  
| | * 6ca71ec Test foo
| |/  
| *   2472346 Merge branch 'feature/create-foo' into feature/add-foo
| |\  
| | * c4f870a Create foo
| |/  
| *   e081189 Merge branch 'feature/add-foo-dependency' into feature/add-foo
| |\  
|/ /  
| *   7334e3e Merge branch 'feature/create-foo-dependency' into feature/add-foo-dependency
| |\  
|/ /  
| * bb4c45e Create foo-dependency
|/  
* 26ddd97 (master) Initial commit