Top tips for using Mercurial
By Matt Turk, Columbia University.
I love Mercurial. It's easily my favorite version control system, and I use it for all of my projects. Much like git, bazaar, darcs and so on, it's a distributed version control system - it's decentralised, in that every clone of the repository has a fully-fledged set of history - and it enables you to create local changes, review past changes, and create experimental branches that are later abandoned.
Mercurial, first created by Matt Mackall in 2005, is written in Python, and developed by a community of users and developers spread all over the world. There are a few hosted mercurial offerings. Most notable among these is BitBucket, but it is also supported by SourceForge, RhodeCode, and Savannah. Mercurial is designed around the concept of a Directed Acyclic Graph (DAG) - basically, a line of development and changes that proceeds back to the initial source, only going in one direction. All the forks, branches, everything that happens in a source repository becomes part of this DAG (more on that later). Mercurial is released on a regular schedule - and there's little to no danger upgrading, because of rigorous backwards compatibility standards enforced in the code base.
If you want to get started using Mercurial, just type hg init. That creates a repository, and you can do hg add, hg commit, hg pull and hg push all you like. There's an introductory tutorial at Hg Init, but once you've gotten the basics down - pushing, pulling, committing and so on - there are a number of ways to speed up your workflow and take advantage of the powerful engine underlying Mercurial.
Here are my top ten tips for using Mercurial.
1. Use the Graph!
The graph of commits is extremely useful, and this is something I didn't get right away. In short, every changeset is a state of the repository - a version, if you will. It is identified by a hash (which is global) or a number (which is local to your repository). Each changeset can have one or two parents, and infinite children (although usually it's not that many.)
If you want to see everything that created a revision, or a state in the code, check it out by typing:
$ hg log -G
This will display the changelog, as well as a little ASCII part of the parentage of changesets. This helps figure out where merges happened, which parent changeset created something, and so on and so forth.
2. hg serve
If you want a very nice and easy to read source browser that works on any platform, mercurial ships with one! All you have to do is run the serve command in the directory of your repository:
$ hg serve
This will spin up a webserver which you can connect to, where you can view the graph, see individual changesets and their diffs, check when something was changed, and on and on. In fact, you can even use this to share changes on a local network. For instance if you're collaborating and the wifi is spotty and you can't push to your remote server, you can spin up hg serve and share
changesets over the local network instead!
Mercurial is designed to be very stable by default. It's known for its approach to backwards compatibility. Often this gets misinterpreted as being stagnant, or being afraid to change - but really, things change quite frequently in Mercurial, it just requires that you ask for the changes in its functionality. Almost always, changes and additions of functionality are contained in extensions. Many extensions are distributed externally to Mercurial, but many are considered part of the core functionality and are shipped with it by default. Recently, Facebook has moved development to Mercurial, and they blogged about some extensions they released in support of the move.
Extensions can enable functionality, such as history rewriting, tracking of remote branches, shelving of changesets for later work, code review uploading, and turning on a pager (for long output from commands) by default. These extensions are turned on by adding lines in the section [extensions] in your hgrc (located in ~/.hgrc by default). If they're bundled, you can just put an = sign, but if you are pointing to another script location you have to list the full path to the script. For example, in my hgrc, I have:
[extensions] churn= extdiff= purge= shelve= evolve= record=
For more about extensions, see the Mercurial Wiki which includes a list of extensions included with Mercurial by default.
I'd definitely recommend checking out the record and shelve extensions - record lets you select which bits of a commit to commit at any given time, and shelve lets you store changes for later, in case you're not done but want to work on something else.
For a few years now, bookmarks have been included in Mercurial core. Bookmarks hew pretty closely to their source analogy - you bookmark something, make some commits, and the bookmark moves with the commits. So you can then change to another bookmark, change back, and you have found your place again. The notion of bookmark is different from a branch in mercurial, in that a branch is a heavyweight piece of metadata that gets painted onto a commit - they're for long-lived development where it's important to know that each commit was part of a particular strain of development. Bookmarks, on the other hand, are used for ephemeral development - I might want to keep track of my work on a fix for the units in a simulation code, but it's not important that once my work gets merged that I remember it was in support of that.
To use a bookmark, you can just:
$ hg bookmark some_name
Then you can push up to your server with:
$ hg push -B some_name
And it'll push both the bookmark and your changesets! You can pull bookmarks from other repositories, too. And when you want to delete a bookmark (so you don't get too many) it's just:
$ hg bookmark -d some_name $ hg push -B some_name
That will both delete the bookmark and push the deletion to the server.
5. Revsets: Finding something you did
Revsets are so cool, I've devoted two of my top ten to them. Revsets are basically a little programming language for describing changesets. Like, let's say you know you made a change to a particular file, you know you did it between March and April of 2011, and you know that you mentioned a particular word in the changelog. For instance, let's say I wanted to find the exact changeset where I made a change and I knew I mentioned boxlib in my log. I could run this command:
$ hg log -r "date('>05/01/11') and date('<06/01/11') and user(matthewturk) and desc('boxlib')"
This would look for all the revisions where the date was between 1 May 2011 and 1 June 2011, made by a user whose name contains that (my gmail address) and where the description of the commit includes the word boxlib.
There are tons of revsets that you can use to query individual changesets, shorthand for various things, and even examples. To see more, type:
$ hg help revsets
If you're digging down into the history of a particular file, you also use the command hg annotate, which will show the last time any line in a file was changed, and by whom.
6. Revsets: Finding out some things about a changeset
On the projects I work on, we often have some long-lived lines of development, where we periodically merge back and forth. One of the ways we keep things on track is with semi-frequent merging from mainline into development, to ensure that bug fixes (all done on the stable branch) propagate into the development branch. Sometimes these show up as merge conflicts, and if the changes are
particularly invasive, this can be kind of tricky to work out!
Revsets can help with this. There are a few handy ones that help compute things about the graph - the last time a line of development was merged, the most recent common ancestor of two changesets, and so on. For instance, let's say I'm in the middle of a merge. I want to see the differences that happened to a particular file between the last merge and the changeset I'm trying to
merge into. I can ask for all the diffs between the ancestor and parent two of the current working directory, like so:
$ hg diff -r "ancestor(p1(), p2())" -r "p2()" some_file.py
This says, show me the diff between the ancestor of p1() (local parent) and p2() (the other parent/changeset we're merging) and p2() itself. This means, all the changes that happened in the other line of development to that file.
Now, if we want to see all the changes that happened in our line of development to the file, we just swap the second revision:
$ hg diff -r "ancestor(p1(), p2())" -r "p1()" some_file.py
There are lots of other ways to specify things - as children, parents, mergers, and so on and so forth. The cool thing about revsets is that you can take whatever you want, and you can use them in as simple or as complex a fashion as you like. A few revset functions I particularly like:
- p1() and p2()
- remote() (this one lets you query remote repositories -- to find out the tip of something, or where a bookmark is somewhere else, for instance!)
- and, ::, not - mechanisms for describing groups of revisions.
Sometimes you'll run into revsets that you want to use all the time, in which case you might want to consider ...
If you find yourself repeatedly typing the same thing, you can alias this in hg by adding to your [alias] section in your hgrc. For example, here are a few from my hgrc:
[alias] nudge = push -r . rdiff = diff -r "ancestor(p1(),p2())" -r "p2()" mdiff = diff -r "ancestor(p1(),p2())" -r "p1()"
The last two are just aliases for the commands I described above, so when I'm mid-merge I can type hg rdiff and hg mdiff for remote diff and my diff to see what's been happening between this merge and the last one. But the first one, nudge, is pretty fun. In my repositories I often have a bunch of different lines of development ongoing - not all of which do I want to accidentally push up to the repository.
So nudge only pushes the current revision and whatever part of the graph from the current revision is necessary. That means if I have eight or nine lines of development locally, I'm only pushing the one I'm currently working on.
8. Alternate Merge Tools
There are lots of merge tools you can use for anything, and mercurial makes it easy to use them. Personally I like vimdiff, but there are others as well. I've got the command vimdiff set up when I want to interactively explore diffs in vimdiff, but there are others too. Check out the Wiki page on Extdiff for more information.
9. Mercurial won't delete your history unless you ask it to
This is something that people coming from over version control systems often wonder about - how do I clean up my commits? How do I delete old commits? How do I wipe out things that I don't want anymore? Unless you deliberately ask it to, Mercurial won't rewrite history. But if you want it to, it will.
That being said, if you really want to, there are a number of extensions that do this (shipped with code!) either in a safe way or an unsafe way. Extensions to squash history (turn five commits into one, for example) like rebase, to remove history like strip, and to graft/transplant (graft and transplant) can all modify your history. I'm not going to include examples here, because I'm not as accustomed to using these, but you can find out information on the Mercurial wiki.
The easiest, and safest, way to edit history is probably the histedit extension.
If you just want to prevent some changes from being pushed, ever, you can mark them as secret with the phase command, and they'll never leave your current repository. Recent versions of Mercurial have added the ability to retain old commits while marking them as obsolete - this is a lot like keeping the master tapes of a record, but only releasing the final mix. If you need to, you can go back and figuring things out, but by default only the final results get output. Those features are still being developed, but are being rolled out presently. For more information on this see the wiki.
10. Ask for help!
Mercurial has a very friendly community. You can find help on the Mercurial Wiki, mailing lists and IRC channels.
But, if you're running into trouble, the best place to look first is mercurial itself - just type hg help and you'll get the basic help, and supply a topic if you'd like more.information. And if you get really stuck, just shoot me an email.
Thanks for reading, and thanks to Sean Farley for proof-reading.
Posted by s.hettrick on Wednesday 5 February 2014.