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