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.
--
mvh
Morten Brekkevold
UNINETT