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