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