Mercurial merge technique

This page is a mirrored copy of an article originally posted on the LShift blog; see the archive index here.

We’re using Mercurial here at LShift for much of our development work, now, and we’re finding it a great tool. We make heavy use of branches (”branch per bug”) for many projects, and this is also a pretty smooth experience. One issue that has come up is policy regarding merging the trunk (”default”) into any long-lived feature/bug branches: should you do it, or should you not?

My vote is that you should merge default into long-lived branches fairly regularly; otherwise, you have a big-bang, all-at-once nightmare of a merge looming ahead of you. If you do merge frequently, though, there’s one subtlety to be aware of: hg diff is not history aware, so in order to get an accurate, focussed picture of all the changes that have been made on your long-lived branch, you need to do one of two things:

  • either, merge default into your long-lived branch right before you merge the long-lived branch back into default, and run hg diff after that’s complete; or
  • (recommended) do a throw-away test-merge of the long-lived branch into default directly.

Imagine a history like this:

 (2) (3)
  |   |
   \ /

… where (1) is an ancestral revision, (2) is the default branch, and (3) is the long-lived branch - let’s call it “foo”.

Given this history, running hg update -C default (to make the working copy be the default branch, i.e. revision (2)) followed by hg diff foo will give you a misleading diff - one that undoes the changes (1) to (2) before doing the changes from (1) to (3). This is almost certainly not what you want!

Instead, run a test merge, by hg update -C default followed by hg merge foo and then plain old hg diff. Note that this modifies your working copy! You will need to revert (by hg update -C default) if you decide the merge isn’t ready to be committed.

The output of hg diff after the hg merge shows a history-aware summary of the changes that the merge would introduce to your checked-out branch. It’s this history-awareness (”three-way merge”) that makes it so much superior to the history-unaware simple diff (”two-way merge”).