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.