Sunday, March 03, 2013

Mercurial Cherry-Picking: Branches vs Clones

At LogicBlox we're using Mercurial, and generally we are quite happy with it (before Mercurial we used CVS and later Subversion, so that wasn't too much of a competition for Mercurial). Mercurial offers quite a few alternative methods for managing diverging development though, which can be a bit confusing. In the early days we mostly used clones, except for the branches of work of individual developers. After some debates on the confusion of having two different methods in use, we switched from clones to branches.

This weekend I was doing a merge of two branches, and accidentally introduced a problem that was caused by earlier cherry-picking of changesets between the two branches. I thought this really should not have been possible, so I decided to sanity-check my understanding of Mercurial branches. It turns out that cherry-picking and Mercurial branches really do not work well together.

The example I used is as follows:


This seems fairly typical: at some point you branch for a specific version. During your development it turns out that you need a changeset (revision 1) on your default branch, so you cherry-pick that revision. Later you need to merge all 1.0 development into default, so you do a merge. Now what happens?


To setup the branches, we reuse this shell function in the following examples.

function init
{
  # initialize a repository
  hg init test1
  cd test1
  echo "foo" > file.txt
  hg add file.txt
  hg commit -m "first commit"

  # make a branch and make two consecutive changes
  hg branch 1.0
  echo "bar" >> file.txt
  hg commit -m "bar"
  echo "fred" >> file.txt
  hg commit -m "fred"
}


Option 1: Manually Making Changes

With this option the developer adds the line 'bar' manually to both branches. You would expect a merge conflict here, because the system really has no information that these changes are correlated, and perhaps should not assume that they are.

This scenario can be executed as follows:

function sample1
{
  init
  hg up default
  echo "bar" >> file.txt
  hg commit -m "bar"
  hg merge 1.0 || echo "conflict expected"
}

You will see that Hg nicely reports the merge failure:

merging file.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolve

Of course, applying a diff using diff/patch will result in the same result, because it does not matter how you modify the files.


Option 2: Mercurial Import/Export

The second option is to export the changeset using hg export, and import it using hg import. The hope here would be that Mercurial would correctly remember the origin of the changeset. To my surprise, it does not.

function sample2
{
  init

  hg export 1 > bar.diff
  hg up default
  hg import bar.diff

  hg merge 1.0 || echo "conflict??"
}

Result:
merging file.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolve


Option 3: Transplant/Graft

Puzzled by this result, let's try graft (which is roughly the 2.0 implementation of transplant)

function sample3
{
  init
  hg up default
  hg graft 1
  hg merge 1.0 || echo "conflict???"
}

Result:
merging file.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolve

Option 4: Clones

People who regularly work with Mercurial will of course immediately see that this is not a problem with clones. In fact, this is how you do distributed development at all. Just for the sake it, here is an example:

function sample4
{
  hg init test1
  cd test1
  echo "foo" > file.txt
  hg add file.txt
  hg commit -m "first commit"

  cd ..
  hg clone test1 test2

  cd test2
  echo "bar" >> file.txt
  hg commit -m "bar"
  echo "fred" >> file.txt
  hg commit -m "fred"
  hg export 1 > ../bar.diff

  cd ../test1
  hg import ../bar.diff
  hg pull ../test2
  hg update
}

Result:

1 files updated, 0 files merged, 0 files removed, 0 files unresolved

I'm not really a complete Mercurial guru, so I might be missing something here, but clearly this demonstrates that clones work better for this purpose of cherry-picking + merging. If anybody with deeper understanding of Mercurial branches can present a working example that would be great!