Rebasing in Mercurial

So, you’ve adopted git. Or maybe Mercurial. And you’ve started to use it. The problem is, every other commit reads some variant of, “Merged with stable,” “merged with base,” or some such. These commits convey no useful information, yet completely clutter your commit history. How do you make your history more useful?

The answer lies in rebasing. Rebasing is a technique made popular by git where you rewrite your not-yet-pushed patches so that they apply against the current remote tip, rather than against the tip of the repository you happened to last pull. The benefit is that your merge history shows useful merges—merges between major branches—rather than simply every single merge you did with the upstream repository.

Talking to a colleague of mine today about the upcoming features in Mercurial 1.1, I was surprised to hear him say, “Oh, great, so Mercurial will finally have rebasing!” Well, while the next version of Mercurial does add a pull --rebase command, all it does is automate some mq bookkeeping behind the scenes. If you want, you can do that mq bookkeeping explicitly right now.

Note: the following will work in modern versions of Mercurial, but using hg rebase and its sister command hg pull --rebase is a much, much easier way to do what the following describes, and has been available in Mercurial for over a year at this point. Before learning MQ, see whether your version of Mercurial has a rebase command by running hg help rebase. If it does, you’re probably better off using that.

Let’s take a look at my Copilot development repository, for example:

gozer:Host benjamin$ hg out
comparing with ssh://fcs//hg/copilot-helpers-devel
searching for changes
changeset:   1937:fe42e5e8e0cf
user:        Benjamin Pollack <bpollack@fogcreek.com>
date:        Mon Nov 24 14:49:30 2008 -0500
summary:     Make CPUpgrader a proper framework so that OneClick can include it

changeset:   1938:20033e199dc7
user:        Benjamin Pollack <bpollack@fogcreek.com>
date:        Mon Nov 24 20:08:06 2008 -0500
summary:     Undo accidental rename of Host to OneClick

changeset:   1939:127619345a57
tag:         tip
user:        Benjamin Pollack <bpollack@fogcreek.com>
date:        Mon Nov 24 20:12:39 2008 -0500
summary:     Add OneClick

gozer:Host benjamin$ hg in stable
comparing with ssh://fcs//hg/copilot-helpers-stable
searching for changes
changeset:   1940:6db7e59a82c5
tag:         tip
parent:      1936:3140f971d7ab
user:        Benjamin Pollack <bpollack@fogcreek.com>
date:        Mon Nov 24 12:38:14 2008 -0500
summary:     Added tag 000253 for changeset 3140f971d7ab

gozer:Host benjamin$

So, I’ve got three outgoing changes, and one incoming change—and the incoming change is just a tag addition. Having an explicit merge is overkill and simply clutters my history; there’s no benefit to having it listed. It’s the perfect candidate for a rebase commit.

Our battle plan, then, is as follows:

  1. Import all outgoing patches to mq patches
  2. Pop them off the patch stack
  3. hg pull from the stable repository
  4. Reapply my outgoing patches
  5. Convert my patches back into Mercurial changesets

Let’s give it a shot. My base commit is changeset fe42e5e8e0cf, so I need to import everything between that and tip into mq and then pop it off:

gozer:Host benjamin$ hg qimport -r fe42:tip
gozer:Host benjamin$ hg qpop -a
Patch queue now empty
gozer:Host benjamin$

If everything was successful, I should have no outbound patches…

gozer:Host benjamin$ hg out
comparing with ssh://fcs//hg/copilot-helpers-devel
searching for changes
no changes found
gozer:Host benjamin$

…and we indeed see that’s the case. That means I can now simply hg pull -u from the remote repository…

gozer:Host benjamin$ hg pull -u stable
pulling from ssh://fcs//hg/copilot-helpers-stable
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
gozer:Host benjamin$

…and then reapply my patches, and convert them back to changesets:

gozer:Host benjamin$ hg qpush -a
applying 1937.diff
applying 1938.diff
applying 1939.diff
gozer:Host benjamin$ hg qdelete -r qbase:qtip
gozer:Host benjamin$

And finally, verify that it worked as expected:

gozer:Host benjamin$ hg glog -l 4
@  changeset:   1940:0b08e0a83965
|  tag:         tip
|  user:        Benjamin Pollack <bpollack@fogcreek.com>
|  date:        Mon Nov 24 20:12:39 2008 -0500
|  summary:     Add OneClick
|
o  changeset:   1939:a730a92acb01
|  user:        Benjamin Pollack <bpollack@fogcreek.com>
|  date:        Mon Nov 24 20:08:06 2008 -0500
|  summary:     Undo accidental rename of Host to OneClick
|
o  changeset:   1938:01954591e76e
|  user:        Benjamin Pollack <bpollack@fogcreek.com>
|  date:        Mon Nov 24 14:49:30 2008 -0500
|  summary:     Make CPUpgrader a proper framework so that OneClick can include it
|
o  changeset:   1937:6db7e59a82c5
|  user:        Benjamin Pollack <bpollack@fogcreek.com>
|  date:        Mon Nov 24 12:38:14 2008 -0500
|  summary:     Added tag 000253 for changeset 3140f971d7ab
|
gozer:Host benjamin$

Perfect. I have a linear history, and have rebased my changes on top of the remote tip.

It’s definitely more involved than hg pull --rebase, but if you don’t want to wait for Mercurial 1.1, you’ve got the same functionality available right now.