Managing a project’s stability during development and release cycles is a problem that every software shop must face. There are many approaches to tackling this problem, and today I’m going to talk about GitFlow.
When a team is working on a new project, there are often many needs for source control management. These needs often consist of:
You may find this list lengthy, but I could easily add more. Keeping all these concerns in line can often feel like gymnastics, and there’s nothing worse than when a bad merge or revert ends with your team spending a good deal of time recovering. Source control management is a complex problem, and it’s hard for any approach to be perfect. In my experience, GitFlow works more often than not, and even if you make a mistake, following GitFlow makes recovery a lot easier.
Atlassian gives a great explanation on GitFlow at their website. Here’s an image from their site:
In this timeline, you can see 5 different branches, split by color code. Let’s craft a scenario to go along with the image:
Team has 5 branch channels that they manage in their source control.
Their release branch is ‘master’ and is represented by light blue.
Their develop branch is ‘develop’ and is represented by purple.
Their hotfix branches are under ‘hotfix/’ and are represented by white.
Their feature branches are under ‘feature/’ and are represented by green.
Their bug fix branches are under ‘bug/’ and are represented by turquoise.
Following these methods, any needs encountered in the core needs we outlined earlier were adequately handled. The release channel was isolated from development channels but easily patched from the hotfix channels between development iterations. The team maintained a central develop branch to keep the team in sync. Features and Bug Fixes were implemented in isolation. The team was able to quickly update any channel with new changes and merge conflicts were reduced or avoided entirely.
The core concept behind GitFlow is in the name: flow. When looking at your commit history, like in GitK, it is reminiscent of a forking river. GitFlow streamlines that flow of commits in such a way that it’s harder to make mistakes, and conflicts are less frequent.
GitFlow can be expanded to include more channels, but here is the basic setup that I use.
Release strategies should be determined by your team after considering all the requirements of your project. The common approach is to schedule releases and only release from the Master branch. When you’re ready to release, it’s a good idea to tag a commit for that release as a quick way to find the “last commit” for a release.
Following the GitFlow pattern, merge conflicts are reduced but still possible. To further ease the struggle against merge conflicts, “back-merging” should be practiced regularly by devs. Back-merging is when you merge the shared branch (develop in most cases) into the branch you are currently working on. If conflicts are found, they are usually minimal using this method. It also encourages the dev close to the conflict to be the one to handle the conflicts, which reduces risk (as opposed to a third party, like a QA team, trying to do it).
As a standard practice, devs should back-merge their shared branch into their working branch regularly. I suggest daily, or whenever changes are made to the shared branch by other devs, or at the very least, prior to creating a pull request.
We’ve talked a lot about GitFlow so far, but we’ve done little to experience it. Let’s put together a simple example and demo how GitFlow simplifies the merge strategy.
We have a project which two devs are working on simultaneously. The devs are working on separate features which share classes. While their features have similar goals, the goals are also different. This means that we expect to have conflicting changes between the two devs.
The devs start by pulling latest for the Develop branch. This branch consists of two simple classes:
The first dev will create a new branch, feature/1st-feature
He needs to add a name property to Class 1 and a Guid property to Class 2. He also chooses to add ctors.
The dev makes his changes, commits the work, and creates a pull request.
The second dev will create a new branch, feature/2nd-feature.
She needs to add name and guid properties to both classes. She decides to refactor both classes so that Id is always an int type. She decides to create ctors too, but explicitly makes all setters private.
The dev makes her changes and commits the work and creates a pull request.
We have two pending pull requests for features 1 & 2.
Let's complete the first pull request.
Yay! The 1st feature is now merged into develop. Other developers may now back-merge develop into their working branches to make sure they have the latest changes.
Now. let’s complete the second pull request. Git lets us know that the target branch has been updated, so we’ll re-evaluate.
As expected, we have encountered some merge conflicts due to two devs touching the same areas with conflicting changes.
At this point, the working branch is out-of-sync with the shared Develop branch. All that needs to be done now is to reach out to the owner of this branch and ask them to perform a back-merge. Let’s do this now.
First, they will need to pull the latest from Develop. They will check out Develop and perform a pull.
In this case, we just need to take everything from the source branch.
The dev commits the merge, and now the pull request is ready for completion.
Develop is now updated and other devs could now back-merge Develop again to get the latest changes. If a scheduled release was ready, we would then create a pull request from develop into master and tag the merge commit for the release.
If you look at your branch history in GitK or Visual Studio for your Develop branch now, you’ll see a nice clean tree:
If we wanted, we could revert the 2nd feature, and we would have a project with feature 1 only which should be still stable. Alternatively, we should also be able to revert the 1st feature and still have a stable branch afterwards. The process of back-merging helped us with merge conflict resolution and encouraged the original dev to be responsible for resolving it. We successfully isolated work for different devs, and we also maintained synchronization via back-merging.
In all my years developing, this merge strategy has always been the one that works for me the best. I hope you like it too!