comparison 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
comparison
equal deleted inserted replaced
17201:afd75476939e 17202:1ae119269ddc
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # 5 #
6 # This software may be used and distributed according to the terms of the 6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version. 7 # GNU General Public License version 2 or any later version.
8 8
9 import os, copy 9 import os, mimetypes, copy
10 from mercurial import match, patch, scmutil, error, ui, util 10 from mercurial import match, patch, scmutil, error, ui, util
11 from mercurial.i18n import _ 11 from mercurial.i18n import _
12 from mercurial.node import hex, nullid 12 from mercurial.node import hex, nullid
13 import difflib
13 14
14 def up(p): 15 def up(p):
15 if p[0] != "/": 16 if p[0] != "/":
16 p = "/" + p 17 p = "/" + p
17 if p[-1] == "/": 18 if p[-1] == "/":
218 block.append(chunk) 219 block.append(chunk)
219 blockno = blockcount.next() 220 blockno = blockcount.next()
220 yield tmpl('diffblock', parity=parity.next(), blockno=blockno, 221 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
221 lines=prettyprintlines(''.join(block), blockno)) 222 lines=prettyprintlines(''.join(block), blockno))
222 223
224 def compare(tmpl, ctx, path, context):
225 '''Generator function that provides side-by-side comparison data.'''
226
227 def filelines(f):
228 if util.binary(f.data()):
229 mt = mimetypes.guess_type(f.path())[0]
230 if not mt:
231 mt = 'application/octet-stream'
232 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
233 return f.data().splitlines()
234
235 def compline(type, leftlineno, leftline, rightlineno, rightline):
236 lineid = leftlineno and ("l%s" % leftlineno) or ''
237 lineid += rightlineno and ("r%s" % rightlineno) or ''
238 return tmpl('comparisonline',
239 type=type,
240 lineid=lineid,
241 leftlinenumber="% 6s" % (leftlineno or ''),
242 leftline=leftline or '',
243 rightlinenumber="% 6s" % (rightlineno or ''),
244 rightline=rightline or '')
245
246 def getblock(opcodes):
247 for type, llo, lhi, rlo, rhi in opcodes:
248 len1 = lhi - llo
249 len2 = rhi - rlo
250 count = min(len1, len2)
251 for i in xrange(count):
252 yield compline(type=type,
253 leftlineno=llo + i + 1,
254 leftline=leftlines[llo + i],
255 rightlineno=rlo + i + 1,
256 rightline=rightlines[rlo + i])
257 if len1 > len2:
258 for i in xrange(llo + count, lhi):
259 yield compline(type=type,
260 leftlineno=i + 1,
261 leftline=leftlines[i],
262 rightlineno=None,
263 rightline=None)
264 elif len2 > len1:
265 for i in xrange(rlo + count, rhi):
266 yield compline(type=type,
267 leftlineno=None,
268 leftline=None,
269 rightlineno=i + 1,
270 rightline=rightlines[i])
271
272 if path in ctx:
273 fctx = ctx[path]
274 rightrev = fctx.filerev()
275 rightnode = fctx.filenode()
276 rightlines = filelines(fctx)
277 parents = fctx.parents()
278 if not parents:
279 leftrev = -1
280 leftnode = nullid
281 leftlines = ()
282 else:
283 pfctx = parents[0]
284 leftrev = pfctx.filerev()
285 leftnode = pfctx.filenode()
286 leftlines = filelines(pfctx)
287 else:
288 rightrev = -1
289 rightnode = nullid
290 rightlines = ()
291 fctx = ctx.parents()[0][path]
292 leftrev = fctx.filerev()
293 leftnode = fctx.filenode()
294 leftlines = filelines(fctx)
295
296 s = difflib.SequenceMatcher(None, leftlines, rightlines)
297 if context < 0:
298 blocks = [tmpl('comparisonblock', lines=getblock(s.get_opcodes()))]
299 else:
300 blocks = (tmpl('comparisonblock', lines=getblock(oc))
301 for oc in s.get_grouped_opcodes(n=context))
302
303 yield tmpl('comparison',
304 leftrev=leftrev,
305 leftnode=hex(leftnode),
306 rightrev=rightrev,
307 rightnode=hex(rightnode),
308 blocks=blocks)
309
223 def diffstatgen(ctx): 310 def diffstatgen(ctx):
224 '''Generator function that provides the diffstat data.''' 311 '''Generator function that provides the diffstat data.'''
225 312
226 stats = patch.diffstatdata(util.iterlines(ctx.diff())) 313 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
227 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats) 314 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)