##// END OF EJS Templates
mdiff: remove use of __slots__...
Gregory Szorc -
r29416:30789efb default
parent child Browse files
Show More
@@ -1,386 +1,384
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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11 import struct
12 12 import zlib
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 base85,
17 17 bdiff,
18 18 error,
19 19 mpatch,
20 20 util,
21 21 )
22 22
23 23 def splitnewlines(text):
24 24 '''like str.splitlines, but only split on newlines.'''
25 25 lines = [l + '\n' for l in text.split('\n')]
26 26 if lines:
27 27 if lines[-1] == '\n':
28 28 lines.pop()
29 29 else:
30 30 lines[-1] = lines[-1][:-1]
31 31 return lines
32 32
33 33 class diffopts(object):
34 34 '''context is the number of context lines
35 35 text treats all files as text
36 36 showfunc enables diff -p output
37 37 git enables the git extended patch format
38 38 nodates removes dates from diff headers
39 39 nobinary ignores binary files
40 40 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
41 41 ignorews ignores all whitespace changes in the diff
42 42 ignorewsamount ignores changes in the amount of whitespace
43 43 ignoreblanklines ignores changes whose lines are all blank
44 44 upgrade generates git diffs to avoid data loss
45 45 '''
46 46
47 47 defaults = {
48 48 'context': 3,
49 49 'text': False,
50 50 'showfunc': False,
51 51 'git': False,
52 52 'nodates': False,
53 53 'nobinary': False,
54 54 'noprefix': False,
55 55 'ignorews': False,
56 56 'ignorewsamount': False,
57 57 'ignoreblanklines': False,
58 58 'upgrade': False,
59 59 }
60 60
61 __slots__ = defaults.keys()
62
63 61 def __init__(self, **opts):
64 for k in self.__slots__:
62 for k in self.defaults.keys():
65 63 v = opts.get(k)
66 64 if v is None:
67 65 v = self.defaults[k]
68 66 setattr(self, k, v)
69 67
70 68 try:
71 69 self.context = int(self.context)
72 70 except ValueError:
73 71 raise error.Abort(_('diff context lines count must be '
74 72 'an integer, not %r') % self.context)
75 73
76 74 def copy(self, **kwargs):
77 75 opts = dict((k, getattr(self, k)) for k in self.defaults)
78 76 opts.update(kwargs)
79 77 return diffopts(**opts)
80 78
81 79 defaultopts = diffopts()
82 80
83 81 def wsclean(opts, text, blank=True):
84 82 if opts.ignorews:
85 83 text = bdiff.fixws(text, 1)
86 84 elif opts.ignorewsamount:
87 85 text = bdiff.fixws(text, 0)
88 86 if blank and opts.ignoreblanklines:
89 87 text = re.sub('\n+', '\n', text).strip('\n')
90 88 return text
91 89
92 90 def splitblock(base1, lines1, base2, lines2, opts):
93 91 # The input lines matches except for interwoven blank lines. We
94 92 # transform it into a sequence of matching blocks and blank blocks.
95 93 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
96 94 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
97 95 s1, e1 = 0, len(lines1)
98 96 s2, e2 = 0, len(lines2)
99 97 while s1 < e1 or s2 < e2:
100 98 i1, i2, btype = s1, s2, '='
101 99 if (i1 >= e1 or lines1[i1] == 0
102 100 or i2 >= e2 or lines2[i2] == 0):
103 101 # Consume the block of blank lines
104 102 btype = '~'
105 103 while i1 < e1 and lines1[i1] == 0:
106 104 i1 += 1
107 105 while i2 < e2 and lines2[i2] == 0:
108 106 i2 += 1
109 107 else:
110 108 # Consume the matching lines
111 109 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
112 110 i1 += 1
113 111 i2 += 1
114 112 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
115 113 s1 = i1
116 114 s2 = i2
117 115
118 116 def allblocks(text1, text2, opts=None, lines1=None, lines2=None, refine=False):
119 117 """Return (block, type) tuples, where block is an mdiff.blocks
120 118 line entry. type is '=' for blocks matching exactly one another
121 119 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
122 120 matching only after having filtered blank lines. If refine is True,
123 121 then '~' blocks are refined and are only made of blank lines.
124 122 line1 and line2 are text1 and text2 split with splitnewlines() if
125 123 they are already available.
126 124 """
127 125 if opts is None:
128 126 opts = defaultopts
129 127 if opts.ignorews or opts.ignorewsamount:
130 128 text1 = wsclean(opts, text1, False)
131 129 text2 = wsclean(opts, text2, False)
132 130 diff = bdiff.blocks(text1, text2)
133 131 for i, s1 in enumerate(diff):
134 132 # The first match is special.
135 133 # we've either found a match starting at line 0 or a match later
136 134 # in the file. If it starts later, old and new below will both be
137 135 # empty and we'll continue to the next match.
138 136 if i > 0:
139 137 s = diff[i - 1]
140 138 else:
141 139 s = [0, 0, 0, 0]
142 140 s = [s[1], s1[0], s[3], s1[2]]
143 141
144 142 # bdiff sometimes gives huge matches past eof, this check eats them,
145 143 # and deals with the special first match case described above
146 144 if s[0] != s[1] or s[2] != s[3]:
147 145 type = '!'
148 146 if opts.ignoreblanklines:
149 147 if lines1 is None:
150 148 lines1 = splitnewlines(text1)
151 149 if lines2 is None:
152 150 lines2 = splitnewlines(text2)
153 151 old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
154 152 new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
155 153 if old == new:
156 154 type = '~'
157 155 yield s, type
158 156 yield s1, '='
159 157
160 158 def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts):
161 159 def datetag(date, fn=None):
162 160 if not opts.git and not opts.nodates:
163 161 return '\t%s\n' % date
164 162 if fn and ' ' in fn:
165 163 return '\t\n'
166 164 return '\n'
167 165
168 166 if not a and not b:
169 167 return ""
170 168
171 169 if opts.noprefix:
172 170 aprefix = bprefix = ''
173 171 else:
174 172 aprefix = 'a/'
175 173 bprefix = 'b/'
176 174
177 175 epoch = util.datestr((0, 0))
178 176
179 177 fn1 = util.pconvert(fn1)
180 178 fn2 = util.pconvert(fn2)
181 179
182 180 if not opts.text and (util.binary(a) or util.binary(b)):
183 181 if a and b and len(a) == len(b) and a == b:
184 182 return ""
185 183 l = ['Binary file %s has changed\n' % fn1]
186 184 elif not a:
187 185 b = splitnewlines(b)
188 186 if a is None:
189 187 l1 = '--- /dev/null%s' % datetag(epoch)
190 188 else:
191 189 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
192 190 l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
193 191 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
194 192 l = [l1, l2, l3] + ["+" + e for e in b]
195 193 elif not b:
196 194 a = splitnewlines(a)
197 195 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
198 196 if b is None:
199 197 l2 = '+++ /dev/null%s' % datetag(epoch)
200 198 else:
201 199 l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
202 200 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
203 201 l = [l1, l2, l3] + ["-" + e for e in a]
204 202 else:
205 203 al = splitnewlines(a)
206 204 bl = splitnewlines(b)
207 205 l = list(_unidiff(a, b, al, bl, opts=opts))
208 206 if not l:
209 207 return ""
210 208
211 209 l.insert(0, "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)))
212 210 l.insert(1, "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)))
213 211
214 212 for ln in xrange(len(l)):
215 213 if l[ln][-1] != '\n':
216 214 l[ln] += "\n\ No newline at end of file\n"
217 215
218 216 return "".join(l)
219 217
220 218 # creates a headerless unified diff
221 219 # t1 and t2 are the text to be diffed
222 220 # l1 and l2 are the text broken up into lines
223 221 def _unidiff(t1, t2, l1, l2, opts=defaultopts):
224 222 def contextend(l, len):
225 223 ret = l + opts.context
226 224 if ret > len:
227 225 ret = len
228 226 return ret
229 227
230 228 def contextstart(l):
231 229 ret = l - opts.context
232 230 if ret < 0:
233 231 return 0
234 232 return ret
235 233
236 234 lastfunc = [0, '']
237 235 def yieldhunk(hunk):
238 236 (astart, a2, bstart, b2, delta) = hunk
239 237 aend = contextend(a2, len(l1))
240 238 alen = aend - astart
241 239 blen = b2 - bstart + aend - a2
242 240
243 241 func = ""
244 242 if opts.showfunc:
245 243 lastpos, func = lastfunc
246 244 # walk backwards from the start of the context up to the start of
247 245 # the previous hunk context until we find a line starting with an
248 246 # alphanumeric char.
249 247 for i in xrange(astart - 1, lastpos - 1, -1):
250 248 if l1[i][0].isalnum():
251 249 func = ' ' + l1[i].rstrip()[:40]
252 250 lastfunc[1] = func
253 251 break
254 252 # by recording this hunk's starting point as the next place to
255 253 # start looking for function lines, we avoid reading any line in
256 254 # the file more than once.
257 255 lastfunc[0] = astart
258 256
259 257 # zero-length hunk ranges report their start line as one less
260 258 if alen:
261 259 astart += 1
262 260 if blen:
263 261 bstart += 1
264 262
265 263 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart, alen,
266 264 bstart, blen, func)
267 265 for x in delta:
268 266 yield x
269 267 for x in xrange(a2, aend):
270 268 yield ' ' + l1[x]
271 269
272 270 # bdiff.blocks gives us the matching sequences in the files. The loop
273 271 # below finds the spaces between those matching sequences and translates
274 272 # them into diff output.
275 273 #
276 274 hunk = None
277 275 ignoredlines = 0
278 276 for s, stype in allblocks(t1, t2, opts, l1, l2):
279 277 a1, a2, b1, b2 = s
280 278 if stype != '!':
281 279 if stype == '~':
282 280 # The diff context lines are based on t1 content. When
283 281 # blank lines are ignored, the new lines offsets must
284 282 # be adjusted as if equivalent blocks ('~') had the
285 283 # same sizes on both sides.
286 284 ignoredlines += (b2 - b1) - (a2 - a1)
287 285 continue
288 286 delta = []
289 287 old = l1[a1:a2]
290 288 new = l2[b1:b2]
291 289
292 290 b1 -= ignoredlines
293 291 b2 -= ignoredlines
294 292 astart = contextstart(a1)
295 293 bstart = contextstart(b1)
296 294 prev = None
297 295 if hunk:
298 296 # join with the previous hunk if it falls inside the context
299 297 if astart < hunk[1] + opts.context + 1:
300 298 prev = hunk
301 299 astart = hunk[1]
302 300 bstart = hunk[3]
303 301 else:
304 302 for x in yieldhunk(hunk):
305 303 yield x
306 304 if prev:
307 305 # we've joined the previous hunk, record the new ending points.
308 306 hunk[1] = a2
309 307 hunk[3] = b2
310 308 delta = hunk[4]
311 309 else:
312 310 # create a new hunk
313 311 hunk = [astart, a2, bstart, b2, delta]
314 312
315 313 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
316 314 delta[len(delta):] = ['-' + x for x in old]
317 315 delta[len(delta):] = ['+' + x for x in new]
318 316
319 317 if hunk:
320 318 for x in yieldhunk(hunk):
321 319 yield x
322 320
323 321 def b85diff(to, tn):
324 322 '''print base85-encoded binary diff'''
325 323 def fmtline(line):
326 324 l = len(line)
327 325 if l <= 26:
328 326 l = chr(ord('A') + l - 1)
329 327 else:
330 328 l = chr(l - 26 + ord('a') - 1)
331 329 return '%c%s\n' % (l, base85.b85encode(line, True))
332 330
333 331 def chunk(text, csize=52):
334 332 l = len(text)
335 333 i = 0
336 334 while i < l:
337 335 yield text[i:i + csize]
338 336 i += csize
339 337
340 338 if to is None:
341 339 to = ''
342 340 if tn is None:
343 341 tn = ''
344 342
345 343 if to == tn:
346 344 return ''
347 345
348 346 # TODO: deltas
349 347 ret = []
350 348 ret.append('GIT binary patch\n')
351 349 ret.append('literal %s\n' % len(tn))
352 350 for l in chunk(zlib.compress(tn)):
353 351 ret.append(fmtline(l))
354 352 ret.append('\n')
355 353
356 354 return ''.join(ret)
357 355
358 356 def patchtext(bin):
359 357 pos = 0
360 358 t = []
361 359 while pos < len(bin):
362 360 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
363 361 pos += 12
364 362 t.append(bin[pos:pos + l])
365 363 pos += l
366 364 return "".join(t)
367 365
368 366 def patch(a, bin):
369 367 if len(a) == 0:
370 368 # skip over trivial delta header
371 369 return util.buffer(bin, 12)
372 370 return mpatch.patches(a, [bin])
373 371
374 372 # similar to difflib.SequenceMatcher.get_matching_blocks
375 373 def get_matching_blocks(a, b):
376 374 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
377 375
378 376 def trivialdiffheader(length):
379 377 return struct.pack(">lll", 0, 0, length) if length else ''
380 378
381 379 def replacediffheader(oldlen, newlen):
382 380 return struct.pack(">lll", 0, oldlen, newlen)
383 381
384 382 patches = mpatch.patches
385 383 patchedsize = mpatch.patchedsize
386 384 textdiff = bdiff.bdiff
General Comments 0
You need to be logged in to leave comments. Login now