On rebasing
Let’s start by setting up a straw man: Git’s rebase gives it an inherent advantage over Mercurial.
I’ve become a regular Mercurial user in the past year, whereas I’ve only dabbled in Git, mostly when contributing to other projects. Most of the Ruby Web development world (i.e. Rails, etc.), on the other hand, has switched full-bore to Git. Advocates of git sing the virtues of the branch-implement-rebase workflow; its detractors proclaim rebase harmful.
Rather than waste bits talking Git down or making apologia for Mercurial, though, I’ll describe how I use Mercurial to achieve the same workflows that Git users seem to espouse. I think the Mercurial way is cleaner, but I’m also pretty sure that we’re all newbies in the DVCS game.
Update: There was some good discussion on the Mercurial mailing list about this.
<span id="more-1135"></span>
<h2>Small topic branches against a changing trunk/master/default</h2>
The seemingly most-quoted use of rebase is this: you create a “topic” branch, i.e. a short-lived branch to develop a particular feature, and start implementing. Meanwhile, the master branch (or trunk to Subversion users, default branch to Mercurial users) has some changes committed to it that you want to pull in.
Now, when you finally finish implementing your feature, you want to submit a clear, linear patch set upstream. (See git-rebase(1) for a fuller description.) In Subversion, you would just refrain from committing any changes until your implementation work was finished. In Git, you’d commit as you go, then rebase your repository against the current master, and then push your changes upstream.
The first thing to do when you have the urge to do this is to ask yourself, do I actually need to push a linear revision history? If you’re working on a branch to implement more than one feature, or doing a major subsystem rewrite, you should probably just fork off a named branch and let the revision graph make some cross-connects. On the other hand, if you’re working on a small, quick-to-implement patch that you’ll submit via e-mail, you probably do want this workflow.
In Mercurial, you would use the Mercurial Queues extension (mq, distributed with Mercurial ) to do this. What I do is as follows; for an alternate (probably better) strategy, see MqMerge on the Mercurial wiki.
# get working copy if we haven't already
$ hg clone http://example.com/upstream/repo
# initialize patch queue
$ hg qinit
# prepare to implement new feature.
# -m gives the commit message to be used.
$ hg qnew -m 'implement feature X' feature_x.patch
(edit/test/etc.)
# update the patch from our changes
$ hg qrefresh
# pop all patches and pull upstream changes
$ hg qpop -a
$ hg pull -u
# push our patches back on top of the updated tree
$ hg push -a
To me, this is a cleaner solution than rebasing. Using rebase, your local topic branch can’t be cloned by anyone else, or you’ll risk corrupting the version history. mq builds in this understanding. It won’t let you push changes upstream unless you either pop or finish (commit as real changesets) all your patches. Moreover, you can safely let others clone your repository by first popping all patches. They get your clean repository and none of your patches.
If you want to share your patches, you can use hg qinit -c to make the patch queue a repository in its own right. Then others can clone your repository, and then clone your patch queue, to help you test your patches.
Migrating a topic branch from one upstream branch to another
A second common use of git rebase is to migrate a set of changes on one branch to another. This is the example for rebase --onto used in git-rebase(1).
In Mercurial, you’d use the transplant extension (also distributed with Mercurial) to do this. Using the example from git-rebase(1), say you have this:
o---o---o---o---o default
\
o---o---o---o---o next
\
o---o---o topic
and you want to move the changesets in the topic branch back onto default. I’d do this:
$ hg clone http://example.com/upstream/repo
$ hg update default
$ hg transplant --branch topic
If one of the changesets causes a conflict, transplant will bail out to let you fix the merge by hand. You can then continue the in-progress transplant:
$ hg transplant --continue # or -c
Problems and other cases
There are some unresolved issues with the above. Using mq requires that you plan your next commit before you start implementing it; I find this to be extra mental overhead that’s not really necessary. It’s also not particularly obvious how to collaborate safely on shared patch queues. I think what you’d end up doing is having patches that alter the effects of previous patches, all stacked together. Since each patch is a commit, this is effectively the same as a normal revision history, but I don’t know if the patch-queue-as-repository setup works as I think it would in this case.
There also appears to be no --abort switch for the transplant command. One could be implemented easily, using the strip command from mq.
Finally, I’m sure there are more use cases for git rebase than the two I’ve mentioned here. I’m hoping to see some comments from you, dear readers, exploring edge cases and challenging new solutions.
4 Comments
Jump to comment form | comments rss [?] | trackback uri [?]