##// END OF EJS Templates
mdiff/patch: fix bad hunk handling for unified diffs with zero context...
Nicolas Venegas -
r15462:2b1ec74c stable
parent child Browse files
Show More
@@ -1,281 +1,287
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import bdiff, mpatch, util
9 import bdiff, mpatch, util
10 import re, struct
10 import re, struct
11
11
12 def splitnewlines(text):
12 def splitnewlines(text):
13 '''like str.splitlines, but only split on newlines.'''
13 '''like str.splitlines, but only split on newlines.'''
14 lines = [l + '\n' for l in text.split('\n')]
14 lines = [l + '\n' for l in text.split('\n')]
15 if lines:
15 if lines:
16 if lines[-1] == '\n':
16 if lines[-1] == '\n':
17 lines.pop()
17 lines.pop()
18 else:
18 else:
19 lines[-1] = lines[-1][:-1]
19 lines[-1] = lines[-1][:-1]
20 return lines
20 return lines
21
21
22 class diffopts(object):
22 class diffopts(object):
23 '''context is the number of context lines
23 '''context is the number of context lines
24 text treats all files as text
24 text treats all files as text
25 showfunc enables diff -p output
25 showfunc enables diff -p output
26 git enables the git extended patch format
26 git enables the git extended patch format
27 nodates removes dates from diff headers
27 nodates removes dates from diff headers
28 ignorews ignores all whitespace changes in the diff
28 ignorews ignores all whitespace changes in the diff
29 ignorewsamount ignores changes in the amount of whitespace
29 ignorewsamount ignores changes in the amount of whitespace
30 ignoreblanklines ignores changes whose lines are all blank
30 ignoreblanklines ignores changes whose lines are all blank
31 upgrade generates git diffs to avoid data loss
31 upgrade generates git diffs to avoid data loss
32 '''
32 '''
33
33
34 defaults = {
34 defaults = {
35 'context': 3,
35 'context': 3,
36 'text': False,
36 'text': False,
37 'showfunc': False,
37 'showfunc': False,
38 'git': False,
38 'git': False,
39 'nodates': False,
39 'nodates': False,
40 'ignorews': False,
40 'ignorews': False,
41 'ignorewsamount': False,
41 'ignorewsamount': False,
42 'ignoreblanklines': False,
42 'ignoreblanklines': False,
43 'upgrade': False,
43 'upgrade': False,
44 }
44 }
45
45
46 __slots__ = defaults.keys()
46 __slots__ = defaults.keys()
47
47
48 def __init__(self, **opts):
48 def __init__(self, **opts):
49 for k in self.__slots__:
49 for k in self.__slots__:
50 v = opts.get(k)
50 v = opts.get(k)
51 if v is None:
51 if v is None:
52 v = self.defaults[k]
52 v = self.defaults[k]
53 setattr(self, k, v)
53 setattr(self, k, v)
54
54
55 try:
55 try:
56 self.context = int(self.context)
56 self.context = int(self.context)
57 except ValueError:
57 except ValueError:
58 raise util.Abort(_('diff context lines count must be '
58 raise util.Abort(_('diff context lines count must be '
59 'an integer, not %r') % self.context)
59 'an integer, not %r') % self.context)
60
60
61 def copy(self, **kwargs):
61 def copy(self, **kwargs):
62 opts = dict((k, getattr(self, k)) for k in self.defaults)
62 opts = dict((k, getattr(self, k)) for k in self.defaults)
63 opts.update(kwargs)
63 opts.update(kwargs)
64 return diffopts(**opts)
64 return diffopts(**opts)
65
65
66 defaultopts = diffopts()
66 defaultopts = diffopts()
67
67
68 def wsclean(opts, text, blank=True):
68 def wsclean(opts, text, blank=True):
69 if opts.ignorews:
69 if opts.ignorews:
70 text = re.sub('[ \t\r]+', '', text)
70 text = re.sub('[ \t\r]+', '', text)
71 elif opts.ignorewsamount:
71 elif opts.ignorewsamount:
72 text = re.sub('[ \t\r]+', ' ', text)
72 text = re.sub('[ \t\r]+', ' ', text)
73 text = text.replace(' \n', '\n')
73 text = text.replace(' \n', '\n')
74 if blank and opts.ignoreblanklines:
74 if blank and opts.ignoreblanklines:
75 text = re.sub('\n+', '', text)
75 text = re.sub('\n+', '', text)
76 return text
76 return text
77
77
78 def diffline(revs, a, b, opts):
78 def diffline(revs, a, b, opts):
79 parts = ['diff']
79 parts = ['diff']
80 if opts.git:
80 if opts.git:
81 parts.append('--git')
81 parts.append('--git')
82 if revs and not opts.git:
82 if revs and not opts.git:
83 parts.append(' '.join(["-r %s" % rev for rev in revs]))
83 parts.append(' '.join(["-r %s" % rev for rev in revs]))
84 if opts.git:
84 if opts.git:
85 parts.append('a/%s' % a)
85 parts.append('a/%s' % a)
86 parts.append('b/%s' % b)
86 parts.append('b/%s' % b)
87 else:
87 else:
88 parts.append(a)
88 parts.append(a)
89 return ' '.join(parts) + '\n'
89 return ' '.join(parts) + '\n'
90
90
91 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
91 def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
92 def datetag(date, addtab=True):
92 def datetag(date, addtab=True):
93 if not opts.git and not opts.nodates:
93 if not opts.git and not opts.nodates:
94 return '\t%s\n' % date
94 return '\t%s\n' % date
95 if addtab and ' ' in fn1:
95 if addtab and ' ' in fn1:
96 return '\t\n'
96 return '\t\n'
97 return '\n'
97 return '\n'
98
98
99 if not a and not b:
99 if not a and not b:
100 return ""
100 return ""
101 epoch = util.datestr((0, 0))
101 epoch = util.datestr((0, 0))
102
102
103 if not opts.text and (util.binary(a) or util.binary(b)):
103 if not opts.text and (util.binary(a) or util.binary(b)):
104 if a and b and len(a) == len(b) and a == b:
104 if a and b and len(a) == len(b) and a == b:
105 return ""
105 return ""
106 l = ['Binary file %s has changed\n' % fn1]
106 l = ['Binary file %s has changed\n' % fn1]
107 elif not a:
107 elif not a:
108 b = splitnewlines(b)
108 b = splitnewlines(b)
109 if a is None:
109 if a is None:
110 l1 = '--- /dev/null%s' % datetag(epoch, False)
110 l1 = '--- /dev/null%s' % datetag(epoch, False)
111 else:
111 else:
112 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
112 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
113 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
113 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
114 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
114 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
115 l = [l1, l2, l3] + ["+" + e for e in b]
115 l = [l1, l2, l3] + ["+" + e for e in b]
116 elif not b:
116 elif not b:
117 a = splitnewlines(a)
117 a = splitnewlines(a)
118 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
118 l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
119 if b is None:
119 if b is None:
120 l2 = '+++ /dev/null%s' % datetag(epoch, False)
120 l2 = '+++ /dev/null%s' % datetag(epoch, False)
121 else:
121 else:
122 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
122 l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
123 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
123 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
124 l = [l1, l2, l3] + ["-" + e for e in a]
124 l = [l1, l2, l3] + ["-" + e for e in a]
125 else:
125 else:
126 al = splitnewlines(a)
126 al = splitnewlines(a)
127 bl = splitnewlines(b)
127 bl = splitnewlines(b)
128 l = list(_unidiff(a, b, al, bl, opts=opts))
128 l = list(_unidiff(a, b, al, bl, opts=opts))
129 if not l:
129 if not l:
130 return ""
130 return ""
131
131
132 l.insert(0, "--- a/%s%s" % (fn1, datetag(ad)))
132 l.insert(0, "--- a/%s%s" % (fn1, datetag(ad)))
133 l.insert(1, "+++ b/%s%s" % (fn2, datetag(bd)))
133 l.insert(1, "+++ b/%s%s" % (fn2, datetag(bd)))
134
134
135 for ln in xrange(len(l)):
135 for ln in xrange(len(l)):
136 if l[ln][-1] != '\n':
136 if l[ln][-1] != '\n':
137 l[ln] += "\n\ No newline at end of file\n"
137 l[ln] += "\n\ No newline at end of file\n"
138
138
139 if r:
139 if r:
140 l.insert(0, diffline(r, fn1, fn2, opts))
140 l.insert(0, diffline(r, fn1, fn2, opts))
141
141
142 return "".join(l)
142 return "".join(l)
143
143
144 # creates a headerless unified diff
144 # creates a headerless unified diff
145 # t1 and t2 are the text to be diffed
145 # t1 and t2 are the text to be diffed
146 # l1 and l2 are the text broken up into lines
146 # l1 and l2 are the text broken up into lines
147 def _unidiff(t1, t2, l1, l2, opts=defaultopts):
147 def _unidiff(t1, t2, l1, l2, opts=defaultopts):
148 def contextend(l, len):
148 def contextend(l, len):
149 ret = l + opts.context
149 ret = l + opts.context
150 if ret > len:
150 if ret > len:
151 ret = len
151 ret = len
152 return ret
152 return ret
153
153
154 def contextstart(l):
154 def contextstart(l):
155 ret = l - opts.context
155 ret = l - opts.context
156 if ret < 0:
156 if ret < 0:
157 return 0
157 return 0
158 return ret
158 return ret
159
159
160 lastfunc = [0, '']
160 lastfunc = [0, '']
161 def yieldhunk(hunk):
161 def yieldhunk(hunk):
162 (astart, a2, bstart, b2, delta) = hunk
162 (astart, a2, bstart, b2, delta) = hunk
163 aend = contextend(a2, len(l1))
163 aend = contextend(a2, len(l1))
164 alen = aend - astart
164 alen = aend - astart
165 blen = b2 - bstart + aend - a2
165 blen = b2 - bstart + aend - a2
166
166
167 func = ""
167 func = ""
168 if opts.showfunc:
168 if opts.showfunc:
169 lastpos, func = lastfunc
169 lastpos, func = lastfunc
170 # walk backwards from the start of the context up to the start of
170 # walk backwards from the start of the context up to the start of
171 # the previous hunk context until we find a line starting with an
171 # the previous hunk context until we find a line starting with an
172 # alphanumeric char.
172 # alphanumeric char.
173 for i in xrange(astart - 1, lastpos - 1, -1):
173 for i in xrange(astart - 1, lastpos - 1, -1):
174 if l1[i][0].isalnum():
174 if l1[i][0].isalnum():
175 func = ' ' + l1[i].rstrip()[:40]
175 func = ' ' + l1[i].rstrip()[:40]
176 lastfunc[1] = func
176 lastfunc[1] = func
177 break
177 break
178 # by recording this hunk's starting point as the next place to
178 # by recording this hunk's starting point as the next place to
179 # start looking for function lines, we avoid reading any line in
179 # start looking for function lines, we avoid reading any line in
180 # the file more than once.
180 # the file more than once.
181 lastfunc[0] = astart
181 lastfunc[0] = astart
182
182
183 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
183 # zero-length hunk ranges report their start line as one less
184 bstart + 1, blen, func)
184 if alen:
185 astart += 1
186 if blen:
187 bstart += 1
188
189 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart, alen,
190 bstart, blen, func)
185 for x in delta:
191 for x in delta:
186 yield x
192 yield x
187 for x in xrange(a2, aend):
193 for x in xrange(a2, aend):
188 yield ' ' + l1[x]
194 yield ' ' + l1[x]
189
195
190 # bdiff.blocks gives us the matching sequences in the files. The loop
196 # bdiff.blocks gives us the matching sequences in the files. The loop
191 # below finds the spaces between those matching sequences and translates
197 # below finds the spaces between those matching sequences and translates
192 # them into diff output.
198 # them into diff output.
193 #
199 #
194 if opts.ignorews or opts.ignorewsamount:
200 if opts.ignorews or opts.ignorewsamount:
195 t1 = wsclean(opts, t1, False)
201 t1 = wsclean(opts, t1, False)
196 t2 = wsclean(opts, t2, False)
202 t2 = wsclean(opts, t2, False)
197
203
198 diff = bdiff.blocks(t1, t2)
204 diff = bdiff.blocks(t1, t2)
199 hunk = None
205 hunk = None
200 for i, s1 in enumerate(diff):
206 for i, s1 in enumerate(diff):
201 # The first match is special.
207 # The first match is special.
202 # we've either found a match starting at line 0 or a match later
208 # we've either found a match starting at line 0 or a match later
203 # in the file. If it starts later, old and new below will both be
209 # in the file. If it starts later, old and new below will both be
204 # empty and we'll continue to the next match.
210 # empty and we'll continue to the next match.
205 if i > 0:
211 if i > 0:
206 s = diff[i - 1]
212 s = diff[i - 1]
207 else:
213 else:
208 s = [0, 0, 0, 0]
214 s = [0, 0, 0, 0]
209 delta = []
215 delta = []
210 a1 = s[1]
216 a1 = s[1]
211 a2 = s1[0]
217 a2 = s1[0]
212 b1 = s[3]
218 b1 = s[3]
213 b2 = s1[2]
219 b2 = s1[2]
214
220
215 old = l1[a1:a2]
221 old = l1[a1:a2]
216 new = l2[b1:b2]
222 new = l2[b1:b2]
217
223
218 # bdiff sometimes gives huge matches past eof, this check eats them,
224 # bdiff sometimes gives huge matches past eof, this check eats them,
219 # and deals with the special first match case described above
225 # and deals with the special first match case described above
220 if not old and not new:
226 if not old and not new:
221 continue
227 continue
222
228
223 if opts.ignoreblanklines:
229 if opts.ignoreblanklines:
224 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
230 if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
225 continue
231 continue
226
232
227 astart = contextstart(a1)
233 astart = contextstart(a1)
228 bstart = contextstart(b1)
234 bstart = contextstart(b1)
229 prev = None
235 prev = None
230 if hunk:
236 if hunk:
231 # join with the previous hunk if it falls inside the context
237 # join with the previous hunk if it falls inside the context
232 if astart < hunk[1] + opts.context + 1:
238 if astart < hunk[1] + opts.context + 1:
233 prev = hunk
239 prev = hunk
234 astart = hunk[1]
240 astart = hunk[1]
235 bstart = hunk[3]
241 bstart = hunk[3]
236 else:
242 else:
237 for x in yieldhunk(hunk):
243 for x in yieldhunk(hunk):
238 yield x
244 yield x
239 if prev:
245 if prev:
240 # we've joined the previous hunk, record the new ending points.
246 # we've joined the previous hunk, record the new ending points.
241 hunk[1] = a2
247 hunk[1] = a2
242 hunk[3] = b2
248 hunk[3] = b2
243 delta = hunk[4]
249 delta = hunk[4]
244 else:
250 else:
245 # create a new hunk
251 # create a new hunk
246 hunk = [astart, a2, bstart, b2, delta]
252 hunk = [astart, a2, bstart, b2, delta]
247
253
248 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
254 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
249 delta[len(delta):] = ['-' + x for x in old]
255 delta[len(delta):] = ['-' + x for x in old]
250 delta[len(delta):] = ['+' + x for x in new]
256 delta[len(delta):] = ['+' + x for x in new]
251
257
252 if hunk:
258 if hunk:
253 for x in yieldhunk(hunk):
259 for x in yieldhunk(hunk):
254 yield x
260 yield x
255
261
256 def patchtext(bin):
262 def patchtext(bin):
257 pos = 0
263 pos = 0
258 t = []
264 t = []
259 while pos < len(bin):
265 while pos < len(bin):
260 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
266 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
261 pos += 12
267 pos += 12
262 t.append(bin[pos:pos + l])
268 t.append(bin[pos:pos + l])
263 pos += l
269 pos += l
264 return "".join(t)
270 return "".join(t)
265
271
266 def patch(a, bin):
272 def patch(a, bin):
267 if len(a) == 0:
273 if len(a) == 0:
268 # skip over trivial delta header
274 # skip over trivial delta header
269 return buffer(bin, 12)
275 return buffer(bin, 12)
270 return mpatch.patches(a, [bin])
276 return mpatch.patches(a, [bin])
271
277
272 # similar to difflib.SequenceMatcher.get_matching_blocks
278 # similar to difflib.SequenceMatcher.get_matching_blocks
273 def get_matching_blocks(a, b):
279 def get_matching_blocks(a, b):
274 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
280 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
275
281
276 def trivialdiffheader(length):
282 def trivialdiffheader(length):
277 return struct.pack(">lll", 0, 0, length)
283 return struct.pack(">lll", 0, 0, length)
278
284
279 patches = mpatch.patches
285 patches = mpatch.patches
280 patchedsize = mpatch.patchedsize
286 patchedsize = mpatch.patchedsize
281 textdiff = bdiff.bdiff
287 textdiff = bdiff.bdiff
@@ -1,1870 +1,1869
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 import context
15 import context
16
16
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18
18
19 class PatchError(Exception):
19 class PatchError(Exception):
20 pass
20 pass
21
21
22
22
23 # public functions
23 # public functions
24
24
25 def split(stream):
25 def split(stream):
26 '''return an iterator of individual patches from a stream'''
26 '''return an iterator of individual patches from a stream'''
27 def isheader(line, inheader):
27 def isheader(line, inheader):
28 if inheader and line[0] in (' ', '\t'):
28 if inheader and line[0] in (' ', '\t'):
29 # continuation
29 # continuation
30 return True
30 return True
31 if line[0] in (' ', '-', '+'):
31 if line[0] in (' ', '-', '+'):
32 # diff line - don't check for header pattern in there
32 # diff line - don't check for header pattern in there
33 return False
33 return False
34 l = line.split(': ', 1)
34 l = line.split(': ', 1)
35 return len(l) == 2 and ' ' not in l[0]
35 return len(l) == 2 and ' ' not in l[0]
36
36
37 def chunk(lines):
37 def chunk(lines):
38 return cStringIO.StringIO(''.join(lines))
38 return cStringIO.StringIO(''.join(lines))
39
39
40 def hgsplit(stream, cur):
40 def hgsplit(stream, cur):
41 inheader = True
41 inheader = True
42
42
43 for line in stream:
43 for line in stream:
44 if not line.strip():
44 if not line.strip():
45 inheader = False
45 inheader = False
46 if not inheader and line.startswith('# HG changeset patch'):
46 if not inheader and line.startswith('# HG changeset patch'):
47 yield chunk(cur)
47 yield chunk(cur)
48 cur = []
48 cur = []
49 inheader = True
49 inheader = True
50
50
51 cur.append(line)
51 cur.append(line)
52
52
53 if cur:
53 if cur:
54 yield chunk(cur)
54 yield chunk(cur)
55
55
56 def mboxsplit(stream, cur):
56 def mboxsplit(stream, cur):
57 for line in stream:
57 for line in stream:
58 if line.startswith('From '):
58 if line.startswith('From '):
59 for c in split(chunk(cur[1:])):
59 for c in split(chunk(cur[1:])):
60 yield c
60 yield c
61 cur = []
61 cur = []
62
62
63 cur.append(line)
63 cur.append(line)
64
64
65 if cur:
65 if cur:
66 for c in split(chunk(cur[1:])):
66 for c in split(chunk(cur[1:])):
67 yield c
67 yield c
68
68
69 def mimesplit(stream, cur):
69 def mimesplit(stream, cur):
70 def msgfp(m):
70 def msgfp(m):
71 fp = cStringIO.StringIO()
71 fp = cStringIO.StringIO()
72 g = email.Generator.Generator(fp, mangle_from_=False)
72 g = email.Generator.Generator(fp, mangle_from_=False)
73 g.flatten(m)
73 g.flatten(m)
74 fp.seek(0)
74 fp.seek(0)
75 return fp
75 return fp
76
76
77 for line in stream:
77 for line in stream:
78 cur.append(line)
78 cur.append(line)
79 c = chunk(cur)
79 c = chunk(cur)
80
80
81 m = email.Parser.Parser().parse(c)
81 m = email.Parser.Parser().parse(c)
82 if not m.is_multipart():
82 if not m.is_multipart():
83 yield msgfp(m)
83 yield msgfp(m)
84 else:
84 else:
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 for part in m.walk():
86 for part in m.walk():
87 ct = part.get_content_type()
87 ct = part.get_content_type()
88 if ct not in ok_types:
88 if ct not in ok_types:
89 continue
89 continue
90 yield msgfp(part)
90 yield msgfp(part)
91
91
92 def headersplit(stream, cur):
92 def headersplit(stream, cur):
93 inheader = False
93 inheader = False
94
94
95 for line in stream:
95 for line in stream:
96 if not inheader and isheader(line, inheader):
96 if not inheader and isheader(line, inheader):
97 yield chunk(cur)
97 yield chunk(cur)
98 cur = []
98 cur = []
99 inheader = True
99 inheader = True
100 if inheader and not isheader(line, inheader):
100 if inheader and not isheader(line, inheader):
101 inheader = False
101 inheader = False
102
102
103 cur.append(line)
103 cur.append(line)
104
104
105 if cur:
105 if cur:
106 yield chunk(cur)
106 yield chunk(cur)
107
107
108 def remainder(cur):
108 def remainder(cur):
109 yield chunk(cur)
109 yield chunk(cur)
110
110
111 class fiter(object):
111 class fiter(object):
112 def __init__(self, fp):
112 def __init__(self, fp):
113 self.fp = fp
113 self.fp = fp
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return self
116 return self
117
117
118 def next(self):
118 def next(self):
119 l = self.fp.readline()
119 l = self.fp.readline()
120 if not l:
120 if not l:
121 raise StopIteration
121 raise StopIteration
122 return l
122 return l
123
123
124 inheader = False
124 inheader = False
125 cur = []
125 cur = []
126
126
127 mimeheaders = ['content-type']
127 mimeheaders = ['content-type']
128
128
129 if not util.safehasattr(stream, 'next'):
129 if not util.safehasattr(stream, 'next'):
130 # http responses, for example, have readline but not next
130 # http responses, for example, have readline but not next
131 stream = fiter(stream)
131 stream = fiter(stream)
132
132
133 for line in stream:
133 for line in stream:
134 cur.append(line)
134 cur.append(line)
135 if line.startswith('# HG changeset patch'):
135 if line.startswith('# HG changeset patch'):
136 return hgsplit(stream, cur)
136 return hgsplit(stream, cur)
137 elif line.startswith('From '):
137 elif line.startswith('From '):
138 return mboxsplit(stream, cur)
138 return mboxsplit(stream, cur)
139 elif isheader(line, inheader):
139 elif isheader(line, inheader):
140 inheader = True
140 inheader = True
141 if line.split(':', 1)[0].lower() in mimeheaders:
141 if line.split(':', 1)[0].lower() in mimeheaders:
142 # let email parser handle this
142 # let email parser handle this
143 return mimesplit(stream, cur)
143 return mimesplit(stream, cur)
144 elif line.startswith('--- ') and inheader:
144 elif line.startswith('--- ') and inheader:
145 # No evil headers seen by diff start, split by hand
145 # No evil headers seen by diff start, split by hand
146 return headersplit(stream, cur)
146 return headersplit(stream, cur)
147 # Not enough info, keep reading
147 # Not enough info, keep reading
148
148
149 # if we are here, we have a very plain patch
149 # if we are here, we have a very plain patch
150 return remainder(cur)
150 return remainder(cur)
151
151
152 def extract(ui, fileobj):
152 def extract(ui, fileobj):
153 '''extract patch from data read from fileobj.
153 '''extract patch from data read from fileobj.
154
154
155 patch can be a normal patch or contained in an email message.
155 patch can be a normal patch or contained in an email message.
156
156
157 return tuple (filename, message, user, date, branch, node, p1, p2).
157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 Any item in the returned tuple can be None. If filename is None,
158 Any item in the returned tuple can be None. If filename is None,
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160
160
161 # attempt to detect the start of a patch
161 # attempt to detect the start of a patch
162 # (this heuristic is borrowed from quilt)
162 # (this heuristic is borrowed from quilt)
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167
167
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170 try:
170 try:
171 msg = email.Parser.Parser().parse(fileobj)
171 msg = email.Parser.Parser().parse(fileobj)
172
172
173 subject = msg['Subject']
173 subject = msg['Subject']
174 user = msg['From']
174 user = msg['From']
175 if not subject and not user:
175 if not subject and not user:
176 # Not an email, restore parsed headers if any
176 # Not an email, restore parsed headers if any
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178
178
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 # should try to parse msg['Date']
180 # should try to parse msg['Date']
181 date = None
181 date = None
182 nodeid = None
182 nodeid = None
183 branch = None
183 branch = None
184 parents = []
184 parents = []
185
185
186 if subject:
186 if subject:
187 if subject.startswith('[PATCH'):
187 if subject.startswith('[PATCH'):
188 pend = subject.find(']')
188 pend = subject.find(']')
189 if pend >= 0:
189 if pend >= 0:
190 subject = subject[pend + 1:].lstrip()
190 subject = subject[pend + 1:].lstrip()
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 ui.debug('Subject: %s\n' % subject)
192 ui.debug('Subject: %s\n' % subject)
193 if user:
193 if user:
194 ui.debug('From: %s\n' % user)
194 ui.debug('From: %s\n' % user)
195 diffs_seen = 0
195 diffs_seen = 0
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 message = ''
197 message = ''
198 for part in msg.walk():
198 for part in msg.walk():
199 content_type = part.get_content_type()
199 content_type = part.get_content_type()
200 ui.debug('Content-Type: %s\n' % content_type)
200 ui.debug('Content-Type: %s\n' % content_type)
201 if content_type not in ok_types:
201 if content_type not in ok_types:
202 continue
202 continue
203 payload = part.get_payload(decode=True)
203 payload = part.get_payload(decode=True)
204 m = diffre.search(payload)
204 m = diffre.search(payload)
205 if m:
205 if m:
206 hgpatch = False
206 hgpatch = False
207 hgpatchheader = False
207 hgpatchheader = False
208 ignoretext = False
208 ignoretext = False
209
209
210 ui.debug('found patch at byte %d\n' % m.start(0))
210 ui.debug('found patch at byte %d\n' % m.start(0))
211 diffs_seen += 1
211 diffs_seen += 1
212 cfp = cStringIO.StringIO()
212 cfp = cStringIO.StringIO()
213 for line in payload[:m.start(0)].splitlines():
213 for line in payload[:m.start(0)].splitlines():
214 if line.startswith('# HG changeset patch') and not hgpatch:
214 if line.startswith('# HG changeset patch') and not hgpatch:
215 ui.debug('patch generated by hg export\n')
215 ui.debug('patch generated by hg export\n')
216 hgpatch = True
216 hgpatch = True
217 hgpatchheader = True
217 hgpatchheader = True
218 # drop earlier commit message content
218 # drop earlier commit message content
219 cfp.seek(0)
219 cfp.seek(0)
220 cfp.truncate()
220 cfp.truncate()
221 subject = None
221 subject = None
222 elif hgpatchheader:
222 elif hgpatchheader:
223 if line.startswith('# User '):
223 if line.startswith('# User '):
224 user = line[7:]
224 user = line[7:]
225 ui.debug('From: %s\n' % user)
225 ui.debug('From: %s\n' % user)
226 elif line.startswith("# Date "):
226 elif line.startswith("# Date "):
227 date = line[7:]
227 date = line[7:]
228 elif line.startswith("# Branch "):
228 elif line.startswith("# Branch "):
229 branch = line[9:]
229 branch = line[9:]
230 elif line.startswith("# Node ID "):
230 elif line.startswith("# Node ID "):
231 nodeid = line[10:]
231 nodeid = line[10:]
232 elif line.startswith("# Parent "):
232 elif line.startswith("# Parent "):
233 parents.append(line[10:])
233 parents.append(line[10:])
234 elif not line.startswith("# "):
234 elif not line.startswith("# "):
235 hgpatchheader = False
235 hgpatchheader = False
236 elif line == '---' and gitsendmail:
236 elif line == '---' and gitsendmail:
237 ignoretext = True
237 ignoretext = True
238 if not hgpatchheader and not ignoretext:
238 if not hgpatchheader and not ignoretext:
239 cfp.write(line)
239 cfp.write(line)
240 cfp.write('\n')
240 cfp.write('\n')
241 message = cfp.getvalue()
241 message = cfp.getvalue()
242 if tmpfp:
242 if tmpfp:
243 tmpfp.write(payload)
243 tmpfp.write(payload)
244 if not payload.endswith('\n'):
244 if not payload.endswith('\n'):
245 tmpfp.write('\n')
245 tmpfp.write('\n')
246 elif not diffs_seen and message and content_type == 'text/plain':
246 elif not diffs_seen and message and content_type == 'text/plain':
247 message += '\n' + payload
247 message += '\n' + payload
248 except:
248 except:
249 tmpfp.close()
249 tmpfp.close()
250 os.unlink(tmpname)
250 os.unlink(tmpname)
251 raise
251 raise
252
252
253 if subject and not message.startswith(subject):
253 if subject and not message.startswith(subject):
254 message = '%s\n%s' % (subject, message)
254 message = '%s\n%s' % (subject, message)
255 tmpfp.close()
255 tmpfp.close()
256 if not diffs_seen:
256 if not diffs_seen:
257 os.unlink(tmpname)
257 os.unlink(tmpname)
258 return None, message, user, date, branch, None, None, None
258 return None, message, user, date, branch, None, None, None
259 p1 = parents and parents.pop(0) or None
259 p1 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
261 return tmpname, message, user, date, branch, nodeid, p1, p2
261 return tmpname, message, user, date, branch, nodeid, p1, p2
262
262
263 class patchmeta(object):
263 class patchmeta(object):
264 """Patched file metadata
264 """Patched file metadata
265
265
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 'islink' is True if the file is a symlink and 'isexec' is True if
270 'islink' is True if the file is a symlink and 'isexec' is True if
271 the file is executable. Otherwise, 'mode' is None.
271 the file is executable. Otherwise, 'mode' is None.
272 """
272 """
273 def __init__(self, path):
273 def __init__(self, path):
274 self.path = path
274 self.path = path
275 self.oldpath = None
275 self.oldpath = None
276 self.mode = None
276 self.mode = None
277 self.op = 'MODIFY'
277 self.op = 'MODIFY'
278 self.binary = False
278 self.binary = False
279
279
280 def setmode(self, mode):
280 def setmode(self, mode):
281 islink = mode & 020000
281 islink = mode & 020000
282 isexec = mode & 0100
282 isexec = mode & 0100
283 self.mode = (islink, isexec)
283 self.mode = (islink, isexec)
284
284
285 def copy(self):
285 def copy(self):
286 other = patchmeta(self.path)
286 other = patchmeta(self.path)
287 other.oldpath = self.oldpath
287 other.oldpath = self.oldpath
288 other.mode = self.mode
288 other.mode = self.mode
289 other.op = self.op
289 other.op = self.op
290 other.binary = self.binary
290 other.binary = self.binary
291 return other
291 return other
292
292
293 def __repr__(self):
293 def __repr__(self):
294 return "<patchmeta %s %r>" % (self.op, self.path)
294 return "<patchmeta %s %r>" % (self.op, self.path)
295
295
296 def readgitpatch(lr):
296 def readgitpatch(lr):
297 """extract git-style metadata about patches from <patchname>"""
297 """extract git-style metadata about patches from <patchname>"""
298
298
299 # Filter patch for git information
299 # Filter patch for git information
300 gp = None
300 gp = None
301 gitpatches = []
301 gitpatches = []
302 for line in lr:
302 for line in lr:
303 line = line.rstrip(' \r\n')
303 line = line.rstrip(' \r\n')
304 if line.startswith('diff --git'):
304 if line.startswith('diff --git'):
305 m = gitre.match(line)
305 m = gitre.match(line)
306 if m:
306 if m:
307 if gp:
307 if gp:
308 gitpatches.append(gp)
308 gitpatches.append(gp)
309 dst = m.group(2)
309 dst = m.group(2)
310 gp = patchmeta(dst)
310 gp = patchmeta(dst)
311 elif gp:
311 elif gp:
312 if line.startswith('--- '):
312 if line.startswith('--- '):
313 gitpatches.append(gp)
313 gitpatches.append(gp)
314 gp = None
314 gp = None
315 continue
315 continue
316 if line.startswith('rename from '):
316 if line.startswith('rename from '):
317 gp.op = 'RENAME'
317 gp.op = 'RENAME'
318 gp.oldpath = line[12:]
318 gp.oldpath = line[12:]
319 elif line.startswith('rename to '):
319 elif line.startswith('rename to '):
320 gp.path = line[10:]
320 gp.path = line[10:]
321 elif line.startswith('copy from '):
321 elif line.startswith('copy from '):
322 gp.op = 'COPY'
322 gp.op = 'COPY'
323 gp.oldpath = line[10:]
323 gp.oldpath = line[10:]
324 elif line.startswith('copy to '):
324 elif line.startswith('copy to '):
325 gp.path = line[8:]
325 gp.path = line[8:]
326 elif line.startswith('deleted file'):
326 elif line.startswith('deleted file'):
327 gp.op = 'DELETE'
327 gp.op = 'DELETE'
328 elif line.startswith('new file mode '):
328 elif line.startswith('new file mode '):
329 gp.op = 'ADD'
329 gp.op = 'ADD'
330 gp.setmode(int(line[-6:], 8))
330 gp.setmode(int(line[-6:], 8))
331 elif line.startswith('new mode '):
331 elif line.startswith('new mode '):
332 gp.setmode(int(line[-6:], 8))
332 gp.setmode(int(line[-6:], 8))
333 elif line.startswith('GIT binary patch'):
333 elif line.startswith('GIT binary patch'):
334 gp.binary = True
334 gp.binary = True
335 if gp:
335 if gp:
336 gitpatches.append(gp)
336 gitpatches.append(gp)
337
337
338 return gitpatches
338 return gitpatches
339
339
340 class linereader(object):
340 class linereader(object):
341 # simple class to allow pushing lines back into the input stream
341 # simple class to allow pushing lines back into the input stream
342 def __init__(self, fp):
342 def __init__(self, fp):
343 self.fp = fp
343 self.fp = fp
344 self.buf = []
344 self.buf = []
345
345
346 def push(self, line):
346 def push(self, line):
347 if line is not None:
347 if line is not None:
348 self.buf.append(line)
348 self.buf.append(line)
349
349
350 def readline(self):
350 def readline(self):
351 if self.buf:
351 if self.buf:
352 l = self.buf[0]
352 l = self.buf[0]
353 del self.buf[0]
353 del self.buf[0]
354 return l
354 return l
355 return self.fp.readline()
355 return self.fp.readline()
356
356
357 def __iter__(self):
357 def __iter__(self):
358 while True:
358 while True:
359 l = self.readline()
359 l = self.readline()
360 if not l:
360 if not l:
361 break
361 break
362 yield l
362 yield l
363
363
364 class abstractbackend(object):
364 class abstractbackend(object):
365 def __init__(self, ui):
365 def __init__(self, ui):
366 self.ui = ui
366 self.ui = ui
367
367
368 def getfile(self, fname):
368 def getfile(self, fname):
369 """Return target file data and flags as a (data, (islink,
369 """Return target file data and flags as a (data, (islink,
370 isexec)) tuple.
370 isexec)) tuple.
371 """
371 """
372 raise NotImplementedError
372 raise NotImplementedError
373
373
374 def setfile(self, fname, data, mode, copysource):
374 def setfile(self, fname, data, mode, copysource):
375 """Write data to target file fname and set its mode. mode is a
375 """Write data to target file fname and set its mode. mode is a
376 (islink, isexec) tuple. If data is None, the file content should
376 (islink, isexec) tuple. If data is None, the file content should
377 be left unchanged. If the file is modified after being copied,
377 be left unchanged. If the file is modified after being copied,
378 copysource is set to the original file name.
378 copysource is set to the original file name.
379 """
379 """
380 raise NotImplementedError
380 raise NotImplementedError
381
381
382 def unlink(self, fname):
382 def unlink(self, fname):
383 """Unlink target file."""
383 """Unlink target file."""
384 raise NotImplementedError
384 raise NotImplementedError
385
385
386 def writerej(self, fname, failed, total, lines):
386 def writerej(self, fname, failed, total, lines):
387 """Write rejected lines for fname. total is the number of hunks
387 """Write rejected lines for fname. total is the number of hunks
388 which failed to apply and total the total number of hunks for this
388 which failed to apply and total the total number of hunks for this
389 files.
389 files.
390 """
390 """
391 pass
391 pass
392
392
393 def exists(self, fname):
393 def exists(self, fname):
394 raise NotImplementedError
394 raise NotImplementedError
395
395
396 class fsbackend(abstractbackend):
396 class fsbackend(abstractbackend):
397 def __init__(self, ui, basedir):
397 def __init__(self, ui, basedir):
398 super(fsbackend, self).__init__(ui)
398 super(fsbackend, self).__init__(ui)
399 self.opener = scmutil.opener(basedir)
399 self.opener = scmutil.opener(basedir)
400
400
401 def _join(self, f):
401 def _join(self, f):
402 return os.path.join(self.opener.base, f)
402 return os.path.join(self.opener.base, f)
403
403
404 def getfile(self, fname):
404 def getfile(self, fname):
405 path = self._join(fname)
405 path = self._join(fname)
406 if os.path.islink(path):
406 if os.path.islink(path):
407 return (os.readlink(path), (True, False))
407 return (os.readlink(path), (True, False))
408 isexec = False
408 isexec = False
409 try:
409 try:
410 isexec = os.lstat(path).st_mode & 0100 != 0
410 isexec = os.lstat(path).st_mode & 0100 != 0
411 except OSError, e:
411 except OSError, e:
412 if e.errno != errno.ENOENT:
412 if e.errno != errno.ENOENT:
413 raise
413 raise
414 return (self.opener.read(fname), (False, isexec))
414 return (self.opener.read(fname), (False, isexec))
415
415
416 def setfile(self, fname, data, mode, copysource):
416 def setfile(self, fname, data, mode, copysource):
417 islink, isexec = mode
417 islink, isexec = mode
418 if data is None:
418 if data is None:
419 util.setflags(self._join(fname), islink, isexec)
419 util.setflags(self._join(fname), islink, isexec)
420 return
420 return
421 if islink:
421 if islink:
422 self.opener.symlink(data, fname)
422 self.opener.symlink(data, fname)
423 else:
423 else:
424 self.opener.write(fname, data)
424 self.opener.write(fname, data)
425 if isexec:
425 if isexec:
426 util.setflags(self._join(fname), False, True)
426 util.setflags(self._join(fname), False, True)
427
427
428 def unlink(self, fname):
428 def unlink(self, fname):
429 try:
429 try:
430 util.unlinkpath(self._join(fname))
430 util.unlinkpath(self._join(fname))
431 except OSError, inst:
431 except OSError, inst:
432 if inst.errno != errno.ENOENT:
432 if inst.errno != errno.ENOENT:
433 raise
433 raise
434
434
435 def writerej(self, fname, failed, total, lines):
435 def writerej(self, fname, failed, total, lines):
436 fname = fname + ".rej"
436 fname = fname + ".rej"
437 self.ui.warn(
437 self.ui.warn(
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
439 (failed, total, fname))
439 (failed, total, fname))
440 fp = self.opener(fname, 'w')
440 fp = self.opener(fname, 'w')
441 fp.writelines(lines)
441 fp.writelines(lines)
442 fp.close()
442 fp.close()
443
443
444 def exists(self, fname):
444 def exists(self, fname):
445 return os.path.lexists(self._join(fname))
445 return os.path.lexists(self._join(fname))
446
446
447 class workingbackend(fsbackend):
447 class workingbackend(fsbackend):
448 def __init__(self, ui, repo, similarity):
448 def __init__(self, ui, repo, similarity):
449 super(workingbackend, self).__init__(ui, repo.root)
449 super(workingbackend, self).__init__(ui, repo.root)
450 self.repo = repo
450 self.repo = repo
451 self.similarity = similarity
451 self.similarity = similarity
452 self.removed = set()
452 self.removed = set()
453 self.changed = set()
453 self.changed = set()
454 self.copied = []
454 self.copied = []
455
455
456 def _checkknown(self, fname):
456 def _checkknown(self, fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
459
459
460 def setfile(self, fname, data, mode, copysource):
460 def setfile(self, fname, data, mode, copysource):
461 self._checkknown(fname)
461 self._checkknown(fname)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
463 if copysource is not None:
463 if copysource is not None:
464 self.copied.append((copysource, fname))
464 self.copied.append((copysource, fname))
465 self.changed.add(fname)
465 self.changed.add(fname)
466
466
467 def unlink(self, fname):
467 def unlink(self, fname):
468 self._checkknown(fname)
468 self._checkknown(fname)
469 super(workingbackend, self).unlink(fname)
469 super(workingbackend, self).unlink(fname)
470 self.removed.add(fname)
470 self.removed.add(fname)
471 self.changed.add(fname)
471 self.changed.add(fname)
472
472
473 def close(self):
473 def close(self):
474 wctx = self.repo[None]
474 wctx = self.repo[None]
475 addremoved = set(self.changed)
475 addremoved = set(self.changed)
476 for src, dst in self.copied:
476 for src, dst in self.copied:
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
478 addremoved.discard(src)
478 addremoved.discard(src)
479 if (not self.similarity) and self.removed:
479 if (not self.similarity) and self.removed:
480 wctx.forget(sorted(self.removed))
480 wctx.forget(sorted(self.removed))
481 if addremoved:
481 if addremoved:
482 cwd = self.repo.getcwd()
482 cwd = self.repo.getcwd()
483 if cwd:
483 if cwd:
484 addremoved = [util.pathto(self.repo.root, cwd, f)
484 addremoved = [util.pathto(self.repo.root, cwd, f)
485 for f in addremoved]
485 for f in addremoved]
486 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
486 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
487 return sorted(self.changed)
487 return sorted(self.changed)
488
488
489 class filestore(object):
489 class filestore(object):
490 def __init__(self, maxsize=None):
490 def __init__(self, maxsize=None):
491 self.opener = None
491 self.opener = None
492 self.files = {}
492 self.files = {}
493 self.created = 0
493 self.created = 0
494 self.maxsize = maxsize
494 self.maxsize = maxsize
495 if self.maxsize is None:
495 if self.maxsize is None:
496 self.maxsize = 4*(2**20)
496 self.maxsize = 4*(2**20)
497 self.size = 0
497 self.size = 0
498 self.data = {}
498 self.data = {}
499
499
500 def setfile(self, fname, data, mode, copied=None):
500 def setfile(self, fname, data, mode, copied=None):
501 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
501 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
502 self.data[fname] = (data, mode, copied)
502 self.data[fname] = (data, mode, copied)
503 self.size += len(data)
503 self.size += len(data)
504 else:
504 else:
505 if self.opener is None:
505 if self.opener is None:
506 root = tempfile.mkdtemp(prefix='hg-patch-')
506 root = tempfile.mkdtemp(prefix='hg-patch-')
507 self.opener = scmutil.opener(root)
507 self.opener = scmutil.opener(root)
508 # Avoid filename issues with these simple names
508 # Avoid filename issues with these simple names
509 fn = str(self.created)
509 fn = str(self.created)
510 self.opener.write(fn, data)
510 self.opener.write(fn, data)
511 self.created += 1
511 self.created += 1
512 self.files[fname] = (fn, mode, copied)
512 self.files[fname] = (fn, mode, copied)
513
513
514 def getfile(self, fname):
514 def getfile(self, fname):
515 if fname in self.data:
515 if fname in self.data:
516 return self.data[fname]
516 return self.data[fname]
517 if not self.opener or fname not in self.files:
517 if not self.opener or fname not in self.files:
518 raise IOError()
518 raise IOError()
519 fn, mode, copied = self.files[fname]
519 fn, mode, copied = self.files[fname]
520 return self.opener.read(fn), mode, copied
520 return self.opener.read(fn), mode, copied
521
521
522 def close(self):
522 def close(self):
523 if self.opener:
523 if self.opener:
524 shutil.rmtree(self.opener.base)
524 shutil.rmtree(self.opener.base)
525
525
526 class repobackend(abstractbackend):
526 class repobackend(abstractbackend):
527 def __init__(self, ui, repo, ctx, store):
527 def __init__(self, ui, repo, ctx, store):
528 super(repobackend, self).__init__(ui)
528 super(repobackend, self).__init__(ui)
529 self.repo = repo
529 self.repo = repo
530 self.ctx = ctx
530 self.ctx = ctx
531 self.store = store
531 self.store = store
532 self.changed = set()
532 self.changed = set()
533 self.removed = set()
533 self.removed = set()
534 self.copied = {}
534 self.copied = {}
535
535
536 def _checkknown(self, fname):
536 def _checkknown(self, fname):
537 if fname not in self.ctx:
537 if fname not in self.ctx:
538 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
538 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
539
539
540 def getfile(self, fname):
540 def getfile(self, fname):
541 try:
541 try:
542 fctx = self.ctx[fname]
542 fctx = self.ctx[fname]
543 except error.LookupError:
543 except error.LookupError:
544 raise IOError()
544 raise IOError()
545 flags = fctx.flags()
545 flags = fctx.flags()
546 return fctx.data(), ('l' in flags, 'x' in flags)
546 return fctx.data(), ('l' in flags, 'x' in flags)
547
547
548 def setfile(self, fname, data, mode, copysource):
548 def setfile(self, fname, data, mode, copysource):
549 if copysource:
549 if copysource:
550 self._checkknown(copysource)
550 self._checkknown(copysource)
551 if data is None:
551 if data is None:
552 data = self.ctx[fname].data()
552 data = self.ctx[fname].data()
553 self.store.setfile(fname, data, mode, copysource)
553 self.store.setfile(fname, data, mode, copysource)
554 self.changed.add(fname)
554 self.changed.add(fname)
555 if copysource:
555 if copysource:
556 self.copied[fname] = copysource
556 self.copied[fname] = copysource
557
557
558 def unlink(self, fname):
558 def unlink(self, fname):
559 self._checkknown(fname)
559 self._checkknown(fname)
560 self.removed.add(fname)
560 self.removed.add(fname)
561
561
562 def exists(self, fname):
562 def exists(self, fname):
563 return fname in self.ctx
563 return fname in self.ctx
564
564
565 def close(self):
565 def close(self):
566 return self.changed | self.removed
566 return self.changed | self.removed
567
567
568 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
568 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
569 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
569 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
570 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
570 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
571 eolmodes = ['strict', 'crlf', 'lf', 'auto']
571 eolmodes = ['strict', 'crlf', 'lf', 'auto']
572
572
573 class patchfile(object):
573 class patchfile(object):
574 def __init__(self, ui, gp, backend, store, eolmode='strict'):
574 def __init__(self, ui, gp, backend, store, eolmode='strict'):
575 self.fname = gp.path
575 self.fname = gp.path
576 self.eolmode = eolmode
576 self.eolmode = eolmode
577 self.eol = None
577 self.eol = None
578 self.backend = backend
578 self.backend = backend
579 self.ui = ui
579 self.ui = ui
580 self.lines = []
580 self.lines = []
581 self.exists = False
581 self.exists = False
582 self.missing = True
582 self.missing = True
583 self.mode = gp.mode
583 self.mode = gp.mode
584 self.copysource = gp.oldpath
584 self.copysource = gp.oldpath
585 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
585 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
586 self.remove = gp.op == 'DELETE'
586 self.remove = gp.op == 'DELETE'
587 try:
587 try:
588 if self.copysource is None:
588 if self.copysource is None:
589 data, mode = backend.getfile(self.fname)
589 data, mode = backend.getfile(self.fname)
590 self.exists = True
590 self.exists = True
591 else:
591 else:
592 data, mode = store.getfile(self.copysource)[:2]
592 data, mode = store.getfile(self.copysource)[:2]
593 self.exists = backend.exists(self.fname)
593 self.exists = backend.exists(self.fname)
594 self.missing = False
594 self.missing = False
595 if data:
595 if data:
596 self.lines = mdiff.splitnewlines(data)
596 self.lines = mdiff.splitnewlines(data)
597 if self.mode is None:
597 if self.mode is None:
598 self.mode = mode
598 self.mode = mode
599 if self.lines:
599 if self.lines:
600 # Normalize line endings
600 # Normalize line endings
601 if self.lines[0].endswith('\r\n'):
601 if self.lines[0].endswith('\r\n'):
602 self.eol = '\r\n'
602 self.eol = '\r\n'
603 elif self.lines[0].endswith('\n'):
603 elif self.lines[0].endswith('\n'):
604 self.eol = '\n'
604 self.eol = '\n'
605 if eolmode != 'strict':
605 if eolmode != 'strict':
606 nlines = []
606 nlines = []
607 for l in self.lines:
607 for l in self.lines:
608 if l.endswith('\r\n'):
608 if l.endswith('\r\n'):
609 l = l[:-2] + '\n'
609 l = l[:-2] + '\n'
610 nlines.append(l)
610 nlines.append(l)
611 self.lines = nlines
611 self.lines = nlines
612 except IOError:
612 except IOError:
613 if self.create:
613 if self.create:
614 self.missing = False
614 self.missing = False
615 if self.mode is None:
615 if self.mode is None:
616 self.mode = (False, False)
616 self.mode = (False, False)
617 if self.missing:
617 if self.missing:
618 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
618 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
619
619
620 self.hash = {}
620 self.hash = {}
621 self.dirty = 0
621 self.dirty = 0
622 self.offset = 0
622 self.offset = 0
623 self.skew = 0
623 self.skew = 0
624 self.rej = []
624 self.rej = []
625 self.fileprinted = False
625 self.fileprinted = False
626 self.printfile(False)
626 self.printfile(False)
627 self.hunks = 0
627 self.hunks = 0
628
628
629 def writelines(self, fname, lines, mode):
629 def writelines(self, fname, lines, mode):
630 if self.eolmode == 'auto':
630 if self.eolmode == 'auto':
631 eol = self.eol
631 eol = self.eol
632 elif self.eolmode == 'crlf':
632 elif self.eolmode == 'crlf':
633 eol = '\r\n'
633 eol = '\r\n'
634 else:
634 else:
635 eol = '\n'
635 eol = '\n'
636
636
637 if self.eolmode != 'strict' and eol and eol != '\n':
637 if self.eolmode != 'strict' and eol and eol != '\n':
638 rawlines = []
638 rawlines = []
639 for l in lines:
639 for l in lines:
640 if l and l[-1] == '\n':
640 if l and l[-1] == '\n':
641 l = l[:-1] + eol
641 l = l[:-1] + eol
642 rawlines.append(l)
642 rawlines.append(l)
643 lines = rawlines
643 lines = rawlines
644
644
645 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
645 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
646
646
647 def printfile(self, warn):
647 def printfile(self, warn):
648 if self.fileprinted:
648 if self.fileprinted:
649 return
649 return
650 if warn or self.ui.verbose:
650 if warn or self.ui.verbose:
651 self.fileprinted = True
651 self.fileprinted = True
652 s = _("patching file %s\n") % self.fname
652 s = _("patching file %s\n") % self.fname
653 if warn:
653 if warn:
654 self.ui.warn(s)
654 self.ui.warn(s)
655 else:
655 else:
656 self.ui.note(s)
656 self.ui.note(s)
657
657
658
658
659 def findlines(self, l, linenum):
659 def findlines(self, l, linenum):
660 # looks through the hash and finds candidate lines. The
660 # looks through the hash and finds candidate lines. The
661 # result is a list of line numbers sorted based on distance
661 # result is a list of line numbers sorted based on distance
662 # from linenum
662 # from linenum
663
663
664 cand = self.hash.get(l, [])
664 cand = self.hash.get(l, [])
665 if len(cand) > 1:
665 if len(cand) > 1:
666 # resort our list of potentials forward then back.
666 # resort our list of potentials forward then back.
667 cand.sort(key=lambda x: abs(x - linenum))
667 cand.sort(key=lambda x: abs(x - linenum))
668 return cand
668 return cand
669
669
670 def write_rej(self):
670 def write_rej(self):
671 # our rejects are a little different from patch(1). This always
671 # our rejects are a little different from patch(1). This always
672 # creates rejects in the same form as the original patch. A file
672 # creates rejects in the same form as the original patch. A file
673 # header is inserted so that you can run the reject through patch again
673 # header is inserted so that you can run the reject through patch again
674 # without having to type the filename.
674 # without having to type the filename.
675 if not self.rej:
675 if not self.rej:
676 return
676 return
677 base = os.path.basename(self.fname)
677 base = os.path.basename(self.fname)
678 lines = ["--- %s\n+++ %s\n" % (base, base)]
678 lines = ["--- %s\n+++ %s\n" % (base, base)]
679 for x in self.rej:
679 for x in self.rej:
680 for l in x.hunk:
680 for l in x.hunk:
681 lines.append(l)
681 lines.append(l)
682 if l[-1] != '\n':
682 if l[-1] != '\n':
683 lines.append("\n\ No newline at end of file\n")
683 lines.append("\n\ No newline at end of file\n")
684 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
684 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
685
685
686 def apply(self, h):
686 def apply(self, h):
687 if not h.complete():
687 if not h.complete():
688 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
688 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
689 (h.number, h.desc, len(h.a), h.lena, len(h.b),
689 (h.number, h.desc, len(h.a), h.lena, len(h.b),
690 h.lenb))
690 h.lenb))
691
691
692 self.hunks += 1
692 self.hunks += 1
693
693
694 if self.missing:
694 if self.missing:
695 self.rej.append(h)
695 self.rej.append(h)
696 return -1
696 return -1
697
697
698 if self.exists and self.create:
698 if self.exists and self.create:
699 if self.copysource:
699 if self.copysource:
700 self.ui.warn(_("cannot create %s: destination already "
700 self.ui.warn(_("cannot create %s: destination already "
701 "exists\n" % self.fname))
701 "exists\n" % self.fname))
702 else:
702 else:
703 self.ui.warn(_("file %s already exists\n") % self.fname)
703 self.ui.warn(_("file %s already exists\n") % self.fname)
704 self.rej.append(h)
704 self.rej.append(h)
705 return -1
705 return -1
706
706
707 if isinstance(h, binhunk):
707 if isinstance(h, binhunk):
708 if self.remove:
708 if self.remove:
709 self.backend.unlink(self.fname)
709 self.backend.unlink(self.fname)
710 else:
710 else:
711 self.lines[:] = h.new()
711 self.lines[:] = h.new()
712 self.offset += len(h.new())
712 self.offset += len(h.new())
713 self.dirty = True
713 self.dirty = True
714 return 0
714 return 0
715
715
716 horig = h
716 horig = h
717 if (self.eolmode in ('crlf', 'lf')
717 if (self.eolmode in ('crlf', 'lf')
718 or self.eolmode == 'auto' and self.eol):
718 or self.eolmode == 'auto' and self.eol):
719 # If new eols are going to be normalized, then normalize
719 # If new eols are going to be normalized, then normalize
720 # hunk data before patching. Otherwise, preserve input
720 # hunk data before patching. Otherwise, preserve input
721 # line-endings.
721 # line-endings.
722 h = h.getnormalized()
722 h = h.getnormalized()
723
723
724 # fast case first, no offsets, no fuzz
724 # fast case first, no offsets, no fuzz
725 old = h.old()
725 old = h.old()
726 # patch starts counting at 1 unless we are adding the file
726 start = h.starta + self.offset
727 if h.starta == 0:
727 # zero length hunk ranges already have their start decremented
728 start = 0
728 if h.lena:
729 else:
729 start -= 1
730 start = h.starta + self.offset - 1
731 orig_start = start
730 orig_start = start
732 # if there's skew we want to emit the "(offset %d lines)" even
731 # if there's skew we want to emit the "(offset %d lines)" even
733 # when the hunk cleanly applies at start + skew, so skip the
732 # when the hunk cleanly applies at start + skew, so skip the
734 # fast case code
733 # fast case code
735 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
734 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
736 if self.remove:
735 if self.remove:
737 self.backend.unlink(self.fname)
736 self.backend.unlink(self.fname)
738 else:
737 else:
739 self.lines[start : start + h.lena] = h.new()
738 self.lines[start : start + h.lena] = h.new()
740 self.offset += h.lenb - h.lena
739 self.offset += h.lenb - h.lena
741 self.dirty = True
740 self.dirty = True
742 return 0
741 return 0
743
742
744 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
743 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
745 self.hash = {}
744 self.hash = {}
746 for x, s in enumerate(self.lines):
745 for x, s in enumerate(self.lines):
747 self.hash.setdefault(s, []).append(x)
746 self.hash.setdefault(s, []).append(x)
748 if h.hunk[-1][0] != ' ':
747 if h.hunk[-1][0] != ' ':
749 # if the hunk tried to put something at the bottom of the file
748 # if the hunk tried to put something at the bottom of the file
750 # override the start line and use eof here
749 # override the start line and use eof here
751 search_start = len(self.lines)
750 search_start = len(self.lines)
752 else:
751 else:
753 search_start = orig_start + self.skew
752 search_start = orig_start + self.skew
754
753
755 for fuzzlen in xrange(3):
754 for fuzzlen in xrange(3):
756 for toponly in [True, False]:
755 for toponly in [True, False]:
757 old = h.old(fuzzlen, toponly)
756 old = h.old(fuzzlen, toponly)
758
757
759 cand = self.findlines(old[0][1:], search_start)
758 cand = self.findlines(old[0][1:], search_start)
760 for l in cand:
759 for l in cand:
761 if diffhelpers.testhunk(old, self.lines, l) == 0:
760 if diffhelpers.testhunk(old, self.lines, l) == 0:
762 newlines = h.new(fuzzlen, toponly)
761 newlines = h.new(fuzzlen, toponly)
763 self.lines[l : l + len(old)] = newlines
762 self.lines[l : l + len(old)] = newlines
764 self.offset += len(newlines) - len(old)
763 self.offset += len(newlines) - len(old)
765 self.skew = l - orig_start
764 self.skew = l - orig_start
766 self.dirty = True
765 self.dirty = True
767 offset = l - orig_start - fuzzlen
766 offset = l - orig_start - fuzzlen
768 if fuzzlen:
767 if fuzzlen:
769 msg = _("Hunk #%d succeeded at %d "
768 msg = _("Hunk #%d succeeded at %d "
770 "with fuzz %d "
769 "with fuzz %d "
771 "(offset %d lines).\n")
770 "(offset %d lines).\n")
772 self.printfile(True)
771 self.printfile(True)
773 self.ui.warn(msg %
772 self.ui.warn(msg %
774 (h.number, l + 1, fuzzlen, offset))
773 (h.number, l + 1, fuzzlen, offset))
775 else:
774 else:
776 msg = _("Hunk #%d succeeded at %d "
775 msg = _("Hunk #%d succeeded at %d "
777 "(offset %d lines).\n")
776 "(offset %d lines).\n")
778 self.ui.note(msg % (h.number, l + 1, offset))
777 self.ui.note(msg % (h.number, l + 1, offset))
779 return fuzzlen
778 return fuzzlen
780 self.printfile(True)
779 self.printfile(True)
781 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
780 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
782 self.rej.append(horig)
781 self.rej.append(horig)
783 return -1
782 return -1
784
783
785 def close(self):
784 def close(self):
786 if self.dirty:
785 if self.dirty:
787 self.writelines(self.fname, self.lines, self.mode)
786 self.writelines(self.fname, self.lines, self.mode)
788 self.write_rej()
787 self.write_rej()
789 return len(self.rej)
788 return len(self.rej)
790
789
791 class hunk(object):
790 class hunk(object):
792 def __init__(self, desc, num, lr, context):
791 def __init__(self, desc, num, lr, context):
793 self.number = num
792 self.number = num
794 self.desc = desc
793 self.desc = desc
795 self.hunk = [desc]
794 self.hunk = [desc]
796 self.a = []
795 self.a = []
797 self.b = []
796 self.b = []
798 self.starta = self.lena = None
797 self.starta = self.lena = None
799 self.startb = self.lenb = None
798 self.startb = self.lenb = None
800 if lr is not None:
799 if lr is not None:
801 if context:
800 if context:
802 self.read_context_hunk(lr)
801 self.read_context_hunk(lr)
803 else:
802 else:
804 self.read_unified_hunk(lr)
803 self.read_unified_hunk(lr)
805
804
806 def getnormalized(self):
805 def getnormalized(self):
807 """Return a copy with line endings normalized to LF."""
806 """Return a copy with line endings normalized to LF."""
808
807
809 def normalize(lines):
808 def normalize(lines):
810 nlines = []
809 nlines = []
811 for line in lines:
810 for line in lines:
812 if line.endswith('\r\n'):
811 if line.endswith('\r\n'):
813 line = line[:-2] + '\n'
812 line = line[:-2] + '\n'
814 nlines.append(line)
813 nlines.append(line)
815 return nlines
814 return nlines
816
815
817 # Dummy object, it is rebuilt manually
816 # Dummy object, it is rebuilt manually
818 nh = hunk(self.desc, self.number, None, None)
817 nh = hunk(self.desc, self.number, None, None)
819 nh.number = self.number
818 nh.number = self.number
820 nh.desc = self.desc
819 nh.desc = self.desc
821 nh.hunk = self.hunk
820 nh.hunk = self.hunk
822 nh.a = normalize(self.a)
821 nh.a = normalize(self.a)
823 nh.b = normalize(self.b)
822 nh.b = normalize(self.b)
824 nh.starta = self.starta
823 nh.starta = self.starta
825 nh.startb = self.startb
824 nh.startb = self.startb
826 nh.lena = self.lena
825 nh.lena = self.lena
827 nh.lenb = self.lenb
826 nh.lenb = self.lenb
828 return nh
827 return nh
829
828
830 def read_unified_hunk(self, lr):
829 def read_unified_hunk(self, lr):
831 m = unidesc.match(self.desc)
830 m = unidesc.match(self.desc)
832 if not m:
831 if not m:
833 raise PatchError(_("bad hunk #%d") % self.number)
832 raise PatchError(_("bad hunk #%d") % self.number)
834 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
833 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
835 if self.lena is None:
834 if self.lena is None:
836 self.lena = 1
835 self.lena = 1
837 else:
836 else:
838 self.lena = int(self.lena)
837 self.lena = int(self.lena)
839 if self.lenb is None:
838 if self.lenb is None:
840 self.lenb = 1
839 self.lenb = 1
841 else:
840 else:
842 self.lenb = int(self.lenb)
841 self.lenb = int(self.lenb)
843 self.starta = int(self.starta)
842 self.starta = int(self.starta)
844 self.startb = int(self.startb)
843 self.startb = int(self.startb)
845 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
844 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
846 # if we hit eof before finishing out the hunk, the last line will
845 # if we hit eof before finishing out the hunk, the last line will
847 # be zero length. Lets try to fix it up.
846 # be zero length. Lets try to fix it up.
848 while len(self.hunk[-1]) == 0:
847 while len(self.hunk[-1]) == 0:
849 del self.hunk[-1]
848 del self.hunk[-1]
850 del self.a[-1]
849 del self.a[-1]
851 del self.b[-1]
850 del self.b[-1]
852 self.lena -= 1
851 self.lena -= 1
853 self.lenb -= 1
852 self.lenb -= 1
854 self._fixnewline(lr)
853 self._fixnewline(lr)
855
854
856 def read_context_hunk(self, lr):
855 def read_context_hunk(self, lr):
857 self.desc = lr.readline()
856 self.desc = lr.readline()
858 m = contextdesc.match(self.desc)
857 m = contextdesc.match(self.desc)
859 if not m:
858 if not m:
860 raise PatchError(_("bad hunk #%d") % self.number)
859 raise PatchError(_("bad hunk #%d") % self.number)
861 foo, self.starta, foo2, aend, foo3 = m.groups()
860 foo, self.starta, foo2, aend, foo3 = m.groups()
862 self.starta = int(self.starta)
861 self.starta = int(self.starta)
863 if aend is None:
862 if aend is None:
864 aend = self.starta
863 aend = self.starta
865 self.lena = int(aend) - self.starta
864 self.lena = int(aend) - self.starta
866 if self.starta:
865 if self.starta:
867 self.lena += 1
866 self.lena += 1
868 for x in xrange(self.lena):
867 for x in xrange(self.lena):
869 l = lr.readline()
868 l = lr.readline()
870 if l.startswith('---'):
869 if l.startswith('---'):
871 # lines addition, old block is empty
870 # lines addition, old block is empty
872 lr.push(l)
871 lr.push(l)
873 break
872 break
874 s = l[2:]
873 s = l[2:]
875 if l.startswith('- ') or l.startswith('! '):
874 if l.startswith('- ') or l.startswith('! '):
876 u = '-' + s
875 u = '-' + s
877 elif l.startswith(' '):
876 elif l.startswith(' '):
878 u = ' ' + s
877 u = ' ' + s
879 else:
878 else:
880 raise PatchError(_("bad hunk #%d old text line %d") %
879 raise PatchError(_("bad hunk #%d old text line %d") %
881 (self.number, x))
880 (self.number, x))
882 self.a.append(u)
881 self.a.append(u)
883 self.hunk.append(u)
882 self.hunk.append(u)
884
883
885 l = lr.readline()
884 l = lr.readline()
886 if l.startswith('\ '):
885 if l.startswith('\ '):
887 s = self.a[-1][:-1]
886 s = self.a[-1][:-1]
888 self.a[-1] = s
887 self.a[-1] = s
889 self.hunk[-1] = s
888 self.hunk[-1] = s
890 l = lr.readline()
889 l = lr.readline()
891 m = contextdesc.match(l)
890 m = contextdesc.match(l)
892 if not m:
891 if not m:
893 raise PatchError(_("bad hunk #%d") % self.number)
892 raise PatchError(_("bad hunk #%d") % self.number)
894 foo, self.startb, foo2, bend, foo3 = m.groups()
893 foo, self.startb, foo2, bend, foo3 = m.groups()
895 self.startb = int(self.startb)
894 self.startb = int(self.startb)
896 if bend is None:
895 if bend is None:
897 bend = self.startb
896 bend = self.startb
898 self.lenb = int(bend) - self.startb
897 self.lenb = int(bend) - self.startb
899 if self.startb:
898 if self.startb:
900 self.lenb += 1
899 self.lenb += 1
901 hunki = 1
900 hunki = 1
902 for x in xrange(self.lenb):
901 for x in xrange(self.lenb):
903 l = lr.readline()
902 l = lr.readline()
904 if l.startswith('\ '):
903 if l.startswith('\ '):
905 # XXX: the only way to hit this is with an invalid line range.
904 # XXX: the only way to hit this is with an invalid line range.
906 # The no-eol marker is not counted in the line range, but I
905 # The no-eol marker is not counted in the line range, but I
907 # guess there are diff(1) out there which behave differently.
906 # guess there are diff(1) out there which behave differently.
908 s = self.b[-1][:-1]
907 s = self.b[-1][:-1]
909 self.b[-1] = s
908 self.b[-1] = s
910 self.hunk[hunki - 1] = s
909 self.hunk[hunki - 1] = s
911 continue
910 continue
912 if not l:
911 if not l:
913 # line deletions, new block is empty and we hit EOF
912 # line deletions, new block is empty and we hit EOF
914 lr.push(l)
913 lr.push(l)
915 break
914 break
916 s = l[2:]
915 s = l[2:]
917 if l.startswith('+ ') or l.startswith('! '):
916 if l.startswith('+ ') or l.startswith('! '):
918 u = '+' + s
917 u = '+' + s
919 elif l.startswith(' '):
918 elif l.startswith(' '):
920 u = ' ' + s
919 u = ' ' + s
921 elif len(self.b) == 0:
920 elif len(self.b) == 0:
922 # line deletions, new block is empty
921 # line deletions, new block is empty
923 lr.push(l)
922 lr.push(l)
924 break
923 break
925 else:
924 else:
926 raise PatchError(_("bad hunk #%d old text line %d") %
925 raise PatchError(_("bad hunk #%d old text line %d") %
927 (self.number, x))
926 (self.number, x))
928 self.b.append(s)
927 self.b.append(s)
929 while True:
928 while True:
930 if hunki >= len(self.hunk):
929 if hunki >= len(self.hunk):
931 h = ""
930 h = ""
932 else:
931 else:
933 h = self.hunk[hunki]
932 h = self.hunk[hunki]
934 hunki += 1
933 hunki += 1
935 if h == u:
934 if h == u:
936 break
935 break
937 elif h.startswith('-'):
936 elif h.startswith('-'):
938 continue
937 continue
939 else:
938 else:
940 self.hunk.insert(hunki - 1, u)
939 self.hunk.insert(hunki - 1, u)
941 break
940 break
942
941
943 if not self.a:
942 if not self.a:
944 # this happens when lines were only added to the hunk
943 # this happens when lines were only added to the hunk
945 for x in self.hunk:
944 for x in self.hunk:
946 if x.startswith('-') or x.startswith(' '):
945 if x.startswith('-') or x.startswith(' '):
947 self.a.append(x)
946 self.a.append(x)
948 if not self.b:
947 if not self.b:
949 # this happens when lines were only deleted from the hunk
948 # this happens when lines were only deleted from the hunk
950 for x in self.hunk:
949 for x in self.hunk:
951 if x.startswith('+') or x.startswith(' '):
950 if x.startswith('+') or x.startswith(' '):
952 self.b.append(x[1:])
951 self.b.append(x[1:])
953 # @@ -start,len +start,len @@
952 # @@ -start,len +start,len @@
954 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
953 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
955 self.startb, self.lenb)
954 self.startb, self.lenb)
956 self.hunk[0] = self.desc
955 self.hunk[0] = self.desc
957 self._fixnewline(lr)
956 self._fixnewline(lr)
958
957
959 def _fixnewline(self, lr):
958 def _fixnewline(self, lr):
960 l = lr.readline()
959 l = lr.readline()
961 if l.startswith('\ '):
960 if l.startswith('\ '):
962 diffhelpers.fix_newline(self.hunk, self.a, self.b)
961 diffhelpers.fix_newline(self.hunk, self.a, self.b)
963 else:
962 else:
964 lr.push(l)
963 lr.push(l)
965
964
966 def complete(self):
965 def complete(self):
967 return len(self.a) == self.lena and len(self.b) == self.lenb
966 return len(self.a) == self.lena and len(self.b) == self.lenb
968
967
969 def fuzzit(self, l, fuzz, toponly):
968 def fuzzit(self, l, fuzz, toponly):
970 # this removes context lines from the top and bottom of list 'l'. It
969 # this removes context lines from the top and bottom of list 'l'. It
971 # checks the hunk to make sure only context lines are removed, and then
970 # checks the hunk to make sure only context lines are removed, and then
972 # returns a new shortened list of lines.
971 # returns a new shortened list of lines.
973 fuzz = min(fuzz, len(l)-1)
972 fuzz = min(fuzz, len(l)-1)
974 if fuzz:
973 if fuzz:
975 top = 0
974 top = 0
976 bot = 0
975 bot = 0
977 hlen = len(self.hunk)
976 hlen = len(self.hunk)
978 for x in xrange(hlen - 1):
977 for x in xrange(hlen - 1):
979 # the hunk starts with the @@ line, so use x+1
978 # the hunk starts with the @@ line, so use x+1
980 if self.hunk[x + 1][0] == ' ':
979 if self.hunk[x + 1][0] == ' ':
981 top += 1
980 top += 1
982 else:
981 else:
983 break
982 break
984 if not toponly:
983 if not toponly:
985 for x in xrange(hlen - 1):
984 for x in xrange(hlen - 1):
986 if self.hunk[hlen - bot - 1][0] == ' ':
985 if self.hunk[hlen - bot - 1][0] == ' ':
987 bot += 1
986 bot += 1
988 else:
987 else:
989 break
988 break
990
989
991 # top and bot now count context in the hunk
990 # top and bot now count context in the hunk
992 # adjust them if either one is short
991 # adjust them if either one is short
993 context = max(top, bot, 3)
992 context = max(top, bot, 3)
994 if bot < context:
993 if bot < context:
995 bot = max(0, fuzz - (context - bot))
994 bot = max(0, fuzz - (context - bot))
996 else:
995 else:
997 bot = min(fuzz, bot)
996 bot = min(fuzz, bot)
998 if top < context:
997 if top < context:
999 top = max(0, fuzz - (context - top))
998 top = max(0, fuzz - (context - top))
1000 else:
999 else:
1001 top = min(fuzz, top)
1000 top = min(fuzz, top)
1002
1001
1003 return l[top:len(l)-bot]
1002 return l[top:len(l)-bot]
1004 return l
1003 return l
1005
1004
1006 def old(self, fuzz=0, toponly=False):
1005 def old(self, fuzz=0, toponly=False):
1007 return self.fuzzit(self.a, fuzz, toponly)
1006 return self.fuzzit(self.a, fuzz, toponly)
1008
1007
1009 def new(self, fuzz=0, toponly=False):
1008 def new(self, fuzz=0, toponly=False):
1010 return self.fuzzit(self.b, fuzz, toponly)
1009 return self.fuzzit(self.b, fuzz, toponly)
1011
1010
1012 class binhunk(object):
1011 class binhunk(object):
1013 'A binary patch file. Only understands literals so far.'
1012 'A binary patch file. Only understands literals so far.'
1014 def __init__(self, lr):
1013 def __init__(self, lr):
1015 self.text = None
1014 self.text = None
1016 self.hunk = ['GIT binary patch\n']
1015 self.hunk = ['GIT binary patch\n']
1017 self._read(lr)
1016 self._read(lr)
1018
1017
1019 def complete(self):
1018 def complete(self):
1020 return self.text is not None
1019 return self.text is not None
1021
1020
1022 def new(self):
1021 def new(self):
1023 return [self.text]
1022 return [self.text]
1024
1023
1025 def _read(self, lr):
1024 def _read(self, lr):
1026 line = lr.readline()
1025 line = lr.readline()
1027 self.hunk.append(line)
1026 self.hunk.append(line)
1028 while line and not line.startswith('literal '):
1027 while line and not line.startswith('literal '):
1029 line = lr.readline()
1028 line = lr.readline()
1030 self.hunk.append(line)
1029 self.hunk.append(line)
1031 if not line:
1030 if not line:
1032 raise PatchError(_('could not extract binary patch'))
1031 raise PatchError(_('could not extract binary patch'))
1033 size = int(line[8:].rstrip())
1032 size = int(line[8:].rstrip())
1034 dec = []
1033 dec = []
1035 line = lr.readline()
1034 line = lr.readline()
1036 self.hunk.append(line)
1035 self.hunk.append(line)
1037 while len(line) > 1:
1036 while len(line) > 1:
1038 l = line[0]
1037 l = line[0]
1039 if l <= 'Z' and l >= 'A':
1038 if l <= 'Z' and l >= 'A':
1040 l = ord(l) - ord('A') + 1
1039 l = ord(l) - ord('A') + 1
1041 else:
1040 else:
1042 l = ord(l) - ord('a') + 27
1041 l = ord(l) - ord('a') + 27
1043 dec.append(base85.b85decode(line[1:-1])[:l])
1042 dec.append(base85.b85decode(line[1:-1])[:l])
1044 line = lr.readline()
1043 line = lr.readline()
1045 self.hunk.append(line)
1044 self.hunk.append(line)
1046 text = zlib.decompress(''.join(dec))
1045 text = zlib.decompress(''.join(dec))
1047 if len(text) != size:
1046 if len(text) != size:
1048 raise PatchError(_('binary patch is %d bytes, not %d') %
1047 raise PatchError(_('binary patch is %d bytes, not %d') %
1049 len(text), size)
1048 len(text), size)
1050 self.text = text
1049 self.text = text
1051
1050
1052 def parsefilename(str):
1051 def parsefilename(str):
1053 # --- filename \t|space stuff
1052 # --- filename \t|space stuff
1054 s = str[4:].rstrip('\r\n')
1053 s = str[4:].rstrip('\r\n')
1055 i = s.find('\t')
1054 i = s.find('\t')
1056 if i < 0:
1055 if i < 0:
1057 i = s.find(' ')
1056 i = s.find(' ')
1058 if i < 0:
1057 if i < 0:
1059 return s
1058 return s
1060 return s[:i]
1059 return s[:i]
1061
1060
1062 def pathstrip(path, strip):
1061 def pathstrip(path, strip):
1063 pathlen = len(path)
1062 pathlen = len(path)
1064 i = 0
1063 i = 0
1065 if strip == 0:
1064 if strip == 0:
1066 return '', path.rstrip()
1065 return '', path.rstrip()
1067 count = strip
1066 count = strip
1068 while count > 0:
1067 while count > 0:
1069 i = path.find('/', i)
1068 i = path.find('/', i)
1070 if i == -1:
1069 if i == -1:
1071 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1070 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1072 (count, strip, path))
1071 (count, strip, path))
1073 i += 1
1072 i += 1
1074 # consume '//' in the path
1073 # consume '//' in the path
1075 while i < pathlen - 1 and path[i] == '/':
1074 while i < pathlen - 1 and path[i] == '/':
1076 i += 1
1075 i += 1
1077 count -= 1
1076 count -= 1
1078 return path[:i].lstrip(), path[i:].rstrip()
1077 return path[:i].lstrip(), path[i:].rstrip()
1079
1078
1080 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1079 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1081 nulla = afile_orig == "/dev/null"
1080 nulla = afile_orig == "/dev/null"
1082 nullb = bfile_orig == "/dev/null"
1081 nullb = bfile_orig == "/dev/null"
1083 create = nulla and hunk.starta == 0 and hunk.lena == 0
1082 create = nulla and hunk.starta == 0 and hunk.lena == 0
1084 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1083 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1085 abase, afile = pathstrip(afile_orig, strip)
1084 abase, afile = pathstrip(afile_orig, strip)
1086 gooda = not nulla and backend.exists(afile)
1085 gooda = not nulla and backend.exists(afile)
1087 bbase, bfile = pathstrip(bfile_orig, strip)
1086 bbase, bfile = pathstrip(bfile_orig, strip)
1088 if afile == bfile:
1087 if afile == bfile:
1089 goodb = gooda
1088 goodb = gooda
1090 else:
1089 else:
1091 goodb = not nullb and backend.exists(bfile)
1090 goodb = not nullb and backend.exists(bfile)
1092 missing = not goodb and not gooda and not create
1091 missing = not goodb and not gooda and not create
1093
1092
1094 # some diff programs apparently produce patches where the afile is
1093 # some diff programs apparently produce patches where the afile is
1095 # not /dev/null, but afile starts with bfile
1094 # not /dev/null, but afile starts with bfile
1096 abasedir = afile[:afile.rfind('/') + 1]
1095 abasedir = afile[:afile.rfind('/') + 1]
1097 bbasedir = bfile[:bfile.rfind('/') + 1]
1096 bbasedir = bfile[:bfile.rfind('/') + 1]
1098 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1097 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1099 and hunk.starta == 0 and hunk.lena == 0):
1098 and hunk.starta == 0 and hunk.lena == 0):
1100 create = True
1099 create = True
1101 missing = False
1100 missing = False
1102
1101
1103 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1102 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1104 # diff is between a file and its backup. In this case, the original
1103 # diff is between a file and its backup. In this case, the original
1105 # file should be patched (see original mpatch code).
1104 # file should be patched (see original mpatch code).
1106 isbackup = (abase == bbase and bfile.startswith(afile))
1105 isbackup = (abase == bbase and bfile.startswith(afile))
1107 fname = None
1106 fname = None
1108 if not missing:
1107 if not missing:
1109 if gooda and goodb:
1108 if gooda and goodb:
1110 fname = isbackup and afile or bfile
1109 fname = isbackup and afile or bfile
1111 elif gooda:
1110 elif gooda:
1112 fname = afile
1111 fname = afile
1113
1112
1114 if not fname:
1113 if not fname:
1115 if not nullb:
1114 if not nullb:
1116 fname = isbackup and afile or bfile
1115 fname = isbackup and afile or bfile
1117 elif not nulla:
1116 elif not nulla:
1118 fname = afile
1117 fname = afile
1119 else:
1118 else:
1120 raise PatchError(_("undefined source and destination files"))
1119 raise PatchError(_("undefined source and destination files"))
1121
1120
1122 gp = patchmeta(fname)
1121 gp = patchmeta(fname)
1123 if create:
1122 if create:
1124 gp.op = 'ADD'
1123 gp.op = 'ADD'
1125 elif remove:
1124 elif remove:
1126 gp.op = 'DELETE'
1125 gp.op = 'DELETE'
1127 return gp
1126 return gp
1128
1127
1129 def scangitpatch(lr, firstline):
1128 def scangitpatch(lr, firstline):
1130 """
1129 """
1131 Git patches can emit:
1130 Git patches can emit:
1132 - rename a to b
1131 - rename a to b
1133 - change b
1132 - change b
1134 - copy a to c
1133 - copy a to c
1135 - change c
1134 - change c
1136
1135
1137 We cannot apply this sequence as-is, the renamed 'a' could not be
1136 We cannot apply this sequence as-is, the renamed 'a' could not be
1138 found for it would have been renamed already. And we cannot copy
1137 found for it would have been renamed already. And we cannot copy
1139 from 'b' instead because 'b' would have been changed already. So
1138 from 'b' instead because 'b' would have been changed already. So
1140 we scan the git patch for copy and rename commands so we can
1139 we scan the git patch for copy and rename commands so we can
1141 perform the copies ahead of time.
1140 perform the copies ahead of time.
1142 """
1141 """
1143 pos = 0
1142 pos = 0
1144 try:
1143 try:
1145 pos = lr.fp.tell()
1144 pos = lr.fp.tell()
1146 fp = lr.fp
1145 fp = lr.fp
1147 except IOError:
1146 except IOError:
1148 fp = cStringIO.StringIO(lr.fp.read())
1147 fp = cStringIO.StringIO(lr.fp.read())
1149 gitlr = linereader(fp)
1148 gitlr = linereader(fp)
1150 gitlr.push(firstline)
1149 gitlr.push(firstline)
1151 gitpatches = readgitpatch(gitlr)
1150 gitpatches = readgitpatch(gitlr)
1152 fp.seek(pos)
1151 fp.seek(pos)
1153 return gitpatches
1152 return gitpatches
1154
1153
1155 def iterhunks(fp):
1154 def iterhunks(fp):
1156 """Read a patch and yield the following events:
1155 """Read a patch and yield the following events:
1157 - ("file", afile, bfile, firsthunk): select a new target file.
1156 - ("file", afile, bfile, firsthunk): select a new target file.
1158 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1157 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1159 "file" event.
1158 "file" event.
1160 - ("git", gitchanges): current diff is in git format, gitchanges
1159 - ("git", gitchanges): current diff is in git format, gitchanges
1161 maps filenames to gitpatch records. Unique event.
1160 maps filenames to gitpatch records. Unique event.
1162 """
1161 """
1163 afile = ""
1162 afile = ""
1164 bfile = ""
1163 bfile = ""
1165 state = None
1164 state = None
1166 hunknum = 0
1165 hunknum = 0
1167 emitfile = newfile = False
1166 emitfile = newfile = False
1168 gitpatches = None
1167 gitpatches = None
1169
1168
1170 # our states
1169 # our states
1171 BFILE = 1
1170 BFILE = 1
1172 context = None
1171 context = None
1173 lr = linereader(fp)
1172 lr = linereader(fp)
1174
1173
1175 while True:
1174 while True:
1176 x = lr.readline()
1175 x = lr.readline()
1177 if not x:
1176 if not x:
1178 break
1177 break
1179 if state == BFILE and (
1178 if state == BFILE and (
1180 (not context and x[0] == '@')
1179 (not context and x[0] == '@')
1181 or (context is not False and x.startswith('***************'))
1180 or (context is not False and x.startswith('***************'))
1182 or x.startswith('GIT binary patch')):
1181 or x.startswith('GIT binary patch')):
1183 gp = None
1182 gp = None
1184 if (gitpatches and
1183 if (gitpatches and
1185 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1184 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1186 gp = gitpatches.pop()[2]
1185 gp = gitpatches.pop()[2]
1187 if x.startswith('GIT binary patch'):
1186 if x.startswith('GIT binary patch'):
1188 h = binhunk(lr)
1187 h = binhunk(lr)
1189 else:
1188 else:
1190 if context is None and x.startswith('***************'):
1189 if context is None and x.startswith('***************'):
1191 context = True
1190 context = True
1192 h = hunk(x, hunknum + 1, lr, context)
1191 h = hunk(x, hunknum + 1, lr, context)
1193 hunknum += 1
1192 hunknum += 1
1194 if emitfile:
1193 if emitfile:
1195 emitfile = False
1194 emitfile = False
1196 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1195 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1197 yield 'hunk', h
1196 yield 'hunk', h
1198 elif x.startswith('diff --git'):
1197 elif x.startswith('diff --git'):
1199 m = gitre.match(x)
1198 m = gitre.match(x)
1200 if not m:
1199 if not m:
1201 continue
1200 continue
1202 if not gitpatches:
1201 if not gitpatches:
1203 # scan whole input for git metadata
1202 # scan whole input for git metadata
1204 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1203 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1205 in scangitpatch(lr, x)]
1204 in scangitpatch(lr, x)]
1206 yield 'git', [g[2].copy() for g in gitpatches
1205 yield 'git', [g[2].copy() for g in gitpatches
1207 if g[2].op in ('COPY', 'RENAME')]
1206 if g[2].op in ('COPY', 'RENAME')]
1208 gitpatches.reverse()
1207 gitpatches.reverse()
1209 afile = 'a/' + m.group(1)
1208 afile = 'a/' + m.group(1)
1210 bfile = 'b/' + m.group(2)
1209 bfile = 'b/' + m.group(2)
1211 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1210 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1212 gp = gitpatches.pop()[2]
1211 gp = gitpatches.pop()[2]
1213 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1212 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1214 gp = gitpatches[-1][2]
1213 gp = gitpatches[-1][2]
1215 # copy/rename + modify should modify target, not source
1214 # copy/rename + modify should modify target, not source
1216 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1215 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1217 afile = bfile
1216 afile = bfile
1218 newfile = True
1217 newfile = True
1219 elif x.startswith('---'):
1218 elif x.startswith('---'):
1220 # check for a unified diff
1219 # check for a unified diff
1221 l2 = lr.readline()
1220 l2 = lr.readline()
1222 if not l2.startswith('+++'):
1221 if not l2.startswith('+++'):
1223 lr.push(l2)
1222 lr.push(l2)
1224 continue
1223 continue
1225 newfile = True
1224 newfile = True
1226 context = False
1225 context = False
1227 afile = parsefilename(x)
1226 afile = parsefilename(x)
1228 bfile = parsefilename(l2)
1227 bfile = parsefilename(l2)
1229 elif x.startswith('***'):
1228 elif x.startswith('***'):
1230 # check for a context diff
1229 # check for a context diff
1231 l2 = lr.readline()
1230 l2 = lr.readline()
1232 if not l2.startswith('---'):
1231 if not l2.startswith('---'):
1233 lr.push(l2)
1232 lr.push(l2)
1234 continue
1233 continue
1235 l3 = lr.readline()
1234 l3 = lr.readline()
1236 lr.push(l3)
1235 lr.push(l3)
1237 if not l3.startswith("***************"):
1236 if not l3.startswith("***************"):
1238 lr.push(l2)
1237 lr.push(l2)
1239 continue
1238 continue
1240 newfile = True
1239 newfile = True
1241 context = True
1240 context = True
1242 afile = parsefilename(x)
1241 afile = parsefilename(x)
1243 bfile = parsefilename(l2)
1242 bfile = parsefilename(l2)
1244
1243
1245 if newfile:
1244 if newfile:
1246 newfile = False
1245 newfile = False
1247 emitfile = True
1246 emitfile = True
1248 state = BFILE
1247 state = BFILE
1249 hunknum = 0
1248 hunknum = 0
1250
1249
1251 while gitpatches:
1250 while gitpatches:
1252 gp = gitpatches.pop()[2]
1251 gp = gitpatches.pop()[2]
1253 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1252 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1254
1253
1255 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1254 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1256 """Reads a patch from fp and tries to apply it.
1255 """Reads a patch from fp and tries to apply it.
1257
1256
1258 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1257 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1259 there was any fuzz.
1258 there was any fuzz.
1260
1259
1261 If 'eolmode' is 'strict', the patch content and patched file are
1260 If 'eolmode' is 'strict', the patch content and patched file are
1262 read in binary mode. Otherwise, line endings are ignored when
1261 read in binary mode. Otherwise, line endings are ignored when
1263 patching then normalized according to 'eolmode'.
1262 patching then normalized according to 'eolmode'.
1264 """
1263 """
1265 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1264 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1266 eolmode=eolmode)
1265 eolmode=eolmode)
1267
1266
1268 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1267 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1269 eolmode='strict'):
1268 eolmode='strict'):
1270
1269
1271 def pstrip(p):
1270 def pstrip(p):
1272 return pathstrip(p, strip - 1)[1]
1271 return pathstrip(p, strip - 1)[1]
1273
1272
1274 rejects = 0
1273 rejects = 0
1275 err = 0
1274 err = 0
1276 current_file = None
1275 current_file = None
1277
1276
1278 for state, values in iterhunks(fp):
1277 for state, values in iterhunks(fp):
1279 if state == 'hunk':
1278 if state == 'hunk':
1280 if not current_file:
1279 if not current_file:
1281 continue
1280 continue
1282 ret = current_file.apply(values)
1281 ret = current_file.apply(values)
1283 if ret > 0:
1282 if ret > 0:
1284 err = 1
1283 err = 1
1285 elif state == 'file':
1284 elif state == 'file':
1286 if current_file:
1285 if current_file:
1287 rejects += current_file.close()
1286 rejects += current_file.close()
1288 current_file = None
1287 current_file = None
1289 afile, bfile, first_hunk, gp = values
1288 afile, bfile, first_hunk, gp = values
1290 if gp:
1289 if gp:
1291 path = pstrip(gp.path)
1290 path = pstrip(gp.path)
1292 gp.path = pstrip(gp.path)
1291 gp.path = pstrip(gp.path)
1293 if gp.oldpath:
1292 if gp.oldpath:
1294 gp.oldpath = pstrip(gp.oldpath)
1293 gp.oldpath = pstrip(gp.oldpath)
1295 else:
1294 else:
1296 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1295 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1297 if gp.op == 'RENAME':
1296 if gp.op == 'RENAME':
1298 backend.unlink(gp.oldpath)
1297 backend.unlink(gp.oldpath)
1299 if not first_hunk:
1298 if not first_hunk:
1300 if gp.op == 'DELETE':
1299 if gp.op == 'DELETE':
1301 backend.unlink(gp.path)
1300 backend.unlink(gp.path)
1302 continue
1301 continue
1303 data, mode = None, None
1302 data, mode = None, None
1304 if gp.op in ('RENAME', 'COPY'):
1303 if gp.op in ('RENAME', 'COPY'):
1305 data, mode = store.getfile(gp.oldpath)[:2]
1304 data, mode = store.getfile(gp.oldpath)[:2]
1306 if gp.mode:
1305 if gp.mode:
1307 mode = gp.mode
1306 mode = gp.mode
1308 if gp.op == 'ADD':
1307 if gp.op == 'ADD':
1309 # Added files without content have no hunk and
1308 # Added files without content have no hunk and
1310 # must be created
1309 # must be created
1311 data = ''
1310 data = ''
1312 if data or mode:
1311 if data or mode:
1313 if (gp.op in ('ADD', 'RENAME', 'COPY')
1312 if (gp.op in ('ADD', 'RENAME', 'COPY')
1314 and backend.exists(gp.path)):
1313 and backend.exists(gp.path)):
1315 raise PatchError(_("cannot create %s: destination "
1314 raise PatchError(_("cannot create %s: destination "
1316 "already exists") % gp.path)
1315 "already exists") % gp.path)
1317 backend.setfile(gp.path, data, mode, gp.oldpath)
1316 backend.setfile(gp.path, data, mode, gp.oldpath)
1318 continue
1317 continue
1319 try:
1318 try:
1320 current_file = patcher(ui, gp, backend, store,
1319 current_file = patcher(ui, gp, backend, store,
1321 eolmode=eolmode)
1320 eolmode=eolmode)
1322 except PatchError, inst:
1321 except PatchError, inst:
1323 ui.warn(str(inst) + '\n')
1322 ui.warn(str(inst) + '\n')
1324 current_file = None
1323 current_file = None
1325 rejects += 1
1324 rejects += 1
1326 continue
1325 continue
1327 elif state == 'git':
1326 elif state == 'git':
1328 for gp in values:
1327 for gp in values:
1329 path = pstrip(gp.oldpath)
1328 path = pstrip(gp.oldpath)
1330 data, mode = backend.getfile(path)
1329 data, mode = backend.getfile(path)
1331 store.setfile(path, data, mode)
1330 store.setfile(path, data, mode)
1332 else:
1331 else:
1333 raise util.Abort(_('unsupported parser state: %s') % state)
1332 raise util.Abort(_('unsupported parser state: %s') % state)
1334
1333
1335 if current_file:
1334 if current_file:
1336 rejects += current_file.close()
1335 rejects += current_file.close()
1337
1336
1338 if rejects:
1337 if rejects:
1339 return -1
1338 return -1
1340 return err
1339 return err
1341
1340
1342 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1341 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1343 similarity):
1342 similarity):
1344 """use <patcher> to apply <patchname> to the working directory.
1343 """use <patcher> to apply <patchname> to the working directory.
1345 returns whether patch was applied with fuzz factor."""
1344 returns whether patch was applied with fuzz factor."""
1346
1345
1347 fuzz = False
1346 fuzz = False
1348 args = []
1347 args = []
1349 cwd = repo.root
1348 cwd = repo.root
1350 if cwd:
1349 if cwd:
1351 args.append('-d %s' % util.shellquote(cwd))
1350 args.append('-d %s' % util.shellquote(cwd))
1352 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1351 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1353 util.shellquote(patchname)))
1352 util.shellquote(patchname)))
1354 try:
1353 try:
1355 for line in fp:
1354 for line in fp:
1356 line = line.rstrip()
1355 line = line.rstrip()
1357 ui.note(line + '\n')
1356 ui.note(line + '\n')
1358 if line.startswith('patching file '):
1357 if line.startswith('patching file '):
1359 pf = util.parsepatchoutput(line)
1358 pf = util.parsepatchoutput(line)
1360 printed_file = False
1359 printed_file = False
1361 files.add(pf)
1360 files.add(pf)
1362 elif line.find('with fuzz') >= 0:
1361 elif line.find('with fuzz') >= 0:
1363 fuzz = True
1362 fuzz = True
1364 if not printed_file:
1363 if not printed_file:
1365 ui.warn(pf + '\n')
1364 ui.warn(pf + '\n')
1366 printed_file = True
1365 printed_file = True
1367 ui.warn(line + '\n')
1366 ui.warn(line + '\n')
1368 elif line.find('saving rejects to file') >= 0:
1367 elif line.find('saving rejects to file') >= 0:
1369 ui.warn(line + '\n')
1368 ui.warn(line + '\n')
1370 elif line.find('FAILED') >= 0:
1369 elif line.find('FAILED') >= 0:
1371 if not printed_file:
1370 if not printed_file:
1372 ui.warn(pf + '\n')
1371 ui.warn(pf + '\n')
1373 printed_file = True
1372 printed_file = True
1374 ui.warn(line + '\n')
1373 ui.warn(line + '\n')
1375 finally:
1374 finally:
1376 if files:
1375 if files:
1377 cfiles = list(files)
1376 cfiles = list(files)
1378 cwd = repo.getcwd()
1377 cwd = repo.getcwd()
1379 if cwd:
1378 if cwd:
1380 cfiles = [util.pathto(repo.root, cwd, f)
1379 cfiles = [util.pathto(repo.root, cwd, f)
1381 for f in cfiles]
1380 for f in cfiles]
1382 scmutil.addremove(repo, cfiles, similarity=similarity)
1381 scmutil.addremove(repo, cfiles, similarity=similarity)
1383 code = fp.close()
1382 code = fp.close()
1384 if code:
1383 if code:
1385 raise PatchError(_("patch command failed: %s") %
1384 raise PatchError(_("patch command failed: %s") %
1386 util.explainexit(code)[0])
1385 util.explainexit(code)[0])
1387 return fuzz
1386 return fuzz
1388
1387
1389 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1388 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1390 if files is None:
1389 if files is None:
1391 files = set()
1390 files = set()
1392 if eolmode is None:
1391 if eolmode is None:
1393 eolmode = ui.config('patch', 'eol', 'strict')
1392 eolmode = ui.config('patch', 'eol', 'strict')
1394 if eolmode.lower() not in eolmodes:
1393 if eolmode.lower() not in eolmodes:
1395 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1394 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1396 eolmode = eolmode.lower()
1395 eolmode = eolmode.lower()
1397
1396
1398 store = filestore()
1397 store = filestore()
1399 try:
1398 try:
1400 fp = open(patchobj, 'rb')
1399 fp = open(patchobj, 'rb')
1401 except TypeError:
1400 except TypeError:
1402 fp = patchobj
1401 fp = patchobj
1403 try:
1402 try:
1404 ret = applydiff(ui, fp, backend, store, strip=strip,
1403 ret = applydiff(ui, fp, backend, store, strip=strip,
1405 eolmode=eolmode)
1404 eolmode=eolmode)
1406 finally:
1405 finally:
1407 if fp != patchobj:
1406 if fp != patchobj:
1408 fp.close()
1407 fp.close()
1409 files.update(backend.close())
1408 files.update(backend.close())
1410 store.close()
1409 store.close()
1411 if ret < 0:
1410 if ret < 0:
1412 raise PatchError(_('patch failed to apply'))
1411 raise PatchError(_('patch failed to apply'))
1413 return ret > 0
1412 return ret > 0
1414
1413
1415 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1414 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1416 similarity=0):
1415 similarity=0):
1417 """use builtin patch to apply <patchobj> to the working directory.
1416 """use builtin patch to apply <patchobj> to the working directory.
1418 returns whether patch was applied with fuzz factor."""
1417 returns whether patch was applied with fuzz factor."""
1419 backend = workingbackend(ui, repo, similarity)
1418 backend = workingbackend(ui, repo, similarity)
1420 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1419 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1421
1420
1422 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1421 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1423 eolmode='strict'):
1422 eolmode='strict'):
1424 backend = repobackend(ui, repo, ctx, store)
1423 backend = repobackend(ui, repo, ctx, store)
1425 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1424 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1426
1425
1427 def makememctx(repo, parents, text, user, date, branch, files, store,
1426 def makememctx(repo, parents, text, user, date, branch, files, store,
1428 editor=None):
1427 editor=None):
1429 def getfilectx(repo, memctx, path):
1428 def getfilectx(repo, memctx, path):
1430 data, (islink, isexec), copied = store.getfile(path)
1429 data, (islink, isexec), copied = store.getfile(path)
1431 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1430 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1432 copied=copied)
1431 copied=copied)
1433 extra = {}
1432 extra = {}
1434 if branch:
1433 if branch:
1435 extra['branch'] = encoding.fromlocal(branch)
1434 extra['branch'] = encoding.fromlocal(branch)
1436 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1435 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1437 date, extra)
1436 date, extra)
1438 if editor:
1437 if editor:
1439 ctx._text = editor(repo, ctx, [])
1438 ctx._text = editor(repo, ctx, [])
1440 return ctx
1439 return ctx
1441
1440
1442 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1441 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1443 similarity=0):
1442 similarity=0):
1444 """Apply <patchname> to the working directory.
1443 """Apply <patchname> to the working directory.
1445
1444
1446 'eolmode' specifies how end of lines should be handled. It can be:
1445 'eolmode' specifies how end of lines should be handled. It can be:
1447 - 'strict': inputs are read in binary mode, EOLs are preserved
1446 - 'strict': inputs are read in binary mode, EOLs are preserved
1448 - 'crlf': EOLs are ignored when patching and reset to CRLF
1447 - 'crlf': EOLs are ignored when patching and reset to CRLF
1449 - 'lf': EOLs are ignored when patching and reset to LF
1448 - 'lf': EOLs are ignored when patching and reset to LF
1450 - None: get it from user settings, default to 'strict'
1449 - None: get it from user settings, default to 'strict'
1451 'eolmode' is ignored when using an external patcher program.
1450 'eolmode' is ignored when using an external patcher program.
1452
1451
1453 Returns whether patch was applied with fuzz factor.
1452 Returns whether patch was applied with fuzz factor.
1454 """
1453 """
1455 patcher = ui.config('ui', 'patch')
1454 patcher = ui.config('ui', 'patch')
1456 if files is None:
1455 if files is None:
1457 files = set()
1456 files = set()
1458 try:
1457 try:
1459 if patcher:
1458 if patcher:
1460 return _externalpatch(ui, repo, patcher, patchname, strip,
1459 return _externalpatch(ui, repo, patcher, patchname, strip,
1461 files, similarity)
1460 files, similarity)
1462 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1461 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1463 similarity)
1462 similarity)
1464 except PatchError, err:
1463 except PatchError, err:
1465 raise util.Abort(str(err))
1464 raise util.Abort(str(err))
1466
1465
1467 def changedfiles(ui, repo, patchpath, strip=1):
1466 def changedfiles(ui, repo, patchpath, strip=1):
1468 backend = fsbackend(ui, repo.root)
1467 backend = fsbackend(ui, repo.root)
1469 fp = open(patchpath, 'rb')
1468 fp = open(patchpath, 'rb')
1470 try:
1469 try:
1471 changed = set()
1470 changed = set()
1472 for state, values in iterhunks(fp):
1471 for state, values in iterhunks(fp):
1473 if state == 'file':
1472 if state == 'file':
1474 afile, bfile, first_hunk, gp = values
1473 afile, bfile, first_hunk, gp = values
1475 if gp:
1474 if gp:
1476 gp.path = pathstrip(gp.path, strip - 1)[1]
1475 gp.path = pathstrip(gp.path, strip - 1)[1]
1477 if gp.oldpath:
1476 if gp.oldpath:
1478 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1477 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1479 else:
1478 else:
1480 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1479 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1481 changed.add(gp.path)
1480 changed.add(gp.path)
1482 if gp.op == 'RENAME':
1481 if gp.op == 'RENAME':
1483 changed.add(gp.oldpath)
1482 changed.add(gp.oldpath)
1484 elif state not in ('hunk', 'git'):
1483 elif state not in ('hunk', 'git'):
1485 raise util.Abort(_('unsupported parser state: %s') % state)
1484 raise util.Abort(_('unsupported parser state: %s') % state)
1486 return changed
1485 return changed
1487 finally:
1486 finally:
1488 fp.close()
1487 fp.close()
1489
1488
1490 def b85diff(to, tn):
1489 def b85diff(to, tn):
1491 '''print base85-encoded binary diff'''
1490 '''print base85-encoded binary diff'''
1492 def gitindex(text):
1491 def gitindex(text):
1493 if not text:
1492 if not text:
1494 return hex(nullid)
1493 return hex(nullid)
1495 l = len(text)
1494 l = len(text)
1496 s = util.sha1('blob %d\0' % l)
1495 s = util.sha1('blob %d\0' % l)
1497 s.update(text)
1496 s.update(text)
1498 return s.hexdigest()
1497 return s.hexdigest()
1499
1498
1500 def fmtline(line):
1499 def fmtline(line):
1501 l = len(line)
1500 l = len(line)
1502 if l <= 26:
1501 if l <= 26:
1503 l = chr(ord('A') + l - 1)
1502 l = chr(ord('A') + l - 1)
1504 else:
1503 else:
1505 l = chr(l - 26 + ord('a') - 1)
1504 l = chr(l - 26 + ord('a') - 1)
1506 return '%c%s\n' % (l, base85.b85encode(line, True))
1505 return '%c%s\n' % (l, base85.b85encode(line, True))
1507
1506
1508 def chunk(text, csize=52):
1507 def chunk(text, csize=52):
1509 l = len(text)
1508 l = len(text)
1510 i = 0
1509 i = 0
1511 while i < l:
1510 while i < l:
1512 yield text[i:i + csize]
1511 yield text[i:i + csize]
1513 i += csize
1512 i += csize
1514
1513
1515 tohash = gitindex(to)
1514 tohash = gitindex(to)
1516 tnhash = gitindex(tn)
1515 tnhash = gitindex(tn)
1517 if tohash == tnhash:
1516 if tohash == tnhash:
1518 return ""
1517 return ""
1519
1518
1520 # TODO: deltas
1519 # TODO: deltas
1521 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1520 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1522 (tohash, tnhash, len(tn))]
1521 (tohash, tnhash, len(tn))]
1523 for l in chunk(zlib.compress(tn)):
1522 for l in chunk(zlib.compress(tn)):
1524 ret.append(fmtline(l))
1523 ret.append(fmtline(l))
1525 ret.append('\n')
1524 ret.append('\n')
1526 return ''.join(ret)
1525 return ''.join(ret)
1527
1526
1528 class GitDiffRequired(Exception):
1527 class GitDiffRequired(Exception):
1529 pass
1528 pass
1530
1529
1531 def diffopts(ui, opts=None, untrusted=False):
1530 def diffopts(ui, opts=None, untrusted=False):
1532 def get(key, name=None, getter=ui.configbool):
1531 def get(key, name=None, getter=ui.configbool):
1533 return ((opts and opts.get(key)) or
1532 return ((opts and opts.get(key)) or
1534 getter('diff', name or key, None, untrusted=untrusted))
1533 getter('diff', name or key, None, untrusted=untrusted))
1535 return mdiff.diffopts(
1534 return mdiff.diffopts(
1536 text=opts and opts.get('text'),
1535 text=opts and opts.get('text'),
1537 git=get('git'),
1536 git=get('git'),
1538 nodates=get('nodates'),
1537 nodates=get('nodates'),
1539 showfunc=get('show_function', 'showfunc'),
1538 showfunc=get('show_function', 'showfunc'),
1540 ignorews=get('ignore_all_space', 'ignorews'),
1539 ignorews=get('ignore_all_space', 'ignorews'),
1541 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1540 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1542 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1541 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1543 context=get('unified', getter=ui.config))
1542 context=get('unified', getter=ui.config))
1544
1543
1545 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1544 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1546 losedatafn=None, prefix=''):
1545 losedatafn=None, prefix=''):
1547 '''yields diff of changes to files between two nodes, or node and
1546 '''yields diff of changes to files between two nodes, or node and
1548 working directory.
1547 working directory.
1549
1548
1550 if node1 is None, use first dirstate parent instead.
1549 if node1 is None, use first dirstate parent instead.
1551 if node2 is None, compare node1 with working directory.
1550 if node2 is None, compare node1 with working directory.
1552
1551
1553 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1552 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1554 every time some change cannot be represented with the current
1553 every time some change cannot be represented with the current
1555 patch format. Return False to upgrade to git patch format, True to
1554 patch format. Return False to upgrade to git patch format, True to
1556 accept the loss or raise an exception to abort the diff. It is
1555 accept the loss or raise an exception to abort the diff. It is
1557 called with the name of current file being diffed as 'fn'. If set
1556 called with the name of current file being diffed as 'fn'. If set
1558 to None, patches will always be upgraded to git format when
1557 to None, patches will always be upgraded to git format when
1559 necessary.
1558 necessary.
1560
1559
1561 prefix is a filename prefix that is prepended to all filenames on
1560 prefix is a filename prefix that is prepended to all filenames on
1562 display (used for subrepos).
1561 display (used for subrepos).
1563 '''
1562 '''
1564
1563
1565 if opts is None:
1564 if opts is None:
1566 opts = mdiff.defaultopts
1565 opts = mdiff.defaultopts
1567
1566
1568 if not node1 and not node2:
1567 if not node1 and not node2:
1569 node1 = repo.dirstate.p1()
1568 node1 = repo.dirstate.p1()
1570
1569
1571 def lrugetfilectx():
1570 def lrugetfilectx():
1572 cache = {}
1571 cache = {}
1573 order = []
1572 order = []
1574 def getfilectx(f, ctx):
1573 def getfilectx(f, ctx):
1575 fctx = ctx.filectx(f, filelog=cache.get(f))
1574 fctx = ctx.filectx(f, filelog=cache.get(f))
1576 if f not in cache:
1575 if f not in cache:
1577 if len(cache) > 20:
1576 if len(cache) > 20:
1578 del cache[order.pop(0)]
1577 del cache[order.pop(0)]
1579 cache[f] = fctx.filelog()
1578 cache[f] = fctx.filelog()
1580 else:
1579 else:
1581 order.remove(f)
1580 order.remove(f)
1582 order.append(f)
1581 order.append(f)
1583 return fctx
1582 return fctx
1584 return getfilectx
1583 return getfilectx
1585 getfilectx = lrugetfilectx()
1584 getfilectx = lrugetfilectx()
1586
1585
1587 ctx1 = repo[node1]
1586 ctx1 = repo[node1]
1588 ctx2 = repo[node2]
1587 ctx2 = repo[node2]
1589
1588
1590 if not changes:
1589 if not changes:
1591 changes = repo.status(ctx1, ctx2, match=match)
1590 changes = repo.status(ctx1, ctx2, match=match)
1592 modified, added, removed = changes[:3]
1591 modified, added, removed = changes[:3]
1593
1592
1594 if not modified and not added and not removed:
1593 if not modified and not added and not removed:
1595 return []
1594 return []
1596
1595
1597 revs = None
1596 revs = None
1598 if not repo.ui.quiet:
1597 if not repo.ui.quiet:
1599 hexfunc = repo.ui.debugflag and hex or short
1598 hexfunc = repo.ui.debugflag and hex or short
1600 revs = [hexfunc(node) for node in [node1, node2] if node]
1599 revs = [hexfunc(node) for node in [node1, node2] if node]
1601
1600
1602 copy = {}
1601 copy = {}
1603 if opts.git or opts.upgrade:
1602 if opts.git or opts.upgrade:
1604 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1603 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1605
1604
1606 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1605 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1607 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1606 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1608 if opts.upgrade and not opts.git:
1607 if opts.upgrade and not opts.git:
1609 try:
1608 try:
1610 def losedata(fn):
1609 def losedata(fn):
1611 if not losedatafn or not losedatafn(fn=fn):
1610 if not losedatafn or not losedatafn(fn=fn):
1612 raise GitDiffRequired()
1611 raise GitDiffRequired()
1613 # Buffer the whole output until we are sure it can be generated
1612 # Buffer the whole output until we are sure it can be generated
1614 return list(difffn(opts.copy(git=False), losedata))
1613 return list(difffn(opts.copy(git=False), losedata))
1615 except GitDiffRequired:
1614 except GitDiffRequired:
1616 return difffn(opts.copy(git=True), None)
1615 return difffn(opts.copy(git=True), None)
1617 else:
1616 else:
1618 return difffn(opts, None)
1617 return difffn(opts, None)
1619
1618
1620 def difflabel(func, *args, **kw):
1619 def difflabel(func, *args, **kw):
1621 '''yields 2-tuples of (output, label) based on the output of func()'''
1620 '''yields 2-tuples of (output, label) based on the output of func()'''
1622 headprefixes = [('diff', 'diff.diffline'),
1621 headprefixes = [('diff', 'diff.diffline'),
1623 ('copy', 'diff.extended'),
1622 ('copy', 'diff.extended'),
1624 ('rename', 'diff.extended'),
1623 ('rename', 'diff.extended'),
1625 ('old', 'diff.extended'),
1624 ('old', 'diff.extended'),
1626 ('new', 'diff.extended'),
1625 ('new', 'diff.extended'),
1627 ('deleted', 'diff.extended'),
1626 ('deleted', 'diff.extended'),
1628 ('---', 'diff.file_a'),
1627 ('---', 'diff.file_a'),
1629 ('+++', 'diff.file_b')]
1628 ('+++', 'diff.file_b')]
1630 textprefixes = [('@', 'diff.hunk'),
1629 textprefixes = [('@', 'diff.hunk'),
1631 ('-', 'diff.deleted'),
1630 ('-', 'diff.deleted'),
1632 ('+', 'diff.inserted')]
1631 ('+', 'diff.inserted')]
1633 head = False
1632 head = False
1634 for chunk in func(*args, **kw):
1633 for chunk in func(*args, **kw):
1635 lines = chunk.split('\n')
1634 lines = chunk.split('\n')
1636 for i, line in enumerate(lines):
1635 for i, line in enumerate(lines):
1637 if i != 0:
1636 if i != 0:
1638 yield ('\n', '')
1637 yield ('\n', '')
1639 if head:
1638 if head:
1640 if line.startswith('@'):
1639 if line.startswith('@'):
1641 head = False
1640 head = False
1642 else:
1641 else:
1643 if line and not line[0] in ' +-@':
1642 if line and not line[0] in ' +-@':
1644 head = True
1643 head = True
1645 stripline = line
1644 stripline = line
1646 if not head and line and line[0] in '+-':
1645 if not head and line and line[0] in '+-':
1647 # highlight trailing whitespace, but only in changed lines
1646 # highlight trailing whitespace, but only in changed lines
1648 stripline = line.rstrip()
1647 stripline = line.rstrip()
1649 prefixes = textprefixes
1648 prefixes = textprefixes
1650 if head:
1649 if head:
1651 prefixes = headprefixes
1650 prefixes = headprefixes
1652 for prefix, label in prefixes:
1651 for prefix, label in prefixes:
1653 if stripline.startswith(prefix):
1652 if stripline.startswith(prefix):
1654 yield (stripline, label)
1653 yield (stripline, label)
1655 break
1654 break
1656 else:
1655 else:
1657 yield (line, '')
1656 yield (line, '')
1658 if line != stripline:
1657 if line != stripline:
1659 yield (line[len(stripline):], 'diff.trailingwhitespace')
1658 yield (line[len(stripline):], 'diff.trailingwhitespace')
1660
1659
1661 def diffui(*args, **kw):
1660 def diffui(*args, **kw):
1662 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1661 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1663 return difflabel(diff, *args, **kw)
1662 return difflabel(diff, *args, **kw)
1664
1663
1665
1664
1666 def _addmodehdr(header, omode, nmode):
1665 def _addmodehdr(header, omode, nmode):
1667 if omode != nmode:
1666 if omode != nmode:
1668 header.append('old mode %s\n' % omode)
1667 header.append('old mode %s\n' % omode)
1669 header.append('new mode %s\n' % nmode)
1668 header.append('new mode %s\n' % nmode)
1670
1669
1671 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1670 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1672 copy, getfilectx, opts, losedatafn, prefix):
1671 copy, getfilectx, opts, losedatafn, prefix):
1673
1672
1674 def join(f):
1673 def join(f):
1675 return os.path.join(prefix, f)
1674 return os.path.join(prefix, f)
1676
1675
1677 date1 = util.datestr(ctx1.date())
1676 date1 = util.datestr(ctx1.date())
1678 man1 = ctx1.manifest()
1677 man1 = ctx1.manifest()
1679
1678
1680 gone = set()
1679 gone = set()
1681 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1680 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1682
1681
1683 copyto = dict([(v, k) for k, v in copy.items()])
1682 copyto = dict([(v, k) for k, v in copy.items()])
1684
1683
1685 if opts.git:
1684 if opts.git:
1686 revs = None
1685 revs = None
1687
1686
1688 for f in sorted(modified + added + removed):
1687 for f in sorted(modified + added + removed):
1689 to = None
1688 to = None
1690 tn = None
1689 tn = None
1691 dodiff = True
1690 dodiff = True
1692 header = []
1691 header = []
1693 if f in man1:
1692 if f in man1:
1694 to = getfilectx(f, ctx1).data()
1693 to = getfilectx(f, ctx1).data()
1695 if f not in removed:
1694 if f not in removed:
1696 tn = getfilectx(f, ctx2).data()
1695 tn = getfilectx(f, ctx2).data()
1697 a, b = f, f
1696 a, b = f, f
1698 if opts.git or losedatafn:
1697 if opts.git or losedatafn:
1699 if f in added:
1698 if f in added:
1700 mode = gitmode[ctx2.flags(f)]
1699 mode = gitmode[ctx2.flags(f)]
1701 if f in copy or f in copyto:
1700 if f in copy or f in copyto:
1702 if opts.git:
1701 if opts.git:
1703 if f in copy:
1702 if f in copy:
1704 a = copy[f]
1703 a = copy[f]
1705 else:
1704 else:
1706 a = copyto[f]
1705 a = copyto[f]
1707 omode = gitmode[man1.flags(a)]
1706 omode = gitmode[man1.flags(a)]
1708 _addmodehdr(header, omode, mode)
1707 _addmodehdr(header, omode, mode)
1709 if a in removed and a not in gone:
1708 if a in removed and a not in gone:
1710 op = 'rename'
1709 op = 'rename'
1711 gone.add(a)
1710 gone.add(a)
1712 else:
1711 else:
1713 op = 'copy'
1712 op = 'copy'
1714 header.append('%s from %s\n' % (op, join(a)))
1713 header.append('%s from %s\n' % (op, join(a)))
1715 header.append('%s to %s\n' % (op, join(f)))
1714 header.append('%s to %s\n' % (op, join(f)))
1716 to = getfilectx(a, ctx1).data()
1715 to = getfilectx(a, ctx1).data()
1717 else:
1716 else:
1718 losedatafn(f)
1717 losedatafn(f)
1719 else:
1718 else:
1720 if opts.git:
1719 if opts.git:
1721 header.append('new file mode %s\n' % mode)
1720 header.append('new file mode %s\n' % mode)
1722 elif ctx2.flags(f):
1721 elif ctx2.flags(f):
1723 losedatafn(f)
1722 losedatafn(f)
1724 # In theory, if tn was copied or renamed we should check
1723 # In theory, if tn was copied or renamed we should check
1725 # if the source is binary too but the copy record already
1724 # if the source is binary too but the copy record already
1726 # forces git mode.
1725 # forces git mode.
1727 if util.binary(tn):
1726 if util.binary(tn):
1728 if opts.git:
1727 if opts.git:
1729 dodiff = 'binary'
1728 dodiff = 'binary'
1730 else:
1729 else:
1731 losedatafn(f)
1730 losedatafn(f)
1732 if not opts.git and not tn:
1731 if not opts.git and not tn:
1733 # regular diffs cannot represent new empty file
1732 # regular diffs cannot represent new empty file
1734 losedatafn(f)
1733 losedatafn(f)
1735 elif f in removed:
1734 elif f in removed:
1736 if opts.git:
1735 if opts.git:
1737 # have we already reported a copy above?
1736 # have we already reported a copy above?
1738 if ((f in copy and copy[f] in added
1737 if ((f in copy and copy[f] in added
1739 and copyto[copy[f]] == f) or
1738 and copyto[copy[f]] == f) or
1740 (f in copyto and copyto[f] in added
1739 (f in copyto and copyto[f] in added
1741 and copy[copyto[f]] == f)):
1740 and copy[copyto[f]] == f)):
1742 dodiff = False
1741 dodiff = False
1743 else:
1742 else:
1744 header.append('deleted file mode %s\n' %
1743 header.append('deleted file mode %s\n' %
1745 gitmode[man1.flags(f)])
1744 gitmode[man1.flags(f)])
1746 elif not to or util.binary(to):
1745 elif not to or util.binary(to):
1747 # regular diffs cannot represent empty file deletion
1746 # regular diffs cannot represent empty file deletion
1748 losedatafn(f)
1747 losedatafn(f)
1749 else:
1748 else:
1750 oflag = man1.flags(f)
1749 oflag = man1.flags(f)
1751 nflag = ctx2.flags(f)
1750 nflag = ctx2.flags(f)
1752 binary = util.binary(to) or util.binary(tn)
1751 binary = util.binary(to) or util.binary(tn)
1753 if opts.git:
1752 if opts.git:
1754 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1753 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1755 if binary:
1754 if binary:
1756 dodiff = 'binary'
1755 dodiff = 'binary'
1757 elif binary or nflag != oflag:
1756 elif binary or nflag != oflag:
1758 losedatafn(f)
1757 losedatafn(f)
1759 if opts.git:
1758 if opts.git:
1760 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1759 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1761
1760
1762 if dodiff:
1761 if dodiff:
1763 if dodiff == 'binary':
1762 if dodiff == 'binary':
1764 text = b85diff(to, tn)
1763 text = b85diff(to, tn)
1765 else:
1764 else:
1766 text = mdiff.unidiff(to, date1,
1765 text = mdiff.unidiff(to, date1,
1767 # ctx2 date may be dynamic
1766 # ctx2 date may be dynamic
1768 tn, util.datestr(ctx2.date()),
1767 tn, util.datestr(ctx2.date()),
1769 join(a), join(b), revs, opts=opts)
1768 join(a), join(b), revs, opts=opts)
1770 if header and (text or len(header) > 1):
1769 if header and (text or len(header) > 1):
1771 yield ''.join(header)
1770 yield ''.join(header)
1772 if text:
1771 if text:
1773 yield text
1772 yield text
1774
1773
1775 def diffstatsum(stats):
1774 def diffstatsum(stats):
1776 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1775 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1777 for f, a, r, b in stats:
1776 for f, a, r, b in stats:
1778 maxfile = max(maxfile, encoding.colwidth(f))
1777 maxfile = max(maxfile, encoding.colwidth(f))
1779 maxtotal = max(maxtotal, a + r)
1778 maxtotal = max(maxtotal, a + r)
1780 addtotal += a
1779 addtotal += a
1781 removetotal += r
1780 removetotal += r
1782 binary = binary or b
1781 binary = binary or b
1783
1782
1784 return maxfile, maxtotal, addtotal, removetotal, binary
1783 return maxfile, maxtotal, addtotal, removetotal, binary
1785
1784
1786 def diffstatdata(lines):
1785 def diffstatdata(lines):
1787 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1786 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1788
1787
1789 results = []
1788 results = []
1790 filename, adds, removes, isbinary = None, 0, 0, False
1789 filename, adds, removes, isbinary = None, 0, 0, False
1791
1790
1792 def addresult():
1791 def addresult():
1793 if filename:
1792 if filename:
1794 results.append((filename, adds, removes, isbinary))
1793 results.append((filename, adds, removes, isbinary))
1795
1794
1796 for line in lines:
1795 for line in lines:
1797 if line.startswith('diff'):
1796 if line.startswith('diff'):
1798 addresult()
1797 addresult()
1799 # set numbers to 0 anyway when starting new file
1798 # set numbers to 0 anyway when starting new file
1800 adds, removes, isbinary = 0, 0, False
1799 adds, removes, isbinary = 0, 0, False
1801 if line.startswith('diff --git'):
1800 if line.startswith('diff --git'):
1802 filename = gitre.search(line).group(1)
1801 filename = gitre.search(line).group(1)
1803 elif line.startswith('diff -r'):
1802 elif line.startswith('diff -r'):
1804 # format: "diff -r ... -r ... filename"
1803 # format: "diff -r ... -r ... filename"
1805 filename = diffre.search(line).group(1)
1804 filename = diffre.search(line).group(1)
1806 elif line.startswith('+') and not line.startswith('+++'):
1805 elif line.startswith('+') and not line.startswith('+++'):
1807 adds += 1
1806 adds += 1
1808 elif line.startswith('-') and not line.startswith('---'):
1807 elif line.startswith('-') and not line.startswith('---'):
1809 removes += 1
1808 removes += 1
1810 elif (line.startswith('GIT binary patch') or
1809 elif (line.startswith('GIT binary patch') or
1811 line.startswith('Binary file')):
1810 line.startswith('Binary file')):
1812 isbinary = True
1811 isbinary = True
1813 addresult()
1812 addresult()
1814 return results
1813 return results
1815
1814
1816 def diffstat(lines, width=80, git=False):
1815 def diffstat(lines, width=80, git=False):
1817 output = []
1816 output = []
1818 stats = diffstatdata(lines)
1817 stats = diffstatdata(lines)
1819 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1818 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1820
1819
1821 countwidth = len(str(maxtotal))
1820 countwidth = len(str(maxtotal))
1822 if hasbinary and countwidth < 3:
1821 if hasbinary and countwidth < 3:
1823 countwidth = 3
1822 countwidth = 3
1824 graphwidth = width - countwidth - maxname - 6
1823 graphwidth = width - countwidth - maxname - 6
1825 if graphwidth < 10:
1824 if graphwidth < 10:
1826 graphwidth = 10
1825 graphwidth = 10
1827
1826
1828 def scale(i):
1827 def scale(i):
1829 if maxtotal <= graphwidth:
1828 if maxtotal <= graphwidth:
1830 return i
1829 return i
1831 # If diffstat runs out of room it doesn't print anything,
1830 # If diffstat runs out of room it doesn't print anything,
1832 # which isn't very useful, so always print at least one + or -
1831 # which isn't very useful, so always print at least one + or -
1833 # if there were at least some changes.
1832 # if there were at least some changes.
1834 return max(i * graphwidth // maxtotal, int(bool(i)))
1833 return max(i * graphwidth // maxtotal, int(bool(i)))
1835
1834
1836 for filename, adds, removes, isbinary in stats:
1835 for filename, adds, removes, isbinary in stats:
1837 if isbinary:
1836 if isbinary:
1838 count = 'Bin'
1837 count = 'Bin'
1839 else:
1838 else:
1840 count = adds + removes
1839 count = adds + removes
1841 pluses = '+' * scale(adds)
1840 pluses = '+' * scale(adds)
1842 minuses = '-' * scale(removes)
1841 minuses = '-' * scale(removes)
1843 output.append(' %s%s | %*s %s%s\n' %
1842 output.append(' %s%s | %*s %s%s\n' %
1844 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1843 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1845 countwidth, count, pluses, minuses))
1844 countwidth, count, pluses, minuses))
1846
1845
1847 if stats:
1846 if stats:
1848 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1847 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1849 % (len(stats), totaladds, totalremoves))
1848 % (len(stats), totaladds, totalremoves))
1850
1849
1851 return ''.join(output)
1850 return ''.join(output)
1852
1851
1853 def diffstatui(*args, **kw):
1852 def diffstatui(*args, **kw):
1854 '''like diffstat(), but yields 2-tuples of (output, label) for
1853 '''like diffstat(), but yields 2-tuples of (output, label) for
1855 ui.write()
1854 ui.write()
1856 '''
1855 '''
1857
1856
1858 for line in diffstat(*args, **kw).splitlines():
1857 for line in diffstat(*args, **kw).splitlines():
1859 if line and line[-1] in '+-':
1858 if line and line[-1] in '+-':
1860 name, graph = line.rsplit(' ', 1)
1859 name, graph = line.rsplit(' ', 1)
1861 yield (name + ' ', '')
1860 yield (name + ' ', '')
1862 m = re.search(r'\++', graph)
1861 m = re.search(r'\++', graph)
1863 if m:
1862 if m:
1864 yield (m.group(0), 'diffstat.inserted')
1863 yield (m.group(0), 'diffstat.inserted')
1865 m = re.search(r'-+', graph)
1864 m = re.search(r'-+', graph)
1866 if m:
1865 if m:
1867 yield (m.group(0), 'diffstat.deleted')
1866 yield (m.group(0), 'diffstat.deleted')
1868 else:
1867 else:
1869 yield (line, '')
1868 yield (line, '')
1870 yield ('\n', '')
1869 yield ('\n', '')
@@ -1,111 +1,153
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 [255]
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 [255]
91
91
92 test off-by-one error with diff -p
92 0 lines of context hunk header matches gnu diff hunk header
93
94 $ hg init diffzero
95 $ cd diffzero
96 $ cat > f1 << EOF
97 > c2
98 > c4
99 > c5
100 > EOF
101 $ hg commit -Am0
102 adding f1
103
104 $ cat > f2 << EOF
105 > c1
106 > c2
107 > c3
108 > c4
109 > EOF
110 $ diff -U0 f1 f2
111 --- f1 * (glob)
112 +++ f2 * (glob)
113 @@ -0,0 +1 @@
114 +c1
115 @@ -1,0 +3 @@
116 +c3
117 @@ -3 +4,0 @@
118 -c5
119 [1]
93
120
94 $ hg init diffp
121 $ mv f2 f1
95 $ cd diffp
122 $ hg diff -U0 --nodates
96 $ echo a > a
123 diff -r 55d8ff78db23 f1
97 $ hg ci -Ama
124 --- a/f1
98 adding a
125 +++ b/f1
99 $ rm a
126 @@ -0,0 +1,1 @@
100 $ echo b > a
127 +c1
101 $ echo a >> a
128 @@ -1,0 +3,1 @@
102 $ echo c >> a
129 +c3
103 $ hg diff -U0 -p --nodates
130 @@ -3,1 +4,0 @@
104 diff -r cb9a9f314b8b a
131 -c5
105 --- a/a
106 +++ b/a
107 @@ -1,0 +1,1 @@
108 +b
109 @@ -2,0 +3,1 @@ a
110 +c
111
132
133 $ hg diff -U0 --nodates --git
134 diff --git a/f1 b/f1
135 --- a/f1
136 +++ b/f1
137 @@ -0,0 +1,1 @@
138 +c1
139 @@ -1,0 +3,1 @@
140 +c3
141 @@ -3,1 +4,0 @@
142 -c5
143
144 $ hg diff -U0 --nodates -p
145 diff -r 55d8ff78db23 f1
146 --- a/f1
147 +++ b/f1
148 @@ -0,0 +1,1 @@
149 +c1
150 @@ -1,0 +3,1 @@ c2
151 +c3
152 @@ -3,1 +4,0 @@ c4
153 -c5
@@ -1,960 +1,996
1 $ hg init a
1 $ hg init a
2 $ mkdir a/d1
2 $ mkdir a/d1
3 $ mkdir a/d1/d2
3 $ mkdir a/d1/d2
4 $ echo line 1 > a/a
4 $ echo line 1 > a/a
5 $ echo line 1 > a/d1/d2/a
5 $ echo line 1 > a/d1/d2/a
6 $ hg --cwd a ci -Ama
6 $ hg --cwd a ci -Ama
7 adding a
7 adding a
8 adding d1/d2/a
8 adding d1/d2/a
9
9
10 $ echo line 2 >> a/a
10 $ echo line 2 >> a/a
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13 import with no args:
13 import with no args:
14
14
15 $ hg --cwd a import
15 $ hg --cwd a import
16 abort: need at least one patch to import
16 abort: need at least one patch to import
17 [255]
17 [255]
18
18
19 generate patches for the test
19 generate patches for the test
20
20
21 $ hg --cwd a export tip > exported-tip.patch
21 $ hg --cwd a export tip > exported-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23
23
24
24
25 import exported patch
25 import exported patch
26
26
27 $ hg clone -r0 a b
27 $ hg clone -r0 a b
28 adding changesets
28 adding changesets
29 adding manifests
29 adding manifests
30 adding file changes
30 adding file changes
31 added 1 changesets with 2 changes to 2 files
31 added 1 changesets with 2 changes to 2 files
32 updating to branch default
32 updating to branch default
33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ hg --cwd b import ../exported-tip.patch
34 $ hg --cwd b import ../exported-tip.patch
35 applying ../exported-tip.patch
35 applying ../exported-tip.patch
36
36
37 message and committer should be same
37 message and committer should be same
38
38
39 $ hg --cwd b tip
39 $ hg --cwd b tip
40 changeset: 1:1d4bd90af0e4
40 changeset: 1:1d4bd90af0e4
41 tag: tip
41 tag: tip
42 user: someone
42 user: someone
43 date: Thu Jan 01 00:00:01 1970 +0000
43 date: Thu Jan 01 00:00:01 1970 +0000
44 summary: second change
44 summary: second change
45
45
46 $ rm -r b
46 $ rm -r b
47
47
48
48
49 import exported patch with external patcher
49 import exported patch with external patcher
50
50
51 $ cat > dummypatch.py <<EOF
51 $ cat > dummypatch.py <<EOF
52 > print 'patching file a'
52 > print 'patching file a'
53 > file('a', 'wb').write('line2\n')
53 > file('a', 'wb').write('line2\n')
54 > EOF
54 > EOF
55 $ chmod +x dummypatch.py
55 $ chmod +x dummypatch.py
56 $ hg clone -r0 a b
56 $ hg clone -r0 a b
57 adding changesets
57 adding changesets
58 adding manifests
58 adding manifests
59 adding file changes
59 adding file changes
60 added 1 changesets with 2 changes to 2 files
60 added 1 changesets with 2 changes to 2 files
61 updating to branch default
61 updating to branch default
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
63 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
64 applying ../exported-tip.patch
64 applying ../exported-tip.patch
65 $ cat b/a
65 $ cat b/a
66 line2
66 line2
67 $ rm -r b
67 $ rm -r b
68
68
69
69
70 import of plain diff should fail without message
70 import of plain diff should fail without message
71
71
72 $ hg clone -r0 a b
72 $ hg clone -r0 a b
73 adding changesets
73 adding changesets
74 adding manifests
74 adding manifests
75 adding file changes
75 adding file changes
76 added 1 changesets with 2 changes to 2 files
76 added 1 changesets with 2 changes to 2 files
77 updating to branch default
77 updating to branch default
78 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 $ hg --cwd b import ../diffed-tip.patch
79 $ hg --cwd b import ../diffed-tip.patch
80 applying ../diffed-tip.patch
80 applying ../diffed-tip.patch
81 abort: empty commit message
81 abort: empty commit message
82 [255]
82 [255]
83 $ rm -r b
83 $ rm -r b
84
84
85
85
86 import of plain diff should be ok with message
86 import of plain diff should be ok with message
87
87
88 $ hg clone -r0 a b
88 $ hg clone -r0 a b
89 adding changesets
89 adding changesets
90 adding manifests
90 adding manifests
91 adding file changes
91 adding file changes
92 added 1 changesets with 2 changes to 2 files
92 added 1 changesets with 2 changes to 2 files
93 updating to branch default
93 updating to branch default
94 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 $ hg --cwd b import -mpatch ../diffed-tip.patch
95 $ hg --cwd b import -mpatch ../diffed-tip.patch
96 applying ../diffed-tip.patch
96 applying ../diffed-tip.patch
97 $ rm -r b
97 $ rm -r b
98
98
99
99
100 import of plain diff with specific date and user
100 import of plain diff with specific date and user
101
101
102 $ hg clone -r0 a b
102 $ hg clone -r0 a b
103 adding changesets
103 adding changesets
104 adding manifests
104 adding manifests
105 adding file changes
105 adding file changes
106 added 1 changesets with 2 changes to 2 files
106 added 1 changesets with 2 changes to 2 files
107 updating to branch default
107 updating to branch default
108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
109 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
110 applying ../diffed-tip.patch
110 applying ../diffed-tip.patch
111 $ hg -R b tip -pv
111 $ hg -R b tip -pv
112 changeset: 1:ca68f19f3a40
112 changeset: 1:ca68f19f3a40
113 tag: tip
113 tag: tip
114 user: user@nowhere.net
114 user: user@nowhere.net
115 date: Thu Jan 01 00:00:01 1970 +0000
115 date: Thu Jan 01 00:00:01 1970 +0000
116 files: a
116 files: a
117 description:
117 description:
118 patch
118 patch
119
119
120
120
121 diff -r 80971e65b431 -r ca68f19f3a40 a
121 diff -r 80971e65b431 -r ca68f19f3a40 a
122 --- a/a Thu Jan 01 00:00:00 1970 +0000
122 --- a/a Thu Jan 01 00:00:00 1970 +0000
123 +++ b/a Thu Jan 01 00:00:01 1970 +0000
123 +++ b/a Thu Jan 01 00:00:01 1970 +0000
124 @@ -1,1 +1,2 @@
124 @@ -1,1 +1,2 @@
125 line 1
125 line 1
126 +line 2
126 +line 2
127
127
128 $ rm -r b
128 $ rm -r b
129
129
130
130
131 import of plain diff should be ok with --no-commit
131 import of plain diff should be ok with --no-commit
132
132
133 $ hg clone -r0 a b
133 $ hg clone -r0 a b
134 adding changesets
134 adding changesets
135 adding manifests
135 adding manifests
136 adding file changes
136 adding file changes
137 added 1 changesets with 2 changes to 2 files
137 added 1 changesets with 2 changes to 2 files
138 updating to branch default
138 updating to branch default
139 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 $ hg --cwd b import --no-commit ../diffed-tip.patch
140 $ hg --cwd b import --no-commit ../diffed-tip.patch
141 applying ../diffed-tip.patch
141 applying ../diffed-tip.patch
142 $ hg --cwd b diff --nodates
142 $ hg --cwd b diff --nodates
143 diff -r 80971e65b431 a
143 diff -r 80971e65b431 a
144 --- a/a
144 --- a/a
145 +++ b/a
145 +++ b/a
146 @@ -1,1 +1,2 @@
146 @@ -1,1 +1,2 @@
147 line 1
147 line 1
148 +line 2
148 +line 2
149 $ rm -r b
149 $ rm -r b
150
150
151
151
152 import of malformed plain diff should fail
152 import of malformed plain diff should fail
153
153
154 $ hg clone -r0 a b
154 $ hg clone -r0 a b
155 adding changesets
155 adding changesets
156 adding manifests
156 adding manifests
157 adding file changes
157 adding file changes
158 added 1 changesets with 2 changes to 2 files
158 added 1 changesets with 2 changes to 2 files
159 updating to branch default
159 updating to branch default
160 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
161 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
161 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
162 $ hg --cwd b import -mpatch ../broken.patch
162 $ hg --cwd b import -mpatch ../broken.patch
163 applying ../broken.patch
163 applying ../broken.patch
164 abort: bad hunk #1
164 abort: bad hunk #1
165 [255]
165 [255]
166 $ rm -r b
166 $ rm -r b
167
167
168
168
169 hg -R repo import
169 hg -R repo import
170 put the clone in a subdir - having a directory named "a"
170 put the clone in a subdir - having a directory named "a"
171 used to hide a bug.
171 used to hide a bug.
172
172
173 $ mkdir dir
173 $ mkdir dir
174 $ hg clone -r0 a dir/b
174 $ hg clone -r0 a dir/b
175 adding changesets
175 adding changesets
176 adding manifests
176 adding manifests
177 adding file changes
177 adding file changes
178 added 1 changesets with 2 changes to 2 files
178 added 1 changesets with 2 changes to 2 files
179 updating to branch default
179 updating to branch default
180 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
180 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 $ cd dir
181 $ cd dir
182 $ hg -R b import ../exported-tip.patch
182 $ hg -R b import ../exported-tip.patch
183 applying ../exported-tip.patch
183 applying ../exported-tip.patch
184 $ cd ..
184 $ cd ..
185 $ rm -r dir
185 $ rm -r dir
186
186
187
187
188 import from stdin
188 import from stdin
189
189
190 $ hg clone -r0 a b
190 $ hg clone -r0 a b
191 adding changesets
191 adding changesets
192 adding manifests
192 adding manifests
193 adding file changes
193 adding file changes
194 added 1 changesets with 2 changes to 2 files
194 added 1 changesets with 2 changes to 2 files
195 updating to branch default
195 updating to branch default
196 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
197 $ hg --cwd b import - < exported-tip.patch
197 $ hg --cwd b import - < exported-tip.patch
198 applying patch from stdin
198 applying patch from stdin
199 $ rm -r b
199 $ rm -r b
200
200
201
201
202 import two patches in one stream
202 import two patches in one stream
203
203
204 $ hg init b
204 $ hg init b
205 $ hg --cwd a export 0:tip | hg --cwd b import -
205 $ hg --cwd a export 0:tip | hg --cwd b import -
206 applying patch from stdin
206 applying patch from stdin
207 $ hg --cwd a id
207 $ hg --cwd a id
208 1d4bd90af0e4 tip
208 1d4bd90af0e4 tip
209 $ hg --cwd b id
209 $ hg --cwd b id
210 1d4bd90af0e4 tip
210 1d4bd90af0e4 tip
211 $ rm -r b
211 $ rm -r b
212
212
213
213
214 override commit message
214 override commit message
215
215
216 $ hg clone -r0 a b
216 $ hg clone -r0 a b
217 adding changesets
217 adding changesets
218 adding manifests
218 adding manifests
219 adding file changes
219 adding file changes
220 added 1 changesets with 2 changes to 2 files
220 added 1 changesets with 2 changes to 2 files
221 updating to branch default
221 updating to branch default
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 $ hg --cwd b import -m 'override' - < exported-tip.patch
223 $ hg --cwd b import -m 'override' - < exported-tip.patch
224 applying patch from stdin
224 applying patch from stdin
225 $ hg --cwd b tip | grep override
225 $ hg --cwd b tip | grep override
226 summary: override
226 summary: override
227 $ rm -r b
227 $ rm -r b
228
228
229 $ cat > mkmsg.py <<EOF
229 $ cat > mkmsg.py <<EOF
230 > import email.Message, sys
230 > import email.Message, sys
231 > msg = email.Message.Message()
231 > msg = email.Message.Message()
232 > patch = open(sys.argv[1], 'rb').read()
232 > patch = open(sys.argv[1], 'rb').read()
233 > msg.set_payload('email commit message\n' + patch)
233 > msg.set_payload('email commit message\n' + patch)
234 > msg['Subject'] = 'email patch'
234 > msg['Subject'] = 'email patch'
235 > msg['From'] = 'email patcher'
235 > msg['From'] = 'email patcher'
236 > sys.stdout.write(msg.as_string())
236 > sys.stdout.write(msg.as_string())
237 > EOF
237 > EOF
238
238
239
239
240 plain diff in email, subject, message body
240 plain diff in email, subject, message body
241
241
242 $ hg clone -r0 a b
242 $ hg clone -r0 a b
243 adding changesets
243 adding changesets
244 adding manifests
244 adding manifests
245 adding file changes
245 adding file changes
246 added 1 changesets with 2 changes to 2 files
246 added 1 changesets with 2 changes to 2 files
247 updating to branch default
247 updating to branch default
248 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
248 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 $ python mkmsg.py diffed-tip.patch > msg.patch
249 $ python mkmsg.py diffed-tip.patch > msg.patch
250 $ hg --cwd b import ../msg.patch
250 $ hg --cwd b import ../msg.patch
251 applying ../msg.patch
251 applying ../msg.patch
252 $ hg --cwd b tip | grep email
252 $ hg --cwd b tip | grep email
253 user: email patcher
253 user: email patcher
254 summary: email patch
254 summary: email patch
255 $ rm -r b
255 $ rm -r b
256
256
257
257
258 plain diff in email, no subject, message body
258 plain diff in email, no subject, message body
259
259
260 $ hg clone -r0 a b
260 $ hg clone -r0 a b
261 adding changesets
261 adding changesets
262 adding manifests
262 adding manifests
263 adding file changes
263 adding file changes
264 added 1 changesets with 2 changes to 2 files
264 added 1 changesets with 2 changes to 2 files
265 updating to branch default
265 updating to branch default
266 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
267 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
268 applying patch from stdin
268 applying patch from stdin
269 $ rm -r b
269 $ rm -r b
270
270
271
271
272 plain diff in email, subject, no message body
272 plain diff in email, subject, no message body
273
273
274 $ hg clone -r0 a b
274 $ hg clone -r0 a b
275 adding changesets
275 adding changesets
276 adding manifests
276 adding manifests
277 adding file changes
277 adding file changes
278 added 1 changesets with 2 changes to 2 files
278 added 1 changesets with 2 changes to 2 files
279 updating to branch default
279 updating to branch default
280 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 $ grep -v '^email ' msg.patch | hg --cwd b import -
281 $ grep -v '^email ' msg.patch | hg --cwd b import -
282 applying patch from stdin
282 applying patch from stdin
283 $ rm -r b
283 $ rm -r b
284
284
285
285
286 plain diff in email, no subject, no message body, should fail
286 plain diff in email, no subject, no message body, should fail
287
287
288 $ hg clone -r0 a b
288 $ hg clone -r0 a b
289 adding changesets
289 adding changesets
290 adding manifests
290 adding manifests
291 adding file changes
291 adding file changes
292 added 1 changesets with 2 changes to 2 files
292 added 1 changesets with 2 changes to 2 files
293 updating to branch default
293 updating to branch default
294 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
295 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
296 applying patch from stdin
296 applying patch from stdin
297 abort: empty commit message
297 abort: empty commit message
298 [255]
298 [255]
299 $ rm -r b
299 $ rm -r b
300
300
301
301
302 hg export in email, should use patch header
302 hg export in email, should use patch header
303
303
304 $ hg clone -r0 a b
304 $ hg clone -r0 a b
305 adding changesets
305 adding changesets
306 adding manifests
306 adding manifests
307 adding file changes
307 adding file changes
308 added 1 changesets with 2 changes to 2 files
308 added 1 changesets with 2 changes to 2 files
309 updating to branch default
309 updating to branch default
310 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 $ python mkmsg.py exported-tip.patch | hg --cwd b import -
311 $ python mkmsg.py exported-tip.patch | hg --cwd b import -
312 applying patch from stdin
312 applying patch from stdin
313 $ hg --cwd b tip | grep second
313 $ hg --cwd b tip | grep second
314 summary: second change
314 summary: second change
315 $ rm -r b
315 $ rm -r b
316
316
317
317
318 subject: duplicate detection, removal of [PATCH]
318 subject: duplicate detection, removal of [PATCH]
319 The '---' tests the gitsendmail handling without proper mail headers
319 The '---' tests the gitsendmail handling without proper mail headers
320
320
321 $ cat > mkmsg2.py <<EOF
321 $ cat > mkmsg2.py <<EOF
322 > import email.Message, sys
322 > import email.Message, sys
323 > msg = email.Message.Message()
323 > msg = email.Message.Message()
324 > patch = open(sys.argv[1], 'rb').read()
324 > patch = open(sys.argv[1], 'rb').read()
325 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
325 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
326 > msg['Subject'] = '[PATCH] email patch'
326 > msg['Subject'] = '[PATCH] email patch'
327 > msg['From'] = 'email patcher'
327 > msg['From'] = 'email patcher'
328 > sys.stdout.write(msg.as_string())
328 > sys.stdout.write(msg.as_string())
329 > EOF
329 > EOF
330
330
331
331
332 plain diff in email, [PATCH] subject, message body with subject
332 plain diff in email, [PATCH] subject, message body with subject
333
333
334 $ hg clone -r0 a b
334 $ hg clone -r0 a b
335 adding changesets
335 adding changesets
336 adding manifests
336 adding manifests
337 adding file changes
337 adding file changes
338 added 1 changesets with 2 changes to 2 files
338 added 1 changesets with 2 changes to 2 files
339 updating to branch default
339 updating to branch default
340 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 $ python mkmsg2.py diffed-tip.patch | hg --cwd b import -
341 $ python mkmsg2.py diffed-tip.patch | hg --cwd b import -
342 applying patch from stdin
342 applying patch from stdin
343 $ hg --cwd b tip --template '{desc}\n'
343 $ hg --cwd b tip --template '{desc}\n'
344 email patch
344 email patch
345
345
346 next line
346 next line
347 ---
347 ---
348 $ rm -r b
348 $ rm -r b
349
349
350
350
351 Issue963: Parent of working dir incorrect after import of multiple
351 Issue963: Parent of working dir incorrect after import of multiple
352 patches and rollback
352 patches and rollback
353
353
354 We weren't backing up the correct dirstate file when importing many
354 We weren't backing up the correct dirstate file when importing many
355 patches: import patch1 patch2; rollback
355 patches: import patch1 patch2; rollback
356
356
357 $ echo line 3 >> a/a
357 $ echo line 3 >> a/a
358 $ hg --cwd a ci -m'third change'
358 $ hg --cwd a ci -m'third change'
359 $ hg --cwd a export -o '../patch%R' 1 2
359 $ hg --cwd a export -o '../patch%R' 1 2
360 $ hg clone -qr0 a b
360 $ hg clone -qr0 a b
361 $ hg --cwd b parents --template 'parent: {rev}\n'
361 $ hg --cwd b parents --template 'parent: {rev}\n'
362 parent: 0
362 parent: 0
363 $ hg --cwd b import -v ../patch1 ../patch2
363 $ hg --cwd b import -v ../patch1 ../patch2
364 applying ../patch1
364 applying ../patch1
365 patching file a
365 patching file a
366 a
366 a
367 created 1d4bd90af0e4
367 created 1d4bd90af0e4
368 applying ../patch2
368 applying ../patch2
369 patching file a
369 patching file a
370 a
370 a
371 created 6d019af21222
371 created 6d019af21222
372 $ hg --cwd b rollback
372 $ hg --cwd b rollback
373 repository tip rolled back to revision 0 (undo import)
373 repository tip rolled back to revision 0 (undo import)
374 working directory now based on revision 0
374 working directory now based on revision 0
375 $ hg --cwd b parents --template 'parent: {rev}\n'
375 $ hg --cwd b parents --template 'parent: {rev}\n'
376 parent: 0
376 parent: 0
377 $ rm -r b
377 $ rm -r b
378
378
379
379
380 importing a patch in a subdirectory failed at the commit stage
380 importing a patch in a subdirectory failed at the commit stage
381
381
382 $ echo line 2 >> a/d1/d2/a
382 $ echo line 2 >> a/d1/d2/a
383 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
383 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
384
384
385 hg import in a subdirectory
385 hg import in a subdirectory
386
386
387 $ hg clone -r0 a b
387 $ hg clone -r0 a b
388 adding changesets
388 adding changesets
389 adding manifests
389 adding manifests
390 adding file changes
390 adding file changes
391 added 1 changesets with 2 changes to 2 files
391 added 1 changesets with 2 changes to 2 files
392 updating to branch default
392 updating to branch default
393 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 $ hg --cwd a export tip > tmp
394 $ hg --cwd a export tip > tmp
395 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
395 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
396 $ dir=`pwd`
396 $ dir=`pwd`
397 $ cd b/d1/d2 2>&1 > /dev/null
397 $ cd b/d1/d2 2>&1 > /dev/null
398 $ hg import ../../../subdir-tip.patch
398 $ hg import ../../../subdir-tip.patch
399 applying ../../../subdir-tip.patch
399 applying ../../../subdir-tip.patch
400 $ cd "$dir"
400 $ cd "$dir"
401
401
402 message should be 'subdir change'
402 message should be 'subdir change'
403 committer should be 'someoneelse'
403 committer should be 'someoneelse'
404
404
405 $ hg --cwd b tip
405 $ hg --cwd b tip
406 changeset: 1:3577f5aea227
406 changeset: 1:3577f5aea227
407 tag: tip
407 tag: tip
408 user: someoneelse
408 user: someoneelse
409 date: Thu Jan 01 00:00:01 1970 +0000
409 date: Thu Jan 01 00:00:01 1970 +0000
410 summary: subdir change
410 summary: subdir change
411
411
412
412
413 should be empty
413 should be empty
414
414
415 $ hg --cwd b status
415 $ hg --cwd b status
416
416
417
417
418 Test fuzziness (ambiguous patch location, fuzz=2)
418 Test fuzziness (ambiguous patch location, fuzz=2)
419
419
420 $ hg init fuzzy
420 $ hg init fuzzy
421 $ cd fuzzy
421 $ cd fuzzy
422 $ echo line1 > a
422 $ echo line1 > a
423 $ echo line0 >> a
423 $ echo line0 >> a
424 $ echo line3 >> a
424 $ echo line3 >> a
425 $ hg ci -Am adda
425 $ hg ci -Am adda
426 adding a
426 adding a
427 $ echo line1 > a
427 $ echo line1 > a
428 $ echo line2 >> a
428 $ echo line2 >> a
429 $ echo line0 >> a
429 $ echo line0 >> a
430 $ echo line3 >> a
430 $ echo line3 >> a
431 $ hg ci -m change a
431 $ hg ci -m change a
432 $ hg export tip > fuzzy-tip.patch
432 $ hg export tip > fuzzy-tip.patch
433 $ hg up -C 0
433 $ hg up -C 0
434 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 $ echo line1 > a
435 $ echo line1 > a
436 $ echo line0 >> a
436 $ echo line0 >> a
437 $ echo line1 >> a
437 $ echo line1 >> a
438 $ echo line0 >> a
438 $ echo line0 >> a
439 $ hg ci -m brancha
439 $ hg ci -m brancha
440 created new head
440 created new head
441 $ hg import --no-commit -v fuzzy-tip.patch
441 $ hg import --no-commit -v fuzzy-tip.patch
442 applying fuzzy-tip.patch
442 applying fuzzy-tip.patch
443 patching file a
443 patching file a
444 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
444 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
445 applied to working directory
445 applied to working directory
446 $ hg revert -a
446 $ hg revert -a
447 reverting a
447 reverting a
448
448
449
449
450 import with --no-commit should have written .hg/last-message.txt
450 import with --no-commit should have written .hg/last-message.txt
451
451
452 $ cat .hg/last-message.txt
452 $ cat .hg/last-message.txt
453 change (no-eol)
453 change (no-eol)
454
454
455
455
456 test fuzziness with eol=auto
456 test fuzziness with eol=auto
457
457
458 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
458 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
459 applying fuzzy-tip.patch
459 applying fuzzy-tip.patch
460 patching file a
460 patching file a
461 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
461 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
462 applied to working directory
462 applied to working directory
463 $ cd ..
463 $ cd ..
464
464
465
465
466 Test hunk touching empty files (issue906)
466 Test hunk touching empty files (issue906)
467
467
468 $ hg init empty
468 $ hg init empty
469 $ cd empty
469 $ cd empty
470 $ touch a
470 $ touch a
471 $ touch b1
471 $ touch b1
472 $ touch c1
472 $ touch c1
473 $ echo d > d
473 $ echo d > d
474 $ hg ci -Am init
474 $ hg ci -Am init
475 adding a
475 adding a
476 adding b1
476 adding b1
477 adding c1
477 adding c1
478 adding d
478 adding d
479 $ echo a > a
479 $ echo a > a
480 $ echo b > b1
480 $ echo b > b1
481 $ hg mv b1 b2
481 $ hg mv b1 b2
482 $ echo c > c1
482 $ echo c > c1
483 $ hg copy c1 c2
483 $ hg copy c1 c2
484 $ rm d
484 $ rm d
485 $ touch d
485 $ touch d
486 $ hg diff --git
486 $ hg diff --git
487 diff --git a/a b/a
487 diff --git a/a b/a
488 --- a/a
488 --- a/a
489 +++ b/a
489 +++ b/a
490 @@ -0,0 +1,1 @@
490 @@ -0,0 +1,1 @@
491 +a
491 +a
492 diff --git a/b1 b/b2
492 diff --git a/b1 b/b2
493 rename from b1
493 rename from b1
494 rename to b2
494 rename to b2
495 --- a/b1
495 --- a/b1
496 +++ b/b2
496 +++ b/b2
497 @@ -0,0 +1,1 @@
497 @@ -0,0 +1,1 @@
498 +b
498 +b
499 diff --git a/c1 b/c1
499 diff --git a/c1 b/c1
500 --- a/c1
500 --- a/c1
501 +++ b/c1
501 +++ b/c1
502 @@ -0,0 +1,1 @@
502 @@ -0,0 +1,1 @@
503 +c
503 +c
504 diff --git a/c1 b/c2
504 diff --git a/c1 b/c2
505 copy from c1
505 copy from c1
506 copy to c2
506 copy to c2
507 --- a/c1
507 --- a/c1
508 +++ b/c2
508 +++ b/c2
509 @@ -0,0 +1,1 @@
509 @@ -0,0 +1,1 @@
510 +c
510 +c
511 diff --git a/d b/d
511 diff --git a/d b/d
512 --- a/d
512 --- a/d
513 +++ b/d
513 +++ b/d
514 @@ -1,1 +0,0 @@
514 @@ -1,1 +0,0 @@
515 -d
515 -d
516 $ hg ci -m empty
516 $ hg ci -m empty
517 $ hg export --git tip > empty.diff
517 $ hg export --git tip > empty.diff
518 $ hg up -C 0
518 $ hg up -C 0
519 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
519 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
520 $ hg import empty.diff
520 $ hg import empty.diff
521 applying empty.diff
521 applying empty.diff
522 $ for name in a b1 b2 c1 c2 d; do
522 $ for name in a b1 b2 c1 c2 d; do
523 > echo % $name file
523 > echo % $name file
524 > test -f $name && cat $name
524 > test -f $name && cat $name
525 > done
525 > done
526 % a file
526 % a file
527 a
527 a
528 % b1 file
528 % b1 file
529 % b2 file
529 % b2 file
530 b
530 b
531 % c1 file
531 % c1 file
532 c
532 c
533 % c2 file
533 % c2 file
534 c
534 c
535 % d file
535 % d file
536 $ cd ..
536 $ cd ..
537
537
538
538
539 Test importing a patch ending with a binary file removal
539 Test importing a patch ending with a binary file removal
540
540
541 $ hg init binaryremoval
541 $ hg init binaryremoval
542 $ cd binaryremoval
542 $ cd binaryremoval
543 $ echo a > a
543 $ echo a > a
544 $ python -c "file('b', 'wb').write('a\x00b')"
544 $ python -c "file('b', 'wb').write('a\x00b')"
545 $ hg ci -Am addall
545 $ hg ci -Am addall
546 adding a
546 adding a
547 adding b
547 adding b
548 $ hg rm a
548 $ hg rm a
549 $ hg rm b
549 $ hg rm b
550 $ hg st
550 $ hg st
551 R a
551 R a
552 R b
552 R b
553 $ hg ci -m remove
553 $ hg ci -m remove
554 $ hg export --git . > remove.diff
554 $ hg export --git . > remove.diff
555 $ cat remove.diff | grep git
555 $ cat remove.diff | grep git
556 diff --git a/a b/a
556 diff --git a/a b/a
557 diff --git a/b b/b
557 diff --git a/b b/b
558 $ hg up -C 0
558 $ hg up -C 0
559 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
559 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 $ hg import remove.diff
560 $ hg import remove.diff
561 applying remove.diff
561 applying remove.diff
562 $ hg manifest
562 $ hg manifest
563 $ cd ..
563 $ cd ..
564
564
565
565
566 Issue927: test update+rename with common name
566 Issue927: test update+rename with common name
567
567
568 $ hg init t
568 $ hg init t
569 $ cd t
569 $ cd t
570 $ touch a
570 $ touch a
571 $ hg ci -Am t
571 $ hg ci -Am t
572 adding a
572 adding a
573 $ echo a > a
573 $ echo a > a
574
574
575 Here, bfile.startswith(afile)
575 Here, bfile.startswith(afile)
576
576
577 $ hg copy a a2
577 $ hg copy a a2
578 $ hg ci -m copya
578 $ hg ci -m copya
579 $ hg export --git tip > copy.diff
579 $ hg export --git tip > copy.diff
580 $ hg up -C 0
580 $ hg up -C 0
581 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
581 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
582 $ hg import copy.diff
582 $ hg import copy.diff
583 applying copy.diff
583 applying copy.diff
584
584
585 a should contain an 'a'
585 a should contain an 'a'
586
586
587 $ cat a
587 $ cat a
588 a
588 a
589
589
590 and a2 should have duplicated it
590 and a2 should have duplicated it
591
591
592 $ cat a2
592 $ cat a2
593 a
593 a
594 $ cd ..
594 $ cd ..
595
595
596
596
597 test -p0
597 test -p0
598
598
599 $ hg init p0
599 $ hg init p0
600 $ cd p0
600 $ cd p0
601 $ echo a > a
601 $ echo a > a
602 $ hg ci -Am t
602 $ hg ci -Am t
603 adding a
603 adding a
604 $ hg import -p0 - << EOF
604 $ hg import -p0 - << EOF
605 > foobar
605 > foobar
606 > --- a Sat Apr 12 22:43:58 2008 -0400
606 > --- a Sat Apr 12 22:43:58 2008 -0400
607 > +++ a Sat Apr 12 22:44:05 2008 -0400
607 > +++ a Sat Apr 12 22:44:05 2008 -0400
608 > @@ -1,1 +1,1 @@
608 > @@ -1,1 +1,1 @@
609 > -a
609 > -a
610 > +bb
610 > +bb
611 > EOF
611 > EOF
612 applying patch from stdin
612 applying patch from stdin
613 $ hg status
613 $ hg status
614 $ cat a
614 $ cat a
615 bb
615 bb
616 $ cd ..
616 $ cd ..
617
617
618
618
619 test paths outside repo root
619 test paths outside repo root
620
620
621 $ mkdir outside
621 $ mkdir outside
622 $ touch outside/foo
622 $ touch outside/foo
623 $ hg init inside
623 $ hg init inside
624 $ cd inside
624 $ cd inside
625 $ hg import - <<EOF
625 $ hg import - <<EOF
626 > diff --git a/a b/b
626 > diff --git a/a b/b
627 > rename from ../outside/foo
627 > rename from ../outside/foo
628 > rename to bar
628 > rename to bar
629 > EOF
629 > EOF
630 applying patch from stdin
630 applying patch from stdin
631 abort: path contains illegal component: ../outside/foo
631 abort: path contains illegal component: ../outside/foo
632 [255]
632 [255]
633 $ cd ..
633 $ cd ..
634
634
635
635
636 test import with similarity and git and strip (issue295 et al.)
636 test import with similarity and git and strip (issue295 et al.)
637
637
638 $ hg init sim
638 $ hg init sim
639 $ cd sim
639 $ cd sim
640 $ echo 'this is a test' > a
640 $ echo 'this is a test' > a
641 $ hg ci -Ama
641 $ hg ci -Ama
642 adding a
642 adding a
643 $ cat > ../rename.diff <<EOF
643 $ cat > ../rename.diff <<EOF
644 > diff --git a/foo/a b/foo/a
644 > diff --git a/foo/a b/foo/a
645 > deleted file mode 100644
645 > deleted file mode 100644
646 > --- a/foo/a
646 > --- a/foo/a
647 > +++ /dev/null
647 > +++ /dev/null
648 > @@ -1,1 +0,0 @@
648 > @@ -1,1 +0,0 @@
649 > -this is a test
649 > -this is a test
650 > diff --git a/foo/b b/foo/b
650 > diff --git a/foo/b b/foo/b
651 > new file mode 100644
651 > new file mode 100644
652 > --- /dev/null
652 > --- /dev/null
653 > +++ b/foo/b
653 > +++ b/foo/b
654 > @@ -0,0 +1,2 @@
654 > @@ -0,0 +1,2 @@
655 > +this is a test
655 > +this is a test
656 > +foo
656 > +foo
657 > EOF
657 > EOF
658 $ hg import --no-commit -v -s 1 ../rename.diff -p2
658 $ hg import --no-commit -v -s 1 ../rename.diff -p2
659 applying ../rename.diff
659 applying ../rename.diff
660 patching file a
660 patching file a
661 patching file b
661 patching file b
662 removing a
662 removing a
663 adding b
663 adding b
664 recording removal of a as rename to b (88% similar)
664 recording removal of a as rename to b (88% similar)
665 applied to working directory
665 applied to working directory
666 $ hg st -C
666 $ hg st -C
667 A b
667 A b
668 a
668 a
669 R a
669 R a
670 $ hg revert -a
670 $ hg revert -a
671 undeleting a
671 undeleting a
672 forgetting b
672 forgetting b
673 $ rm b
673 $ rm b
674 $ hg import --no-commit -v -s 100 ../rename.diff -p2
674 $ hg import --no-commit -v -s 100 ../rename.diff -p2
675 applying ../rename.diff
675 applying ../rename.diff
676 patching file a
676 patching file a
677 patching file b
677 patching file b
678 removing a
678 removing a
679 adding b
679 adding b
680 applied to working directory
680 applied to working directory
681 $ hg st -C
681 $ hg st -C
682 A b
682 A b
683 R a
683 R a
684 $ cd ..
684 $ cd ..
685
685
686
686
687 Issue1495: add empty file from the end of patch
687 Issue1495: add empty file from the end of patch
688
688
689 $ hg init addemptyend
689 $ hg init addemptyend
690 $ cd addemptyend
690 $ cd addemptyend
691 $ touch a
691 $ touch a
692 $ hg addremove
692 $ hg addremove
693 adding a
693 adding a
694 $ hg ci -m "commit"
694 $ hg ci -m "commit"
695 $ cat > a.patch <<EOF
695 $ cat > a.patch <<EOF
696 > add a, b
696 > add a, b
697 > diff --git a/a b/a
697 > diff --git a/a b/a
698 > --- a/a
698 > --- a/a
699 > +++ b/a
699 > +++ b/a
700 > @@ -0,0 +1,1 @@
700 > @@ -0,0 +1,1 @@
701 > +a
701 > +a
702 > diff --git a/b b/b
702 > diff --git a/b b/b
703 > new file mode 100644
703 > new file mode 100644
704 > EOF
704 > EOF
705 $ hg import --no-commit a.patch
705 $ hg import --no-commit a.patch
706 applying a.patch
706 applying a.patch
707
707
708 apply a good patch followed by an empty patch (mainly to ensure
708 apply a good patch followed by an empty patch (mainly to ensure
709 that dirstate is *not* updated when import crashes)
709 that dirstate is *not* updated when import crashes)
710 $ hg update -q -C .
710 $ hg update -q -C .
711 $ rm b
711 $ rm b
712 $ touch empty.patch
712 $ touch empty.patch
713 $ hg import a.patch empty.patch
713 $ hg import a.patch empty.patch
714 applying a.patch
714 applying a.patch
715 applying empty.patch
715 applying empty.patch
716 transaction abort!
716 transaction abort!
717 rollback completed
717 rollback completed
718 abort: empty.patch: no diffs found
718 abort: empty.patch: no diffs found
719 [255]
719 [255]
720 $ hg tip --template '{rev} {desc|firstline}\n'
720 $ hg tip --template '{rev} {desc|firstline}\n'
721 0 commit
721 0 commit
722 $ hg -q status
722 $ hg -q status
723 M a
723 M a
724 $ cd ..
724 $ cd ..
725
725
726 create file when source is not /dev/null
726 create file when source is not /dev/null
727
727
728 $ cat > create.patch <<EOF
728 $ cat > create.patch <<EOF
729 > diff -Naur proj-orig/foo proj-new/foo
729 > diff -Naur proj-orig/foo proj-new/foo
730 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
730 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
731 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
731 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
732 > @@ -0,0 +1,1 @@
732 > @@ -0,0 +1,1 @@
733 > +a
733 > +a
734 > EOF
734 > EOF
735
735
736 some people have patches like the following too
736 some people have patches like the following too
737
737
738 $ cat > create2.patch <<EOF
738 $ cat > create2.patch <<EOF
739 > diff -Naur proj-orig/foo proj-new/foo
739 > diff -Naur proj-orig/foo proj-new/foo
740 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
740 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
741 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
741 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
742 > @@ -0,0 +1,1 @@
742 > @@ -0,0 +1,1 @@
743 > +a
743 > +a
744 > EOF
744 > EOF
745 $ hg init oddcreate
745 $ hg init oddcreate
746 $ cd oddcreate
746 $ cd oddcreate
747 $ hg import --no-commit ../create.patch
747 $ hg import --no-commit ../create.patch
748 applying ../create.patch
748 applying ../create.patch
749 $ cat foo
749 $ cat foo
750 a
750 a
751 $ rm foo
751 $ rm foo
752 $ hg revert foo
752 $ hg revert foo
753 $ hg import --no-commit ../create2.patch
753 $ hg import --no-commit ../create2.patch
754 applying ../create2.patch
754 applying ../create2.patch
755 $ cat foo
755 $ cat foo
756 a
756 a
757
757
758
758
759 Issue1859: first line mistaken for email headers
759 Issue1859: first line mistaken for email headers
760
760
761 $ hg init emailconfusion
761 $ hg init emailconfusion
762 $ cd emailconfusion
762 $ cd emailconfusion
763 $ cat > a.patch <<EOF
763 $ cat > a.patch <<EOF
764 > module: summary
764 > module: summary
765 >
765 >
766 > description
766 > description
767 >
767 >
768 >
768 >
769 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
769 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
770 > --- /dev/null
770 > --- /dev/null
771 > +++ b/a
771 > +++ b/a
772 > @@ -0,0 +1,1 @@
772 > @@ -0,0 +1,1 @@
773 > +a
773 > +a
774 > EOF
774 > EOF
775 $ hg import -d '0 0' a.patch
775 $ hg import -d '0 0' a.patch
776 applying a.patch
776 applying a.patch
777 $ hg parents -v
777 $ hg parents -v
778 changeset: 0:5a681217c0ad
778 changeset: 0:5a681217c0ad
779 tag: tip
779 tag: tip
780 user: test
780 user: test
781 date: Thu Jan 01 00:00:00 1970 +0000
781 date: Thu Jan 01 00:00:00 1970 +0000
782 files: a
782 files: a
783 description:
783 description:
784 module: summary
784 module: summary
785
785
786 description
786 description
787
787
788
788
789 $ cd ..
789 $ cd ..
790
790
791
791
792 --- in commit message
792 --- in commit message
793
793
794 $ hg init commitconfusion
794 $ hg init commitconfusion
795 $ cd commitconfusion
795 $ cd commitconfusion
796 $ cat > a.patch <<EOF
796 $ cat > a.patch <<EOF
797 > module: summary
797 > module: summary
798 >
798 >
799 > --- description
799 > --- description
800 >
800 >
801 > diff --git a/a b/a
801 > diff --git a/a b/a
802 > new file mode 100644
802 > new file mode 100644
803 > --- /dev/null
803 > --- /dev/null
804 > +++ b/a
804 > +++ b/a
805 > @@ -0,0 +1,1 @@
805 > @@ -0,0 +1,1 @@
806 > +a
806 > +a
807 > EOF
807 > EOF
808 > hg import -d '0 0' a.patch
808 > hg import -d '0 0' a.patch
809 > hg parents -v
809 > hg parents -v
810 > cd ..
810 > cd ..
811 >
811 >
812 > echo '% tricky header splitting'
812 > echo '% tricky header splitting'
813 > cat > trickyheaders.patch <<EOF
813 > cat > trickyheaders.patch <<EOF
814 > From: User A <user@a>
814 > From: User A <user@a>
815 > Subject: [PATCH] from: tricky!
815 > Subject: [PATCH] from: tricky!
816 >
816 >
817 > # HG changeset patch
817 > # HG changeset patch
818 > # User User B
818 > # User User B
819 > # Date 1266264441 18000
819 > # Date 1266264441 18000
820 > # Branch stable
820 > # Branch stable
821 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
821 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
822 > # Parent 0000000000000000000000000000000000000000
822 > # Parent 0000000000000000000000000000000000000000
823 > from: tricky!
823 > from: tricky!
824 >
824 >
825 > That is not a header.
825 > That is not a header.
826 >
826 >
827 > diff -r 000000000000 -r f2be6a1170ac foo
827 > diff -r 000000000000 -r f2be6a1170ac foo
828 > --- /dev/null
828 > --- /dev/null
829 > +++ b/foo
829 > +++ b/foo
830 > @@ -0,0 +1,1 @@
830 > @@ -0,0 +1,1 @@
831 > +foo
831 > +foo
832 > EOF
832 > EOF
833 applying a.patch
833 applying a.patch
834 changeset: 0:f34d9187897d
834 changeset: 0:f34d9187897d
835 tag: tip
835 tag: tip
836 user: test
836 user: test
837 date: Thu Jan 01 00:00:00 1970 +0000
837 date: Thu Jan 01 00:00:00 1970 +0000
838 files: a
838 files: a
839 description:
839 description:
840 module: summary
840 module: summary
841
841
842
842
843 % tricky header splitting
843 % tricky header splitting
844
844
845 $ hg init trickyheaders
845 $ hg init trickyheaders
846 $ cd trickyheaders
846 $ cd trickyheaders
847 $ hg import -d '0 0' ../trickyheaders.patch
847 $ hg import -d '0 0' ../trickyheaders.patch
848 applying ../trickyheaders.patch
848 applying ../trickyheaders.patch
849 $ hg export --git tip
849 $ hg export --git tip
850 # HG changeset patch
850 # HG changeset patch
851 # User User B
851 # User User B
852 # Date 0 0
852 # Date 0 0
853 # Node ID eb56ab91903632294ac504838508cb370c0901d2
853 # Node ID eb56ab91903632294ac504838508cb370c0901d2
854 # Parent 0000000000000000000000000000000000000000
854 # Parent 0000000000000000000000000000000000000000
855 from: tricky!
855 from: tricky!
856
856
857 That is not a header.
857 That is not a header.
858
858
859 diff --git a/foo b/foo
859 diff --git a/foo b/foo
860 new file mode 100644
860 new file mode 100644
861 --- /dev/null
861 --- /dev/null
862 +++ b/foo
862 +++ b/foo
863 @@ -0,0 +1,1 @@
863 @@ -0,0 +1,1 @@
864 +foo
864 +foo
865 $ cd ..
865 $ cd ..
866
866
867
867
868 Issue2102: hg export and hg import speak different languages
868 Issue2102: hg export and hg import speak different languages
869
869
870 $ hg init issue2102
870 $ hg init issue2102
871 $ cd issue2102
871 $ cd issue2102
872 $ mkdir -p src/cmd/gc
872 $ mkdir -p src/cmd/gc
873 $ touch src/cmd/gc/mksys.bash
873 $ touch src/cmd/gc/mksys.bash
874 $ hg ci -Am init
874 $ hg ci -Am init
875 adding src/cmd/gc/mksys.bash
875 adding src/cmd/gc/mksys.bash
876 $ hg import - <<EOF
876 $ hg import - <<EOF
877 > # HG changeset patch
877 > # HG changeset patch
878 > # User Rob Pike
878 > # User Rob Pike
879 > # Date 1216685449 25200
879 > # Date 1216685449 25200
880 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
880 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
881 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
881 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
882 > help management of empty pkg and lib directories in perforce
882 > help management of empty pkg and lib directories in perforce
883 >
883 >
884 > R=gri
884 > R=gri
885 > DELTA=4 (4 added, 0 deleted, 0 changed)
885 > DELTA=4 (4 added, 0 deleted, 0 changed)
886 > OCL=13328
886 > OCL=13328
887 > CL=13328
887 > CL=13328
888 >
888 >
889 > diff --git a/lib/place-holder b/lib/place-holder
889 > diff --git a/lib/place-holder b/lib/place-holder
890 > new file mode 100644
890 > new file mode 100644
891 > --- /dev/null
891 > --- /dev/null
892 > +++ b/lib/place-holder
892 > +++ b/lib/place-holder
893 > @@ -0,0 +1,2 @@
893 > @@ -0,0 +1,2 @@
894 > +perforce does not maintain empty directories.
894 > +perforce does not maintain empty directories.
895 > +this file helps.
895 > +this file helps.
896 > diff --git a/pkg/place-holder b/pkg/place-holder
896 > diff --git a/pkg/place-holder b/pkg/place-holder
897 > new file mode 100644
897 > new file mode 100644
898 > --- /dev/null
898 > --- /dev/null
899 > +++ b/pkg/place-holder
899 > +++ b/pkg/place-holder
900 > @@ -0,0 +1,2 @@
900 > @@ -0,0 +1,2 @@
901 > +perforce does not maintain empty directories.
901 > +perforce does not maintain empty directories.
902 > +this file helps.
902 > +this file helps.
903 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
903 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
904 > old mode 100644
904 > old mode 100644
905 > new mode 100755
905 > new mode 100755
906 > EOF
906 > EOF
907 applying patch from stdin
907 applying patch from stdin
908 $ hg sum
908 $ hg sum
909 parent: 1:d59915696727 tip
909 parent: 1:d59915696727 tip
910 help management of empty pkg and lib directories in perforce
910 help management of empty pkg and lib directories in perforce
911 branch: default
911 branch: default
912 commit: (clean)
912 commit: (clean)
913 update: (current)
913 update: (current)
914 $ hg diff --git -c tip
914 $ hg diff --git -c tip
915 diff --git a/lib/place-holder b/lib/place-holder
915 diff --git a/lib/place-holder b/lib/place-holder
916 new file mode 100644
916 new file mode 100644
917 --- /dev/null
917 --- /dev/null
918 +++ b/lib/place-holder
918 +++ b/lib/place-holder
919 @@ -0,0 +1,2 @@
919 @@ -0,0 +1,2 @@
920 +perforce does not maintain empty directories.
920 +perforce does not maintain empty directories.
921 +this file helps.
921 +this file helps.
922 diff --git a/pkg/place-holder b/pkg/place-holder
922 diff --git a/pkg/place-holder b/pkg/place-holder
923 new file mode 100644
923 new file mode 100644
924 --- /dev/null
924 --- /dev/null
925 +++ b/pkg/place-holder
925 +++ b/pkg/place-holder
926 @@ -0,0 +1,2 @@
926 @@ -0,0 +1,2 @@
927 +perforce does not maintain empty directories.
927 +perforce does not maintain empty directories.
928 +this file helps.
928 +this file helps.
929 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
929 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
930 old mode 100644
930 old mode 100644
931 new mode 100755
931 new mode 100755
932 $ cd ..
932 $ cd ..
933
933
934
934
935 diff lines looking like headers
935 diff lines looking like headers
936
936
937 $ hg init difflineslikeheaders
937 $ hg init difflineslikeheaders
938 $ cd difflineslikeheaders
938 $ cd difflineslikeheaders
939 $ echo a >a
939 $ echo a >a
940 $ echo b >b
940 $ echo b >b
941 $ echo c >c
941 $ echo c >c
942 $ hg ci -Am1
942 $ hg ci -Am1
943 adding a
943 adding a
944 adding b
944 adding b
945 adding c
945 adding c
946
946
947 $ echo "key: value" >>a
947 $ echo "key: value" >>a
948 $ echo "key: value" >>b
948 $ echo "key: value" >>b
949 $ echo "foo" >>c
949 $ echo "foo" >>c
950 $ hg ci -m2
950 $ hg ci -m2
951
951
952 $ hg up -C 0
952 $ hg up -C 0
953 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
953 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
954 $ hg diff --git -c1 >want
954 $ hg diff --git -c1 >want
955 $ hg diff -c1 | hg import --no-commit -
955 $ hg diff -c1 | hg import --no-commit -
956 applying patch from stdin
956 applying patch from stdin
957 $ hg diff --git >have
957 $ hg diff --git >have
958 $ diff want have
958 $ diff want have
959 $ cd ..
959 $ cd ..
960
960
961 import a unified diff with no lines of context (diff -U0)
962
963 $ hg init diffzero
964 $ cd diffzero
965 $ cat > f << EOF
966 > c2
967 > c4
968 > c5
969 > EOF
970 $ hg commit -Am0
971 adding f
972
973 $ hg import --no-commit - << EOF
974 > # HG changeset patch
975 > # User test
976 > # Date 0 0
977 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
978 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
979 > 1
980 > diff -r 8679a12a975b -r f4974ab632f3 f
981 > --- a/f Thu Jan 01 00:00:00 1970 +0000
982 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
983 > @@ -0,0 +1,1 @@
984 > +c1
985 > @@ -1,0 +3,1 @@
986 > +c3
987 > @@ -3,1 +4,0 @@
988 > -c5
989 > EOF
990 applying patch from stdin
991
992 $ cat f
993 c1
994 c2
995 c3
996 c4
General Comments 0
You need to be logged in to leave comments. Login now