Consider the following Mercurial screenshot:
This might look bewildering at first. It’s a custom hg wip
command, a variation of hg log
. You can see the
Current commit
The current commit is simply where you’re standing. It’s the commit that hg diff
or hg status
would use as reference for telling you how the working copy has changed. All that I did for hg wip
was to highlight more clearly what this commit is. Note in the DAG that this commit is marked with an “@”.
Bookmarks
I have chosen to display bookmarks in green. Recall that bookmarks are simply movable markers that follow along with the commits that they’re pointing to. They’re very similar to git branches. Here I am using them to refer to each of my feature branches. There is a special “@” bookmark that indicates where upstream development is.
Branches
In cyan we see the branch names. In this case there are only two commits on the stable branch. Branch names are permanent names tattooed on commits. This is useful for when you want to have a permanent record of where a commit was done.
Phases
This part is one of my favourites. Mercurial phases indicate whether commits have been published yet or not, and whether they should be shared or not. Commits that have been published and therefore immutable are in orange. The commits I’ve been working on but haven’t been accepted into upstream yet are yellow drafts. Finally, a couple of local patches that should never be shared are secret commits in blue. I am using local revision numbers to identify commits, but it would also be possible to use global hashes.
Selective about which commits to show
Another cool thing about my hg wip
command is that it only shows what I consider interesting information about my work-in-progress commits, hence the name of the command. To be precise, it only shows my draft and secret commits, current open heads of development, and the minimum common commits. No other intermediate commits are shown. Actually, usually I don’t even need the extra newlines, and I prefer this more compact format:
So, how is this command built? It actually depends on a number of different Mercurial features, all useful on their own. All of the following snippets go into your ~/.hgrc
config file.
Revsets
Revsets are a very useful language for selecting a subset of commits. Here you can see me talking about them. The revset I used for hg wip
was
wip = (parents(not public()) or not public() or . or head()) and (not obsolete() or unstable()^) and not closed()
It looks like quite a mouthful, so let’s pick it apart:
parents(not public())
- Take the parents of all the non-public commits (i.e. drafts and secrets).
or not public() or . or head()
- Also take all non-public commits, the current commit (that’s “.”), and all heads.
and (not obsolete() or unstable()^)
- But exclude obsolete and unstable commits (this only matters if you’re using the experimental Evolve extension).
and not closed()
- And also exclude any closed branch heads
The nice thing is that all of this complicated selection is also fast!
Templating the output
The next step is to show the output in the format that we want it. That’s what the templating engine is for. The template I used here was
wip = '{label("log.branch", ifeq(branch, "default", "", branch))} {label("changeset.{phase}", rev)} {label("grep.user", author|user)}{label("log.tag", if(tags," {tags}"))} {bookmarks % "{ifeq(bookmark, currentbookmark, label('log.activebookmark', bookmark), label('log.bookmark', bookmark))} "}\n{label(ifcontains(rev, revset('parents()'), 'desc.here'),desc|firstline)}'
Whoa! That’s even worse than the revset. Unfortunately, due to the nature of the templating engine, inserting whitespace to make this more readable would also make this extra whitespace show up in the final output.
If we are willing to define more intermediate templates and move them to an external template file, it can actually be made readable:
ifeq(branch, "default",
"",
branch))}'
wcset = '{label("changeset.{phase}",
rev)}'
wuser = '{label("grep.user",
author|user)}'
wtags = '{label("log.tag",
if(tags," {tags}"))}'
wbook = '{bookmarks % "{ifeq(bookmark, currentbookmark,
label('log.activebookmark', bookmark),
label('log.bookmark', bookmark))} "}'
wdesc = '{label(ifcontains(rev, revset('parents()'), 'desc.here'),
desc|firstline)}'
changeset = '{wbrch} {wcset} {wuser} {wtags} {wbook}\n{wdesc}'
Okay, this is a bit better, but it requires using an extra config file. The syntax is a bit tricky, but most of it is self-explanatory. Special keywords in {braces}
get expanded to their corresponding values. There are a few functions like if()
and ifcontains()
to selectively output extra information if that information exists. For example, I use ifeq()
to hide the branch name if this name is “default”.
Once you’re in a function, keywords are already available. For example, rev
gives you the revision number, and if you wanted global hashes instead, you could change that to node
or shortest(node)
. You can even use a revset in the templating engine! In this case, for selecting the current commits and comparing it to the revision being printed. We use the parents() revset to select the current commit(s), because there may be more than one current commit if there’s an uncommitted merge state on the working directory. For template keywords that return a list of things, such as bookmark, we can iterate over elements of that list using “%” and then select different labels depending if the bookmark is the current bookmark or not.
But what is this label function? Ah, well, labelling is how the colour extension decides how to colourise the output.
Colourful Mercurial
The finishing touches are now to pick colours for all of the labels we defined. For this we enable the colour extension and we configure it:
color=
[color]
mode=terminfo
#Custom colours
color.orange=202
color.lightyellow=191
color.darkorange=220
color.brightyellow=226
#Colours for each label
log.branch=cyan
log.summary=lightyellow
log.description=lightyellow
log.bookmark=green
log.tag=darkorange
log.activebookmark = green bold underline
changeset.public=orange bold
changeset.secret=blue bold
changeset.draft=brightyellow bold
desc.here=bold blue_background
Here we set the mode to terminfo so that we can use 256 colours. Most modern terminals can support it, but sometimes you have to specify the $TERM
enivronment variable to be xterm-256color
or something similar. Then you can define your own colours from the 256 available, referring to the numbers on this chart. If you dislike my colour choices, this is where you can configure them.
Define the command
The final part is to just turn this on:
wip = log --graph --rev=wip --template=wip
This defines the hg wip
command to be an alias to hg log
together with the parameters for displaying the graph, using the wip revset, and the wip template.
And voilà, fancy shmancy colourful hg command! Here is the complete addition to your ~/.hgrc
all at once, for delicious copypasta:
wip = (parents(not public()) or not public() or . or head()) and (not obsolete() or unstable()^) and not closed()
[templates]
wip = '{label("log.branch", ifeq(branch, "default", "", branch))} {label("changeset.{phase}", rev)} {label("grep.user", author|user)}{label("log.tag", if(tags," {tags}"))} {bookmarks % "{ifeq(bookmark, currentbookmark, label('log.activebookmark', bookmark), label('log.bookmark', bookmark))} "}\n{label(ifcontains(rev, revset('parents()'), 'desc.here'),desc|firstline)}'
[extensions]
color=
[color]
mode=terminfo
#Custom colours
color.orange=202
color.lightyellow=191
color.darkorange=220
color.brightyellow=226
#Colours for each label
log.branch=cyan
log.summary=lightyellow
log.description=lightyellow
log.bookmark=green
log.tag=darkorange
log.activebookmark = green bold underline
changeset.public=orange bold
changeset.secret=blue bold
changeset.draft=brightyellow bold
desc.here=bold blue_background
[alias]
wip = log --graph --rev=wip --template=wip
Acknowledgements
This command comes from ideas cobbled together from Steven Losh, Augie Fackler, and Sean Farley. They are all great contributors to Mercurial, and they have taught me so much! Thanks, guys!
Nice! Thanks for sharing.
Is lack of ‘default’ branch intentional? If not it’s enough to change “branches” -> “if(branches, branches, branch)” in the template.
Yes, it’s intentional. If you’re using a bookmark-only flow, the name of the default branch can be noisy. But I’m glad that you easily figured out how to modify the template to make it also display the branch! I assume you meant `if(branches, branches, “default”)`, though, right?
Oh, wait, I see the issue now. The `branches` keyword has been deprecated in favour of `branch`. I still like how it works, though, so I’ve edited the template to use an ifeq alternative. It’s not like the deprecated version will ever stop working, but it’s good that it will fit with the current documentation, which doesn’t show the `branches` keyword anymore.
Thanks for the tutorial! Just a heads up, I copied and pasted your snippet to my .hgrc and it somehow disabled the default colors of the regular hg log command :-(
Oh, you said something about 256 color terminals…yep, log is still colored in gnome-terminal, it’s just my urxvt that loses colors. wip is colored in both.
That’s odd. I assume it’s the color.mode option that disabled colours for the regular log command? Do they come back if you do `hg log –config color.mode=!` ? What does your TERM environment variable say?
$ echo $TERM
rxvt-unicode
With hg log –config color.mode=! there are no colors at all. With just hg log the changeset is a bolder white than the rest of the log output.
If I do hg log –config color.mode=auto then I get the regular colors back…wait, they are different colors (brighter yellow), but I also get these messages:
ignoring unknown color/effect ‘lightyellow’ (configured in color.log.summary)
ignoring unknown color/effect ‘lightyellow’ (configured in color.log.description)
ignoring unknown color/effect ‘darkorange’ (configured in color.log.tag)
ignoring unknown color/effect ‘orange’ (configured in color.changeset.public)
ignoring unknown color/effect ‘brightyellow’ (configured in color.changeset.draft)
It appears the problem is that stock rxvt doesn’t support 256 colours. I found the following discussion about it.
Looks like it’s even easier than that nowadays:
sudo apt-get install rxvt-unicode-256color
Now I have all the pretty colors :-)