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