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