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