To merge or to rebase, that is the question. I prefer rebase. The linear history preserves the utility of
git bisect. Also, rebase forces developers to resolve their conflicts where they are introduced instead of in one possibly big and unreadable merge commit at the end. Of course, rebase can be tricky and cause some headaches. But this is not the next installment of rebase vs. merge. Here I describe the simple rebase-friendly branching model that we use here at Urban Mapping. This process is inspired in part by gitflow, which is a worthy read and beautifully presented. And of course gitflow can work with rebase to some extent. But that beautiful gitflow documentation is written with merge in mind. So to communicate when to rebase, I invited the usual help from Bob and Alice, and their release manager Reggie.
Step 1: Release 1.0
The master branch is where the latest developments go. It’s the end of the 1.0 development cycle. A code freeze is declared and intensive testing begins. Once the code stabilizes, Reggie tags the release and pushes it:
git tag 1.0 git push origin 1.0
The release automation scripts can now deploy version 1.0 something like this:
git clone firstname.lastname@example.org:urbanmapping/myproject.git cd myproject git checkout -b release-1.0 1.0
Hooray. 1.0 is out the door. Now on to the 2.0 development cycle.
Alice is working on a new green feature, and Bob is working on a new purple feature. They start by creating appropriate feature branches locally from the master branch. Here’s how Alice does it for the green feature:
cd /path/to/myproject git fetch -a git checkout -b green origin/master [work, git commit, git rebase -i, git commit --amend, etc.] git push origin green:green
fetch grabs anything from the upstream master branch. Then
checkout creates a new green branch in Alice’s local tree and checks it out. Now actual work can ensue. Finally, the
push creates the new green feature branch in the origin tree. The point of this last step is to back up the branch, and possibly to share the green with other developers. Bob uses the same commands to create his purple feature branch.
Step 3: Push features to master
After the developers are satisfied with their features, they push them to master. Alice goes first:
git fetch -a git rebase origin/master git push origin green:master
By the way, this all happens with the green branch checked out. As in the above step,
fetch ensures that the master branch is up to date with the upstream.
rebase will set aside the green patches, bring in any new patches from the master branch, then put the green patches back on top. Of course at this moment master does not have any new patches, so
Current branch green is up to date. Finally,
push pushes the green patches to master.
Bob goes next. He executes the same commands as Alice. However, because the green features are already in master, the
fetch grabs them and stores them locally. Subsequently, the
rebase inserts the green patches resulting in the state shown below:
If any of Bob’s purple patches conflict with any of Alice’s green patches, Bob will be prompted to resolve the conflicts before he can push to master. If Bob attempted to push without first rebasing, his push would fail with a
non fast-forward error. He could, of course, push with the
--force, but this would blow away Alice’s green patches. So he shouldn’t do that.
Step 4: Release 2.0
Now let’s suppose that this represents all the work for the next release. Reggie tags the release and deploys it as described above. Hooray. Another step forward. The green and purple feature branches can now be deleted.
Step 5: Create next round of features
On to the next release cycle! Alice starts up an orange feature branch as described above, and pushes to master to make the next step forward. Meanwhile, a critical bug is discovered in production and Bob is tasked to fix it.
Step 6: Prepare release 2.1, a hotfix on 2.0
Bob must fix the production bug. But where should he push his code? He could fix the bug at the end of master and then push, but Alice’s orange feature is not really tested and reviewed yet. And Reggie will not accept that in production! Instead, Bob creates a local release branch based on the 2.0 tag and prepares the hotfix:
git checkout -b release-2.1 2.0 [work, git commit, git rebase -i, git commit --amend, etc.]
checkout creates a local branch called release-2.1 where the hotfix patch will be created. After getting the red patch in place just as he likes it, Bob tags the hotfix appropriately and pushes it:
git tag 2.1 git push origin 2.1
tag creates the tag for the new version, and the
push ensures that the fix is available in the origin tree. Note that Bob never actually pushes this release branch. He only pushes the tag. Now Reggie’s release scripts can grab the new version in the same way as before:
git clone email@example.com:urbanmapping/myproject.git cd myproject git checkout -b release-2.1 2.1
Step 7: Push hotfix patches to master
Finally, Bob’s 2.1 patch must make it to master somehow. To achieve this, he rebases the release branch to master and pushes it, just like with any other feature branch.
git fetch -a git rebase master git push release-2.1:master
When first discussing this step with colleagues, I was skeptical about having hotfix commits floating around in the origin tree that are not reachable by a branch. We considered some alternatives. One would be to tolerate
git merge for hotfixes. After all, hotfixes are generally made up of a minimal number of small patches. If the patches are many and/or large, then the bug they are meant to fix is probably best addressed by a downgrade instead of a hotfix. This mitigates most of my resistance to merge. Specifically, if the hotfix patches causes a merge conflict, that conflict is probably small and easy to ascribe to a specific patch. So this is one alternative.
Another alternative that we considered was to host permanent release stabilization branches in the origin tree. Under this model, hotfixes would go on the release stabilization branch and be cherry-picked over to the tip of master. For a project that delivers supported software as opposed to a service this may be a sensible alternative because it provides a clean place to succinctly reproduce and resolve customer issues.