This revision control workflow is designed keep you sane.
There are just two types of long lived branches:
- master - This branch lives forever, and all changes are eventually merged to it.
- release branches - These are branched from master for each major release candidate.
Master
New features are implemented against master, by merging
feature branches into it. Feature branches should be as short lived as possible, because keeping them up-to-date is expensive. If your feature can't be implemented within a single sprint, consider
branching by abstraction.
Release Branches
Release branches are also known as "stabilization branches". Bugs that should be fixed for a release are first merged into the corresponding release branch, and then merged forward to all later releases and then to master.
Change as little code in release branches as possible, because merging to code that has later changed is expensive.
Each published build of a release branch should be tagged so that it can be easily identified and ordered.
Example
In June, we branch master to release/1, and start testing it.
In July, we branch master to release/2, and start testing it.
In August, we find a bug in release/1.
We branch release/1 to bugfix/555, and fix the bug on the bugfix/555 branch.
We merge bugfix/555 to release/1, to release/2, and to master.
Feature development in July and later does not create risk for release/1, and even bugfixes for release/2 do not create risk for release/1.
Tracking Bugfixes
It is straightforward to automatically merge most changes from older releases to newer, and to report regarding changes that have not yet been merged. We want to avoid regressing a bugfix in a later release just because we forgot to merge.
Cherry picking
If we merge a bugfix into a release branch and only realize afterwards that it should've been fixed in an earlier release, we must
cherry pick, and trivial merge as described in the previous section.
Hotfix Branches
It is good to decouple bug fixing from the choice of exactly which build is deployed into production, especially when there are multiple production environments that have different risk profiles. Therefore, the production tag could be different from the tip of the release branch. When this happens and we want to make an urgent fix for that production environment, we don't want to jump to the tip of the release branch. We haven't yet tested all the changes in the release branch, so we hotfix branch from the tag that we have tested.
Example
In September, we deploy the tag 1.2 of release/1 to the London production environment.
In October, we make five bugfixes to release/1 in preparation for its 1.7 New York release.
In November, we discover a critical global bug in release/1.
We branch tag 1.2 to hotfix/1.2-LN, and fix the bug on the hotfix/1.2-LN branch.
We deploy tag 1.2-LN1 of hotfix/1.2-LN to the London production environment.
When we have leisure, we merge hotfix/1.2-LN, to release/1 etc.
Advanced Topics
Gitflow and ProdFlow
The "gitflow" workflow from nvie has two problems: it confusingly changes the meaning of the "master" branch, and it doesn't clearly justify the cost of an additional long lived branch. Both problems could be resolved by tweaking the workflow to support multiple production environments. This is valuable if you actually need to support multiple production environments :). Let's call this improved workflow "prodflow".
Prodflow has release branches and master-as-trunk. Instead of one branch for release history, it uses a branch for each production environment, prefixed with "prod/". So when we deploy release/1 to production in London, we merge it to prod/LN.
An advantage of this approach is that it simplifies hotfixes. Neither gitflow nor prodflow really need hotfix branches; you can just merge bugfixes first into the prod branch and then into release branches and master. We don't need to keep track of the version in production.
Instead of: "
We branch tag 1.2 to hotfix/1.2-LN.
We branch hotfix/1.2-LN to bugfix/777...
" in the example above, that'd be just "We branch prod/LN to bugfix/777...".
Deserializing Merges
When there is a conflict, merging bugfixes from one release branch to the next can be a pain. Developers might not do it quickly, and this blocks merging subsequent bugfixes. We accumulate a backlog of unmerged changes, and it becomes increasingly onerous to work it down, especially with proper code review.
The solution is to "deserialize" merges. When bugfixes are made to different parts of the code, one needn't be blocked by the other. We can convey this to git using "rebase --onto", so that the parent commit of the bugfix branch is a commit that is already present on all the long lived branches. If we didn't rebase, ordinarily the parent commit of the bugfix branch would be the tip of its release branch, and the author of that commit might be procrastinating her merge.
Example
We merge bugfix/888 to release/1 but not to master.
Now we'd like to merge bugfix/999 to release/1 and master without being blocked by bugfix/888.
Before merging to release/1, we run: git rebase --onto origin/master bugfix/999
And it Just Works!
Conclusion
So that's the git release voodoo I learned over the last couple of years. I hope you find it useful!