One of Git’s selling points, some years ago, was that it has lightweight branching compared tools that came before it. As it happens Subversion was also launched with claimed lightweight branching compared to CVS. Subversion were true when they were made, it is just that Git’s branches take that to another level.

Anyway, it’s Git’s merges that are the truly lightweight thing in my opinion. This article tries to show how that is.

Merging back from a task branch with Git

Let’s make a single source file on master, make a branch for a task we’ve been assigned, change the file there, also change it on the master, then merge the task branch back to the master and delete the task branch:

#!/bin/sh
mkdir foo
cd foo
git init
echo "** Create trunk version of file"
echo "Efficiently unleash cross-media information without\nimmediate value. Dramatically\nmaintain clicks-and-mortar solutions without\nfunctional aspects." > file.txt
git add file.txt
git commit -m "initial version on master"
sleep 2
echo "** Create Temp Branch .."
git branch task001
git checkout task001
echo "** A change on task001"
perl -p -i -e "s/immediate/any/g" file.txt
git commit -am "A change on the task001 branch"
sleep 2
git checkout master
echo "** Modify master version of file"
perl -p -i -e "s/cross-media/socially-awkward/g" file.txt
git commit -am "Different change on master made after the change on branch"
sleep 2
echo "** Merge temp task001 into master"
git merge -s ours task001 -m "merge task001 branch back into master"
git branch -D task001

After the event, the history looks like this:

Each of those commits in Git could have a separate code review. That is apart from the last one - the auto-merge that was @merge task001 branch back into master@ and it has no diff associated with it. We have nothing review at the moment of merge in our test case. That’s the key take-away - there’s often no ceremony with the merge, and it’s truly lightweight.

The task branch made task001 is long gone (and was only a ‘local’ branch anyway), but the change made on it is retained. It doesn’t matter whether we keep the task branch or not in this case it does’t affect the merge history of the file. This is cool as it means your code review (if you’re reviewing commit by commit) is very efficient.

Incidentally, the multiple @sleep 2@ statements in the script above allow us to compare the actual order of commits via timestamps, with the visualization of the history (GitX in this case). 19:58:53 followed by 19:58:58 then 19:58:56 and the merge at 20:01:57. Interesting (but not wrong in this representation).

Trying the same thing with Subversion

Now we can’t play with local branches for a pure-subversion setup, so both trunk and the task branch task001 exist on the server. Here’s the script that’s a Subversion version of the Git script above:

# $1 is the name of the repo served locally.
#!/bin/sh
svn --username harry --password harry checkout svn://127.0.0.1/$1
echo "** Create trunk version of file"
echo "Efficiently unleash cross-media information without\nimmediate value. Dramatically\nmaintain clicks-and-mortar solutions without\nfunctional aspects." > $1/trunk/file.txt
svn add $1/trunk/file.txt
svn commit $1 -m "initial version on trunk"
sleep 2
echo "** Create Temp Branch .."
svn copy svn://127.0.0.1/$1/trunk svn://127.0.0.1/$1/branches/task001 -m "Create Branch"
svn up $1
echo "** A change on task001"
perl -p -i -e "s/immediate/any/g" $1/branches/task001/file.txt
svn commit $1 -m "A change on the task001 branch"
sleep 2
echo "** Modify trunk version of file"
perl -p -i -e "s/cross-media/socially-awkward/g" $1/trunk/file.txt
svn commit $1 -m "Different change on trunk made after the change on branch"
svn up $1
sleep 2
echo "** Merge temp task001 into trunk"
svn merge svn://127.0.0.1/$1/branches/task001 $1/trunk
svn ci $1 -m "merge task001 branch back into trunk"

The crucial commit is the merge back from task001 to trunk. The last commit - #7. Here’s what that looks like in Crucible:

But here’s the change that happened on the branch (#5):

They look the same. If we’d done a code review for #5 (the change on the branch), and some time later a code review for #7 (the merge back to trunk) we’d be duplicating work previously done :-(

It’s fair to say that the merge back to trunk for the Subversion and Perforce from a task branch, isn’t lightweight. Perforce is exactly the same. Mercurial and PlasticSCM are as lightweight as Git.

Last word on the importance of lightweight merging between branches

Facebook flipped from a Subversion server to Mercurial (plus tooling) in the last few days. They are doing a Trunk-Based Development (TBD) model like Google, which is not surprising seeing as ex-Googlers are there, and the economics of it are quite clear for enterprises as I’ve said maybe a hundred times. As a developer doing TBD, you only every sync/up/pull and commit/push to trunk. If there’s a merge at all it’s to working-copy and is infinitesimally small and instantaneously quick to get through. Given it’s to working copy, it (the merge) leaves no trace on the server/remote when it arrives there. Google and Facebook developers are not merging between branches. Well Release Engineers (or a bot under their command) might be if a single change-list/set is being cherry picked from trunk to a release branch for a bug fix. That branch may have been made from a release-tag minutes before, and that cherry-pick 99.99% of the time is auto-mergable. Moreover, the code-review for the fix (and approvals) were on the trunk, meaning it doesn’t need another code review, and there’s the added bonus on no regressions. It’s testing too is on trunk, and then a second time in a staging environment made from the release branch, before it gets deployed into production. Maybe that’s a lot of bots, CI and Selenium2 tooling doing that :)

Thus, despite the neatness of lightweight merging, I’m going to say there’s no need for all right thinking TBD teams to be envious.