diff mercurial/hgweb/webutil.py @ 17202:1ae119269ddc

hgweb: side-by-side comparison functionality Adds new web command to the core, ``comparison``, which enables colorful side-by-side change display, which for some might be much easier to work with than the standard line diff output. The idea how to implement comes from the SonicHq extension. The web interface gets a new link to call the comparison functionality. It lets users configure the amount of context lines around change blocks, or to show full files - check help (also in this changeset) for details and defaults. The setting in hgrc can be overridden by adding ``context=<value>`` to the request query string. The comparison creates addressable lines, so as to enable sharing links to specific lines, just as standard diff does. Incorporates updates to all web related styles. Known limitations: * the column diff is done against the first parent, just as the standard diff * this change allows examining diffs for single files only (as I am not sure if examining the whole changeset in this way would be helpful) * syntax highlighting of the output changes is not performed (enabling the highlight extension has no influence on it)
author wujek srujek
date Sun, 08 Jul 2012 17:17:02 +0200
parents e7bf09acd410
children f2d6b4f8e78c
line wrap: on
line diff
--- a/mercurial/hgweb/webutil.py	Fri Jul 06 13:56:40 2012 -0700
+++ b/mercurial/hgweb/webutil.py	Sun Jul 08 17:17:02 2012 +0200
@@ -6,10 +6,11 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import os, copy
+import os, mimetypes, copy
 from mercurial import match, patch, scmutil, error, ui, util
 from mercurial.i18n import _
 from mercurial.node import hex, nullid
+import difflib
 
 def up(p):
     if p[0] != "/":
@@ -220,6 +221,92 @@
     yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
                lines=prettyprintlines(''.join(block), blockno))
 
+def compare(tmpl, ctx, path, context):
+    '''Generator function that provides side-by-side comparison data.'''
+
+    def filelines(f):
+        if util.binary(f.data()):
+            mt = mimetypes.guess_type(f.path())[0]
+            if not mt:
+                mt = 'application/octet-stream'
+            return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
+        return f.data().splitlines()
+
+    def compline(type, leftlineno, leftline, rightlineno, rightline):
+        lineid = leftlineno and ("l%s" % leftlineno) or ''
+        lineid += rightlineno and ("r%s" % rightlineno) or ''
+        return tmpl('comparisonline',
+                    type=type,
+                    lineid=lineid,
+                    leftlinenumber="% 6s" % (leftlineno or ''),
+                    leftline=leftline or '',
+                    rightlinenumber="% 6s" % (rightlineno or ''),
+                    rightline=rightline or '')
+
+    def getblock(opcodes):
+        for type, llo, lhi, rlo, rhi in opcodes:
+            len1 = lhi - llo
+            len2 = rhi - rlo
+            count = min(len1, len2)
+            for i in xrange(count):
+                yield compline(type=type,
+                               leftlineno=llo + i + 1,
+                               leftline=leftlines[llo + i],
+                               rightlineno=rlo + i + 1,
+                               rightline=rightlines[rlo + i])
+            if len1 > len2:
+                for i in xrange(llo + count, lhi):
+                    yield compline(type=type,
+                                   leftlineno=i + 1,
+                                   leftline=leftlines[i],
+                                   rightlineno=None,
+                                   rightline=None)
+            elif len2 > len1:
+                for i in xrange(rlo + count, rhi):
+                    yield compline(type=type,
+                                   leftlineno=None,
+                                   leftline=None,
+                                   rightlineno=i + 1,
+                                   rightline=rightlines[i])
+
+    if path in ctx:
+        fctx = ctx[path]
+        rightrev = fctx.filerev()
+        rightnode = fctx.filenode()
+        rightlines = filelines(fctx)
+        parents = fctx.parents()
+        if not parents:
+            leftrev = -1
+            leftnode = nullid
+            leftlines = ()
+        else:
+            pfctx = parents[0]
+            leftrev = pfctx.filerev()
+            leftnode = pfctx.filenode()
+            leftlines = filelines(pfctx)
+    else:
+        rightrev = -1
+        rightnode = nullid
+        rightlines = ()
+        fctx = ctx.parents()[0][path]
+        leftrev = fctx.filerev()
+        leftnode = fctx.filenode()
+        leftlines = filelines(fctx)
+
+    s = difflib.SequenceMatcher(None, leftlines, rightlines)
+    if context < 0:
+        blocks = [tmpl('comparisonblock', lines=getblock(s.get_opcodes()))]
+    else:
+        blocks = (tmpl('comparisonblock', lines=getblock(oc))
+                     for oc in s.get_grouped_opcodes(n=context))
+
+    yield tmpl('comparison',
+               leftrev=leftrev,
+               leftnode=hex(leftnode),
+               rightrev=rightrev,
+               rightnode=hex(rightnode),
+               blocks=blocks)
+
 def diffstatgen(ctx):
     '''Generator function that provides the diffstat data.'''