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