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