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