##// END OF EJS Templates
remove header handling out of mdiff.bunidiff, rename it
Benoit Boissinot -
r10614:d0050f36 default
parent child Browse files
Show More
@@ -1,283 +1,274
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 i18n import _
9 9 import bdiff, mpatch, util
10 10 import re, struct
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 upgrade generates git diffs to avoid data loss
32 32 '''
33 33
34 34 defaults = {
35 35 'context': 3,
36 36 'text': False,
37 37 'showfunc': False,
38 38 'git': False,
39 39 'nodates': False,
40 40 'ignorews': False,
41 41 'ignorewsamount': False,
42 42 'ignoreblanklines': False,
43 43 'upgrade': False,
44 44 }
45 45
46 46 __slots__ = defaults.keys()
47 47
48 48 def __init__(self, **opts):
49 49 for k in self.__slots__:
50 50 v = opts.get(k)
51 51 if v is None:
52 52 v = self.defaults[k]
53 53 setattr(self, k, v)
54 54
55 55 try:
56 56 self.context = int(self.context)
57 57 except ValueError:
58 58 raise util.Abort(_('diff context lines count must be '
59 59 'an integer, not %r') % self.context)
60 60
61 61 def copy(self, **kwargs):
62 62 opts = dict((k, getattr(self, k)) for k in self.defaults)
63 63 opts.update(kwargs)
64 64 return diffopts(**opts)
65 65
66 66 defaultopts = diffopts()
67 67
68 68 def wsclean(opts, text, blank=True):
69 69 if opts.ignorews:
70 70 text = re.sub('[ \t]+', '', text)
71 71 elif opts.ignorewsamount:
72 72 text = re.sub('[ \t]+', ' ', text)
73 73 text = re.sub('[ \t]+\n', '\n', text)
74 74 if blank and opts.ignoreblanklines:
75 75 text = re.sub('\n+', '', text)
76 76 return text
77 77
78 78 def diffline(revs, a, b, opts):
79 79 parts = ['diff']
80 80 if opts.git:
81 81 parts.append('--git')
82 82 if revs and not opts.git:
83 83 parts.append(' '.join(["-r %s" % rev for rev in revs]))
84 84 if opts.git:
85 85 parts.append('a/%s' % a)
86 86 parts.append('b/%s' % b)
87 87 else:
88 88 parts.append(a)
89 89 return ' '.join(parts) + '\n'
90 90
91 91 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
92 92 def datetag(date, addtab=True):
93 93 if not opts.git and not opts.nodates:
94 94 return '\t%s\n' % date
95 95 if addtab and ' ' in fn1:
96 96 return '\t\n'
97 97 return '\n'
98 98
99 99 if not a and not b:
100 100 return ""
101 101 epoch = util.datestr((0, 0))
102 102
103 103 if not opts.text and (util.binary(a) or util.binary(b)):
104 104 if a and b and len(a) == len(b) and a == b:
105 105 return ""
106 106 l = ['Binary file %s has changed\n' % fn1]
107 107 elif not a:
108 108 b = splitnewlines(b)
109 109 if a is None:
110 110 l1 = '--- /dev/null%s' % datetag(epoch, False)
111 111 else:
112 112 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
113 113 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
114 114 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
115 115 l = [l1, l2, l3] + ["+" + e for e in b]
116 116 elif not b:
117 117 a = splitnewlines(a)
118 118 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
119 119 if b is None:
120 120 l2 = '+++ /dev/null%s' % datetag(epoch, False)
121 121 else:
122 122 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
123 123 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
124 124 l = [l1, l2, l3] + ["-" + e for e in a]
125 125 else:
126 126 al = splitnewlines(a)
127 127 bl = splitnewlines(b)
128 l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
128 l = list(_unidiff(a, b, al, bl, opts=opts))
129 129 if not l:
130 130 return ""
131 # difflib uses a space, rather than a tab
132 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
133 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
131
132 l.insert(0, "--- a/%s%s" % (fn1, datetag(ad)))
133 l.insert(1, "+++ b/%s%s" % (fn2, datetag(bd)))
134 134
135 135 for ln in xrange(len(l)):
136 136 if l[ln][-1] != '\n':
137 137 l[ln] += "\n\ No newline at end of file\n"
138 138
139 139 if r:
140 140 l.insert(0, diffline(r, fn1, fn2, opts))
141 141
142 142 return "".join(l)
143 143
144 # somewhat self contained replacement for difflib.unified_diff
144 # creates a headerless unified diff
145 145 # t1 and t2 are the text to be diffed
146 146 # l1 and l2 are the text broken up into lines
147 # header1 and header2 are the filenames for the diff output
148 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
147 def _unidiff(t1, t2, l1, l2, opts=defaultopts):
149 148 def contextend(l, len):
150 149 ret = l + opts.context
151 150 if ret > len:
152 151 ret = len
153 152 return ret
154 153
155 154 def contextstart(l):
156 155 ret = l - opts.context
157 156 if ret < 0:
158 157 return 0
159 158 return ret
160 159
161 def yieldhunk(hunk, header):
162 if header:
163 for x in header:
164 yield x
160 def yieldhunk(hunk):
165 161 (astart, a2, bstart, b2, delta) = hunk
166 162 aend = contextend(a2, len(l1))
167 163 alen = aend - astart
168 164 blen = b2 - bstart + aend - a2
169 165
170 166 func = ""
171 167 if opts.showfunc:
172 168 # walk backwards from the start of the context
173 169 # to find a line starting with an alphanumeric char.
174 170 for x in xrange(astart - 1, -1, -1):
175 171 t = l1[x].rstrip()
176 172 if funcre.match(t):
177 173 func = ' ' + t[:40]
178 174 break
179 175
180 176 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
181 177 bstart + 1, blen, func)
182 178 for x in delta:
183 179 yield x
184 180 for x in xrange(a2, aend):
185 181 yield ' ' + l1[x]
186 182
187 header = ["--- %s\t\n" % header1, "+++ %s\t\n" % header2]
188
189 183 if opts.showfunc:
190 184 funcre = re.compile('\w')
191 185
192 186 # bdiff.blocks gives us the matching sequences in the files. The loop
193 187 # below finds the spaces between those matching sequences and translates
194 188 # them into diff output.
195 189 #
196 190 if opts.ignorews or opts.ignorewsamount:
197 191 t1 = wsclean(opts, t1, False)
198 192 t2 = wsclean(opts, t2, False)
199 193
200 194 diff = bdiff.blocks(t1, t2)
201 195 hunk = None
202 196 for i, s1 in enumerate(diff):
203 197 # The first match is special.
204 198 # we've either found a match starting at line 0 or a match later
205 199 # in the file. If it starts later, old and new below will both be
206 200 # empty and we'll continue to the next match.
207 201 if i > 0:
208 202 s = diff[i - 1]
209 203 else:
210 204 s = [0, 0, 0, 0]
211 205 delta = []
212 206 a1 = s[1]
213 207 a2 = s1[0]
214 208 b1 = s[3]
215 209 b2 = s1[2]
216 210
217 211 old = l1[a1:a2]
218 212 new = l2[b1:b2]
219 213
220 214 # bdiff sometimes gives huge matches past eof, this check eats them,
221 215 # and deals with the special first match case described above
222 216 if not old and not new:
223 217 continue
224 218
225 219 if opts.ignoreblanklines:
226 220 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
227 221 continue
228 222
229 223 astart = contextstart(a1)
230 224 bstart = contextstart(b1)
231 225 prev = None
232 226 if hunk:
233 227 # join with the previous hunk if it falls inside the context
234 228 if astart < hunk[1] + opts.context + 1:
235 229 prev = hunk
236 230 astart = hunk[1]
237 231 bstart = hunk[3]
238 232 else:
239 for x in yieldhunk(hunk, header):
233 for x in yieldhunk(hunk):
240 234 yield x
241 # we only want to yield the header if the files differ, and
242 # we only want to yield it once.
243 header = None
244 235 if prev:
245 236 # we've joined the previous hunk, record the new ending points.
246 237 hunk[1] = a2
247 238 hunk[3] = b2
248 239 delta = hunk[4]
249 240 else:
250 241 # create a new hunk
251 242 hunk = [astart, a2, bstart, b2, delta]
252 243
253 244 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
254 245 delta[len(delta):] = ['-' + x for x in old]
255 246 delta[len(delta):] = ['+' + x for x in new]
256 247
257 248 if hunk:
258 for x in yieldhunk(hunk, header):
249 for x in yieldhunk(hunk):
259 250 yield x
260 251
261 252 def patchtext(bin):
262 253 pos = 0
263 254 t = []
264 255 while pos < len(bin):
265 256 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
266 257 pos += 12
267 258 t.append(bin[pos:pos + l])
268 259 pos += l
269 260 return "".join(t)
270 261
271 262 def patch(a, bin):
272 263 return mpatch.patches(a, [bin])
273 264
274 265 # similar to difflib.SequenceMatcher.get_matching_blocks
275 266 def get_matching_blocks(a, b):
276 267 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
277 268
278 269 def trivialdiffheader(length):
279 270 return struct.pack(">lll", 0, 0, length)
280 271
281 272 patches = mpatch.patches
282 273 patchedsize = mpatch.patchedsize
283 274 textdiff = bdiff.bdiff
General Comments 0
You need to be logged in to leave comments. Login now