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?
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!
1 comment:
Post a Comment