Bookmarks Revisited Part II: Daily Bookmarking
It’s been a long time since I’ve written part I of the bookmarks revisited series. In the last two years, bookmarks changed a lot. They became part Mercurial’s core functionality and a lot of of tools became bookmark aware.
The current state of bookmarks
It’s safe to say, due to it’s exposure, bookmarks became much more mature of the years. It’s time to take a look at how to use them.
Bookmarks were initially designed for short living branches. I use them as such. It’s indeed possible to use them in different contexts, but I don’t do that. Please be aware, although they were initially intended to be similar to git branches, they often aren’t. They are not branches, they are bookmarks and they should be used like you would use a bookmark in a book. If you advance to the next site, you move the bookmark (or it gets moved).
A bookmark can be active. Only one bookmark can be active at any time, but it’s okay that no bookmark is active. If you have an active bookmark and you commit a new changeset, the bookmark will be moved to the commit. To set a bookmark active you have to update to the bookmark with hg update <name>. To unset, just update to the current revision with hg update ..
A bookmark can have a diverged markers. Bookmarks that are diverged will have a @NAME suffix. For example test@default. Diverged bookmarks are created during push and pull and will be described in Part III.
First, we are going to clone the Mercurial repository.
$ hg clone http://selenic.com/hg hg destination directory: hg requesting all changes adding changesets adding manifests adding file changes added 17684 changesets with 34643 changes to 2168 files updating to branch default 996 files updated, 0 files merged, 0 files removed, 0 files unresolved
The repository is setup to the current tip of the repository. We mark it with the ‘upstream’ bookmark. The star in front of the bookmark shows that it is marked as the current bookmark. Most Mercurial commands try to not be very verbose and only write necessary information to your terminal. It’s okay that creating a new bookmark doesn’t generate any output.
$ hg bookmark upstream $ hg bookmark * upstream 17683:6d7db5794e8c
To start out with a new feature, we have to make sure to not accidentally advance the master bookmark if we start to commit now. Either deactivate the current bookmark using hg update . or create the new bookmark for the feature we want to use right away. I am going to work on a feature to warn people if they create a bookmark with an ambiguous name. Therefore I call the branch topic/bm-warn-ambiguous.
$ hg bookmark topic/bm-warn-ambiguous $ hg bookmark * topic/bm-warn-ambiguous 17683:6d7db5794e8c upstream 17683:6d7db5794e8c
We can now go ahead and write our feature. I start out with the initial bits and later use the MQ extension to modify the changeset, so my change evolves slowly. After hacking a few minutes on the initial code, we can go ahead and commit it. As topic/bm-warn-ambiguous is marked as the current bookmark it will be moved automatically during commit.
$ vim mercurial/bookmarks.py $ hg commit -m'bookmarks: warn if bookmark name is ambiguous' $ hg bookmark * topic/bm-warn-ambiguous 17684:d018b3fda542 upstream 17683:6d7db5794e8c
Using existing bookmarks
$ hg update upstream 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bookmark topic/bm-warn-ambiguous 17684:d018b3fda542 * upstream 17683:6d7db5794e8c $ hg log -r upstream changeset: 17683:6d7db5794e8c bookmark: upstream parent: 17681:a41fd730f230 parent: 17682:829919ef894a user: Matt Mackall
date: Sat Sep 29 12:28:52 2012 -0500 summary: merge with stable
I am not too happy with the name of the bookmark. topic/ seems a way to long prefix. Let’s shorten it and rename the existing bookmark. The hg bookmark -m oldname newname can be used to rename existing bookmarks (-m as in move).
$ hg bookmark -m topic/bm-warn-ambiguous t/warn-ambiguous $ hg bookmark t/warn-ambiguous 17684:d018b3fda542 * upstream 17683:6d7db5794e8c
Bookmarks can be deleted with hg bookmark -d name
$ hg bookmark -d t/warn-ambiguous $ hg bookmark * upstream 17683:6d7db5794e8c $ hg bookmark -r 17684 t/warn-ambiguous $ hg bookmark t/warn-ambiguous 17684:d018b3fda542 * upstream 17683:6d7db5794e8c
If you have diverged heads and your merge is successful, you can commit the merge. The current bookmark is updated to the new merge commit. The bookmark that was merged stays and doesn’t move. Mercurial always sticks to the rule that only the current bookmark moves.
$ hg bookmark t/warn-ambiguous 17684:d018b3fda542 * upstream 17685:861d6aeee6aa $ hg merge t/warn-ambiguous 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg commit -m'Merge' $ hg bookmark t/warn-ambiguous 17684:d018b3fda542 * upstream 17686:c6b62c2e15fc
Bookmarks and revsets
The revset language has support for bookmarks. You can use the bookmarks() function to get a set of all revisions that are bookmarked. Use this in combination with the log command to get a detailed list of all bookmarked commits:
$ hg log -r 'bookmark()' changeset: 17683:6d7db5794e8c bookmark: upstream parent: 17681:a41fd730f230 parent: 17682:829919ef894a user: Matt Mackall
date: Sat Sep 29 12:28:52 2012 -0500 summary: merge with stable changeset: 17684:d018b3fda542 bookmark: t/warn-ambiguous tag: tip user: David Soria Parra date: Tue Oct 02 03:07:05 2012 +0200 summary: bookmarks: warn if bookmake name is ambiguous
More complex queries can be done. If you work with bookmarks on different Mercurial branches you can get a list of all bookmarks of a certain branch with hg log -r ‘branch(stable) and bookmark()’. Have fun playing around. Rewards and cat gifs for posting awesome revset queries the comments.
MQ and bookmarks
Bookmarks were designed to work on short living heads. The usual way to work with slowly evolving, unfinished changesets in Mercurial is MQ (also look at Mercurial phases, a new mechanism to help you with published and drafted commits).
MQ is a stack of queues on top of a Mercurial repository. It’s tightly integrated into Mercurial and uses commits and strip commands to push and pop changes. In my example, I have to continue working on my warn-ambiguous feature. We start by importing the topmost commit into MQ to change the commit. We then edit a new file and use hg qrefresh to update the commit with the new changes.
WARNING: qrefresh will throw away your current bookmark before Mercurial 2.4 (which is not yet released).
$ hg update t/warn-ambiguous 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg qimport -r tip $ hg bookmark * t/warn-ambiguous 17684:d018b3fda542 upstream 17683:6d7db5794e8c $ vim mercurial/bookmarks $ hg qrefresh $ hg bookmark * t/warn-ambiguous 17684:a5bbed225a3f upstream 17683:6d7db5794e8c
If we want to create a new commit, we use the command hg qnew name. It will create a new patch on top of the patch stack. We use it to draft the tests for our new commit. If we want to go back to the previous commit that is still on the stack, we use hg qpop to “pop” the topmost patch (the one with the tests) from the stack. If we want to go back we use hg qpush.
Note that the bookmarks will move back if you qpop and forward if you qpush, following your current state on the stack. Instead of hg commit we have to use hg qrefresh (only on of mq’s oddities).
$ hg qnew tests $ vim tests/test-bookmarks.t $ hg qrefresh
So most parts of MQ are aware of bookmarks. If finalize the MQ patches with hg qfinish -a our bookmark will just stay. The main issue with that workflow is the lack of support in qrefresh, but this is hopefully going to change with the next release. With that, bookmarks are going to be super useful for developing short living branches.
Naming conventions and alias
If you use bookmarks daily, you might want to use setup an alias in your global .hgrc. Just add the entry
[alias] bm = bookmark
to your ~/.hgrc and test if it works with hg bm.
Bookmarks changed a lot of the last years and most of Mercurials code base is bookmark aware. With the change to qrefresh, it’s going to be good enough for my workflows. Whats your bookmark story? Also Steve Losh wrote an excellent article about the different ways of branching in Mercurial (it has pictures, wwoooo). He also wrote an excellent article about MQ. He wrote a lot of interesting stuff at all, visit his page.