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