##// END OF EJS Templates
Don't lie that "binary file has changed"...
tailgunner@smtp.ru -
r4103:544838cc default
parent child Browse files
Show More
@@ -1,252 +1,257 b''
1 1 # mdiff.py - diff and patch routines for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 import bdiff, mpatch
10 demandload(globals(), "re struct util")
10 demandload(globals(), "re struct util md5")
11 11
12 12 def splitnewlines(text):
13 13 '''like str.splitlines, but only split on newlines.'''
14 14 lines = [l + '\n' for l in text.split('\n')]
15 15 if lines:
16 16 if lines[-1] == '\n':
17 17 lines.pop()
18 18 else:
19 19 lines[-1] = lines[-1][:-1]
20 20 return lines
21 21
22 22 class diffopts(object):
23 23 '''context is the number of context lines
24 24 text treats all files as text
25 25 showfunc enables diff -p output
26 26 git enables the git extended patch format
27 27 nodates removes dates from diff headers
28 28 ignorews ignores all whitespace changes in the diff
29 29 ignorewsamount ignores changes in the amount of whitespace
30 30 ignoreblanklines ignores changes whose lines are all blank'''
31 31
32 32 defaults = {
33 33 'context': 3,
34 34 'text': False,
35 35 'showfunc': True,
36 36 'git': False,
37 37 'nodates': False,
38 38 'ignorews': False,
39 39 'ignorewsamount': False,
40 40 'ignoreblanklines': False,
41 41 }
42 42
43 43 __slots__ = defaults.keys()
44 44
45 45 def __init__(self, **opts):
46 46 for k in self.__slots__:
47 47 v = opts.get(k)
48 48 if v is None:
49 49 v = self.defaults[k]
50 50 setattr(self, k, v)
51 51
52 52 defaultopts = diffopts()
53 53
54 54 def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
55 55 def datetag(date):
56 56 return (opts.git or opts.nodates) and '\n' or '\t%s\n' % date
57 57
58 58 if not a and not b: return ""
59 59 epoch = util.datestr((0, 0))
60 60
61 61 if not opts.text and (util.binary(a) or util.binary(b)):
62 def h(v):
63 # md5 is used instead of sha1 because md5 is supposedly faster
64 return md5.new(v).digest()
65 if a and b and len(a) == len(b) and h(a) == h(b):
66 return ""
62 67 l = ['Binary file %s has changed\n' % fn]
63 68 elif not a:
64 69 b = splitnewlines(b)
65 70 if a is None:
66 71 l1 = '--- /dev/null%s' % datetag(epoch)
67 72 else:
68 73 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
69 74 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
70 75 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
71 76 l = [l1, l2, l3] + ["+" + e for e in b]
72 77 elif not b:
73 78 a = splitnewlines(a)
74 79 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
75 80 if b is None:
76 81 l2 = '+++ /dev/null%s' % datetag(epoch)
77 82 else:
78 83 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
79 84 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
80 85 l = [l1, l2, l3] + ["-" + e for e in a]
81 86 else:
82 87 al = splitnewlines(a)
83 88 bl = splitnewlines(b)
84 89 l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
85 90 if not l: return ""
86 91 # difflib uses a space, rather than a tab
87 92 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
88 93 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
89 94
90 95 for ln in xrange(len(l)):
91 96 if l[ln][-1] != '\n':
92 97 l[ln] += "\n\ No newline at end of file\n"
93 98
94 99 if r:
95 100 l.insert(0, "diff %s %s\n" %
96 101 (' '.join(["-r %s" % rev for rev in r]), fn))
97 102
98 103 return "".join(l)
99 104
100 105 # somewhat self contained replacement for difflib.unified_diff
101 106 # t1 and t2 are the text to be diffed
102 107 # l1 and l2 are the text broken up into lines
103 108 # header1 and header2 are the filenames for the diff output
104 109 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
105 110 def contextend(l, len):
106 111 ret = l + opts.context
107 112 if ret > len:
108 113 ret = len
109 114 return ret
110 115
111 116 def contextstart(l):
112 117 ret = l - opts.context
113 118 if ret < 0:
114 119 return 0
115 120 return ret
116 121
117 122 def yieldhunk(hunk, header):
118 123 if header:
119 124 for x in header:
120 125 yield x
121 126 (astart, a2, bstart, b2, delta) = hunk
122 127 aend = contextend(a2, len(l1))
123 128 alen = aend - astart
124 129 blen = b2 - bstart + aend - a2
125 130
126 131 func = ""
127 132 if opts.showfunc:
128 133 # walk backwards from the start of the context
129 134 # to find a line starting with an alphanumeric char.
130 135 for x in xrange(astart, -1, -1):
131 136 t = l1[x].rstrip()
132 137 if funcre.match(t):
133 138 func = ' ' + t[:40]
134 139 break
135 140
136 141 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
137 142 bstart + 1, blen, func)
138 143 for x in delta:
139 144 yield x
140 145 for x in xrange(a2, aend):
141 146 yield ' ' + l1[x]
142 147
143 148 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
144 149
145 150 if opts.showfunc:
146 151 funcre = re.compile('\w')
147 152 if opts.ignorewsamount:
148 153 wsamountre = re.compile('[ \t]+')
149 154 wsappendedre = re.compile(' \n')
150 155 if opts.ignoreblanklines:
151 156 wsblanklinesre = re.compile('\n')
152 157 if opts.ignorews:
153 158 wsre = re.compile('[ \t]')
154 159
155 160 # bdiff.blocks gives us the matching sequences in the files. The loop
156 161 # below finds the spaces between those matching sequences and translates
157 162 # them into diff output.
158 163 #
159 164 diff = bdiff.blocks(t1, t2)
160 165 hunk = None
161 166 for i in xrange(len(diff)):
162 167 # The first match is special.
163 168 # we've either found a match starting at line 0 or a match later
164 169 # in the file. If it starts later, old and new below will both be
165 170 # empty and we'll continue to the next match.
166 171 if i > 0:
167 172 s = diff[i-1]
168 173 else:
169 174 s = [0, 0, 0, 0]
170 175 delta = []
171 176 s1 = diff[i]
172 177 a1 = s[1]
173 178 a2 = s1[0]
174 179 b1 = s[3]
175 180 b2 = s1[2]
176 181
177 182 old = l1[a1:a2]
178 183 new = l2[b1:b2]
179 184
180 185 # bdiff sometimes gives huge matches past eof, this check eats them,
181 186 # and deals with the special first match case described above
182 187 if not old and not new:
183 188 continue
184 189
185 190 if opts.ignoreblanklines:
186 191 wsold = wsblanklinesre.sub('', "".join(old))
187 192 wsnew = wsblanklinesre.sub('', "".join(new))
188 193 if wsold == wsnew:
189 194 continue
190 195
191 196 if opts.ignorewsamount:
192 197 wsold = wsamountre.sub(' ', "".join(old))
193 198 wsold = wsappendedre.sub('\n', wsold)
194 199 wsnew = wsamountre.sub(' ', "".join(new))
195 200 wsnew = wsappendedre.sub('\n', wsnew)
196 201 if wsold == wsnew:
197 202 continue
198 203
199 204 if opts.ignorews:
200 205 wsold = wsre.sub('', "".join(old))
201 206 wsnew = wsre.sub('', "".join(new))
202 207 if wsold == wsnew:
203 208 continue
204 209
205 210 astart = contextstart(a1)
206 211 bstart = contextstart(b1)
207 212 prev = None
208 213 if hunk:
209 214 # join with the previous hunk if it falls inside the context
210 215 if astart < hunk[1] + opts.context + 1:
211 216 prev = hunk
212 217 astart = hunk[1]
213 218 bstart = hunk[3]
214 219 else:
215 220 for x in yieldhunk(hunk, header):
216 221 yield x
217 222 # we only want to yield the header if the files differ, and
218 223 # we only want to yield it once.
219 224 header = None
220 225 if prev:
221 226 # we've joined the previous hunk, record the new ending points.
222 227 hunk[1] = a2
223 228 hunk[3] = b2
224 229 delta = hunk[4]
225 230 else:
226 231 # create a new hunk
227 232 hunk = [ astart, a2, bstart, b2, delta ]
228 233
229 234 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
230 235 delta[len(delta):] = [ '-' + x for x in old ]
231 236 delta[len(delta):] = [ '+' + x for x in new ]
232 237
233 238 if hunk:
234 239 for x in yieldhunk(hunk, header):
235 240 yield x
236 241
237 242 def patchtext(bin):
238 243 pos = 0
239 244 t = []
240 245 while pos < len(bin):
241 246 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
242 247 pos += 12
243 248 t.append(bin[pos:pos + l])
244 249 pos += l
245 250 return "".join(t)
246 251
247 252 def patch(a, bin):
248 253 return mpatch.patches(a, [bin])
249 254
250 255 patches = mpatch.patches
251 256 patchedsize = mpatch.patchedsize
252 257 textdiff = bdiff.bdiff
General Comments 0
You need to be logged in to leave comments. Login now