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