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