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