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,

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.

Running Tests

To run the tests, first clone mercurial. Then

$ HGROOT=path/to/hgrepo make tests

Run a against a specific version of hg with:

$ HGROOT=path/to/hgrepo make tests-4.9

To update the expected output of the tests:

$ HGROOT=path/to/hgrepo HGTESTFLAGS=-i make tests