##// END OF EJS Templates
errors: use detailed exit code for non-integer number of diff context lines...
Martin von Zweigbergk -
r49188:67064c23 default
parent child Browse files
Show More
@@ -1,556 +1,556 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 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@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 .pycompat import (
15 from .pycompat import (
16 getattr,
16 getattr,
17 setattr,
17 setattr,
18 )
18 )
19 from . import (
19 from . import (
20 diffhelper,
20 diffhelper,
21 encoding,
21 encoding,
22 error,
22 error,
23 policy,
23 policy,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 )
26 )
27 from .utils import dateutil
27 from .utils import dateutil
28
28
29 bdiff = policy.importmod('bdiff')
29 bdiff = policy.importmod('bdiff')
30 mpatch = policy.importmod('mpatch')
30 mpatch = policy.importmod('mpatch')
31
31
32 blocks = bdiff.blocks
32 blocks = bdiff.blocks
33 fixws = bdiff.fixws
33 fixws = bdiff.fixws
34 patches = mpatch.patches
34 patches = mpatch.patches
35 patchedsize = mpatch.patchedsize
35 patchedsize = mpatch.patchedsize
36 textdiff = bdiff.bdiff
36 textdiff = bdiff.bdiff
37 splitnewlines = bdiff.splitnewlines
37 splitnewlines = bdiff.splitnewlines
38
38
39
39
40 # TODO: this looks like it could be an attrs, which might help pytype
40 # TODO: this looks like it could be an attrs, which might help pytype
41 class diffopts(object):
41 class diffopts(object):
42 """context is the number of context lines
42 """context is the number of context lines
43 text treats all files as text
43 text treats all files as text
44 showfunc enables diff -p output
44 showfunc enables diff -p output
45 git enables the git extended patch format
45 git enables the git extended patch format
46 nodates removes dates from diff headers
46 nodates removes dates from diff headers
47 nobinary ignores binary files
47 nobinary ignores binary files
48 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
48 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
49 ignorews ignores all whitespace changes in the diff
49 ignorews ignores all whitespace changes in the diff
50 ignorewsamount ignores changes in the amount of whitespace
50 ignorewsamount ignores changes in the amount of whitespace
51 ignoreblanklines ignores changes whose lines are all blank
51 ignoreblanklines ignores changes whose lines are all blank
52 upgrade generates git diffs to avoid data loss
52 upgrade generates git diffs to avoid data loss
53 """
53 """
54
54
55 _HAS_DYNAMIC_ATTRIBUTES = True
55 _HAS_DYNAMIC_ATTRIBUTES = True
56
56
57 defaults = {
57 defaults = {
58 b'context': 3,
58 b'context': 3,
59 b'text': False,
59 b'text': False,
60 b'showfunc': False,
60 b'showfunc': False,
61 b'git': False,
61 b'git': False,
62 b'nodates': False,
62 b'nodates': False,
63 b'nobinary': False,
63 b'nobinary': False,
64 b'noprefix': False,
64 b'noprefix': False,
65 b'index': 0,
65 b'index': 0,
66 b'ignorews': False,
66 b'ignorews': False,
67 b'ignorewsamount': False,
67 b'ignorewsamount': False,
68 b'ignorewseol': False,
68 b'ignorewseol': False,
69 b'ignoreblanklines': False,
69 b'ignoreblanklines': False,
70 b'upgrade': False,
70 b'upgrade': False,
71 b'showsimilarity': False,
71 b'showsimilarity': False,
72 b'worddiff': False,
72 b'worddiff': False,
73 b'xdiff': False,
73 b'xdiff': False,
74 }
74 }
75
75
76 def __init__(self, **opts):
76 def __init__(self, **opts):
77 opts = pycompat.byteskwargs(opts)
77 opts = pycompat.byteskwargs(opts)
78 for k in self.defaults.keys():
78 for k in self.defaults.keys():
79 v = opts.get(k)
79 v = opts.get(k)
80 if v is None:
80 if v is None:
81 v = self.defaults[k]
81 v = self.defaults[k]
82 setattr(self, k, v)
82 setattr(self, k, v)
83
83
84 try:
84 try:
85 self.context = int(self.context)
85 self.context = int(self.context)
86 except ValueError:
86 except ValueError:
87 raise error.Abort(
87 raise error.InputError(
88 _(b'diff context lines count must be an integer, not %r')
88 _(b'diff context lines count must be an integer, not %r')
89 % pycompat.bytestr(self.context)
89 % pycompat.bytestr(self.context)
90 )
90 )
91
91
92 def copy(self, **kwargs):
92 def copy(self, **kwargs):
93 opts = {k: getattr(self, k) for k in self.defaults}
93 opts = {k: getattr(self, k) for k in self.defaults}
94 opts = pycompat.strkwargs(opts)
94 opts = pycompat.strkwargs(opts)
95 opts.update(kwargs)
95 opts.update(kwargs)
96 return diffopts(**opts)
96 return diffopts(**opts)
97
97
98
98
99 defaultopts = diffopts()
99 defaultopts = diffopts()
100
100
101
101
102 def wsclean(opts, text, blank=True):
102 def wsclean(opts, text, blank=True):
103 if opts.ignorews:
103 if opts.ignorews:
104 text = bdiff.fixws(text, 1)
104 text = bdiff.fixws(text, 1)
105 elif opts.ignorewsamount:
105 elif opts.ignorewsamount:
106 text = bdiff.fixws(text, 0)
106 text = bdiff.fixws(text, 0)
107 if blank and opts.ignoreblanklines:
107 if blank and opts.ignoreblanklines:
108 text = re.sub(b'\n+', b'\n', text).strip(b'\n')
108 text = re.sub(b'\n+', b'\n', text).strip(b'\n')
109 if opts.ignorewseol:
109 if opts.ignorewseol:
110 text = re.sub(br'[ \t\r\f]+\n', br'\n', text)
110 text = re.sub(br'[ \t\r\f]+\n', br'\n', text)
111 return text
111 return text
112
112
113
113
114 def splitblock(base1, lines1, base2, lines2, opts):
114 def splitblock(base1, lines1, base2, lines2, opts):
115 # The input lines matches except for interwoven blank lines. We
115 # The input lines matches except for interwoven blank lines. We
116 # transform it into a sequence of matching blocks and blank blocks.
116 # transform it into a sequence of matching blocks and blank blocks.
117 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
117 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
118 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
118 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
119 s1, e1 = 0, len(lines1)
119 s1, e1 = 0, len(lines1)
120 s2, e2 = 0, len(lines2)
120 s2, e2 = 0, len(lines2)
121 while s1 < e1 or s2 < e2:
121 while s1 < e1 or s2 < e2:
122 i1, i2, btype = s1, s2, b'='
122 i1, i2, btype = s1, s2, b'='
123 if i1 >= e1 or lines1[i1] == 0 or i2 >= e2 or lines2[i2] == 0:
123 if i1 >= e1 or lines1[i1] == 0 or i2 >= e2 or lines2[i2] == 0:
124 # Consume the block of blank lines
124 # Consume the block of blank lines
125 btype = b'~'
125 btype = b'~'
126 while i1 < e1 and lines1[i1] == 0:
126 while i1 < e1 and lines1[i1] == 0:
127 i1 += 1
127 i1 += 1
128 while i2 < e2 and lines2[i2] == 0:
128 while i2 < e2 and lines2[i2] == 0:
129 i2 += 1
129 i2 += 1
130 else:
130 else:
131 # Consume the matching lines
131 # Consume the matching lines
132 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
132 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
133 i1 += 1
133 i1 += 1
134 i2 += 1
134 i2 += 1
135 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
135 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
136 s1 = i1
136 s1 = i1
137 s2 = i2
137 s2 = i2
138
138
139
139
140 def hunkinrange(hunk, linerange):
140 def hunkinrange(hunk, linerange):
141 """Return True if `hunk` defined as (start, length) is in `linerange`
141 """Return True if `hunk` defined as (start, length) is in `linerange`
142 defined as (lowerbound, upperbound).
142 defined as (lowerbound, upperbound).
143
143
144 >>> hunkinrange((5, 10), (2, 7))
144 >>> hunkinrange((5, 10), (2, 7))
145 True
145 True
146 >>> hunkinrange((5, 10), (6, 12))
146 >>> hunkinrange((5, 10), (6, 12))
147 True
147 True
148 >>> hunkinrange((5, 10), (13, 17))
148 >>> hunkinrange((5, 10), (13, 17))
149 True
149 True
150 >>> hunkinrange((5, 10), (3, 17))
150 >>> hunkinrange((5, 10), (3, 17))
151 True
151 True
152 >>> hunkinrange((5, 10), (1, 3))
152 >>> hunkinrange((5, 10), (1, 3))
153 False
153 False
154 >>> hunkinrange((5, 10), (18, 20))
154 >>> hunkinrange((5, 10), (18, 20))
155 False
155 False
156 >>> hunkinrange((5, 10), (1, 5))
156 >>> hunkinrange((5, 10), (1, 5))
157 False
157 False
158 >>> hunkinrange((5, 10), (15, 27))
158 >>> hunkinrange((5, 10), (15, 27))
159 False
159 False
160 """
160 """
161 start, length = hunk
161 start, length = hunk
162 lowerbound, upperbound = linerange
162 lowerbound, upperbound = linerange
163 return lowerbound < start + length and start < upperbound
163 return lowerbound < start + length and start < upperbound
164
164
165
165
166 def blocksinrange(blocks, rangeb):
166 def blocksinrange(blocks, rangeb):
167 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
167 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
168 `rangeb` from ``(b1, b2)`` point of view.
168 `rangeb` from ``(b1, b2)`` point of view.
169
169
170 Return `filteredblocks, rangea` where:
170 Return `filteredblocks, rangea` where:
171
171
172 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
172 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
173 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
173 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
174 block ``(b1, b2)`` being inside `rangeb` if
174 block ``(b1, b2)`` being inside `rangeb` if
175 ``rangeb[0] < b2 and b1 < rangeb[1]``;
175 ``rangeb[0] < b2 and b1 < rangeb[1]``;
176 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
176 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
177 """
177 """
178 lbb, ubb = rangeb
178 lbb, ubb = rangeb
179 lba, uba = None, None
179 lba, uba = None, None
180 filteredblocks = []
180 filteredblocks = []
181 for block in blocks:
181 for block in blocks:
182 (a1, a2, b1, b2), stype = block
182 (a1, a2, b1, b2), stype = block
183 if lbb >= b1 and ubb <= b2 and stype == b'=':
183 if lbb >= b1 and ubb <= b2 and stype == b'=':
184 # rangeb is within a single "=" hunk, restrict back linerange1
184 # rangeb is within a single "=" hunk, restrict back linerange1
185 # by offsetting rangeb
185 # by offsetting rangeb
186 lba = lbb - b1 + a1
186 lba = lbb - b1 + a1
187 uba = ubb - b1 + a1
187 uba = ubb - b1 + a1
188 else:
188 else:
189 if b1 <= lbb < b2:
189 if b1 <= lbb < b2:
190 if stype == b'=':
190 if stype == b'=':
191 lba = a2 - (b2 - lbb)
191 lba = a2 - (b2 - lbb)
192 else:
192 else:
193 lba = a1
193 lba = a1
194 if b1 < ubb <= b2:
194 if b1 < ubb <= b2:
195 if stype == b'=':
195 if stype == b'=':
196 uba = a1 + (ubb - b1)
196 uba = a1 + (ubb - b1)
197 else:
197 else:
198 uba = a2
198 uba = a2
199 if hunkinrange((b1, (b2 - b1)), rangeb):
199 if hunkinrange((b1, (b2 - b1)), rangeb):
200 filteredblocks.append(block)
200 filteredblocks.append(block)
201 if lba is None or uba is None or uba < lba:
201 if lba is None or uba is None or uba < lba:
202 raise error.InputError(_(b'line range exceeds file size'))
202 raise error.InputError(_(b'line range exceeds file size'))
203 return filteredblocks, (lba, uba)
203 return filteredblocks, (lba, uba)
204
204
205
205
206 def chooseblocksfunc(opts=None):
206 def chooseblocksfunc(opts=None):
207 if (
207 if (
208 opts is None
208 opts is None
209 or not opts.xdiff
209 or not opts.xdiff
210 or not util.safehasattr(bdiff, b'xdiffblocks')
210 or not util.safehasattr(bdiff, b'xdiffblocks')
211 ):
211 ):
212 return bdiff.blocks
212 return bdiff.blocks
213 else:
213 else:
214 return bdiff.xdiffblocks
214 return bdiff.xdiffblocks
215
215
216
216
217 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
217 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
218 """Return (block, type) tuples, where block is an mdiff.blocks
218 """Return (block, type) tuples, where block is an mdiff.blocks
219 line entry. type is '=' for blocks matching exactly one another
219 line entry. type is '=' for blocks matching exactly one another
220 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
220 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
221 matching only after having filtered blank lines.
221 matching only after having filtered blank lines.
222 line1 and line2 are text1 and text2 split with splitnewlines() if
222 line1 and line2 are text1 and text2 split with splitnewlines() if
223 they are already available.
223 they are already available.
224 """
224 """
225 if opts is None:
225 if opts is None:
226 opts = defaultopts
226 opts = defaultopts
227 if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
227 if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
228 text1 = wsclean(opts, text1, False)
228 text1 = wsclean(opts, text1, False)
229 text2 = wsclean(opts, text2, False)
229 text2 = wsclean(opts, text2, False)
230 diff = chooseblocksfunc(opts)(text1, text2)
230 diff = chooseblocksfunc(opts)(text1, text2)
231 for i, s1 in enumerate(diff):
231 for i, s1 in enumerate(diff):
232 # The first match is special.
232 # The first match is special.
233 # we've either found a match starting at line 0 or a match later
233 # we've either found a match starting at line 0 or a match later
234 # in the file. If it starts later, old and new below will both be
234 # in the file. If it starts later, old and new below will both be
235 # empty and we'll continue to the next match.
235 # empty and we'll continue to the next match.
236 if i > 0:
236 if i > 0:
237 s = diff[i - 1]
237 s = diff[i - 1]
238 else:
238 else:
239 s = [0, 0, 0, 0]
239 s = [0, 0, 0, 0]
240 s = [s[1], s1[0], s[3], s1[2]]
240 s = [s[1], s1[0], s[3], s1[2]]
241
241
242 # bdiff sometimes gives huge matches past eof, this check eats them,
242 # bdiff sometimes gives huge matches past eof, this check eats them,
243 # and deals with the special first match case described above
243 # and deals with the special first match case described above
244 if s[0] != s[1] or s[2] != s[3]:
244 if s[0] != s[1] or s[2] != s[3]:
245 type = b'!'
245 type = b'!'
246 if opts.ignoreblanklines:
246 if opts.ignoreblanklines:
247 if lines1 is None:
247 if lines1 is None:
248 lines1 = splitnewlines(text1)
248 lines1 = splitnewlines(text1)
249 if lines2 is None:
249 if lines2 is None:
250 lines2 = splitnewlines(text2)
250 lines2 = splitnewlines(text2)
251 old = wsclean(opts, b"".join(lines1[s[0] : s[1]]))
251 old = wsclean(opts, b"".join(lines1[s[0] : s[1]]))
252 new = wsclean(opts, b"".join(lines2[s[2] : s[3]]))
252 new = wsclean(opts, b"".join(lines2[s[2] : s[3]]))
253 if old == new:
253 if old == new:
254 type = b'~'
254 type = b'~'
255 yield s, type
255 yield s, type
256 yield s1, b'='
256 yield s1, b'='
257
257
258
258
259 def unidiff(a, ad, b, bd, fn1, fn2, binary, opts=defaultopts):
259 def unidiff(a, ad, b, bd, fn1, fn2, binary, opts=defaultopts):
260 """Return a unified diff as a (headers, hunks) tuple.
260 """Return a unified diff as a (headers, hunks) tuple.
261
261
262 If the diff is not null, `headers` is a list with unified diff header
262 If the diff is not null, `headers` is a list with unified diff header
263 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
263 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
264 (hunkrange, hunklines) coming from _unidiff().
264 (hunkrange, hunklines) coming from _unidiff().
265 Otherwise, `headers` and `hunks` are empty.
265 Otherwise, `headers` and `hunks` are empty.
266
266
267 Set binary=True if either a or b should be taken as a binary file.
267 Set binary=True if either a or b should be taken as a binary file.
268 """
268 """
269
269
270 def datetag(date, fn=None):
270 def datetag(date, fn=None):
271 if not opts.git and not opts.nodates:
271 if not opts.git and not opts.nodates:
272 return b'\t%s' % date
272 return b'\t%s' % date
273 if fn and b' ' in fn:
273 if fn and b' ' in fn:
274 return b'\t'
274 return b'\t'
275 return b''
275 return b''
276
276
277 sentinel = [], ()
277 sentinel = [], ()
278 if not a and not b:
278 if not a and not b:
279 return sentinel
279 return sentinel
280
280
281 if opts.noprefix:
281 if opts.noprefix:
282 aprefix = bprefix = b''
282 aprefix = bprefix = b''
283 else:
283 else:
284 aprefix = b'a/'
284 aprefix = b'a/'
285 bprefix = b'b/'
285 bprefix = b'b/'
286
286
287 epoch = dateutil.datestr((0, 0))
287 epoch = dateutil.datestr((0, 0))
288
288
289 fn1 = util.pconvert(fn1)
289 fn1 = util.pconvert(fn1)
290 fn2 = util.pconvert(fn2)
290 fn2 = util.pconvert(fn2)
291
291
292 if binary:
292 if binary:
293 if a and b and len(a) == len(b) and a == b:
293 if a and b and len(a) == len(b) and a == b:
294 return sentinel
294 return sentinel
295 headerlines = []
295 headerlines = []
296 hunks = ((None, [b'Binary file %s has changed\n' % fn1]),)
296 hunks = ((None, [b'Binary file %s has changed\n' % fn1]),)
297 elif not a:
297 elif not a:
298 without_newline = not b.endswith(b'\n')
298 without_newline = not b.endswith(b'\n')
299 b = splitnewlines(b)
299 b = splitnewlines(b)
300 if a is None:
300 if a is None:
301 l1 = b'--- /dev/null%s' % datetag(epoch)
301 l1 = b'--- /dev/null%s' % datetag(epoch)
302 else:
302 else:
303 l1 = b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
303 l1 = b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
304 l2 = b"+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
304 l2 = b"+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
305 headerlines = [l1, l2]
305 headerlines = [l1, l2]
306 size = len(b)
306 size = len(b)
307 hunkrange = (0, 0, 1, size)
307 hunkrange = (0, 0, 1, size)
308 hunklines = [b"@@ -0,0 +1,%d @@\n" % size] + [b"+" + e for e in b]
308 hunklines = [b"@@ -0,0 +1,%d @@\n" % size] + [b"+" + e for e in b]
309 if without_newline:
309 if without_newline:
310 hunklines[-1] += b'\n'
310 hunklines[-1] += b'\n'
311 hunklines.append(diffhelper.MISSING_NEWLINE_MARKER)
311 hunklines.append(diffhelper.MISSING_NEWLINE_MARKER)
312 hunks = ((hunkrange, hunklines),)
312 hunks = ((hunkrange, hunklines),)
313 elif not b:
313 elif not b:
314 without_newline = not a.endswith(b'\n')
314 without_newline = not a.endswith(b'\n')
315 a = splitnewlines(a)
315 a = splitnewlines(a)
316 l1 = b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
316 l1 = b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
317 if b is None:
317 if b is None:
318 l2 = b'+++ /dev/null%s' % datetag(epoch)
318 l2 = b'+++ /dev/null%s' % datetag(epoch)
319 else:
319 else:
320 l2 = b"+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
320 l2 = b"+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
321 headerlines = [l1, l2]
321 headerlines = [l1, l2]
322 size = len(a)
322 size = len(a)
323 hunkrange = (1, size, 0, 0)
323 hunkrange = (1, size, 0, 0)
324 hunklines = [b"@@ -1,%d +0,0 @@\n" % size] + [b"-" + e for e in a]
324 hunklines = [b"@@ -1,%d +0,0 @@\n" % size] + [b"-" + e for e in a]
325 if without_newline:
325 if without_newline:
326 hunklines[-1] += b'\n'
326 hunklines[-1] += b'\n'
327 hunklines.append(diffhelper.MISSING_NEWLINE_MARKER)
327 hunklines.append(diffhelper.MISSING_NEWLINE_MARKER)
328 hunks = ((hunkrange, hunklines),)
328 hunks = ((hunkrange, hunklines),)
329 else:
329 else:
330 hunks = _unidiff(a, b, opts=opts)
330 hunks = _unidiff(a, b, opts=opts)
331 if not next(hunks):
331 if not next(hunks):
332 return sentinel
332 return sentinel
333
333
334 headerlines = [
334 headerlines = [
335 b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
335 b"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
336 b"+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
336 b"+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
337 ]
337 ]
338
338
339 return headerlines, hunks
339 return headerlines, hunks
340
340
341
341
342 def _unidiff(t1, t2, opts=defaultopts):
342 def _unidiff(t1, t2, opts=defaultopts):
343 """Yield hunks of a headerless unified diff from t1 and t2 texts.
343 """Yield hunks of a headerless unified diff from t1 and t2 texts.
344
344
345 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
345 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
346 tuple (s1, l1, s2, l2) representing the range information of the hunk to
346 tuple (s1, l1, s2, l2) representing the range information of the hunk to
347 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
347 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
348 of the hunk combining said header followed by line additions and
348 of the hunk combining said header followed by line additions and
349 deletions.
349 deletions.
350
350
351 The hunks are prefixed with a bool.
351 The hunks are prefixed with a bool.
352 """
352 """
353 l1 = splitnewlines(t1)
353 l1 = splitnewlines(t1)
354 l2 = splitnewlines(t2)
354 l2 = splitnewlines(t2)
355
355
356 def contextend(l, len):
356 def contextend(l, len):
357 ret = l + opts.context
357 ret = l + opts.context
358 if ret > len:
358 if ret > len:
359 ret = len
359 ret = len
360 return ret
360 return ret
361
361
362 def contextstart(l):
362 def contextstart(l):
363 ret = l - opts.context
363 ret = l - opts.context
364 if ret < 0:
364 if ret < 0:
365 return 0
365 return 0
366 return ret
366 return ret
367
367
368 lastfunc = [0, b'']
368 lastfunc = [0, b'']
369
369
370 def yieldhunk(hunk):
370 def yieldhunk(hunk):
371 (astart, a2, bstart, b2, delta) = hunk
371 (astart, a2, bstart, b2, delta) = hunk
372 aend = contextend(a2, len(l1))
372 aend = contextend(a2, len(l1))
373 alen = aend - astart
373 alen = aend - astart
374 blen = b2 - bstart + aend - a2
374 blen = b2 - bstart + aend - a2
375
375
376 func = b""
376 func = b""
377 if opts.showfunc:
377 if opts.showfunc:
378 lastpos, func = lastfunc
378 lastpos, func = lastfunc
379 # walk backwards from the start of the context up to the start of
379 # walk backwards from the start of the context up to the start of
380 # the previous hunk context until we find a line starting with an
380 # the previous hunk context until we find a line starting with an
381 # alphanumeric char.
381 # alphanumeric char.
382 for i in pycompat.xrange(astart - 1, lastpos - 1, -1):
382 for i in pycompat.xrange(astart - 1, lastpos - 1, -1):
383 if l1[i][0:1].isalnum():
383 if l1[i][0:1].isalnum():
384 func = b' ' + l1[i].rstrip()
384 func = b' ' + l1[i].rstrip()
385 # split long function name if ASCII. otherwise we have no
385 # split long function name if ASCII. otherwise we have no
386 # idea where the multi-byte boundary is, so just leave it.
386 # idea where the multi-byte boundary is, so just leave it.
387 if encoding.isasciistr(func):
387 if encoding.isasciistr(func):
388 func = func[:41]
388 func = func[:41]
389 lastfunc[1] = func
389 lastfunc[1] = func
390 break
390 break
391 # by recording this hunk's starting point as the next place to
391 # by recording this hunk's starting point as the next place to
392 # start looking for function lines, we avoid reading any line in
392 # start looking for function lines, we avoid reading any line in
393 # the file more than once.
393 # the file more than once.
394 lastfunc[0] = astart
394 lastfunc[0] = astart
395
395
396 # zero-length hunk ranges report their start line as one less
396 # zero-length hunk ranges report their start line as one less
397 if alen:
397 if alen:
398 astart += 1
398 astart += 1
399 if blen:
399 if blen:
400 bstart += 1
400 bstart += 1
401
401
402 hunkrange = astart, alen, bstart, blen
402 hunkrange = astart, alen, bstart, blen
403 hunklines = (
403 hunklines = (
404 [b"@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
404 [b"@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
405 + delta
405 + delta
406 + [b' ' + l1[x] for x in pycompat.xrange(a2, aend)]
406 + [b' ' + l1[x] for x in pycompat.xrange(a2, aend)]
407 )
407 )
408 # If either file ends without a newline and the last line of
408 # If either file ends without a newline and the last line of
409 # that file is part of a hunk, a marker is printed. If the
409 # that file is part of a hunk, a marker is printed. If the
410 # last line of both files is identical and neither ends in
410 # last line of both files is identical and neither ends in
411 # a newline, print only one marker. That's the only case in
411 # a newline, print only one marker. That's the only case in
412 # which the hunk can end in a shared line without a newline.
412 # which the hunk can end in a shared line without a newline.
413 skip = False
413 skip = False
414 if not t1.endswith(b'\n') and astart + alen == len(l1) + 1:
414 if not t1.endswith(b'\n') and astart + alen == len(l1) + 1:
415 for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
415 for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
416 if hunklines[i].startswith((b'-', b' ')):
416 if hunklines[i].startswith((b'-', b' ')):
417 if hunklines[i].startswith(b' '):
417 if hunklines[i].startswith(b' '):
418 skip = True
418 skip = True
419 hunklines[i] += b'\n'
419 hunklines[i] += b'\n'
420 hunklines.insert(i + 1, diffhelper.MISSING_NEWLINE_MARKER)
420 hunklines.insert(i + 1, diffhelper.MISSING_NEWLINE_MARKER)
421 break
421 break
422 if not skip and not t2.endswith(b'\n') and bstart + blen == len(l2) + 1:
422 if not skip and not t2.endswith(b'\n') and bstart + blen == len(l2) + 1:
423 for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
423 for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
424 if hunklines[i].startswith(b'+'):
424 if hunklines[i].startswith(b'+'):
425 hunklines[i] += b'\n'
425 hunklines[i] += b'\n'
426 hunklines.insert(i + 1, diffhelper.MISSING_NEWLINE_MARKER)
426 hunklines.insert(i + 1, diffhelper.MISSING_NEWLINE_MARKER)
427 break
427 break
428 yield hunkrange, hunklines
428 yield hunkrange, hunklines
429
429
430 # bdiff.blocks gives us the matching sequences in the files. The loop
430 # bdiff.blocks gives us the matching sequences in the files. The loop
431 # below finds the spaces between those matching sequences and translates
431 # below finds the spaces between those matching sequences and translates
432 # them into diff output.
432 # them into diff output.
433 #
433 #
434 hunk = None
434 hunk = None
435 ignoredlines = 0
435 ignoredlines = 0
436 has_hunks = False
436 has_hunks = False
437 for s, stype in allblocks(t1, t2, opts, l1, l2):
437 for s, stype in allblocks(t1, t2, opts, l1, l2):
438 a1, a2, b1, b2 = s
438 a1, a2, b1, b2 = s
439 if stype != b'!':
439 if stype != b'!':
440 if stype == b'~':
440 if stype == b'~':
441 # The diff context lines are based on t1 content. When
441 # The diff context lines are based on t1 content. When
442 # blank lines are ignored, the new lines offsets must
442 # blank lines are ignored, the new lines offsets must
443 # be adjusted as if equivalent blocks ('~') had the
443 # be adjusted as if equivalent blocks ('~') had the
444 # same sizes on both sides.
444 # same sizes on both sides.
445 ignoredlines += (b2 - b1) - (a2 - a1)
445 ignoredlines += (b2 - b1) - (a2 - a1)
446 continue
446 continue
447 delta = []
447 delta = []
448 old = l1[a1:a2]
448 old = l1[a1:a2]
449 new = l2[b1:b2]
449 new = l2[b1:b2]
450
450
451 b1 -= ignoredlines
451 b1 -= ignoredlines
452 b2 -= ignoredlines
452 b2 -= ignoredlines
453 astart = contextstart(a1)
453 astart = contextstart(a1)
454 bstart = contextstart(b1)
454 bstart = contextstart(b1)
455 prev = None
455 prev = None
456 if hunk:
456 if hunk:
457 # join with the previous hunk if it falls inside the context
457 # join with the previous hunk if it falls inside the context
458 if astart < hunk[1] + opts.context + 1:
458 if astart < hunk[1] + opts.context + 1:
459 prev = hunk
459 prev = hunk
460 astart = hunk[1]
460 astart = hunk[1]
461 bstart = hunk[3]
461 bstart = hunk[3]
462 else:
462 else:
463 if not has_hunks:
463 if not has_hunks:
464 has_hunks = True
464 has_hunks = True
465 yield True
465 yield True
466 for x in yieldhunk(hunk):
466 for x in yieldhunk(hunk):
467 yield x
467 yield x
468 if prev:
468 if prev:
469 # we've joined the previous hunk, record the new ending points.
469 # we've joined the previous hunk, record the new ending points.
470 hunk[1] = a2
470 hunk[1] = a2
471 hunk[3] = b2
471 hunk[3] = b2
472 delta = hunk[4]
472 delta = hunk[4]
473 else:
473 else:
474 # create a new hunk
474 # create a new hunk
475 hunk = [astart, a2, bstart, b2, delta]
475 hunk = [astart, a2, bstart, b2, delta]
476
476
477 delta[len(delta) :] = [b' ' + x for x in l1[astart:a1]]
477 delta[len(delta) :] = [b' ' + x for x in l1[astart:a1]]
478 delta[len(delta) :] = [b'-' + x for x in old]
478 delta[len(delta) :] = [b'-' + x for x in old]
479 delta[len(delta) :] = [b'+' + x for x in new]
479 delta[len(delta) :] = [b'+' + x for x in new]
480
480
481 if hunk:
481 if hunk:
482 if not has_hunks:
482 if not has_hunks:
483 has_hunks = True
483 has_hunks = True
484 yield True
484 yield True
485 for x in yieldhunk(hunk):
485 for x in yieldhunk(hunk):
486 yield x
486 yield x
487 elif not has_hunks:
487 elif not has_hunks:
488 yield False
488 yield False
489
489
490
490
491 def b85diff(to, tn):
491 def b85diff(to, tn):
492 '''print base85-encoded binary diff'''
492 '''print base85-encoded binary diff'''
493
493
494 def fmtline(line):
494 def fmtline(line):
495 l = len(line)
495 l = len(line)
496 if l <= 26:
496 if l <= 26:
497 l = pycompat.bytechr(ord(b'A') + l - 1)
497 l = pycompat.bytechr(ord(b'A') + l - 1)
498 else:
498 else:
499 l = pycompat.bytechr(l - 26 + ord(b'a') - 1)
499 l = pycompat.bytechr(l - 26 + ord(b'a') - 1)
500 return b'%c%s\n' % (l, util.b85encode(line, True))
500 return b'%c%s\n' % (l, util.b85encode(line, True))
501
501
502 def chunk(text, csize=52):
502 def chunk(text, csize=52):
503 l = len(text)
503 l = len(text)
504 i = 0
504 i = 0
505 while i < l:
505 while i < l:
506 yield text[i : i + csize]
506 yield text[i : i + csize]
507 i += csize
507 i += csize
508
508
509 if to is None:
509 if to is None:
510 to = b''
510 to = b''
511 if tn is None:
511 if tn is None:
512 tn = b''
512 tn = b''
513
513
514 if to == tn:
514 if to == tn:
515 return b''
515 return b''
516
516
517 # TODO: deltas
517 # TODO: deltas
518 ret = []
518 ret = []
519 ret.append(b'GIT binary patch\n')
519 ret.append(b'GIT binary patch\n')
520 ret.append(b'literal %d\n' % len(tn))
520 ret.append(b'literal %d\n' % len(tn))
521 for l in chunk(zlib.compress(tn)):
521 for l in chunk(zlib.compress(tn)):
522 ret.append(fmtline(l))
522 ret.append(fmtline(l))
523 ret.append(b'\n')
523 ret.append(b'\n')
524
524
525 return b''.join(ret)
525 return b''.join(ret)
526
526
527
527
528 def patchtext(bin):
528 def patchtext(bin):
529 pos = 0
529 pos = 0
530 t = []
530 t = []
531 while pos < len(bin):
531 while pos < len(bin):
532 p1, p2, l = struct.unpack(b">lll", bin[pos : pos + 12])
532 p1, p2, l = struct.unpack(b">lll", bin[pos : pos + 12])
533 pos += 12
533 pos += 12
534 t.append(bin[pos : pos + l])
534 t.append(bin[pos : pos + l])
535 pos += l
535 pos += l
536 return b"".join(t)
536 return b"".join(t)
537
537
538
538
539 def patch(a, bin):
539 def patch(a, bin):
540 if len(a) == 0:
540 if len(a) == 0:
541 # skip over trivial delta header
541 # skip over trivial delta header
542 return util.buffer(bin, 12)
542 return util.buffer(bin, 12)
543 return mpatch.patches(a, [bin])
543 return mpatch.patches(a, [bin])
544
544
545
545
546 # similar to difflib.SequenceMatcher.get_matching_blocks
546 # similar to difflib.SequenceMatcher.get_matching_blocks
547 def get_matching_blocks(a, b):
547 def get_matching_blocks(a, b):
548 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
548 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
549
549
550
550
551 def trivialdiffheader(length):
551 def trivialdiffheader(length):
552 return struct.pack(b">lll", 0, 0, length) if length else b''
552 return struct.pack(b">lll", 0, 0, length) if length else b''
553
553
554
554
555 def replacediffheader(oldlen, newlen):
555 def replacediffheader(oldlen, newlen):
556 return struct.pack(b">lll", 0, oldlen, newlen)
556 return struct.pack(b">lll", 0, oldlen, newlen)
@@ -1,481 +1,481 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ cat > a <<EOF
3 $ cat > a <<EOF
4 > c
4 > c
5 > c
5 > c
6 > a
6 > a
7 > a
7 > a
8 > b
8 > b
9 > a
9 > a
10 > a
10 > a
11 > c
11 > c
12 > c
12 > c
13 > EOF
13 > EOF
14 $ hg ci -Am adda
14 $ hg ci -Am adda
15 adding a
15 adding a
16
16
17 $ cat > a <<EOF
17 $ cat > a <<EOF
18 > c
18 > c
19 > c
19 > c
20 > a
20 > a
21 > a
21 > a
22 > dd
22 > dd
23 > a
23 > a
24 > a
24 > a
25 > c
25 > c
26 > c
26 > c
27 > EOF
27 > EOF
28
28
29 default context
29 default context
30
30
31 $ hg diff --nodates
31 $ hg diff --nodates
32 diff -r cf9f4ba66af2 a
32 diff -r cf9f4ba66af2 a
33 --- a/a
33 --- a/a
34 +++ b/a
34 +++ b/a
35 @@ -2,7 +2,7 @@
35 @@ -2,7 +2,7 @@
36 c
36 c
37 a
37 a
38 a
38 a
39 -b
39 -b
40 +dd
40 +dd
41 a
41 a
42 a
42 a
43 c
43 c
44
44
45 invalid --unified
45 invalid --unified
46
46
47 $ hg diff --nodates -U foo
47 $ hg diff --nodates -U foo
48 abort: diff context lines count must be an integer, not 'foo'
48 abort: diff context lines count must be an integer, not 'foo'
49 [255]
49 [10]
50
50
51
51
52 $ hg diff --nodates -U 2
52 $ hg diff --nodates -U 2
53 diff -r cf9f4ba66af2 a
53 diff -r cf9f4ba66af2 a
54 --- a/a
54 --- a/a
55 +++ b/a
55 +++ b/a
56 @@ -3,5 +3,5 @@
56 @@ -3,5 +3,5 @@
57 a
57 a
58 a
58 a
59 -b
59 -b
60 +dd
60 +dd
61 a
61 a
62 a
62 a
63
63
64 $ hg --config diff.unified=2 diff --nodates
64 $ hg --config diff.unified=2 diff --nodates
65 diff -r cf9f4ba66af2 a
65 diff -r cf9f4ba66af2 a
66 --- a/a
66 --- a/a
67 +++ b/a
67 +++ b/a
68 @@ -3,5 +3,5 @@
68 @@ -3,5 +3,5 @@
69 a
69 a
70 a
70 a
71 -b
71 -b
72 +dd
72 +dd
73 a
73 a
74 a
74 a
75
75
76 $ hg diff --nodates -U 1
76 $ hg diff --nodates -U 1
77 diff -r cf9f4ba66af2 a
77 diff -r cf9f4ba66af2 a
78 --- a/a
78 --- a/a
79 +++ b/a
79 +++ b/a
80 @@ -4,3 +4,3 @@
80 @@ -4,3 +4,3 @@
81 a
81 a
82 -b
82 -b
83 +dd
83 +dd
84 a
84 a
85
85
86 invalid diff.unified
86 invalid diff.unified
87
87
88 $ hg --config diff.unified=foo diff --nodates
88 $ hg --config diff.unified=foo diff --nodates
89 abort: diff context lines count must be an integer, not 'foo'
89 abort: diff context lines count must be an integer, not 'foo'
90 [255]
90 [10]
91
91
92 noprefix config and option
92 noprefix config and option
93
93
94 $ hg --config diff.noprefix=True diff --nodates
94 $ hg --config diff.noprefix=True diff --nodates
95 diff -r cf9f4ba66af2 a
95 diff -r cf9f4ba66af2 a
96 --- a
96 --- a
97 +++ a
97 +++ a
98 @@ -2,7 +2,7 @@
98 @@ -2,7 +2,7 @@
99 c
99 c
100 a
100 a
101 a
101 a
102 -b
102 -b
103 +dd
103 +dd
104 a
104 a
105 a
105 a
106 c
106 c
107 $ hg diff --noprefix --nodates
107 $ hg diff --noprefix --nodates
108 diff -r cf9f4ba66af2 a
108 diff -r cf9f4ba66af2 a
109 --- a
109 --- a
110 +++ a
110 +++ a
111 @@ -2,7 +2,7 @@
111 @@ -2,7 +2,7 @@
112 c
112 c
113 a
113 a
114 a
114 a
115 -b
115 -b
116 +dd
116 +dd
117 a
117 a
118 a
118 a
119 c
119 c
120
120
121 noprefix config disabled in plain mode, but option still enabled
121 noprefix config disabled in plain mode, but option still enabled
122
122
123 $ HGPLAIN=1 hg --config diff.noprefix=True diff --nodates
123 $ HGPLAIN=1 hg --config diff.noprefix=True diff --nodates
124 diff -r cf9f4ba66af2 a
124 diff -r cf9f4ba66af2 a
125 --- a/a
125 --- a/a
126 +++ b/a
126 +++ b/a
127 @@ -2,7 +2,7 @@
127 @@ -2,7 +2,7 @@
128 c
128 c
129 a
129 a
130 a
130 a
131 -b
131 -b
132 +dd
132 +dd
133 a
133 a
134 a
134 a
135 c
135 c
136 $ HGPLAIN=1 hg diff --noprefix --nodates
136 $ HGPLAIN=1 hg diff --noprefix --nodates
137 diff -r cf9f4ba66af2 a
137 diff -r cf9f4ba66af2 a
138 --- a
138 --- a
139 +++ a
139 +++ a
140 @@ -2,7 +2,7 @@
140 @@ -2,7 +2,7 @@
141 c
141 c
142 a
142 a
143 a
143 a
144 -b
144 -b
145 +dd
145 +dd
146 a
146 a
147 a
147 a
148 c
148 c
149
149
150 $ cd ..
150 $ cd ..
151
151
152
152
153 0 lines of context hunk header matches gnu diff hunk header
153 0 lines of context hunk header matches gnu diff hunk header
154
154
155 $ hg init diffzero
155 $ hg init diffzero
156 $ cd diffzero
156 $ cd diffzero
157 $ cat > f1 << EOF
157 $ cat > f1 << EOF
158 > c2
158 > c2
159 > c4
159 > c4
160 > c5
160 > c5
161 > EOF
161 > EOF
162 $ hg commit -Am0
162 $ hg commit -Am0
163 adding f1
163 adding f1
164
164
165 $ cat > f2 << EOF
165 $ cat > f2 << EOF
166 > c1
166 > c1
167 > c2
167 > c2
168 > c3
168 > c3
169 > c4
169 > c4
170 > EOF
170 > EOF
171 $ mv f2 f1
171 $ mv f2 f1
172 $ hg diff -U0 --nodates
172 $ hg diff -U0 --nodates
173 diff -r 55d8ff78db23 f1
173 diff -r 55d8ff78db23 f1
174 --- a/f1
174 --- a/f1
175 +++ b/f1
175 +++ b/f1
176 @@ -0,0 +1,1 @@
176 @@ -0,0 +1,1 @@
177 +c1
177 +c1
178 @@ -1,0 +3,1 @@
178 @@ -1,0 +3,1 @@
179 +c3
179 +c3
180 @@ -3,1 +4,0 @@
180 @@ -3,1 +4,0 @@
181 -c5
181 -c5
182
182
183 $ hg diff -U0 --nodates --git
183 $ hg diff -U0 --nodates --git
184 diff --git a/f1 b/f1
184 diff --git a/f1 b/f1
185 --- a/f1
185 --- a/f1
186 +++ b/f1
186 +++ b/f1
187 @@ -0,0 +1,1 @@
187 @@ -0,0 +1,1 @@
188 +c1
188 +c1
189 @@ -1,0 +3,1 @@
189 @@ -1,0 +3,1 @@
190 +c3
190 +c3
191 @@ -3,1 +4,0 @@
191 @@ -3,1 +4,0 @@
192 -c5
192 -c5
193
193
194 $ hg diff -U0 --nodates -p
194 $ hg diff -U0 --nodates -p
195 diff -r 55d8ff78db23 f1
195 diff -r 55d8ff78db23 f1
196 --- a/f1
196 --- a/f1
197 +++ b/f1
197 +++ b/f1
198 @@ -0,0 +1,1 @@
198 @@ -0,0 +1,1 @@
199 +c1
199 +c1
200 @@ -1,0 +3,1 @@ c2
200 @@ -1,0 +3,1 @@ c2
201 +c3
201 +c3
202 @@ -3,1 +4,0 @@ c4
202 @@ -3,1 +4,0 @@ c4
203 -c5
203 -c5
204
204
205 $ echo a > f1
205 $ echo a > f1
206 $ hg ci -m movef2
206 $ hg ci -m movef2
207
207
208 Test diff headers terminating with TAB when necessary (issue3357)
208 Test diff headers terminating with TAB when necessary (issue3357)
209 Regular diff --nodates, file creation
209 Regular diff --nodates, file creation
210
210
211 $ hg mv f1 'f 1'
211 $ hg mv f1 'f 1'
212 $ echo b > 'f 1'
212 $ echo b > 'f 1'
213 $ hg diff --nodates 'f 1'
213 $ hg diff --nodates 'f 1'
214 diff -r 7574207d0d15 f 1
214 diff -r 7574207d0d15 f 1
215 --- /dev/null
215 --- /dev/null
216 +++ b/f 1
216 +++ b/f 1
217 @@ -0,0 +1,1 @@
217 @@ -0,0 +1,1 @@
218 +b
218 +b
219
219
220 Git diff, adding space
220 Git diff, adding space
221
221
222 $ hg diff --git
222 $ hg diff --git
223 diff --git a/f1 b/f 1
223 diff --git a/f1 b/f 1
224 rename from f1
224 rename from f1
225 rename to f 1
225 rename to f 1
226 --- a/f1
226 --- a/f1
227 +++ b/f 1
227 +++ b/f 1
228 @@ -1,1 +1,1 @@
228 @@ -1,1 +1,1 @@
229 -a
229 -a
230 +b
230 +b
231
231
232 Git diff, adding extended headers
232 Git diff, adding extended headers
233
233
234 $ hg diff --git --config experimental.extendedheader.index=7 --config experimental.extendedheader.similarity=True
234 $ hg diff --git --config experimental.extendedheader.index=7 --config experimental.extendedheader.similarity=True
235 diff --git a/f1 b/f 1
235 diff --git a/f1 b/f 1
236 similarity index 0%
236 similarity index 0%
237 rename from f1
237 rename from f1
238 rename to f 1
238 rename to f 1
239 index 7898192..6178079 100644
239 index 7898192..6178079 100644
240 --- a/f1
240 --- a/f1
241 +++ b/f 1
241 +++ b/f 1
242 @@ -1,1 +1,1 @@
242 @@ -1,1 +1,1 @@
243 -a
243 -a
244 +b
244 +b
245
245
246 $ hg diff --git --config experimental.extendedheader.index=-1
246 $ hg diff --git --config experimental.extendedheader.index=-1
247 invalid length for extendedheader.index: '-1'
247 invalid length for extendedheader.index: '-1'
248 diff --git a/f1 b/f 1
248 diff --git a/f1 b/f 1
249 rename from f1
249 rename from f1
250 rename to f 1
250 rename to f 1
251 --- a/f1
251 --- a/f1
252 +++ b/f 1
252 +++ b/f 1
253 @@ -1,1 +1,1 @@
253 @@ -1,1 +1,1 @@
254 -a
254 -a
255 +b
255 +b
256
256
257 $ hg diff --git --config experimental.extendedheader.index=whatever
257 $ hg diff --git --config experimental.extendedheader.index=whatever
258 invalid value for extendedheader.index: 'whatever'
258 invalid value for extendedheader.index: 'whatever'
259 diff --git a/f1 b/f 1
259 diff --git a/f1 b/f 1
260 rename from f1
260 rename from f1
261 rename to f 1
261 rename to f 1
262 --- a/f1
262 --- a/f1
263 +++ b/f 1
263 +++ b/f 1
264 @@ -1,1 +1,1 @@
264 @@ -1,1 +1,1 @@
265 -a
265 -a
266 +b
266 +b
267
267
268 Git diff with noprefix
268 Git diff with noprefix
269
269
270 $ hg --config diff.noprefix=True diff --git --nodates
270 $ hg --config diff.noprefix=True diff --git --nodates
271 diff --git f1 f 1
271 diff --git f1 f 1
272 rename from f1
272 rename from f1
273 rename to f 1
273 rename to f 1
274 --- f1
274 --- f1
275 +++ f 1
275 +++ f 1
276 @@ -1,1 +1,1 @@
276 @@ -1,1 +1,1 @@
277 -a
277 -a
278 +b
278 +b
279
279
280 noprefix config disabled in plain mode, but option still enabled
280 noprefix config disabled in plain mode, but option still enabled
281
281
282 $ HGPLAIN=1 hg --config diff.noprefix=True diff --git --nodates
282 $ HGPLAIN=1 hg --config diff.noprefix=True diff --git --nodates
283 diff --git a/f1 b/f 1
283 diff --git a/f1 b/f 1
284 rename from f1
284 rename from f1
285 rename to f 1
285 rename to f 1
286 --- a/f1
286 --- a/f1
287 +++ b/f 1
287 +++ b/f 1
288 @@ -1,1 +1,1 @@
288 @@ -1,1 +1,1 @@
289 -a
289 -a
290 +b
290 +b
291 $ HGPLAIN=1 hg diff --git --noprefix --nodates
291 $ HGPLAIN=1 hg diff --git --noprefix --nodates
292 diff --git f1 f 1
292 diff --git f1 f 1
293 rename from f1
293 rename from f1
294 rename to f 1
294 rename to f 1
295 --- f1
295 --- f1
296 +++ f 1
296 +++ f 1
297 @@ -1,1 +1,1 @@
297 @@ -1,1 +1,1 @@
298 -a
298 -a
299 +b
299 +b
300
300
301 Regular diff --nodates, file deletion
301 Regular diff --nodates, file deletion
302
302
303 $ hg ci -m addspace
303 $ hg ci -m addspace
304 $ hg mv 'f 1' f1
304 $ hg mv 'f 1' f1
305 $ echo a > f1
305 $ echo a > f1
306 $ hg diff --nodates 'f 1'
306 $ hg diff --nodates 'f 1'
307 diff -r ca50fe67c9c7 f 1
307 diff -r ca50fe67c9c7 f 1
308 --- a/f 1
308 --- a/f 1
309 +++ /dev/null
309 +++ /dev/null
310 @@ -1,1 +0,0 @@
310 @@ -1,1 +0,0 @@
311 -b
311 -b
312
312
313 Git diff, removing space
313 Git diff, removing space
314
314
315 $ hg diff --git
315 $ hg diff --git
316 diff --git a/f 1 b/f1
316 diff --git a/f 1 b/f1
317 rename from f 1
317 rename from f 1
318 rename to f1
318 rename to f1
319 --- a/f 1
319 --- a/f 1
320 +++ b/f1
320 +++ b/f1
321 @@ -1,1 +1,1 @@
321 @@ -1,1 +1,1 @@
322 -b
322 -b
323 +a
323 +a
324
324
325 showfunc diff
325 showfunc diff
326 $ cat > f1 << EOF
326 $ cat > f1 << EOF
327 > int main() {
327 > int main() {
328 > int a = 0;
328 > int a = 0;
329 > int b = 1;
329 > int b = 1;
330 > int c = 2;
330 > int c = 2;
331 > int d = 3;
331 > int d = 3;
332 > return a + b + c + d;
332 > return a + b + c + d;
333 > }
333 > }
334 > EOF
334 > EOF
335 $ hg commit -m addfunction
335 $ hg commit -m addfunction
336 $ cat > f1 << EOF
336 $ cat > f1 << EOF
337 > int main() {
337 > int main() {
338 > int a = 0;
338 > int a = 0;
339 > int b = 1;
339 > int b = 1;
340 > int c = 2;
340 > int c = 2;
341 > int e = 3;
341 > int e = 3;
342 > return a + b + c + e;
342 > return a + b + c + e;
343 > }
343 > }
344 > EOF
344 > EOF
345 $ hg diff --git
345 $ hg diff --git
346 diff --git a/f1 b/f1
346 diff --git a/f1 b/f1
347 --- a/f1
347 --- a/f1
348 +++ b/f1
348 +++ b/f1
349 @@ -2,6 +2,6 @@
349 @@ -2,6 +2,6 @@
350 int a = 0;
350 int a = 0;
351 int b = 1;
351 int b = 1;
352 int c = 2;
352 int c = 2;
353 - int d = 3;
353 - int d = 3;
354 - return a + b + c + d;
354 - return a + b + c + d;
355 + int e = 3;
355 + int e = 3;
356 + return a + b + c + e;
356 + return a + b + c + e;
357 }
357 }
358 $ hg diff --config diff.showfunc=True --git
358 $ hg diff --config diff.showfunc=True --git
359 diff --git a/f1 b/f1
359 diff --git a/f1 b/f1
360 --- a/f1
360 --- a/f1
361 +++ b/f1
361 +++ b/f1
362 @@ -2,6 +2,6 @@ int main() {
362 @@ -2,6 +2,6 @@ int main() {
363 int a = 0;
363 int a = 0;
364 int b = 1;
364 int b = 1;
365 int c = 2;
365 int c = 2;
366 - int d = 3;
366 - int d = 3;
367 - return a + b + c + d;
367 - return a + b + c + d;
368 + int e = 3;
368 + int e = 3;
369 + return a + b + c + e;
369 + return a + b + c + e;
370 }
370 }
371
371
372 If [diff] git is set to true, but the user says --no-git, we should
372 If [diff] git is set to true, but the user says --no-git, we should
373 *not* get git diffs
373 *not* get git diffs
374 $ hg diff --nodates --config diff.git=1 --no-git
374 $ hg diff --nodates --config diff.git=1 --no-git
375 diff -r f2c7c817fa55 f1
375 diff -r f2c7c817fa55 f1
376 --- a/f1
376 --- a/f1
377 +++ b/f1
377 +++ b/f1
378 @@ -2,6 +2,6 @@
378 @@ -2,6 +2,6 @@
379 int a = 0;
379 int a = 0;
380 int b = 1;
380 int b = 1;
381 int c = 2;
381 int c = 2;
382 - int d = 3;
382 - int d = 3;
383 - return a + b + c + d;
383 - return a + b + c + d;
384 + int e = 3;
384 + int e = 3;
385 + return a + b + c + e;
385 + return a + b + c + e;
386 }
386 }
387
387
388 $ cd ..
388 $ cd ..
389
389
390 Long function names should be abbreviated, but multi-byte character shouldn't
390 Long function names should be abbreviated, but multi-byte character shouldn't
391 be broken up
391 be broken up
392
392
393 $ hg init longfunc
393 $ hg init longfunc
394 $ cd longfunc
394 $ cd longfunc
395
395
396 >>> with open('a', 'wb') as f:
396 >>> with open('a', 'wb') as f:
397 ... f.write(b'a' * 39 + b'bb' + b'\n') and None
397 ... f.write(b'a' * 39 + b'bb' + b'\n') and None
398 ... f.write(b' .\n' * 3) and None
398 ... f.write(b' .\n' * 3) and None
399 ... f.write(b' 0 b\n') and None
399 ... f.write(b' 0 b\n') and None
400 ... f.write(b' .\n' * 3) and None
400 ... f.write(b' .\n' * 3) and None
401 ... f.write(b'a' * 39 + b'\xc3\xa0' + b'\n') and None
401 ... f.write(b'a' * 39 + b'\xc3\xa0' + b'\n') and None
402 ... f.write(b' .\n' * 3) and None
402 ... f.write(b' .\n' * 3) and None
403 ... f.write(b' 0 a with grave (single code point)\n') and None
403 ... f.write(b' 0 a with grave (single code point)\n') and None
404 ... f.write(b' .\n' * 3) and None
404 ... f.write(b' .\n' * 3) and None
405 ... f.write(b'a' * 39 + b'a\xcc\x80' + b'\n') and None
405 ... f.write(b'a' * 39 + b'a\xcc\x80' + b'\n') and None
406 ... f.write(b' .\n' * 3) and None
406 ... f.write(b' .\n' * 3) and None
407 ... f.write(b' 0 a with grave (composition)\n') and None
407 ... f.write(b' 0 a with grave (composition)\n') and None
408 ... f.write(b' .\n' * 3) and None
408 ... f.write(b' .\n' * 3) and None
409 $ hg ci -qAm0
409 $ hg ci -qAm0
410
410
411 >>> with open('a', 'wb') as f:
411 >>> with open('a', 'wb') as f:
412 ... f.write(b'a' * 39 + b'bb' + b'\n') and None
412 ... f.write(b'a' * 39 + b'bb' + b'\n') and None
413 ... f.write(b' .\n' * 3) and None
413 ... f.write(b' .\n' * 3) and None
414 ... f.write(b' 1 b\n') and None
414 ... f.write(b' 1 b\n') and None
415 ... f.write(b' .\n' * 3) and None
415 ... f.write(b' .\n' * 3) and None
416 ... f.write(b'a' * 39 + b'\xc3\xa0' + b'\n') and None
416 ... f.write(b'a' * 39 + b'\xc3\xa0' + b'\n') and None
417 ... f.write(b' .\n' * 3) and None
417 ... f.write(b' .\n' * 3) and None
418 ... f.write(b' 1 a with grave (single code point)\n') and None
418 ... f.write(b' 1 a with grave (single code point)\n') and None
419 ... f.write(b' .\n' * 3) and None
419 ... f.write(b' .\n' * 3) and None
420 ... f.write(b'a' * 39 + b'a\xcc\x80' + b'\n') and None
420 ... f.write(b'a' * 39 + b'a\xcc\x80' + b'\n') and None
421 ... f.write(b' .\n' * 3) and None
421 ... f.write(b' .\n' * 3) and None
422 ... f.write(b' 1 a with grave (composition)\n') and None
422 ... f.write(b' 1 a with grave (composition)\n') and None
423 ... f.write(b' .\n' * 3) and None
423 ... f.write(b' .\n' * 3) and None
424 $ hg ci -m1
424 $ hg ci -m1
425
425
426 $ hg diff -c1 --nodates --show-function
426 $ hg diff -c1 --nodates --show-function
427 diff -r 3e92dd6fa812 -r a256341606cb a
427 diff -r 3e92dd6fa812 -r a256341606cb a
428 --- a/a
428 --- a/a
429 +++ b/a
429 +++ b/a
430 @@ -2,7 +2,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
430 @@ -2,7 +2,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
431 .
431 .
432 .
432 .
433 .
433 .
434 - 0 b
434 - 0 b
435 + 1 b
435 + 1 b
436 .
436 .
437 .
437 .
438 .
438 .
439 @@ -10,7 +10,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xc3\xa0 (esc)
439 @@ -10,7 +10,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xc3\xa0 (esc)
440 .
440 .
441 .
441 .
442 .
442 .
443 - 0 a with grave (single code point)
443 - 0 a with grave (single code point)
444 + 1 a with grave (single code point)
444 + 1 a with grave (single code point)
445 .
445 .
446 .
446 .
447 .
447 .
448 @@ -18,7 +18,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xcc\x80 (esc)
448 @@ -18,7 +18,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xcc\x80 (esc)
449 .
449 .
450 .
450 .
451 .
451 .
452 - 0 a with grave (composition)
452 - 0 a with grave (composition)
453 + 1 a with grave (composition)
453 + 1 a with grave (composition)
454 .
454 .
455 .
455 .
456 .
456 .
457
457
458 $ cd ..
458 $ cd ..
459
459
460 Make sure `hg diff --git` differentiate "file did not exists" and "file is empty"
460 Make sure `hg diff --git` differentiate "file did not exists" and "file is empty"
461 for git blob oids
461 for git blob oids
462
462
463 $ hg init bloboids
463 $ hg init bloboids
464 $ cd bloboids
464 $ cd bloboids
465
465
466 $ touch a
466 $ touch a
467 $ hg ci -Am "empty a"
467 $ hg ci -Am "empty a"
468 adding a
468 adding a
469 $ hg diff -c 0 --git --config experimental.extendedheader.index=full | grep index
469 $ hg diff -c 0 --git --config experimental.extendedheader.index=full | grep index
470 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
470 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
471
471
472 Make sure `hg diff --git` differentiate "file was empty" and "file is removed"
472 Make sure `hg diff --git` differentiate "file was empty" and "file is removed"
473 for git blob oids
473 for git blob oids
474
474
475 $ rm a
475 $ rm a
476 $ hg ci -Am "removed a"
476 $ hg ci -Am "removed a"
477 removing a
477 removing a
478 $ hg diff -c 1 --git --config experimental.extendedheader.index=full | grep index
478 $ hg diff -c 1 --git --config experimental.extendedheader.index=full | grep index
479 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 100644
479 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 100644
480
480
481 $ cd ..
481 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now