Hi everyone,
Since switching to Mercurial, I've heard about several developers running into scenarios where rebasing would be a nice feature. Some quick research on my part reveals that Mercural can do this, and that there is more than one way to do it (tm) ;-)
First off, if you're unclear on what rebasing actually means, consider the following. You've cloned the default repository to work on a specific feature foo. You're committing changes to your foo branch, and then you feel the urge to pull the latest changes from the default branch. Your repo now looks like this:
a---b---C---D (foo) \ ---o---p---q (default)
Changeset q is now your repo tip, and your repo contains two heads. Mercurial will suggest that you merge these heads, thus creating this:
a---b---C---D-----E (foo) \ / ---o---p---q (default)
As you continue to develop in this fashion, you might end up with a history looking something like this, which looks kinda messy:
a---b---C---D-----E---F---G---H---I---J (foo) \ / / / ---o---p---q-------r---s-------t (default)
Since your foo branch really doesn't conflict with, or depend on anything going on in the default branch, you'd much rather have your history look like this:
a---b---o---p---q---r---s---t---C---D---F---G---I foo)
The previous graph shows your local foo changes rebased on top of the latest changeset from the default branch. Now you (presumably) know what rebasing means. Now, on to solutions:
How to do it with MQ ====================
Mercurial Queues lets you maintain your changes as a patch queue, and also allows you to arbitrarily reorder your patches. Your named patches are stored in .hg/patches/ and can be applied and unapplied to your tree using the hg qpush and qpop commands. Your applied patches will look like regular changesets in your history, but they have special tags that mark them as the property of MQ. When you pop and push these patches, you're actually manipulating the history of your local repository. For more information about Mercurial Queues, please refer to http://hgbook.red-bean.com/hgbookch12.html .
Using the above scenario:
a---b---C---D (foo) \ ---o---p---q (default)
C and D are two MQ changesets in your repository, and Mercurial pull has just suggested that you perform a merge. No way, you say, and do a "hg qpop -a", popping all your MQ changesets off the history, and then "hg up" to update your working copy to the new tip (revision q):
a---b---o---p---q (foo)
Then, you do "hg qpush -a" to push all your patches back on to the history as MQ changesets, thus:
a---b---o---p---q---C---D (foo)
As for the log messages of MQ changesets, we found that MQ will use any plaintext you insert at the beginning of the patch files stored in .hg/patches. To make sure an updated text appears in the log message, the patch must be popped, the text edited, and the patch pushed again.
When you are ready to turn an applied MQ changeset/patch into a regular changeset (e.g. when you want to push it to another repo), you use "hg qdel -r C" and "hg qdel -r D", referring to the above scenario.
How to do it with transplant ============================
The transplant extension can specifically be used to rebase changesets. In the same scenario as above, you have just pulled the latest changes from default:
a---b---C---D (foo) \ ---o---p---q (default)
So to rebase your C and D changesets you first need to update your working copy to the new tip (or q, if you will):
hg up -C tip
Then take the branch that revision D belongs to and rebase it onto the current working copy parent (q):
hg transplant -b D
The only catch is that your repo now looks like this:
a---b---o---p---q---c---d (default) \ C---D (foo)
c and d are copies of C and D. The capital C and capital D are still in your history. This is where "hg strip" comes in; this command can strip changesets from your history. With the above repo, you would want to strip the branch that begins with revision C, thus:
hg strip C
Which means you are now left with (don't forget hg update):
a---b---o---p---q---c---d (foo)
Warnings ========
Be warned that I am unsure how the transplant/strip operation will work if you are using named branches.
With these scenarios, you are munging your local history a lot, and you should beware not to do this with branches/repos you are sharing with others. Things will start to get confusing when changesets that have been pulled by others (or pushed somewhere by you) are disappearing from your repo.