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 thatbootstrap.py
sets up. It lists recent heads in graph format with a terse log, along with colourized output.
Mercurial's default
hg log
has a different philosophy thangit log
. Wheregit log
shows you a relative view of history from your current working directory or specified revision, Mercurial'slog
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 findhg 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 thewip
extension.Honestly though, I use
hg wip
about 10x more often than I usehg {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 withhistedit
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.