Bookbinder is a mercurial extension to simplify many bookmark based workflows. A book binder is a person or machine which binds pages to books. In a similar vein, bookbinder binds commits to bookmarks.

It does this by implementing a feature revset which returns all revisions "within" a particular bookmark. This allows bookbinder to subvert all commands that have a REV argument and replace any detected bookmark names with the feature revset. In other words, if you pass in -r my_bookmark to a command, it will replace my_bookmark with a revset containing all changesets "within" the bookmark.


Clone this repository somewhere on your local machine:

$ hg clone

Edit your ~/.hgrc and add the following to enable the extension:

bookbinder = /path/to/bookbinder


If -r <bookmark> is passed into a command, replace the bookmark's revision with a revset containing all changesets in the bookmark. E.g:

$ hg log -r <bookmark>
$ hg rebase -r <bookmark> -d <destination>
$ hg prune -r <bookmark>
$ hg fold -r <bookmark>

Bookbinder detects when bookmarks are based on top of one another, so you can rebase distinct features and easily keep track of them:

$ hg rebase -r bookmark_2 -d bookmark_1
$ hg rebase -r bookmark_3 -d bookmark_2
$ hg log -r bookmark_1
$ hg log -r bookmark_2
$ hg log -r bookmark_3

If rebasing bookmarks on top of one another, it's highly recommended to use the evolve extension, which can get you out of trouble if you mutate a bookmark that has descendants.

If you want bookbinder to treat a bookmark as a label to a revision, it's still possible by escaping the bookmark name with a period:

$ hg log -r .my_bookmark

What is a feature?

The name feature comes from the fact that bookmarks are often used to track feature work. This word is misleading, please help me come up with a better one! It can be used like so:

$ hg log -r "feature(bookmark_1) or feature(bookmark_2)"

While it was primarily designed for bookmarks, it works just as well with any arbitrary revisions.

A commit C is within feature branch ending at revision R if all of the following conditions are met:

  1. C is R or C is an ancestor of R
  2. C is not public
  3. C is not a merge commit
  4. C is not obsolete
  5. no bookmarks exist in [C, R) for C != R
  6. all commits in (C, R) are also within R for C != R

In other words, all ancestors of a revision that aren't public, a merge commit or part of a different bookmark, are within that revision's 'feature'. One thing to be aware of, is that applying a bookmark to a public commit results in an 'empty' feature. Running hg log -r <bookmark> on one of these will not result in any output.


You can change the escape sequence used by setting bookbinder.escape. The escape character can appear either before or after the bookmark (or both). In your hgrc, add:

escape = %s~

Make sure to include the '%s' string to denote where the bookmark text should be. This changes the escape sequence to hg log -r my_feature~.

Finally you can invert the behaviour of the escape sequence by adding:

invert_escape = True

This keeps original behaviour by default, and bookmarks will only be expanded if you specify the escape sequence. For example, hg log -r <bookmark> will continue to be a simple pointer to a commit, whereas hg log -r .bookmark will contain the entire series of commits in the feature. Inverse behaviour will also be enabled if you have $HGPLAIN set.