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