# HG changeset patch # User mason@suse.com # Date 1138068130 -46800 # Node ID 3b1b44b917f47fc44ac1e45995d9fa02a4db5e79 # Parent 7da32bb3d1d39aae138b54c59a47bbad95e45bed Add new bdiff based unidiff generation. diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -295,20 +295,26 @@ def dodiff(fp, ui, repo, node1, node2, f mmap = repo.manifest.read(change[0]) date1 = util.datestr(change[2]) + diffopts = ui.diffopts() + showfunc = diffopts['showfunc'] + ignorews = diffopts['ignorews'] for f in modified: to = None if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) for f in added: to = None tn = read(f) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) for f in removed: to = repo.file(f).read(mmap[f]) tn = None - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) def trimuser(ui, name, rev, revcache): """trim the name of the user who committed a change""" diff --git a/mercurial/hgweb.py b/mercurial/hgweb.py --- a/mercurial/hgweb.py +++ b/mercurial/hgweb.py @@ -270,18 +270,24 @@ class hgweb(object): modified, added, removed = map(lambda x: filterfiles(files, x), (modified, added, removed)) + diffopts = self.repo.ui.diffopts() + showfunc = diffopts['showfunc'] + ignorews = diffopts['ignorews'] for f in modified: to = r.file(f).read(mmap1[f]) tn = r.file(f).read(mmap2[f]) - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) for f in added: to = None tn = r.file(f).read(mmap2[f]) - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) for f in removed: to = r.file(f).read(mmap1[f]) tn = None - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) def changelog(self, pos): def changenav(**map): diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py --- a/mercurial/mdiff.py +++ b/mercurial/mdiff.py @@ -5,9 +5,13 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import difflib, struct, bdiff, util, mpatch +from demandload import demandload +import struct, bdiff, util, mpatch +demandload(globals(), "re") -def unidiff(a, ad, b, bd, fn, r=None, text=False): + +def unidiff(a, ad, b, bd, fn, r=None, text=False, + showfunc=False, ignorews=False): if not a and not b: return "" epoch = util.datestr((0, 0)) @@ -27,9 +31,10 @@ def unidiff(a, ad, b, bd, fn, r=None, te l3 = "@@ -1,%d +0,0 @@\n" % len(a) l = [l1, l2, l3] + ["-" + e for e in a] else: - a = a.splitlines(1) - b = b.splitlines(1) - l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn)) + al = a.splitlines(1) + bl = b.splitlines(1) + l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, + showfunc=showfunc, ignorews=ignorews)) if not l: return "" # difflib uses a space, rather than a tab l[0] = "%s\t%s\n" % (l[0][:-2], ad) @@ -45,6 +50,128 @@ def unidiff(a, ad, b, bd, fn, r=None, te return "".join(l) +# somewhat self contained replacement for difflib.unified_diff +# t1 and t2 are the text to be diffed +# l1 and l2 are the text broken up into lines +# header1 and header2 are the filenames for the diff output +# context is the number of context lines +# showfunc enables diff -p output +# ignorews ignores all whitespace changes in the diff +def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False, + ignorews=False): + def contextend(l, len): + ret = l + context + if ret > len: + ret = len + return ret + + def contextstart(l): + ret = l - context + if ret < 0: + return 0 + return ret + + def yieldhunk(hunk, header): + if header: + for x in header: + yield x + (astart, a2, bstart, b2, delta) = hunk + aend = contextend(a2, len(l1)) + alen = aend - astart + blen = b2 - bstart + aend - a2 + + func = "" + if showfunc: + # walk backwards from the start of the context + # to find a line starting with an alphanumeric char. + for x in xrange(astart, -1, -1): + t = l1[x].rstrip() + if funcre.match(t): + func = ' ' + t[:40] + break + + yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen, + bstart + 1, blen, func) + for x in delta: + yield x + for x in xrange(a2, aend): + yield ' ' + l1[x] + + header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ] + + if showfunc: + funcre = re.compile('\w') + if ignorews: + wsre = re.compile('[ \t]') + + # bdiff.blocks gives us the matching sequences in the files. The loop + # below finds the spaces between those matching sequences and translates + # them into diff output. + # + diff = bdiff.blocks(t1, t2) + hunk = None + for i in xrange(len(diff)): + # The first match is special. + # we've either found a match starting at line 0 or a match later + # in the file. If it starts later, old and new below will both be + # empty and we'll continue to the next match. + if i > 0: + s = diff[i-1] + else: + s = [0, 0, 0, 0] + delta = [] + s1 = diff[i] + a1 = s[1] + a2 = s1[0] + b1 = s[3] + b2 = s1[2] + + old = l1[a1:a2] + new = l2[b1:b2] + + # bdiff sometimes gives huge matches past eof, this check eats them, + # and deals with the special first match case described above + if not old and not new: + continue + + if ignorews: + wsold = wsre.sub('', "".join(old)) + wsnew = wsre.sub('', "".join(new)) + if wsold == wsnew: + continue + + astart = contextstart(a1) + bstart = contextstart(b1) + prev = None + if hunk: + # join with the previous hunk if it falls inside the context + if astart < hunk[1] + context + 1: + prev = hunk + astart = hunk[1] + bstart = hunk[3] + else: + for x in yieldhunk(hunk, header): + yield x + # we only want to yield the header if the files differ, and + # we only want to yield it once. + header = None + if prev: + # we've joined the previous hunk, record the new ending points. + hunk[1] = a2 + hunk[3] = b2 + delta = hunk[4] + else: + # create a new hunk + hunk = [ astart, a2, bstart, b2, delta ] + + delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ] + delta[len(delta):] = [ '-' + x for x in old ] + delta[len(delta):] = [ '+' + x for x in new ] + + if hunk: + for x in yieldhunk(hunk, header): + yield x + def patchtext(bin): pos = 0 t = [] diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -23,6 +23,7 @@ class ui(object): self.interactive = self.configbool("ui", "interactive", True) self.updateopts(verbose, debug, quiet, interactive) + self.diffcache = None def updateopts(self, verbose=False, debug=False, quiet=False, interactive=True): @@ -76,6 +77,23 @@ class ui(object): def extensions(self): return self.configitems("extensions") + def diffopts(self): + if self.diffcache: + return self.diffcache + ret = { 'showfunc' : True, 'ignorews' : False} + for x in self.configitems("diff"): + k = x[0].lower() + v = x[1] + if v: + v = v.lower() + if v == 'true': + value = True + else: + value = False + ret[k] = value + self.diffcache = ret + return ret + def username(self): return (os.environ.get("HGUSER") or self.config("ui", "username") or diff --git a/tests/test-merge-revert2.out b/tests/test-merge-revert2.out --- a/tests/test-merge-revert2.out +++ b/tests/test-merge-revert2.out @@ -11,7 +11,7 @@ merging file1 failed! diff -r f4d7a8c73d23 file1 --- a/file1 +++ b/file1 -@@ -1,3 +1,7 @@ +@@ -1,3 +1,7 @@ added file1 added file1 another line of text +<<<<<<< diff --git a/tests/test-up-local-change.out b/tests/test-up-local-change.out --- a/tests/test-up-local-change.out +++ b/tests/test-up-local-change.out @@ -2,7 +2,7 @@ adding a diff -r c19d34741b0a a --- a/a +++ b/a -@@ -1,1 +1,1 @@ +@@ -1,1 +1,1 @@ a -a +abc adding b @@ -47,6 +47,6 @@ 1 diff -r 1e71731e6fbb a --- a/a +++ b/a -@@ -1,1 +1,1 @@ +@@ -1,1 +1,1 @@ a2 -a2 +abc