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