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