Git temporary-branch workflow in Mercurial
04 May 2014While I prefer Mercurial to Git in large part because I don't entirely agree with Git's “history is flexible” philosoply, I have come to like its idea that features are developed on temporary branches that are then squashed into a single commit on the main branch. This approach is used by my current company, so I decided to see if the work-flow could be used with Mercurial. Below is based on some notes I made when experimenting a few months back. As usual, caveat emptor.
Demonstration repository
For demonstration purposes, you can use the code below to create a proof-of-concept repository that has a default branch with incoming changes alongside your fix branch:- Create prior work
- echo "111" > file.txt hg add file.txt hg commit -m "Added 111" echo "222" >> file.txt echo "333" >> file.txt hg commit -m "Added 222 & 333"
- Create branch & make changes
- hg branch fix cat file.txt | sed 's/222/222A\n222B/g' > tmp.txt cat tmp.txt > file.txt hg commit -m "Amended 222" cat file.txt | sed 's/222A/222AA/g' > tmp.txt cat tmp.txt > file.txt hg commit -m "Amended 222A"
- Further work on default branch (in reality this would be
hg pull -b default
) - hg update default echo "444" >> file.txt hg commit -m "Added 444" echo "555" >> file.txt hg commit -m "Added 555"
- Merge in changes from default
- hg merge default
In reality the second and third stages (making changes and merging in default) would be done repeatedly, but the underlying principle are much the same.
The no-merges rebase approach
If your fix branch has not had any changes from default merged in since it was created, you can just do the following (add the--keep
parameter if you want fix to remain afterwards):
rebase -b fix -d default -collapse
Problem is that the rebase may well trigger merge collisions, and it will not work if there have been no changes to default since the branch was made.
The with-merges re-base method
One problem with doing the previous approach in cases where fix has had changes from default merged in, you end up having to do unnecessary merges, and I think it is only necessary to rebase from the last merge onwards. In fact if you've done a merge from default as the last step, all you really want to do it move that changeset, sans the family history from the fix branch. Firstly some preperation:hg update default hg tag target
You need to do the tag target
(a trick found elsewhere) in order to make a new head that is not an ancestor of the merged head, otherwise the rebase will fail. Once this is done, your repository should look like the following:
You are ready to rebase:
hg update default hg tag target hg rebase -s fix -d default -collapse
At the end of all this, you will end up with a collapsed revision on default that does not have any parentage in fix:
In principle it should be possible to use Collapse or Histedit to get rid of the changeset that added the target, but I have not looked into it so far.
The patch-apply method
The idea here is to get a patch that can be applied to the latest changeset in default that has no ancestors within fix. You firstly need to get a patch that has all the updates between the most recent default changeset, and the merged head sitting on fix:hg diff --rev default:fix > temp.patch
You then want to apply this patch to default, and then strip away the head that resulted from merging default into fix. In the latest Subversion strip
is in its own extension, but there is a good chance your distribution is an older build where it was still part of MqExtension. The only difference is how it is enabled.
hg update default hg patch temp.patch hg strip fix
Actually, as long as you haven't pushed any of the fix branch, you can strip it all away by repeatedly using hg strip fix
. Although Mercurial will do some sort of backups in the background, those who are paranoid about screwing stuff up can make the patch on a throwaway clone of the repository, then apply it to the original. I suspect this latter approach is more in line with intended Mercurial workflows.