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.
Source Control Management Needs
When a team is working on a new project, there are often many needs for source control management. These needs often consist of:
- The need to quickly make new releases, with a gatekeeping method prior to each release
- The need to keep all developers in sync
- The need for different developers working on different tasks to have an isolated environment free from merge conflicts and outside forces during development
- The need to isolate feature development from the release channels
- The need to isolate bug fixes from the other channels
- The need to quickly hotfix new releases for emergencies, isolated from current feature and bug iterations
- The need to easily merge hotfixes, bugs, and features into all channels for synchronization
- The need to easily revert changes, including entire features, from all channels.
- The need to avoid or reduce merge conflicts
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.
An In-Depth Example
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.
- The project began with the develop branch and the master branch with v0.1.
- After the v0.1 prototype, a bug in production was found. This is an emergency, so a hotfix branch was created to patch the master branch.
- Simultaneously, the team began developing the next feature iteration.
- The hotfix is confirmed and merged into the master and develop branches. V0.2 is published with the new hotfix.
- The team finishes their feature iteration and merges the feature to develop. Develop now has both the new features and the hot fix changes.
- At this time, two bugs are found after integrating these changes, and they are discovered before release. The team creates two bug branches, and after confirming the changes, merges them into both develop and master to update both release and develop channels. V1.0 is then released.
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.
How the Flow Works
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.
CORE BRANCH CHANNELS
GitFlow can be expanded to include more channels, but here is the basic setup that I use.
- Master – This is the ‘release’ branch and an effort to only merge in stable work to this branch should be maintained. Only develop, hotfix, and sometimes bug branches should be merged into Master.
- Develop – This is the branch the developers should work from. Work shouldn’t be done directly against the develop branch, it should be completed in a separate feature/bug/hotfix branch and merged in when the work is complete so that other devs can sync upstream when they choose.
- Hotfix – This is the branch used for hotfixes. Hotfixes are bugs that are found in production and can’t wait to be fixed until the next scheduled release. When this happens, usually a hotfix branch is created. When the hotfix is completed, it should be merged into both Develop and Master branches before being released.
- Features – These branches should be created and used by devs when implementing new features. The devs should always get latest from Develop before branching off develop. As other devs commit work to the Develop branch, long-standing feature branches should periodically back-merge the Develop branch to maintain synchronization with the Develop branch.
- Bugs – These branches should be created and used by devs when fixing bugs prior to a scheduled release. When bug fixes are completed, they should be merged into the Develop branch, or depending on circumstances, both the Develop and Master branch.
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.
Seeing It In Action
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.
THE MAGIC MOMENT - COMPLETING THE PULL REQUESTS
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.
Then they will switch to their working branch and merge the Develop branch into it.
Only 1 conflict was found. Let's peek into it.
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!