Moving Mercurial Changesets to a New Clone

I’m setting up a new laptop, and I didn’t want to copy a whole mercurial clone from one laptop to another. All I wanted to do was move my not-yet-public changesets from my local repo to a new clone.

Turns out, with revsets and bundle, this isn’t too hard!

(OldRepo) $ hg bundle --base 423bdf7a802b -r 'author(mgaudet) and not public()' MattsChanges.bundle

Now, because the base revision is pretty old (because my eldest non-public change I wanted to bring along has a pretty old base), the bundle is big, relative to the number of changesets it's bringing across.

However, it applies nicely!

(NewRepo) $ hg unbundle ../MattsChanges.bundle 
adding changesets
adding manifests
adding file changes
added 20 changesets with 49 changes to 83824 files (+16 heads)

I got confused briefly when I tried to use hg import; it complained abort: ../MattsChanges.bundle: no diffs found which isn't a great error message, but I figured it out.

Cleaning a Mercurial Tree

(Should have mentioned this in the last post. Oops!)

You've littered your tree with debugging commits. Or you've landed a patch without pushing yourself, so it exists upstream already and you don't need your local copy. Slowly but surely hg wip becomes less useful.

You need hg prune.

Works just like your garden's pruning shears. Except, it's powered by Evolve, and so really it just marks changesets as obsolete, hiding them from view.

My Mercurial Workflow

When I joined Mozilla I decided to leap head first into Mercurial, as most of Mozilla's code is stored in Mercurial. Coming from a git background, the first little while was a bit rough, but I'm increasingly finding I prefer Mercurial's approach to things.

I really do find the staging area too complex, and branches heavierweight than necessary (see my earlier post Grinding my Gears with Git), and increasingly I really appreciate the plugin architecture that allows the creation of a Mercurial that works for you.

I have to say though, where I do envy git is that they've got some great looking docs theses days, and Mercurial is pretty far behind there, as often the best docs are those on the wiki, and it doesn't always feel very maintained.

With that in mind, let's talk about how I work with Mercurial. This post is heavily inspired by (and my workflow definitely inspired by) Steve Fink's post from last year.

Setup

I mostly used the initial setup of .hgrc provided by Mozilla's bootstrap.py. I have made a couple tweaks though:

Diff settings

[diff]
git = true
showfunc = true
unified = 8

The above stanza helps get diffs in a friendly format for your reviewers. Last time I checked, bootstrap.py didn't set unified = 8.

[color]
diff.trailingwhitespace = bold red_background

The SpiderMonkey team is a stickler for some things, including trailing whitespace. This colourization rule helps it stand out when you inspect a change with hg diff.

Aliases

  • I'm a huge fan of the wip extension that bootstrap.py sets up. It lists recent heads in graph format with a terse log, along with colourized output.
Green Hashes are draft revisions; blue are public. The red text highlights the current working directory parent, and yellow text are bookmark names.

Green Hashes are draft revisions; blue are public. The red text highlights the current working directory parent, and yellow text are bookmark names.

  • Mercurial's default hg log has a different philosophy than git log. Where git log shows you a relative view of history from your current working directory or specified revision, Mercurial's log command by default shows a global view of history in your repository. In a small project, I can imagine that making sense, but to be honest, 95% of the time I find hg log does the wrong thing for what I want. So:

    [alias]
    flog = log -f --template=wip

    Adds hg flog as an alias for following-log, which is closer in behaviour to the git log. The --template-wip bit uses the colourization and line formatting already provided for the wip extension.

    Honestly though, I use hg wip about 10x more often than I use hg {f}log.

Phases

One of the cool things about Mercurial is its well developed reasoning about history rewriting. One key element to that is the notion of 'phases' which help define when a rewrite of a changeset can happen. There's a darn good chance this will be a sensible default for you in your .hgrc:

[phases]
publish = false

Getting to work

I use a clone of mozilla-unified as the starting point. When I start working on something new, unless I have a good reason not to, I'll typically start work off of the most recent central tag in the repo.

$ hg pull && hg up central

Labelling (Bookmarks)

When working in Mercurial, one of the things you get to decide is whether or not you label you commits or not. This article goes into more detail, but suffice it to say, there's no requirement, as there is in in git, to label your lightweight branches (using Bookmarks).

I have experimented both with labelling and not these days, and I have to say, so long as I have hg wip, I think it's pretty reasonable to get by without bookmarks, especially as my commit messages typically end up having the bug numbers in them, so it feels almost like redundant info to label the branch. Maybe if you work on a project where commit messages aren't associated with a bug or other identifier labelling might be more useful.

When developing, I tend to use commits as checkpoints. Then later, what I will do is use history rewriting tools to create the commits that tell the story I want to tell. In Mercurial, this means you'll want to enable the Evolve and Histedit extensions (Facebook's chistedit.py is also nice, but not necessary). You'll also want rebase (unlike in git, rebase and histedit are two distinct operations).

A tip with histedit: When I first started with mercurial, I found myself more uncomfortable with histedit than I was with git. This was because I was used to doing 'experimental' history editing, always knowing I could get back to the repo state I stared from just by moving my branch pointer back to the commit I left behind.

Mercurial, with the evolve extension enabled, has a more complicated story for how history editing works. Over time, you'll learn about it, but in the mean time, if you want to be able to keep your old history: hg histedit --keep will preserve the old history and create the new history under a new head.

Evolve knows some pretty cool tricks, but I think I'll save that for later once I can explain the the magic a little more clearly.

More Extensions

absorb

Absorb is the coolest extension for mercurial I know of. What it does is automate applying edits to a stack of patches, exactly the kind of edits that show up in a code-review based workflow. If you create stacks of commits and get them reviewed as a stack, it's worth looking into this.

The best explanation I know of is this little whitepaer written by Jun Wu, the original author.

share

One extension I adore, is the share extension, which ships with Mercurial. It's very similar in spirit to git-worktree This allows me to have multiple working copies, but a common repo storage. Even better, it works great to have a working copy inside my VM that's backed by my current repo.


So that was a long, rambling blog post: Mostly I just wanted to share the pieces of Mercurial that make me really happy I stuck to it, and to encourage others to give it a shot again. While I doubt mercurial will ever supplant Git, as Git has mindshare for days, at the very least I think Mercurial is worth exploring as a different point in the DVCS design space.

Please, if you've got tips and tricks you'd like to share, or cool extensions, feel free to reach out or leave a comment.