mercurial/mdiff.py
changeset 2873 4ec58b157265
parent 2858 345bac2bc4ec
child 2892 8b02af865990
equal deleted inserted replaced
2872:5dd6631c8238 2873:4ec58b157265
    17             lines.pop()
    17             lines.pop()
    18         else:
    18         else:
    19             lines[-1] = lines[-1][:-1]
    19             lines[-1] = lines[-1][:-1]
    20     return lines
    20     return lines
    21 
    21 
    22 def unidiff(a, ad, b, bd, fn, r=None, text=False,
    22 class diffopts(object):
    23             showfunc=False, ignorews=False, ignorewsamount=False,
    23     '''context is the number of context lines
    24             ignoreblanklines=False):
    24     text treats all files as text
    25 
    25     showfunc enables diff -p output
       
    26     ignorews ignores all whitespace changes in the diff
       
    27     ignorewsamount ignores changes in the amount of whitespace
       
    28     ignoreblanklines ignores changes whose lines are all blank'''
       
    29 
       
    30     defaults = {
       
    31         'context': 3,
       
    32         'text': False,
       
    33         'showfunc': True,
       
    34         'ignorews': False,
       
    35         'ignorewsamount': False,
       
    36         'ignoreblanklines': False,
       
    37         }
       
    38 
       
    39     __slots__ = defaults.keys()
       
    40 
       
    41     def __init__(self, **opts):
       
    42         for k in self.__slots__:
       
    43             v = opts.get(k)
       
    44             if v is None:
       
    45                 v = self.defaults[k]
       
    46             setattr(self, k, v)
       
    47 
       
    48 defaultopts = diffopts()
       
    49 
       
    50 def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
    26     if not a and not b: return ""
    51     if not a and not b: return ""
    27     epoch = util.datestr((0, 0))
    52     epoch = util.datestr((0, 0))
    28 
    53 
    29     if not text and (util.binary(a) or util.binary(b)):
    54     if not opts.text and (util.binary(a) or util.binary(b)):
    30         l = ['Binary file %s has changed\n' % fn]
    55         l = ['Binary file %s has changed\n' % fn]
    31     elif not a:
    56     elif not a:
    32         b = splitnewlines(b)
    57         b = splitnewlines(b)
    33         if a is None:
    58         if a is None:
    34             l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
    59             l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
    47         l3 = "@@ -1,%d +0,0 @@\n" % len(a)
    72         l3 = "@@ -1,%d +0,0 @@\n" % len(a)
    48         l = [l1, l2, l3] + ["-" + e for e in a]
    73         l = [l1, l2, l3] + ["-" + e for e in a]
    49     else:
    74     else:
    50         al = splitnewlines(a)
    75         al = splitnewlines(a)
    51         bl = splitnewlines(b)
    76         bl = splitnewlines(b)
    52         l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn,
    77         l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
    53                           showfunc=showfunc, ignorews=ignorews,
       
    54                           ignorewsamount=ignorewsamount,
       
    55                           ignoreblanklines=ignoreblanklines))
       
    56         if not l: return ""
    78         if not l: return ""
    57         # difflib uses a space, rather than a tab
    79         # difflib uses a space, rather than a tab
    58         l[0] = "%s\t%s\n" % (l[0][:-2], ad)
    80         l[0] = "%s\t%s\n" % (l[0][:-2], ad)
    59         l[1] = "%s\t%s\n" % (l[1][:-2], bd)
    81         l[1] = "%s\t%s\n" % (l[1][:-2], bd)
    60 
    82 
    70 
    92 
    71 # somewhat self contained replacement for difflib.unified_diff
    93 # somewhat self contained replacement for difflib.unified_diff
    72 # t1 and t2 are the text to be diffed
    94 # t1 and t2 are the text to be diffed
    73 # l1 and l2 are the text broken up into lines
    95 # l1 and l2 are the text broken up into lines
    74 # header1 and header2 are the filenames for the diff output
    96 # header1 and header2 are the filenames for the diff output
    75 # context is the number of context lines
    97 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
    76 # showfunc enables diff -p output
       
    77 # ignorews ignores all whitespace changes in the diff
       
    78 # ignorewsamount ignores changes in the amount of whitespace
       
    79 # ignoreblanklines ignores changes whose lines are all blank
       
    80 def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False,
       
    81              ignorews=False, ignorewsamount=False, ignoreblanklines=False):
       
    82     def contextend(l, len):
    98     def contextend(l, len):
    83         ret = l + context
    99         ret = l + opts.context
    84         if ret > len:
   100         if ret > len:
    85             ret = len
   101             ret = len
    86         return ret
   102         return ret
    87 
   103 
    88     def contextstart(l):
   104     def contextstart(l):
    89         ret = l - context
   105         ret = l - opts.context
    90         if ret < 0:
   106         if ret < 0:
    91             return 0
   107             return 0
    92         return ret
   108         return ret
    93 
   109 
    94     def yieldhunk(hunk, header):
   110     def yieldhunk(hunk, header):
    99         aend = contextend(a2, len(l1))
   115         aend = contextend(a2, len(l1))
   100         alen = aend - astart
   116         alen = aend - astart
   101         blen = b2 - bstart + aend - a2
   117         blen = b2 - bstart + aend - a2
   102 
   118 
   103         func = ""
   119         func = ""
   104         if showfunc:
   120         if opts.showfunc:
   105             # walk backwards from the start of the context
   121             # walk backwards from the start of the context
   106             # to find a line starting with an alphanumeric char.
   122             # to find a line starting with an alphanumeric char.
   107             for x in xrange(astart, -1, -1):
   123             for x in xrange(astart, -1, -1):
   108                 t = l1[x].rstrip()
   124                 t = l1[x].rstrip()
   109                 if funcre.match(t):
   125                 if funcre.match(t):
   117         for x in xrange(a2, aend):
   133         for x in xrange(a2, aend):
   118             yield ' ' + l1[x]
   134             yield ' ' + l1[x]
   119 
   135 
   120     header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
   136     header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
   121 
   137 
   122     if showfunc:
   138     if opts.showfunc:
   123         funcre = re.compile('\w')
   139         funcre = re.compile('\w')
   124     if ignorewsamount:
   140     if opts.ignorewsamount:
   125         wsamountre = re.compile('[ \t]+')
   141         wsamountre = re.compile('[ \t]+')
   126         wsappendedre = re.compile(' \n')
   142         wsappendedre = re.compile(' \n')
   127     if ignoreblanklines:
   143     if opts.ignoreblanklines:
   128         wsblanklinesre = re.compile('\n')
   144         wsblanklinesre = re.compile('\n')
   129     if ignorews:
   145     if opts.ignorews:
   130         wsre = re.compile('[ \t]')
   146         wsre = re.compile('[ \t]')
   131 
   147 
   132     # bdiff.blocks gives us the matching sequences in the files.  The loop
   148     # bdiff.blocks gives us the matching sequences in the files.  The loop
   133     # below finds the spaces between those matching sequences and translates
   149     # below finds the spaces between those matching sequences and translates
   134     # them into diff output.
   150     # them into diff output.
   157         # bdiff sometimes gives huge matches past eof, this check eats them,
   173         # bdiff sometimes gives huge matches past eof, this check eats them,
   158         # and deals with the special first match case described above
   174         # and deals with the special first match case described above
   159         if not old and not new:
   175         if not old and not new:
   160             continue
   176             continue
   161 
   177 
   162         if ignoreblanklines:
   178         if opts.ignoreblanklines:
   163             wsold = wsblanklinesre.sub('', "".join(old))
   179             wsold = wsblanklinesre.sub('', "".join(old))
   164             wsnew = wsblanklinesre.sub('', "".join(new))
   180             wsnew = wsblanklinesre.sub('', "".join(new))
   165             if wsold == wsnew:
   181             if wsold == wsnew:
   166                 continue
   182                 continue
   167 
   183 
   168         if ignorewsamount:
   184         if opts.ignorewsamount:
   169             wsold = wsamountre.sub(' ', "".join(old))
   185             wsold = wsamountre.sub(' ', "".join(old))
   170             wsold = wsappendedre.sub('\n', wsold)
   186             wsold = wsappendedre.sub('\n', wsold)
   171             wsnew = wsamountre.sub(' ', "".join(new))
   187             wsnew = wsamountre.sub(' ', "".join(new))
   172             wsnew = wsappendedre.sub('\n', wsnew)
   188             wsnew = wsappendedre.sub('\n', wsnew)
   173             if wsold == wsnew:
   189             if wsold == wsnew:
   174                 continue
   190                 continue
   175 
   191 
   176         if ignorews:
   192         if opts.ignorews:
   177             wsold = wsre.sub('', "".join(old))
   193             wsold = wsre.sub('', "".join(old))
   178             wsnew = wsre.sub('', "".join(new))
   194             wsnew = wsre.sub('', "".join(new))
   179             if wsold == wsnew:
   195             if wsold == wsnew:
   180                 continue
   196                 continue
   181 
   197 
   182         astart = contextstart(a1)
   198         astart = contextstart(a1)
   183         bstart = contextstart(b1)
   199         bstart = contextstart(b1)
   184         prev = None
   200         prev = None
   185         if hunk:
   201         if hunk:
   186             # join with the previous hunk if it falls inside the context
   202             # join with the previous hunk if it falls inside the context
   187             if astart < hunk[1] + context + 1:
   203             if astart < hunk[1] + opts.context + 1:
   188                 prev = hunk
   204                 prev = hunk
   189                 astart = hunk[1]
   205                 astart = hunk[1]
   190                 bstart = hunk[3]
   206                 bstart = hunk[3]
   191             else:
   207             else:
   192                 for x in yieldhunk(hunk, header):
   208                 for x in yieldhunk(hunk, header):