This will be standard and easily understandable to anyone who has already been working with git for a while. If you come from a centralized, old school version control background and you are thinking of switching to Git – which I heartily recommend – you will find the topic of this post awesome, almost magical. Not as magical as mini ponies but … you get the picture.
It is still awesome to me even though I’ve been working with git for a while already.
I am talking about automated merges from stable/maintenance branches to master.
Real World Scenario
Let me give you an example taken from the way the Stash development team works. The Stash team maintains, at any given point in time, several stable code lines that are out in the market (let’s limit the example to 2.1, 2.2 and 2.3 releases) while at the same time they merge feature branches into master in preparation for an upcoming release branch 2.4.
Now check out the magical bit. Whenever a maintenance fix is applied to any of the stable branches it is automatically merged(!!) in cascade to the master branch. No user interaction is required (except in the rare occasion when a conflict arises). (It is understood that a fix is committed to a stable branch only after the change has been peer reviewed, all tests pass, the builds are green and the performance metrics have not been affected).
As you can see a fix applied to the 2.2 code line will automatically be first applied to the 2.3 branch and then from the 2.3 branch to master. All without user interaction. How cool is that?
How is that even possible you ask? Can I have that too please? Well of course.
Automatic merges are the rule, not the exception
One of the areas where git excels at is merging code lines. git is so good at this that most of the times the merges just work. Except when they don’t. In the case of stable code lines, merge conflicts are even more rare given that the amount of code involved in those fixes is generally small.
How to setup automated merges, the manual way with hooks
Let me show you how you can implement a similar workflow. First let’s write something that will accomplish it on a local machine.
The first part of the puzzle is to write a script that you can run after you’ve committed a fix on a stable branch:
#!/bin/sh
# Automatically merge the last commit through the following branches:
# 2.1 -} 2.2 -} 2.3 -} master
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
LAST_COMMIT=$(git rev-list -1 HEAD)
echo Automatically merging commit $LAST_COMMIT from $CURRENT_BRANCH rippling to master
case $CURRENT_BRANCH in
2.1)
git checkout 2.2 && git merge $CURRENT_BRANCH
git checkout 2.3 && git merge 2.2
git checkout master && git merge 2.3
git checkout $CURRENT_BRANCH
;;
2.2)
git checkout 2.3 && git merge 2.2
git checkout master && git merge 2.3
git checkout $CURRENT_BRANCH
;;
2.3)
git checkout master && git merge 2.3
git checkout $CURRENT_BRANCH
;;
esac
(Please bear with me as I leave the error conditions and the conflicts scenarios out of this example as I just want to highlight the potential for workflow automation. In fact I will show you a more robust, visual and less error prone way to accomplish the same thing if you read on.)
Save it as auto-merge.sh and make it executable with:
chmod 755 auto-merge.sh
So after you check out a stable branch and commit a fix – that passes all tests and has been reviewed – run the script and you will get an output like the following:
$ git commit -am"PRJ-1 bugfix on 2.1"
Automatically merging commit e42cefd7597b04a619e7f3f1b8a0bdfd3514c296 from 2.1 rippling to master
Switched to branch '2.2'
Updating 032c6a6..e42cefd
Fast-forward
a.txt | 1 +
1 file changed, 1 insertion(+)
Switched to branch '2.3'
Updating 032c6a6..e42cefd
Fast-forward
a.txt | 1 +
1 file changed, 1 insertion(+)
Switched to branch 'master'
Updating 032c6a6..e42cefd
Fast-forward
a.txt | 1 +
1 file changed, 1 insertion(+)
Switched to branch '2.1'
[2.1 e42cefd] PRJ-1 bugfix on 2.1
1 file changed, 1 insertion(+)
Good starting point. Now we want to automate this process for the whole team. If you review all the available local and remote hooks you will quickly realize that the one we need is the update one, which kicks in anytime a single HEAD(i.e. branch) is pushed on to the server.
Catering for error conditions makes writing this in bash less than optimal so I spent some time cooking the update hook in Python. It grew a bit too much for me to show you the source in its entirety here. Check it out on bitbucket at automatic-merge-hook. How to use it? Edit the branches_flow variable (line 71), upload into the hooks folder of your bare repository on your server and you can have automatic merges too:
branches_flow = ['2.1','2.2','2.3', 'master']
Done! Now when you create a new commit on say the 2.1 branch you will get a nice ripple merge to master.
Here is the core Python logic that will automatically merge the branch with the next in the list:
def automatic_merge(head_before_start, latest, branches_flow):
log.debug('Auto-merging through: %s' % branches_flow)
reset = False
for branch in branches_flow:
onto = next(branch, branches_flow)
if onto:
exitcode = call('git checkout %s && git merge refs/heads/%s' % (onto, branch))
if exitcode:
log.debug('Merge of %s onto %s failed, must reset to original state' % (branch, onto))
call('git reset --hard %s' % (latest[onto]))
reset = True
break
if reset:
for to_reset in branches_flow:
call('git checkout %s && git reset --hard %s && git checkout %s' % (
to_reset, latest[to_reset], head_before_start))
sys.exit('Automated merges failed, reset to original state')
Automatic merges are included in Stash!
What if you’re not a fan of fiddling with hooks on the command line or on your remote server via ssh? There is an easier shrink-wrapped way to accomplish the same thing using Stash.
The only thing you have to do to take advantage of it is to describe the branch topology of your repository in the settings (or accept the default):
You’re done. Now the magic happens, automagically. Stash will recognize when there is a new commit on one of your release/ branches and will merge those changes upwards from your older branches to the newer ones up to master without user intervention. It will also correctly handle all the edge cases, for example, if there are conflicts Stash will open a pull request with the code changes so that the integration can still happen by hand.
If that is not awesome, I don’t know what is!
Bonus: sample branching model of the Stash development team
I’ve only skimmed over it earlier because it’s not the topic of this post but If you’re very familiar with git development workflows and promise not to be overwhelmed you might be interested to see the full branching model used by Atlassian’s Stash Development team.
If the picture is unclear fear not. The way the Stash team works will be explained in detail soon on this channel. Follow me @durdn on Twitter to get it fresh out of the presses.
(In the snippets above I had to use the unicode symbol & in place for the regular ampersand as WordPress is rather zealous with regards of escaping them, they would appear as “&”. Please replace the & with a regular ampersand. The source code on bitbucket is already fine, of course.).