Blog entries september 2017 [1]

For about a year, Logilab has been involved in Mercurial development in the framework of a Mozilla Open Source Support (MOSS) program. Mercurial is a foundational technology for Mozilla, as the VCS used for the development of Firefox. As the main protagonist of the program, I'd first like to mention this has been a very nice experience for me, both from the social and technical perspectives. I've learned a lot and hope to continue working on this nice piece of software.

The general objective of the program was to improve "code archaeology" workflows, especially when using hgweb (the web UI for Mercurial). Outcomes of program spread from versions 4.1 to 4.4 of Mercurial and I'm going to present the main (new) features in this post.

Better navigation in "blame/annotate" view (hgweb)

The first feature concerns the "annotate" view in hgweb; the idea was to improve navigation along "blamed" revisions, a process that is often tedious and involves a lot of clicks in the web UI (not easier from the CLI, for that matter). Basically, we added an hover box on the left side of file content displaying more context on blamed revision along with a couple of links to make navigation easier (links to parent changesets, diff and changeset views). See below for an example about mercurial.error module (to try it at https://www.mercurial-scm.org/repo/hg/annotate/4.3/mercurial/error.py)

The hover box in annotate view in hgweb.

Followlines

While this wasn't exactly in the initial scope of the program, the most interesting result of my work is arguably the introduction of the "followlines" feature set. The idea is to build upon the log command instead of annotate to make it easier to follow changes across revisions and the new thing is to make this possible by filtering changes only affecting a block of lines in a particular file.

The first component introduced a revset, named followlines, which accepts at least two arguments a file path and a line range written as fromline:toline (following Python slice syntax). For instance, say we are interested the history of LookupError class in mercurial/error.py module which, at tag 4.3, lives between line 43 and 59, we'll use the revset as follows:

$ hg log -r 'followlines(mercurial/error.py, 43:59)'
changeset:   7633:08cabecfa8a8
user:        Matt Mackall <mpm@selenic.com>
date:        Sun Jan 11 22:48:28 2009 -0600
summary:     errors: move revlog errors

changeset:   24038:10d02cd18604
user:        Martin von Zweigbergk <martinvonz@google.com>
date:        Wed Feb 04 13:57:35 2015 -0800
summary:     error: store filename and message on LookupError for later

changeset:   24137:dcfdfd63bde4
user:        Siddharth Agarwal <sid0@fb.com>
date:        Wed Feb 18 16:45:16 2015 -0800
summary:     error.LookupError: rename 'message' property to something
else

changeset:   25945:147bd9e238a1
user:        Gregory Szorc <gregory.szorc@gmail.com>
date:        Sat Aug 08 19:09:09 2015 -0700
summary:     error: use absolute_import

changeset:   34016:6df193b5c437
user:        Yuya Nishihara <yuya@tcha.org>
date:        Thu Jun 01 22:43:24 2017 +0900
summary:     py3: implement __bytes__() on most of our exception classes

This only yielded changesets touching this class.

This is not an exact science (because the algorithm works on diff hunks), but in many situations it gives interesting results way faster that with an iterative "annotate" process (in which one has to step from revision to revision and run annotate every times).

The followlines() predicate accepts other arguments, in particular, descend which, in combination with the startrev, lets you walk the history of a block of lines in the descending direction of history. Below the detailed help (hg help revset.followlines) of this revset:

$ hg help revsets.followlines
    "followlines(file, fromline:toline[, startrev=., descend=False])"
      Changesets modifying 'file' in line range ('fromline', 'toline').

      Line range corresponds to 'file' content at 'startrev' and should hence
      be consistent with file size. If startrev is not specified, working
      directory's parent is used.

      By default, ancestors of 'startrev' are returned. If 'descend' is True,
      descendants of 'startrev' are returned though renames are (currently)
      not followed in this direction.

In hgweb

The second milestone of the program was to port this followlines filtering feature into hgweb. This has been implemented as a line selection mechanism (using mouse) reachable from both the file and annotate views. There, you'll see a small green ± icon close to the line number. By clicking on this icon, you start the line selection process which can be completed by clicking on a similar icon on another line of the file.

Starting a line selection for followlines in hgweb.

After that, you'll see a box inviting you to follow the history of lines <selected range> , either in the ascending (older) or descending (newer) direction.

Line selection completed for followlines in hgweb.

Here clicking on the "newer" link, we get:

Followlines result in hgweb.

As you can notice, this gives a similar result as with the command line but it also displays the patch of each changeset. In these patch blocks, only the diff hunks affecting selected line range are displayed (sometimes with some extra context).

What's next?

At this point, the "followlines" feature is probably complete as far as hgweb is concerned. In the remainder of the MOSS program, I'd like to focus on a command-line interface producing a similar output as the one above in hgweb, i.e. filtering patches to only show followed lines. That would take the form of a --followlines/-L option to hg log command, e.g.:

$ hg log -L mercurial/error.py,43:59 -p

That's something I'd like to tackle at the upcoming Mercurial 4.4 sprint in Dublin!

blog entry of