
Using git-flow to Improve Software Delivery
We're extremely fortunate to have reliable version control systems in software development. Whilst there are a variety of workflow, tools and GUIs available to assist with managing team delivery, we'll be investigating Git Flow by Vincent Driessen that was published in 2010.
Git Flow is predominately useful for versioned or projects not relying on continually deploying multiple times a day from the master
branch. This is because we're using the concept of a develop
branch to release
new versions of our application into master
at a specified time, giving us an explicitly tagged release.
For a simpler approach that's effective in fast-paced projects that rely on several daily automated merges/deployments, the GitHub Flow will likely be an easy to implement alternative.
Let's dive in and investigate git-flow
with a mobile application using Flutter. The actual code here is of little significance. It's all in the Git, baby!
Project Setup
Let's start by installing the git-flow
CLI tool.
Installing the git-flow CLI tool
To install git-flow
on your respective platform, check out the installation instructions here. We're using git-flow-avh
here as it seems to be better supported than the original git-flow repository.
Although we can accomplish the same ideas here without using a CLI tool, the git-flow
tool makes it as simple as typing commands like:
# Initialise Git Flow
$ git flow init
# New branch at feature/contacts
$ git flow feature start contacts
# feature/contacts merged into develop branch
$ git flow feature finish contacts
James M Greene has a great Markdown document which compares the git-flow
commands to their underlying git
calls if you're interested in knowing exactly what each command does. You can find that here.
New Project
To practice git-flow, go ahead and create a new project in a language/framework of your choice. I've elected to use Flutter for this, but the code is not of any relevance.
If you're following along, run the following in your terminal:
$ flutter create flutter_flow
$ cd flutter_flow
$ code . (or your favourite editor)
This will create a counter application where the number is increased every time a user taps a button.
Initialising a Git Repository
Flutter doesn't create a Git repository for us by default when we create a new project. Let's initialise one now:
$ git init
Initialized empty Git repository in /flutter_flow/.git/
We can stage all files inside of the starter project by using:
$ git add -A
Finally, we can commit these as the "first commit" which will act as as the starting timeline for our project.
$ git commit -m "Initialise project"
Initialising git-flow
Now that we've got a base that we can compare our changes to, we can go ahead and initialise git-flow within this project:
$ git flow init
If you get a git-flow cannot be found error, then ensure you've installed the git-flow tool and restarted your terminal.
We're then asked a variety of questions which I've elected to keep as the default values:
Which branch should be used for bringing forth production releases?
Branch name for production releases: [master]
Branch name for "next release" development: [develop]
How to name your supporting branch prefixes?
Feature branches? [feature/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
The first thing that we'll notice is that we've been switched to the develop
branch, and that branch matches the commit
id of master
(for now):

New Feature
Our stakeholders have came to us and said that our mobile application needs a new feature. They want to be able to:
- List a varying number of
Contact
s
Apparently the counter application that we currently have has nothing to do with the ability to track Contacts. What do they know anyway? Pff.
We can use git-flow
to create a new feature like so:
$ git flow feature start contacts
Switched to a new branch 'feature/contacts'
Summary of actions:
- A new branch 'feature/contacts' was created, based on 'develop'
- You are now on branch 'feature/contacts'
Now, start committing on your feature. When done, use:
git flow feature finish contacts
We've now got three branches. Once again, they're all currently based off the latest commit ID, but that'll change in a second:

Contact
We'll start off by creating a very small Contact
model, effectively consisting of a name
:
@immutable
class Contact {
final String name;
const Contact({
this.name,
});
}
We can then use this Contact
model to create a List<Contact>
which will be used in our UI soon:
const List<Contact> contacts = [
Contact(name: "Paul"),
Contact(name: "Dave"),
Contact(name: "Sarah"),
Contact(name: "Catherine"),
Contact(name: "Henry"),
];
Contact List Page
We can display the contacts
inside of a ListView
like so:
class ContactListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Contact List"),
),
body: ListView.builder(
itemCount: contacts.length,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(
contacts[index].name,
),
),
),
);
}
}
Finally, we'll need to update main.dart
to set the home
to our ContactListPage
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Contact List',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: ContactListPage(),
);
}
}
Committing Changes
We can commit the changes to our feature branch by using:
$ git add .
$ git commit -m "Added contact list and mock data"
[feature/contacts 4ee7abd] Added contact list and mock data
4 files changed, 60 insertions(+), 113 deletions(-)
rewrite lib/main.dart (94%)
create mode 100644 lib/src/contacts/application/constants/contact_constants.dart
create mode 100644 lib/src/contacts/domain/models/contact_model.dart
create mode 100644 lib/src/contacts/presentation/pages/contact_list_page.dart
Finalising Contacts Feature
We've shown our stakeholders the new Contact List feature and they're ecstatic. They want this in production as soon as possible. Steady on. Before we do that we must "finish" this feature on our end and merge it into the develop
branch.
If we look at the commit ids of our branches, now our feature/contacts
is ahead of everything else:

Let's run the following git-flow
command:
$ git flow feature finish contacts
This gives us the following messages (I've edited it a little to be more readable):
Switched to branch 'develop'
Updating 31ee053..4ee7abd
Fast-forward
4 files changed, 45 insertions(+), 98 deletions(-)
- create mode 100644 application/constants/contact_constants.dart
- create mode 100644 domain/models/contact_model.dart
- create mode 100644 presentation/pages/contact_list_page.dart
Deleted branch feature/contacts (was 4ee7abd).
Summary of actions:
- The feature branch 'feature/contacts' was merged into 'develop'
- Feature branch 'feature/contacts' has been removed
- You are now on branch 'develop'
We now don't have the feature/contacts
branch anymore. The develop
branch has the latest 4ee7abd
commit id as seen in our feature/contacts
branch.

If we were working with others on our team, or wanted it available on our origin, we could've called:
$ git flow feature publish contacts
This would've pushed the feature/contacts
branch to our origin
.
Starting a Release
So far we've achieved the following:
- Created the
feature/contacts
branch and surrounding feature - Finalised the
feature/contacts
branch and updated thedevelop
branch to match.
The last thing outstanding to satisfy our stakeholder is:
- Release the new feature to production.
In order to do this, we'll start a release build using git-flow
:
$ git flow release start 0.1.0
This has the following side effects:
Switched to a new branch 'release/0.1.0'
Summary of actions:
- A new branch 'release/0.1.0' was created, based on 'develop'
- You are now on branch 'release/0.1.0'
Follow-up actions:
- Bump the version number now!
- Start committing last-minute fixes in preparing your release
- When done, run:
git flow release finish '0.1.0'
We once again have three branches now, but this time instead of a feature
it's a release
:

At this point, no more feature/X
branches should be merged into the release/0.1.0
branch. Any late bug fixes or documentation oriented changes can happen here, but not much else should happen on this branch.
As we have no further changes to add at this point, let's finalise the release
branch by using:
$ git flow release finish '0.1.0'
We'll have to set a commit message for our merge and tag. After doing so, we get:
Summary of actions:
- Latest objects have been fetched from 'origin'
- Release branch has been merged into 'master'
- The release was tagged '0.1.0'
- Release branch has been back-merged into 'develop'
- Release branch 'release/0.1.0' has been deleted
We can see that our tag was created and the release was assigned like so:
$ git show 0.1.0
tag 0.1.0
Tagger: Paul Halliday
Date: Sun Dec 6 15:36:21 2020 +0000
Release 0.1.0
commit 77682d1e9865b78c3761fe7e5ec056bd4b783b6d (HEAD -> master, tag: 0.1.0)
Merge: 31ee053 4ee7abd
Author: Paul Halliday
Date: Sun Dec 6 15:34:36 2020 +0000
Merge branch 'release/0.1.0'
If we wanted the tags to appear on our origin, we'd have to enable the auto pushing of annotated tags inside of our git config like so:
git config --global push.followTags true
If we didn't want to push every tag then we can do this manually instead:
# 0.1.0 tag only
$ git push origin 0.1.0
# All tags in project
$ git push --tags origin
Our final branch structure now looks like this:

Hotfixes
Uh oh. We just had a conversation with one of our stakeholders. They're a little upset that our Contacts list doesn't contain the head of the board. They want this to be updated, today.
Our Contact
list isn't based off any database and instead is just a file inside of our repo, so this just needs updating. The problem is, we've already deployed our release to production.
So, we think to ourselves... is this a new feature
? No. It's a hotfix
!
New Hotfix
Hotfixes can be used to patch production (master) releases and we can create a new one like so:
$ git flow hotfix start 0.1.1
This has the following side effects:
Switched to a new branch 'hotfix/0.1.1'
Summary of actions:
- A new branch 'hotfix/0.1.1' was created, based on 'master'
- You are now on branch 'hotfix/0.1.1'
Follow-up actions:
- Bump the version number now!
- Start committing your hot fixes
- When done, run:
git flow hotfix finish '0.1.1'
Notice the key branch difference here. This hotfix
branch is based off master
and not the current develop
branch.
In our scenario the develop
branch doesn't have any extra merged features, but imagine a larger team where this branch could've changed a myriad of ways prior to creating this hotfix
.
We now have four branches as expected:

We can add the stakeholder to the list like so:
const List<Contact> contacts = [
Contact(name: "Paul"),
Contact(name: "Dave"),
Contact(name: "Sarah"),
Contact(name: "Catherine"),
Contact(name: "Henry"),
// New
Contact(name: "Giles")
];
We'll then need to commit this to our hotfix/contact-names
branch:
$ git commit -am "Updated contact list"
[hotfix/0.1.1 3063eaa] Updated contact list
1 file changed, 3 insertions(+)
We're ready to have this merged back into master
and develop
.
Finalise Hotfix
This can be finalised in a similar way to every other git-flow
feature. By running the following:
$ git flow hotfix finish 0.1.1
This has the following side effects:
Switched to branch 'master'
Merge made by the 'recursive' strategy.
- lib/src/contacts/application/constants/contact_constants.dart | 3 +++
1 file changed, 3 insertions(+)
Switched to branch 'develop'
Merge made by the 'recursive' strategy.
- lib/src/contacts/application/constants/contact_constants.dart | 3 +++
1 file changed, 3 insertions(+)
Deleted branch hotfix/0.1.1 (was 3063eaa).
Summary of actions:
- Latest objects have been fetched from 'origin'
- Hotfix branch has been merged into 'master'
- The hotfix was tagged '0.1.1'
- Hotfix branch has been back-merged into 'develop'
- Hotfix branch 'hotfix/0.1.1' has been deleted
We now have two tags 0.1.0
and 0.1.1
:

If we take a look at our git log
for both the master
and develop
branch, we can see that our hotfix
has been applied:
$ git log
commit afb78ac902e183fd059d25532aead01d0daabb92 (HEAD -> develop)
Merge: 4ee7abd 3063eaa
Author: Paul Halliday
Date: Sun Dec 6 16:11:34 2020 +0000
Merge branch 'hotfix/0.1.1' into develop
commit 3063eaa986ccfda676fd8d363876d4659b6b51c5
Author: Paul Halliday
Date: Sun Dec 6 16:10:42 2020 +0000
Updated contact list
commit 77682d1e9865b78c3761fe7e5ec056bd4b783b6d (tag: 0.1.0)
Merge: 31ee053 4ee7abd
Author: Paul Halliday
Date: Sun Dec 6 15:34:36 2020 +0000
Merge branch 'release/0.1.0'
Summary
In this article we looked at how to use git-flow in the majority of use-cases. It may not always be right for your project and requirements, but it does offer a workflow to work with release-based repositories.
developer.school Newsletter
Join the newsletter to receive the latest updates in your inbox.