i-think Twenty-Two

Now with more coherency.

Mercurial: Taming Multiple Heads with Bookmarks

| Comments

I’m a huge fan of version control. I have fond memories of first learning Subversion and setting up a source control server. I also remember the pain involved every time I wanted to create a new repository. Sure, I had a good process and it wasn’t that bad, but it was still something. For a long time I really thought that nothing could be better. Then I was introduced to this:

$ hg init .

That one line changed my life forever. Pretty soon I had just about everything I was actively working on managed by source control. It truly was a golden age. At this time I was generally working inside my own repositories sheltered from the rest of the world. Even for a single user Mercurial came to my aid by protecting me from myself. Coupled with an awesome diff tool I felt unstoppable. I was able to better break down problems and experiment more knowing that I could easily get back to a known good state and easily examine exactly what I’d just changed.

Of course as these things go it was only a matter of time before I was able to really dive into a repository that was actively maintained by other users. Here I was finally able to see if Mercurial was worthy of all the hype that I had read having consumed plenty of documentation (and forgetting all the things I wasn’t using on a regular basis).

And I was generally happy. But one thing continued to niggle at me. The number of “Merge” commits seemed extremely unnecessary. So I looked into the rebase extension. Now I was able to graft my changes at the end of everyone else’s history. Most of my changes were fairly isolated and there were rarely any conflicts (and when there were easily resolved with a simple three way merge).

And so again, life was good. But as time moved on I became wary of all this rewriting of history. One instance of sending duplicate changesets to the ‘master’ repository will quickly identify how easy it is to do the wrong thing. Wasn’t Mercurial supposed to save me from all this? I lost faith and began to look elsewhere, learnt the ins and outs of Git and really came to like the light weight branching it offered. This seemed to be perfect for what I wanted. I could have feature branches on bits of work that I might do and then merge them into the trunk when I’m done. Suddenly the merges didn’t seem so bad as each served a specific purpose of bringing a feature to the main line.

So I looked into Mercurial branches. Branches in Mercurial aren’t as lightweight as in Git. And even when they are closed traces of them seem to last forever. That might be fine for some cases, but for the work I was doing I didn’t want to worry about coming up with unique names for my branches and have those choices persisted for all eternity.

So I went for a search to find how other Mercurial users tackle this apparent shortfall. The answer was to maintain multiple heads and to use bookmarks to manage each head.

When I was first learning about Mercurial I suppose I gave myself the impression that multiple heads were bad and whenever there was more than one head you need to merge. This is of course completely wrong.

Mercurial quite happily chugs along with multiple heads and indeed it is fairly fundamental to how it works. When it comes time to push your changes you might run into issues, but even here we can work around them.

How I use bookmarks to manage multiple heads

When you have multiple heads to work with it becomes evident fairly quickly that you will need to have a good way to switch between these heads. This is where bookmarks come into play. So let’s look at an example of how this works:

I’ve opened up my trusty console window and have updated my repository.

$ hg pull -u

I’ve used the -u switch here to update the repository at the same time as pulling the latest changes. I don’t have any bookmarks set yet so I’m going to create one now so that I can easily track the ‘tip’ from the ‘master’ repository. I’m going to call it ‘master’ because that’s easy to remember.

$ hg book master

I’ve shortened the bookmarks command to book because book is easier to type and still gets the point across without being too cryptic. Now I’m going to start working on a fairly major change. I want to be able to commit often as I know that I will get things into a partially working state frequently but don’t want to share my changes until I’m completely done. So I’ll create a new bookmark to keep track of these changes:

$ hg book batman

So now I have two bookmarks that both point to the same changeset. Importantly the active bookmark is batman because it’s the last bookmark I used. You can only have one active bookmark at a time. To see the bookmarks that I have I can just run the book command with no parameters.

$ hg book
 * batman               34:7f6c4f9e45fb
   master               34:7f6c4f9e45fb

Looks good. The * indicates which bookmark is currently active. Note that they both point to the same changeset. Now I’m going to make some changes and commit:

$ hg commit -m "Improved grapple."

Now if I look at my bookmarks I’ll see that batman has moved with my new changeset and master has stayed put.

$ hg book
 * batman               35:63a4549bc962
   master               34:7f6c4f9e45fb

This is generally the point where someone might interrupt me with an urgent change. This time I know it is a simple change and we need to get it into the master ASAP. I don’t want to risk my improved grapple though and this change is unrelated anyway so I’ll start back at the master bookmark.

$ hg update master
1 file updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg book robin
$ hg book
   batman               35:63a4549bc962
   master               34:7f6c4f9e45fb
 * robin                34:7f6c4f9e45fb

Now I can make my change and commit.

$ hg commit -m "Reduce brightness of Robin costume."
$ hg book
   batman               35:63a4549bc962
   master               34:7f6c4f9e45fb
 * robin                36:9832ab432fec

Now I can see that the robin bookmark has moved, the batman bookmark stays in place and the master bookmark still points to the last changeset I pulled from the master repository. At this point I technically have two heads.

$ hg heads .
changeset:   35:63a4549bc962
bookmark:    batman
user:        Rhys Parry <rhys@example.com>
date:        Wed Apr 3 20:40:12 2013 +1000
summary:     Improved grapple.

changeset:  36:9832ab432fec
bookmark:   robin
tag:        tip
user:       Rhys Parry <rhys@example.com>
date:       Wed Apr 3 20:52:19 2013 +1000
summary:    Reduce brightness of Robin costume.

Because we have bookmarks for these changesets we can quickly switch between them. Here you can also see that the robin changeset also is regarded as the tip of the repository. Because this change is urgent I want to push it to the master repository ASAP. So first I’ll check if there is anything new in the master repository.

$ hg in
comparing with B:\Batcave
searching for changes
changeset:   37: f33b6a00e172
tag:         tip
user:        Alfred Pennyworth <alfred@example.com>
date:        Wed Apr 3 20:38:19 2013 +1000
summary:     Improve delivery of hot cocoa.

Here we can see that there has been a change since I started working. If I want to push this change I’ll need to merge my changes. But first I’ll make updating as simple as possible.

$ hg update master
1 file updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg pull -u

This will pull in Alfred’s changes and leave us with three heads. Importantly when we used hg update master Mercurial knew we were switching to a bookmark so it set it as the active bookmark. When we pulled in Alfred’s changes the bookmark followed the update so now master is pointing where we were expecting it to.

$ hg book
   batman               35:63a4549bc962
 * master               37:f33b6a00e172
   robin                36:9832ab432fec

Now we just need to merge the robin branch into master and we’ll be in a position to start pushing our changes. Again, this is straightforward.

$ hg merge robin
$ hg commit -m "Merge Robin costume improvements."

This time I can easily come up with a merge message because I know that there is a common theme to what I am merging. The same would apply if I was merging one hundred changesets. I can also now think of the merge as a true change and not just a nasty side effect of my choice of version control system. Finally I am ready to push my changes to the master repository. Because I don’t want the changes from my batman bookmark going up I’ll take a bit more care and do the following:

$ hg push -r master

This will push the master bookmark and all its descendants (which now includes the robin changes as well but not the batman changes). If you want to preview what changesets you will be sending to the master repository you can always run the following command first:

$ hg out -r master

Sometimes you might forget to include the -r flag. If you do Mercurial will kindly refuse your push and none of your changes will be pushed. By default Mercurial isn’t keen on letting you push extra heads to other repositories.

With this in mind we can feel a little safer knowing that Mercurial will prevent us from inflicting our unfinished changes on others before their time. We can of course force these changes if we want:

$ hg push --force -B master -B batman backup

Here I’ve forced these extra heads to be pushed to my own backup repository so that if my machine fails all my changes, including the bookmarks (which are not pushed by default).

So that’s a peak at one of the ways I use Mercurial on a daily basis. I’m sure there are many improvements to the way I am currently working and I relish the opportunity to explore more of Mercurial’s functionality.