Mercurial > hg
view mercurial/mdiff.py @ 13750:7eb82f88e157
# User Dan Villiom Podlaski Christiansen <danchr@gmail.com>
# Date 1289564504 -3600
# Node ID b75264c15cc888cf38c3c7b8f619801e3c2589c7
# Parent 89b2e5d940f669e590096c6be70eee61c9172fff
revsets: overload the branch() revset to also take a branch name.
This should only change semantics in the specific case of a tag/branch
conflict where the tag wasn't done on the branch with the same
name. Previously, branch(whatever) would resolve to the branch of the
tag in that case, whereas now it will resolve to the branch of the
name. The previous behaviour, while documented, seemed very
counter-intuitive to me.
An alternate approach would be to introduce a new revset such as
branchname() or namedbranch(). While this would retain backwards
compatibility, the distinction between it and branch() would not be
readily apparent to users. The most intuitive behaviour would be to
have branch(x) require 'x' to be a branch name, and something like
branchof(x) or samebranch(x) do what branch(x) currently
does. Unfortunately, our backwards compatibility guarantees prevent us
from doing that.
Please note that while 'hg tag' guards against shadowing a branch, 'hg
branch' does not. Besides, even if it did, that wouldn't solve the
issue of conversions with such tags and branches...
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Wed, 23 Mar 2011 19:28:16 -0500 |
parents | 8eb758ea738c |
children | 16dc9a32ca04 |
line wrap: on
line source
# mdiff.py - diff and patch routines for mercurial # # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from i18n import _ import bdiff, mpatch, util import re, struct def splitnewlines(text): '''like str.splitlines, but only split on newlines.''' lines = [l + '\n' for l in text.split('\n')] if lines: if lines[-1] == '\n': lines.pop() else: lines[-1] = lines[-1][:-1] return lines class diffopts(object): '''context is the number of context lines text treats all files as text showfunc enables diff -p output git enables the git extended patch format nodates removes dates from diff headers ignorews ignores all whitespace changes in the diff ignorewsamount ignores changes in the amount of whitespace ignoreblanklines ignores changes whose lines are all blank upgrade generates git diffs to avoid data loss ''' defaults = { 'context': 3, 'text': False, 'showfunc': False, 'git': False, 'nodates': False, 'ignorews': False, 'ignorewsamount': False, 'ignoreblanklines': False, 'upgrade': False, } __slots__ = defaults.keys() def __init__(self, **opts): for k in self.__slots__: v = opts.get(k) if v is None: v = self.defaults[k] setattr(self, k, v) try: self.context = int(self.context) except ValueError: raise util.Abort(_('diff context lines count must be ' 'an integer, not %r') % self.context) def copy(self, **kwargs): opts = dict((k, getattr(self, k)) for k in self.defaults) opts.update(kwargs) return diffopts(**opts) defaultopts = diffopts() def wsclean(opts, text, blank=True): if opts.ignorews: text = re.sub('[ \t\r]+', '', text) elif opts.ignorewsamount: text = re.sub('[ \t\r]+', ' ', text) text = text.replace(' \n', '\n') if blank and opts.ignoreblanklines: text = re.sub('\n+', '', text) return text def diffline(revs, a, b, opts): parts = ['diff'] if opts.git: parts.append('--git') if revs and not opts.git: parts.append(' '.join(["-r %s" % rev for rev in revs])) if opts.git: parts.append('a/%s' % a) parts.append('b/%s' % b) else: parts.append(a) return ' '.join(parts) + '\n' def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts): def datetag(date, addtab=True): if not opts.git and not opts.nodates: return '\t%s\n' % date if addtab and ' ' in fn1: return '\t\n' return '\n' if not a and not b: return "" epoch = util.datestr((0, 0)) if not opts.text and (util.binary(a) or util.binary(b)): if a and b and len(a) == len(b) and a == b: return "" l = ['Binary file %s has changed\n' % fn1] elif not a: b = splitnewlines(b) if a is None: l1 = '--- /dev/null%s' % datetag(epoch, False) else: l1 = "--- %s%s" % ("a/" + fn1, datetag(ad)) l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd)) l3 = "@@ -0,0 +1,%d @@\n" % len(b) l = [l1, l2, l3] + ["+" + e for e in b] elif not b: a = splitnewlines(a) l1 = "--- %s%s" % ("a/" + fn1, datetag(ad)) if b is None: l2 = '+++ /dev/null%s' % datetag(epoch, False) else: l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd)) l3 = "@@ -1,%d +0,0 @@\n" % len(a) l = [l1, l2, l3] + ["-" + e for e in a] else: al = splitnewlines(a) bl = splitnewlines(b) l = list(_unidiff(a, b, al, bl, opts=opts)) if not l: return "" l.insert(0, "--- a/%s%s" % (fn1, datetag(ad))) l.insert(1, "+++ b/%s%s" % (fn2, datetag(bd))) for ln in xrange(len(l)): if l[ln][-1] != '\n': l[ln] += "\n\ No newline at end of file\n" if r: l.insert(0, diffline(r, fn1, fn2, opts)) return "".join(l) # creates a headerless unified diff # t1 and t2 are the text to be diffed # l1 and l2 are the text broken up into lines def _unidiff(t1, t2, l1, l2, opts=defaultopts): def contextend(l, len): ret = l + opts.context if ret > len: ret = len return ret def contextstart(l): ret = l - opts.context if ret < 0: return 0 return ret def yieldhunk(hunk): (astart, a2, bstart, b2, delta) = hunk aend = contextend(a2, len(l1)) alen = aend - astart blen = b2 - bstart + aend - a2 func = "" if opts.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, -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] if opts.showfunc: funcre = re.compile('\w') # 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. # if opts.ignorews or opts.ignorewsamount: t1 = wsclean(opts, t1, False) t2 = wsclean(opts, t2, False) diff = bdiff.blocks(t1, t2) hunk = None for i, s1 in enumerate(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 = [] 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 opts.ignoreblanklines: if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)): 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] + opts.context + 1: prev = hunk astart = hunk[1] bstart = hunk[3] else: for x in yieldhunk(hunk): yield x 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): yield x def patchtext(bin): pos = 0 t = [] while pos < len(bin): p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12]) pos += 12 t.append(bin[pos:pos + l]) pos += l return "".join(t) def patch(a, bin): if len(a) == 0: # skip over trivial delta header return buffer(bin, 12) return mpatch.patches(a, [bin]) # similar to difflib.SequenceMatcher.get_matching_blocks def get_matching_blocks(a, b): return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)] def trivialdiffheader(length): return struct.pack(">lll", 0, 0, length) patches = mpatch.patches patchedsize = mpatch.patchedsize textdiff = bdiff.bdiff