[PATCH RFC] hgweb: improve annotate flow

Patrick Mezard pmezard at gmail.com
Fri Nov 18 08:10:03 CST 2011


# HG changeset patch
# User Patrick Mezard <pmezard at gmail.com>
# Date 1321625296 -3600
# Node ID 95285db262b74b0db54d73b71380ee7251407800
# Parent  68b0ecd8ad4d0baaae3dfa6c784c69c02f365023
hgweb: improve annotate flow

The current hgweb annotate flow looks like:

  1- open file
  2- annotate file
  3- click on annotate link to open a new annotation
  4- switch to changeset, select parent changeset, browse files, pick file
  5- goto [1]

This patch turns it into:

  1- open file
  2- annotate file

  3- click on annotate link to open the changeset at the right location
  4- look at the hunk and click on the annotate link on the left
  5- goto [3]

You can test it by opening (the host is slow, not this patch):

  http://mezard.eu/hg/hg-does-it-look-good-for-you/rev/c8e2a5ea7062

and trying to find when and why the:

  wctx.undelete([name])

line I removed in the changeset was introduced in the first place. Then compare
with a regular setup, like:

  http://hg.intevation.org/mercurial/crew/rev/c8e2a5ea7062

Note 1: I am not an HTML guy, the new grey "ann" link in the changeset view
should at least be blue like the regular annotate one, and there are probably
better way to represent it. And I feel there ought to be better ways to do what
I did with the templating system.

Note 2: the annotate step [3] could be skipped completely by moving from diffs
to diffs. This is a little harder to implement though, as the changeset command
would need to compute the annotate output to generate the "ann" links.

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -12,7 +12,7 @@
 from mercurial.util import binary
 from common import paritygen, staticfile, get_contact, ErrorResponse
 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
-from mercurial import graphmod, patch
+from mercurial import graphmod, patch, util
 from mercurial import help as helpmod
 from mercurial.i18n import _
 
@@ -573,6 +573,13 @@
 diff = filediff
 
 def annotate(web, req, tmpl):
+    def getfileindex(node, path):
+        for i, f in enumerate(web.repo[node].files()):
+            if f == path:
+                return i + 1
+        return 0
+    getfileindex = util.lrucachefunc(getfileindex)
+
     fctx = webutil.filectx(web.repo, req)
     f = fctx.path()
     parity = paritygen(web.stripecount)
@@ -590,6 +597,7 @@
                                             diffopts=diffopts))
         for lineno, ((f, targetline), l) in lines:
             fnode = f.filenode()
+            fileid = getfileindex(f.node(), f.path())
 
             if last != fnode:
                 last = fnode
@@ -603,6 +611,7 @@
                    "targetline": targetline,
                    "line": l,
                    "lineid": "l%d" % (lineno + 1),
+                   "diffid": "t%s.%d" % (fileid, targetline),
                    "linenumber": "% 6d" % (lineno + 1),
                    "revdate": f.date()}
 
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -173,18 +173,55 @@
             start += 1
 
     blockcount = countgen()
-    def prettyprintlines(diff):
+    def prettyprintlines(diff, parentctx, ctx):
         blockno = blockcount.next()
+        filenums = dict((f, i + 1) for i,f in enumerate(ctx.files()))
+        filea, nodea, fileb, indexb = '', '', '', None
+        linea, lineb = 0, 0
         for lineno, l in enumerate(diff.splitlines(True)):
+            islinea, islineb = False, False
             lineno = "%d.%d" % (blockno, lineno + 1)
             if l.startswith('+'):
+                if not fileb and l.startswith('+++ '):
+                    fileb = patch.parsefilename(l)
+                    if fileb != '/dev/null':
+                        fileb = fileb[2:]
+                        indexb = filenums[fileb]
+                else:
+                    islineb = True
+                lineb += 1
                 ltype = "difflineplus"
             elif l.startswith('-'):
+                if not filea and l.startswith('--- '):
+                    filea = patch.parsefilename(l)
+                    if filea != '/dev/null':
+                        filea = filea[2:]
+                        nodea = hex(parentctx[filea].filenode())
+                else:
+                    islinea = True
+                linea += 1
                 ltype = "difflineminus"
             elif l.startswith('@'):
+                linea, lineb = patch.unidesc.match(l).group(1, 3)
+                linea = int(linea) - 1
+                lineb = int(lineb) - 1
                 ltype = "difflineat"
             else:
                 ltype = "diffline"
+                if l.startswith(' '):
+                    if linea is not None:
+                        linea += 1
+                        lineb += 1
+            if islinea:
+                yield tmpl('difffrom',
+                           filea=filea,
+                           linea="l%d" % linea,
+                           nodea=nodea)
+            elif islineb:
+                yield tmpl('diffto',
+                           lineidb='t%d.%d' % (indexb, lineb))
+            else:
+                yield tmpl('diffnofrom')
             yield tmpl(ltype,
                        line=l,
                        lineid="l%s" % lineno,
@@ -197,20 +234,22 @@
 
     diffopts = patch.diffopts(repo.ui, untrusted=True)
     parents = ctx.parents()
-    node1 = parents and parents[0].node() or nullid
+    parentctx = parents and parents[0] or repo[nullid]
+    node1 = parentctx.node()
     node2 = ctx.node()
+    filenums = dict((f, i) for i,f in enumerate(ctx.files()))
 
     block = []
     for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
         if chunk.startswith('diff') and block:
             yield tmpl('diffblock', parity=parity.next(),
-                       lines=prettyprintlines(''.join(block)))
+                       lines=prettyprintlines(''.join(block), parentctx, ctx))
             block = []
         if chunk.startswith('diff') and style != 'raw':
             chunk = ''.join(chunk.splitlines(True)[1:])
         block.append(chunk)
     yield tmpl('diffblock', parity=parity.next(),
-               lines=prettyprintlines(''.join(block)))
+               lines=prettyprintlines(''.join(block), parentctx, ctx))
 
 def diffstatgen(ctx):
     '''Generator function that provides the diffstat data.'''
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -70,7 +70,7 @@
 annotateline = '
   <tr class="parity{parity}">
     <td class="annotate">
-      <a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#{targetline}"
+      <a href="{url}rev/{node|short}/{sessionvars%urlparameter}#{diffid}"
          title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
     </td>
     <td class="source"><a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}</td>
@@ -81,6 +81,9 @@
 difflineminus = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> <span class="minusline">{line|escape}</span>'
 difflineat = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> <span class="atline">{line|escape}</span>'
 diffline = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}'
+difffrom = '<a href="{url}annotate/{nodea|short}/{filea|urlescape}#{linea}">ann</a>'
+diffto = '<a id="{lineidb}">   </a>'
+diffnofrom = '<a>   </a>'
 
 changelogparent = '
   <tr>


More information about the Mercurial-devel mailing list