[PATCH RFC v2] hgweb: improve annotate flow

Patrick Mezard patrick at mezard.eu
Fri May 11 03:50:06 CDT 2012


# HG changeset patch
# User Patrick Mezard <patrick at mezard.eu>
# Date 1333549397 -7200
# Node ID e7c2c26b9d539889ddaddea60c8ae5c42eb8729a
# Parent  ddd4996740c785cc187766249977ea3ece8c17ab
hgweb: improve annotate flow

[Only implemented for paper style, break the others]

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.

v2:
- Highlight target line in changeset view
- Highlight target line in annotate view
- Fix empty merge/empty diffs bugs

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 _
 
@@ -586,6 +586,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)
@@ -603,6 +610,7 @@
                                             diffopts=diffopts))
         for lineno, ((f, targetline), l) in lines:
             fnode = f.filenode()
+            fileid = getfileindex(f.node(), f.path())
 
             if last != fnode:
                 last = fnode
@@ -616,6 +624,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,21 +173,61 @@
             start += 1
 
     blockcount = countgen()
-    def prettyprintlines(diff, blockno):
+    def prettyprintlines(diff, blockno, parentctx, ctx):
+        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)
+                    indexb = None
+                    if fileb != '/dev/null':
+                        fileb = fileb[2:]
+                        if fileb in filenums:
+                            indexb = filenums[fileb]
+                elif indexb is not None:
+                    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('diffnoto')
             yield tmpl(ltype,
                        line=l,
                        lineid="l%s" % lineno,
                        linenumber="% 8s" % lineno)
+            yield tmpl('diffend')
 
     if files:
         m = match.exact(repo.root, repo.getcwd(), files)
@@ -196,22 +236,24 @@
 
     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:
             blockno = blockcount.next()
-            yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
-                       lines=prettyprintlines(''.join(block), blockno))
+            yield tmpl('diffblock', parity=parity.next(),
+                       lines=prettyprintlines(''.join(block), blockno, parentctx, ctx))
             block = []
         if chunk.startswith('diff') and style != 'raw':
             chunk = ''.join(chunk.splitlines(True)[1:])
         block.append(chunk)
     blockno = blockcount.next()
-    yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
-               lines=prettyprintlines(''.join(block), blockno))
+    yield tmpl('diffblock', parity=parity.next(),
+               lines=prettyprintlines(''.join(block), blockno, parentctx, ctx))
 
 def diffstatgen(ctx):
     '''Generator function that provides the diffstat data.'''
diff --git a/mercurial/templates/paper/changeset.tmpl b/mercurial/templates/paper/changeset.tmpl
--- a/mercurial/templates/paper/changeset.tmpl
+++ b/mercurial/templates/paper/changeset.tmpl
@@ -1,5 +1,8 @@
 {header}
 <title>{repo|escape}: {node|short}</title>
+<style type="text/css">
+       div:target \{ background-color: #ffd; }
+</style>
 </head>
 <body>
 <div class="container">
diff --git a/mercurial/templates/paper/fileannotate.tmpl b/mercurial/templates/paper/fileannotate.tmpl
--- a/mercurial/templates/paper/fileannotate.tmpl
+++ b/mercurial/templates/paper/fileannotate.tmpl
@@ -1,5 +1,8 @@
 {header}
 <title>{repo|escape}: {file|escape} annotate</title>
+<style type="text/css">
+    tr:target \{ background-color: #ffd; }
+</style> 
 </head>
 <body>
 
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -68,12 +68,12 @@
 filelogentry = filelogentry.tmpl
 
 annotateline = '
-  <tr class="parity{parity}">
+  <tr class="parity{parity}" id="{lineid}">
     <td class="annotate">
-      <a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l{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>
+    <td class="source"><a href="#{lineid}">{linenumber}</a> {line|escape}</td>
   </tr>'
 
 diffblock = '<div class="source bottomline parity{parity}"><pre>{lines}</pre></div>'
@@ -81,6 +81,10 @@
 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 = '<div><a href="{url}annotate/{nodea|short}/{filea|urlescape}#{linea}">ann</a>'
+diffto = '<div id="{lineidb}"><a>   </a>'
+diffnoto = '<div><a>   </a>'
+diffend = '</div>'
 
 changelogparent = '
   <tr>
diff --git a/mercurial/templates/raw/map b/mercurial/templates/raw/map
--- a/mercurial/templates/raw/map
+++ b/mercurial/templates/raw/map
@@ -28,3 +28,7 @@
 bookmarkentry = '{bookmark}	{node}\n'
 branches = '{entries%branchentry}'
 branchentry = '{branch}	{node}	{status}\n'
+difffrom = ''
+diffto = ''
+diffnoto = ''
+diffend = ''


More information about the Mercurial-devel mailing list