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

As of Mercurial 1.8 bookmarks are part of the Mercurials core. You don’t have to activate the extension anymore. Bookmarks are supported by every major Mercurial hosting platform. Commands like hg summary or hd id will display bookmark information. In addition, the push and pull mechanism changed. I will go into details about his Part III of the series.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 cheap water slides.

Bookmark semantics

Bookmarks are pointers to commits. Think of it as a name for a specific commit. Unlike branches in Mercurial, bookmarks are not recorded in the changeset. They don’t have a history. If you delete them, they will be gone forever.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.

A start

I use bookmarks to keep track of short living heads that I use for feature development. One bookmark keeps track of the upstream repository, they rest are my personal markers. Let’s start with creating the initial marker that keeps track of upstream. In this example, I show how I work on Mercurial.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

We can use the existing bookmarks and go back to our initial state what we pulled from the main repository. Just use bookmark the same way you use revisions. You can use it together with any command that requires a revision argument.

$ 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

Merging

Suppose we are done with our nice little feature and want to move it into our main development branch which is tracked by upstream. There are a few behaviors that you might be unfamiliar with. Just go ahead and use hg merge bookmark. Obviously, the bookmark cannot be a descendant of the bookmark to merge. Unlike git there is no way to force the merge. Also Mercurial won’t update the bookmark. It will just abort and stay were you are. (For git users: This means, Mercurial doesn’t have the notion of a fast-forward for bookmarks). I am not super happy with this behavior, but it will stay for the moment.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

Revset is a query language for revisions and files, build into mercurial. hg help revsets will explain the syntax. It’s a very powerful language. If you have the time, learn it. It makes your Mercurial experience that much better and will probably make you complain about endless lines of command line options in other VCS.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

So far we have dealt with basic commands. We committed a changeset, we edited a few 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

It is useful to have some naming conventions for bookmarks. I personally use the prefix t/<name> to denote short living topic branches. I use release/<version> for release branches. Bookmarks without any prefix are used for upstream bookmarks and bookmarks that were automatically pulled from the remote repository.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.

Conclusion

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.

ps: without flattrs, comments or anything like that, there is probably not going to be a part III

3 thoughts on “Bookmarks Revisited Part II: Daily Bookmarking

  1. Pingback: experimentalworks » Blog Archive » Mercurial Bookmarks Revisited – Part I

  2. Landon

    I just saw this, so for me it’s a Christmas miracle! (2 years 3 months 9 days pure goodness baked in). Thanks for the solid article! Looking forward to part III if you pursue it! In any case, thanks for the help and information.

    Reply
  3. Senthil Kumaran

    Thanks for the concise and an elegant article on bookmarks.

    “(For git users: This means, Mercurial doesn’t have the notion of a fast-forward for bookmarks).”

    Mention of this was great. Yeah, I miss that feature too. I think sometimes. ff- linear history is a great thing to have. Work arounds we end up with is like creating a patch against a revision and then applying the patch in the mainline and committing it.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>