##// END OF EJS Templates
patch.extract: fix test-import breakage introduced in the previous changeset
Brendan Cully -
r4778:e321f16f default
parent child Browse files
Show More
@@ -1,674 +1,675 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 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import base85, cmdutil, mdiff, util, context, revlog
10 import base85, cmdutil, mdiff, util, context, revlog
11 import cStringIO, email.Parser, os, popen2, re, sha
11 import cStringIO, email.Parser, os, popen2, re, sha
12 import sys, tempfile, zlib
12 import sys, tempfile, zlib
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28
28
29 util.copyfile(abssrc, absdst)
29 util.copyfile(abssrc, absdst)
30
30
31 # public functions
31 # public functions
32
32
33 def extract(ui, fileobj):
33 def extract(ui, fileobj):
34 '''extract patch from data read from fileobj.
34 '''extract patch from data read from fileobj.
35
35
36 patch can be a normal patch or contained in an email message.
36 patch can be a normal patch or contained in an email message.
37
37
38 return tuple (filename, message, user, date, node, p1, p2).
38 return tuple (filename, message, user, date, node, p1, p2).
39 Any item in the returned tuple can be None. If filename is None,
39 Any item in the returned tuple can be None. If filename is None,
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
41
41
42 # attempt to detect the start of a patch
42 # attempt to detect the start of a patch
43 # (this heuristic is borrowed from quilt)
43 # (this heuristic is borrowed from quilt)
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47
47
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 tmpfp = os.fdopen(fd, 'w')
49 tmpfp = os.fdopen(fd, 'w')
50 try:
50 try:
51 msg = email.Parser.Parser().parse(fileobj)
51 msg = email.Parser.Parser().parse(fileobj)
52
52
53 subject = msg['Subject']
53 subject = msg['Subject']
54 user = msg['From']
54 user = msg['From']
55 # should try to parse msg['Date']
55 # should try to parse msg['Date']
56 date = None
56 date = None
57 nodeid = None
57 nodeid = None
58 branch = None
58 branch = None
59 parents = []
59 parents = []
60
60
61 if subject:
61 if subject:
62 if subject.startswith('[PATCH'):
62 if subject.startswith('[PATCH'):
63 pend = subject.find(']')
63 pend = subject.find(']')
64 if pend >= 0:
64 if pend >= 0:
65 subject = subject[pend+1:].lstrip()
65 subject = subject[pend+1:].lstrip()
66 subject = subject.replace('\n\t', ' ')
66 subject = subject.replace('\n\t', ' ')
67 ui.debug('Subject: %s\n' % subject)
67 ui.debug('Subject: %s\n' % subject)
68 if user:
68 if user:
69 ui.debug('From: %s\n' % user)
69 ui.debug('From: %s\n' % user)
70 diffs_seen = 0
70 diffs_seen = 0
71 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
71 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
72
72
73 for part in msg.walk():
73 for part in msg.walk():
74 content_type = part.get_content_type()
74 content_type = part.get_content_type()
75 ui.debug('Content-Type: %s\n' % content_type)
75 ui.debug('Content-Type: %s\n' % content_type)
76 if content_type not in ok_types:
76 if content_type not in ok_types:
77 continue
77 continue
78 payload = part.get_payload(decode=True)
78 payload = part.get_payload(decode=True)
79 m = diffre.search(payload)
79 m = diffre.search(payload)
80 if m:
80 if m:
81 hgpatch = False
81 hgpatch = False
82 ignoretext = False
82 ignoretext = False
83
83
84 ui.debug(_('found patch at byte %d\n') % m.start(0))
84 ui.debug(_('found patch at byte %d\n') % m.start(0))
85 diffs_seen += 1
85 diffs_seen += 1
86 cfp = cStringIO.StringIO()
86 cfp = cStringIO.StringIO()
87 for line in payload[:m.start(0)].splitlines():
87 for line in payload[:m.start(0)].splitlines():
88 if line.startswith('# HG changeset patch'):
88 if line.startswith('# HG changeset patch'):
89 ui.debug(_('patch generated by hg export\n'))
89 ui.debug(_('patch generated by hg export\n'))
90 hgpatch = True
90 hgpatch = True
91 # drop earlier commit message content
91 # drop earlier commit message content
92 cfp.seek(0)
92 cfp.seek(0)
93 cfp.truncate()
93 cfp.truncate()
94 subject = None
94 elif hgpatch:
95 elif hgpatch:
95 if line.startswith('# User '):
96 if line.startswith('# User '):
96 user = line[7:]
97 user = line[7:]
97 ui.debug('From: %s\n' % user)
98 ui.debug('From: %s\n' % user)
98 elif line.startswith("# Date "):
99 elif line.startswith("# Date "):
99 date = line[7:]
100 date = line[7:]
100 elif line.startswith("# Branch "):
101 elif line.startswith("# Branch "):
101 branch = line[9:]
102 branch = line[9:]
102 elif line.startswith("# Node ID "):
103 elif line.startswith("# Node ID "):
103 nodeid = line[10:]
104 nodeid = line[10:]
104 elif line.startswith("# Parent "):
105 elif line.startswith("# Parent "):
105 parents.append(line[10:])
106 parents.append(line[10:])
106 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
107 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
107 ignoretext = True
108 ignoretext = True
108 if not line.startswith('# ') and not ignoretext:
109 if not line.startswith('# ') and not ignoretext:
109 cfp.write(line)
110 cfp.write(line)
110 cfp.write('\n')
111 cfp.write('\n')
111 message = cfp.getvalue()
112 message = cfp.getvalue()
112 if tmpfp:
113 if tmpfp:
113 tmpfp.write(payload)
114 tmpfp.write(payload)
114 if not payload.endswith('\n'):
115 if not payload.endswith('\n'):
115 tmpfp.write('\n')
116 tmpfp.write('\n')
116 elif not diffs_seen and message and content_type == 'text/plain':
117 elif not diffs_seen and message and content_type == 'text/plain':
117 message += '\n' + payload
118 message += '\n' + payload
118 except:
119 except:
119 tmpfp.close()
120 tmpfp.close()
120 os.unlink(tmpname)
121 os.unlink(tmpname)
121 raise
122 raise
122
123
123 if subject and not message.startswith(subject):
124 if subject and not message.startswith(subject):
124 message = '%s\n%s' % (subject, message)
125 message = '%s\n%s' % (subject, message)
125 tmpfp.close()
126 tmpfp.close()
126 if not diffs_seen:
127 if not diffs_seen:
127 os.unlink(tmpname)
128 os.unlink(tmpname)
128 return None, message, user, date, branch, None, None, None
129 return None, message, user, date, branch, None, None, None
129 p1 = parents and parents.pop(0) or None
130 p1 = parents and parents.pop(0) or None
130 p2 = parents and parents.pop(0) or None
131 p2 = parents and parents.pop(0) or None
131 return tmpname, message, user, date, branch, nodeid, p1, p2
132 return tmpname, message, user, date, branch, nodeid, p1, p2
132
133
133 GP_PATCH = 1 << 0 # we have to run patch
134 GP_PATCH = 1 << 0 # we have to run patch
134 GP_FILTER = 1 << 1 # there's some copy/rename operation
135 GP_FILTER = 1 << 1 # there's some copy/rename operation
135 GP_BINARY = 1 << 2 # there's a binary patch
136 GP_BINARY = 1 << 2 # there's a binary patch
136
137
137 def readgitpatch(patchname):
138 def readgitpatch(patchname):
138 """extract git-style metadata about patches from <patchname>"""
139 """extract git-style metadata about patches from <patchname>"""
139 class gitpatch:
140 class gitpatch:
140 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
141 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
141 def __init__(self, path):
142 def __init__(self, path):
142 self.path = path
143 self.path = path
143 self.oldpath = None
144 self.oldpath = None
144 self.mode = None
145 self.mode = None
145 self.op = 'MODIFY'
146 self.op = 'MODIFY'
146 self.copymod = False
147 self.copymod = False
147 self.lineno = 0
148 self.lineno = 0
148 self.binary = False
149 self.binary = False
149
150
150 # Filter patch for git information
151 # Filter patch for git information
151 gitre = re.compile('diff --git a/(.*) b/(.*)')
152 gitre = re.compile('diff --git a/(.*) b/(.*)')
152 pf = file(patchname)
153 pf = file(patchname)
153 gp = None
154 gp = None
154 gitpatches = []
155 gitpatches = []
155 # Can have a git patch with only metadata, causing patch to complain
156 # Can have a git patch with only metadata, causing patch to complain
156 dopatch = 0
157 dopatch = 0
157
158
158 lineno = 0
159 lineno = 0
159 for line in pf:
160 for line in pf:
160 lineno += 1
161 lineno += 1
161 if line.startswith('diff --git'):
162 if line.startswith('diff --git'):
162 m = gitre.match(line)
163 m = gitre.match(line)
163 if m:
164 if m:
164 if gp:
165 if gp:
165 gitpatches.append(gp)
166 gitpatches.append(gp)
166 src, dst = m.group(1, 2)
167 src, dst = m.group(1, 2)
167 gp = gitpatch(dst)
168 gp = gitpatch(dst)
168 gp.lineno = lineno
169 gp.lineno = lineno
169 elif gp:
170 elif gp:
170 if line.startswith('--- '):
171 if line.startswith('--- '):
171 if gp.op in ('COPY', 'RENAME'):
172 if gp.op in ('COPY', 'RENAME'):
172 gp.copymod = True
173 gp.copymod = True
173 dopatch |= GP_FILTER
174 dopatch |= GP_FILTER
174 gitpatches.append(gp)
175 gitpatches.append(gp)
175 gp = None
176 gp = None
176 dopatch |= GP_PATCH
177 dopatch |= GP_PATCH
177 continue
178 continue
178 if line.startswith('rename from '):
179 if line.startswith('rename from '):
179 gp.op = 'RENAME'
180 gp.op = 'RENAME'
180 gp.oldpath = line[12:].rstrip()
181 gp.oldpath = line[12:].rstrip()
181 elif line.startswith('rename to '):
182 elif line.startswith('rename to '):
182 gp.path = line[10:].rstrip()
183 gp.path = line[10:].rstrip()
183 elif line.startswith('copy from '):
184 elif line.startswith('copy from '):
184 gp.op = 'COPY'
185 gp.op = 'COPY'
185 gp.oldpath = line[10:].rstrip()
186 gp.oldpath = line[10:].rstrip()
186 elif line.startswith('copy to '):
187 elif line.startswith('copy to '):
187 gp.path = line[8:].rstrip()
188 gp.path = line[8:].rstrip()
188 elif line.startswith('deleted file'):
189 elif line.startswith('deleted file'):
189 gp.op = 'DELETE'
190 gp.op = 'DELETE'
190 elif line.startswith('new file mode '):
191 elif line.startswith('new file mode '):
191 gp.op = 'ADD'
192 gp.op = 'ADD'
192 gp.mode = int(line.rstrip()[-3:], 8)
193 gp.mode = int(line.rstrip()[-3:], 8)
193 elif line.startswith('new mode '):
194 elif line.startswith('new mode '):
194 gp.mode = int(line.rstrip()[-3:], 8)
195 gp.mode = int(line.rstrip()[-3:], 8)
195 elif line.startswith('GIT binary patch'):
196 elif line.startswith('GIT binary patch'):
196 dopatch |= GP_BINARY
197 dopatch |= GP_BINARY
197 gp.binary = True
198 gp.binary = True
198 if gp:
199 if gp:
199 gitpatches.append(gp)
200 gitpatches.append(gp)
200
201
201 if not gitpatches:
202 if not gitpatches:
202 dopatch = GP_PATCH
203 dopatch = GP_PATCH
203
204
204 return (dopatch, gitpatches)
205 return (dopatch, gitpatches)
205
206
206 def dogitpatch(patchname, gitpatches, cwd=None):
207 def dogitpatch(patchname, gitpatches, cwd=None):
207 """Preprocess git patch so that vanilla patch can handle it"""
208 """Preprocess git patch so that vanilla patch can handle it"""
208 def extractbin(fp):
209 def extractbin(fp):
209 i = [0] # yuck
210 i = [0] # yuck
210 def readline():
211 def readline():
211 i[0] += 1
212 i[0] += 1
212 return fp.readline().rstrip()
213 return fp.readline().rstrip()
213 line = readline()
214 line = readline()
214 while line and not line.startswith('literal '):
215 while line and not line.startswith('literal '):
215 line = readline()
216 line = readline()
216 if not line:
217 if not line:
217 return None, i[0]
218 return None, i[0]
218 size = int(line[8:])
219 size = int(line[8:])
219 dec = []
220 dec = []
220 line = readline()
221 line = readline()
221 while line:
222 while line:
222 l = line[0]
223 l = line[0]
223 if l <= 'Z' and l >= 'A':
224 if l <= 'Z' and l >= 'A':
224 l = ord(l) - ord('A') + 1
225 l = ord(l) - ord('A') + 1
225 else:
226 else:
226 l = ord(l) - ord('a') + 27
227 l = ord(l) - ord('a') + 27
227 dec.append(base85.b85decode(line[1:])[:l])
228 dec.append(base85.b85decode(line[1:])[:l])
228 line = readline()
229 line = readline()
229 text = zlib.decompress(''.join(dec))
230 text = zlib.decompress(''.join(dec))
230 if len(text) != size:
231 if len(text) != size:
231 raise util.Abort(_('binary patch is %d bytes, not %d') %
232 raise util.Abort(_('binary patch is %d bytes, not %d') %
232 (len(text), size))
233 (len(text), size))
233 return text, i[0]
234 return text, i[0]
234
235
235 pf = file(patchname)
236 pf = file(patchname)
236 pfline = 1
237 pfline = 1
237
238
238 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
239 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
239 tmpfp = os.fdopen(fd, 'w')
240 tmpfp = os.fdopen(fd, 'w')
240
241
241 try:
242 try:
242 for i in xrange(len(gitpatches)):
243 for i in xrange(len(gitpatches)):
243 p = gitpatches[i]
244 p = gitpatches[i]
244 if not p.copymod and not p.binary:
245 if not p.copymod and not p.binary:
245 continue
246 continue
246
247
247 # rewrite patch hunk
248 # rewrite patch hunk
248 while pfline < p.lineno:
249 while pfline < p.lineno:
249 tmpfp.write(pf.readline())
250 tmpfp.write(pf.readline())
250 pfline += 1
251 pfline += 1
251
252
252 if p.binary:
253 if p.binary:
253 text, delta = extractbin(pf)
254 text, delta = extractbin(pf)
254 if not text:
255 if not text:
255 raise util.Abort(_('binary patch extraction failed'))
256 raise util.Abort(_('binary patch extraction failed'))
256 pfline += delta
257 pfline += delta
257 if not cwd:
258 if not cwd:
258 cwd = os.getcwd()
259 cwd = os.getcwd()
259 absdst = os.path.join(cwd, p.path)
260 absdst = os.path.join(cwd, p.path)
260 basedir = os.path.dirname(absdst)
261 basedir = os.path.dirname(absdst)
261 if not os.path.isdir(basedir):
262 if not os.path.isdir(basedir):
262 os.makedirs(basedir)
263 os.makedirs(basedir)
263 out = file(absdst, 'wb')
264 out = file(absdst, 'wb')
264 out.write(text)
265 out.write(text)
265 out.close()
266 out.close()
266 elif p.copymod:
267 elif p.copymod:
267 copyfile(p.oldpath, p.path, basedir=cwd)
268 copyfile(p.oldpath, p.path, basedir=cwd)
268 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
269 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
269 line = pf.readline()
270 line = pf.readline()
270 pfline += 1
271 pfline += 1
271 while not line.startswith('--- a/'):
272 while not line.startswith('--- a/'):
272 tmpfp.write(line)
273 tmpfp.write(line)
273 line = pf.readline()
274 line = pf.readline()
274 pfline += 1
275 pfline += 1
275 tmpfp.write('--- a/%s\n' % p.path)
276 tmpfp.write('--- a/%s\n' % p.path)
276
277
277 line = pf.readline()
278 line = pf.readline()
278 while line:
279 while line:
279 tmpfp.write(line)
280 tmpfp.write(line)
280 line = pf.readline()
281 line = pf.readline()
281 except:
282 except:
282 tmpfp.close()
283 tmpfp.close()
283 os.unlink(patchname)
284 os.unlink(patchname)
284 raise
285 raise
285
286
286 tmpfp.close()
287 tmpfp.close()
287 return patchname
288 return patchname
288
289
289 def patch(patchname, ui, strip=1, cwd=None, files={}):
290 def patch(patchname, ui, strip=1, cwd=None, files={}):
290 """apply the patch <patchname> to the working directory.
291 """apply the patch <patchname> to the working directory.
291 a list of patched files is returned"""
292 a list of patched files is returned"""
292
293
293 # helper function
294 # helper function
294 def __patch(patchname):
295 def __patch(patchname):
295 """patch and updates the files and fuzz variables"""
296 """patch and updates the files and fuzz variables"""
296 fuzz = False
297 fuzz = False
297
298
298 args = []
299 args = []
299 patcher = ui.config('ui', 'patch')
300 patcher = ui.config('ui', 'patch')
300 if not patcher:
301 if not patcher:
301 patcher = util.find_exe('gpatch') or util.find_exe('patch')
302 patcher = util.find_exe('gpatch') or util.find_exe('patch')
302 # Try to be smart only if patch call was not supplied
303 # Try to be smart only if patch call was not supplied
303 if util.needbinarypatch():
304 if util.needbinarypatch():
304 args.append('--binary')
305 args.append('--binary')
305
306
306 if not patcher:
307 if not patcher:
307 raise util.Abort(_('no patch command found in hgrc or PATH'))
308 raise util.Abort(_('no patch command found in hgrc or PATH'))
308
309
309 if cwd:
310 if cwd:
310 args.append('-d %s' % util.shellquote(cwd))
311 args.append('-d %s' % util.shellquote(cwd))
311 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
312 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
312 util.shellquote(patchname)))
313 util.shellquote(patchname)))
313
314
314 for line in fp:
315 for line in fp:
315 line = line.rstrip()
316 line = line.rstrip()
316 ui.note(line + '\n')
317 ui.note(line + '\n')
317 if line.startswith('patching file '):
318 if line.startswith('patching file '):
318 pf = util.parse_patch_output(line)
319 pf = util.parse_patch_output(line)
319 printed_file = False
320 printed_file = False
320 files.setdefault(pf, (None, None))
321 files.setdefault(pf, (None, None))
321 elif line.find('with fuzz') >= 0:
322 elif line.find('with fuzz') >= 0:
322 fuzz = True
323 fuzz = True
323 if not printed_file:
324 if not printed_file:
324 ui.warn(pf + '\n')
325 ui.warn(pf + '\n')
325 printed_file = True
326 printed_file = True
326 ui.warn(line + '\n')
327 ui.warn(line + '\n')
327 elif line.find('saving rejects to file') >= 0:
328 elif line.find('saving rejects to file') >= 0:
328 ui.warn(line + '\n')
329 ui.warn(line + '\n')
329 elif line.find('FAILED') >= 0:
330 elif line.find('FAILED') >= 0:
330 if not printed_file:
331 if not printed_file:
331 ui.warn(pf + '\n')
332 ui.warn(pf + '\n')
332 printed_file = True
333 printed_file = True
333 ui.warn(line + '\n')
334 ui.warn(line + '\n')
334 code = fp.close()
335 code = fp.close()
335 if code:
336 if code:
336 raise util.Abort(_("patch command failed: %s") %
337 raise util.Abort(_("patch command failed: %s") %
337 util.explain_exit(code)[0])
338 util.explain_exit(code)[0])
338 return fuzz
339 return fuzz
339
340
340 (dopatch, gitpatches) = readgitpatch(patchname)
341 (dopatch, gitpatches) = readgitpatch(patchname)
341 for gp in gitpatches:
342 for gp in gitpatches:
342 files[gp.path] = (gp.op, gp)
343 files[gp.path] = (gp.op, gp)
343
344
344 fuzz = False
345 fuzz = False
345 if dopatch:
346 if dopatch:
346 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
347 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
347 if filterpatch:
348 if filterpatch:
348 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
349 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
349 try:
350 try:
350 if dopatch & GP_PATCH:
351 if dopatch & GP_PATCH:
351 fuzz = __patch(patchname)
352 fuzz = __patch(patchname)
352 finally:
353 finally:
353 if filterpatch:
354 if filterpatch:
354 os.unlink(patchname)
355 os.unlink(patchname)
355
356
356 return fuzz
357 return fuzz
357
358
358 def diffopts(ui, opts={}, untrusted=False):
359 def diffopts(ui, opts={}, untrusted=False):
359 def get(key, name=None):
360 def get(key, name=None):
360 return (opts.get(key) or
361 return (opts.get(key) or
361 ui.configbool('diff', name or key, None, untrusted=untrusted))
362 ui.configbool('diff', name or key, None, untrusted=untrusted))
362 return mdiff.diffopts(
363 return mdiff.diffopts(
363 text=opts.get('text'),
364 text=opts.get('text'),
364 git=get('git'),
365 git=get('git'),
365 nodates=get('nodates'),
366 nodates=get('nodates'),
366 showfunc=get('show_function', 'showfunc'),
367 showfunc=get('show_function', 'showfunc'),
367 ignorews=get('ignore_all_space', 'ignorews'),
368 ignorews=get('ignore_all_space', 'ignorews'),
368 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
369 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
369 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
370 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
370
371
371 def updatedir(ui, repo, patches, wlock=None):
372 def updatedir(ui, repo, patches, wlock=None):
372 '''Update dirstate after patch application according to metadata'''
373 '''Update dirstate after patch application according to metadata'''
373 if not patches:
374 if not patches:
374 return
375 return
375 copies = []
376 copies = []
376 removes = {}
377 removes = {}
377 cfiles = patches.keys()
378 cfiles = patches.keys()
378 cwd = repo.getcwd()
379 cwd = repo.getcwd()
379 if cwd:
380 if cwd:
380 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
381 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
381 for f in patches:
382 for f in patches:
382 ctype, gp = patches[f]
383 ctype, gp = patches[f]
383 if ctype == 'RENAME':
384 if ctype == 'RENAME':
384 copies.append((gp.oldpath, gp.path, gp.copymod))
385 copies.append((gp.oldpath, gp.path, gp.copymod))
385 removes[gp.oldpath] = 1
386 removes[gp.oldpath] = 1
386 elif ctype == 'COPY':
387 elif ctype == 'COPY':
387 copies.append((gp.oldpath, gp.path, gp.copymod))
388 copies.append((gp.oldpath, gp.path, gp.copymod))
388 elif ctype == 'DELETE':
389 elif ctype == 'DELETE':
389 removes[gp.path] = 1
390 removes[gp.path] = 1
390 for src, dst, after in copies:
391 for src, dst, after in copies:
391 if not after:
392 if not after:
392 copyfile(src, dst, repo.root)
393 copyfile(src, dst, repo.root)
393 repo.copy(src, dst, wlock=wlock)
394 repo.copy(src, dst, wlock=wlock)
394 removes = removes.keys()
395 removes = removes.keys()
395 if removes:
396 if removes:
396 removes.sort()
397 removes.sort()
397 repo.remove(removes, True, wlock=wlock)
398 repo.remove(removes, True, wlock=wlock)
398 for f in patches:
399 for f in patches:
399 ctype, gp = patches[f]
400 ctype, gp = patches[f]
400 if gp and gp.mode:
401 if gp and gp.mode:
401 x = gp.mode & 0100 != 0
402 x = gp.mode & 0100 != 0
402 dst = os.path.join(repo.root, gp.path)
403 dst = os.path.join(repo.root, gp.path)
403 # patch won't create empty files
404 # patch won't create empty files
404 if ctype == 'ADD' and not os.path.exists(dst):
405 if ctype == 'ADD' and not os.path.exists(dst):
405 repo.wwrite(gp.path, '', x and 'x' or '')
406 repo.wwrite(gp.path, '', x and 'x' or '')
406 else:
407 else:
407 util.set_exec(dst, x)
408 util.set_exec(dst, x)
408 cmdutil.addremove(repo, cfiles, wlock=wlock)
409 cmdutil.addremove(repo, cfiles, wlock=wlock)
409 files = patches.keys()
410 files = patches.keys()
410 files.extend([r for r in removes if r not in files])
411 files.extend([r for r in removes if r not in files])
411 files.sort()
412 files.sort()
412
413
413 return files
414 return files
414
415
415 def b85diff(fp, to, tn):
416 def b85diff(fp, to, tn):
416 '''print base85-encoded binary diff'''
417 '''print base85-encoded binary diff'''
417 def gitindex(text):
418 def gitindex(text):
418 if not text:
419 if not text:
419 return '0' * 40
420 return '0' * 40
420 l = len(text)
421 l = len(text)
421 s = sha.new('blob %d\0' % l)
422 s = sha.new('blob %d\0' % l)
422 s.update(text)
423 s.update(text)
423 return s.hexdigest()
424 return s.hexdigest()
424
425
425 def fmtline(line):
426 def fmtline(line):
426 l = len(line)
427 l = len(line)
427 if l <= 26:
428 if l <= 26:
428 l = chr(ord('A') + l - 1)
429 l = chr(ord('A') + l - 1)
429 else:
430 else:
430 l = chr(l - 26 + ord('a') - 1)
431 l = chr(l - 26 + ord('a') - 1)
431 return '%c%s\n' % (l, base85.b85encode(line, True))
432 return '%c%s\n' % (l, base85.b85encode(line, True))
432
433
433 def chunk(text, csize=52):
434 def chunk(text, csize=52):
434 l = len(text)
435 l = len(text)
435 i = 0
436 i = 0
436 while i < l:
437 while i < l:
437 yield text[i:i+csize]
438 yield text[i:i+csize]
438 i += csize
439 i += csize
439
440
440 tohash = gitindex(to)
441 tohash = gitindex(to)
441 tnhash = gitindex(tn)
442 tnhash = gitindex(tn)
442 if tohash == tnhash:
443 if tohash == tnhash:
443 return ""
444 return ""
444
445
445 # TODO: deltas
446 # TODO: deltas
446 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
447 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
447 (tohash, tnhash, len(tn))]
448 (tohash, tnhash, len(tn))]
448 for l in chunk(zlib.compress(tn)):
449 for l in chunk(zlib.compress(tn)):
449 ret.append(fmtline(l))
450 ret.append(fmtline(l))
450 ret.append('\n')
451 ret.append('\n')
451 return ''.join(ret)
452 return ''.join(ret)
452
453
453 def diff(repo, node1=None, node2=None, files=None, match=util.always,
454 def diff(repo, node1=None, node2=None, files=None, match=util.always,
454 fp=None, changes=None, opts=None):
455 fp=None, changes=None, opts=None):
455 '''print diff of changes to files between two nodes, or node and
456 '''print diff of changes to files between two nodes, or node and
456 working directory.
457 working directory.
457
458
458 if node1 is None, use first dirstate parent instead.
459 if node1 is None, use first dirstate parent instead.
459 if node2 is None, compare node1 with working directory.'''
460 if node2 is None, compare node1 with working directory.'''
460
461
461 if opts is None:
462 if opts is None:
462 opts = mdiff.defaultopts
463 opts = mdiff.defaultopts
463 if fp is None:
464 if fp is None:
464 fp = repo.ui
465 fp = repo.ui
465
466
466 if not node1:
467 if not node1:
467 node1 = repo.dirstate.parents()[0]
468 node1 = repo.dirstate.parents()[0]
468
469
469 ccache = {}
470 ccache = {}
470 def getctx(r):
471 def getctx(r):
471 if r not in ccache:
472 if r not in ccache:
472 ccache[r] = context.changectx(repo, r)
473 ccache[r] = context.changectx(repo, r)
473 return ccache[r]
474 return ccache[r]
474
475
475 flcache = {}
476 flcache = {}
476 def getfilectx(f, ctx):
477 def getfilectx(f, ctx):
477 flctx = ctx.filectx(f, filelog=flcache.get(f))
478 flctx = ctx.filectx(f, filelog=flcache.get(f))
478 if f not in flcache:
479 if f not in flcache:
479 flcache[f] = flctx._filelog
480 flcache[f] = flctx._filelog
480 return flctx
481 return flctx
481
482
482 # reading the data for node1 early allows it to play nicely
483 # reading the data for node1 early allows it to play nicely
483 # with repo.status and the revlog cache.
484 # with repo.status and the revlog cache.
484 ctx1 = context.changectx(repo, node1)
485 ctx1 = context.changectx(repo, node1)
485 # force manifest reading
486 # force manifest reading
486 man1 = ctx1.manifest()
487 man1 = ctx1.manifest()
487 date1 = util.datestr(ctx1.date())
488 date1 = util.datestr(ctx1.date())
488
489
489 if not changes:
490 if not changes:
490 changes = repo.status(node1, node2, files, match=match)[:5]
491 changes = repo.status(node1, node2, files, match=match)[:5]
491 modified, added, removed, deleted, unknown = changes
492 modified, added, removed, deleted, unknown = changes
492
493
493 if not modified and not added and not removed:
494 if not modified and not added and not removed:
494 return
495 return
495
496
496 if node2:
497 if node2:
497 ctx2 = context.changectx(repo, node2)
498 ctx2 = context.changectx(repo, node2)
498 execf2 = ctx2.manifest().execf
499 execf2 = ctx2.manifest().execf
499 else:
500 else:
500 ctx2 = context.workingctx(repo)
501 ctx2 = context.workingctx(repo)
501 execf2 = util.execfunc(repo.root, None)
502 execf2 = util.execfunc(repo.root, None)
502 if execf2 is None:
503 if execf2 is None:
503 execf2 = ctx2.parents()[0].manifest().copy().execf
504 execf2 = ctx2.parents()[0].manifest().copy().execf
504
505
505 # returns False if there was no rename between ctx1 and ctx2
506 # returns False if there was no rename between ctx1 and ctx2
506 # returns None if the file was created between ctx1 and ctx2
507 # returns None if the file was created between ctx1 and ctx2
507 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
508 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
508 def renamed(f):
509 def renamed(f):
509 startrev = ctx1.rev()
510 startrev = ctx1.rev()
510 c = ctx2
511 c = ctx2
511 crev = c.rev()
512 crev = c.rev()
512 if crev is None:
513 if crev is None:
513 crev = repo.changelog.count()
514 crev = repo.changelog.count()
514 orig = f
515 orig = f
515 while crev > startrev:
516 while crev > startrev:
516 if f in c.files():
517 if f in c.files():
517 try:
518 try:
518 src = getfilectx(f, c).renamed()
519 src = getfilectx(f, c).renamed()
519 except revlog.LookupError:
520 except revlog.LookupError:
520 return None
521 return None
521 if src:
522 if src:
522 f = src[0]
523 f = src[0]
523 crev = c.parents()[0].rev()
524 crev = c.parents()[0].rev()
524 # try to reuse
525 # try to reuse
525 c = getctx(crev)
526 c = getctx(crev)
526 if f not in man1:
527 if f not in man1:
527 return None
528 return None
528 if f == orig:
529 if f == orig:
529 return False
530 return False
530 return f
531 return f
531
532
532 if repo.ui.quiet:
533 if repo.ui.quiet:
533 r = None
534 r = None
534 else:
535 else:
535 hexfunc = repo.ui.debugflag and hex or short
536 hexfunc = repo.ui.debugflag and hex or short
536 r = [hexfunc(node) for node in [node1, node2] if node]
537 r = [hexfunc(node) for node in [node1, node2] if node]
537
538
538 if opts.git:
539 if opts.git:
539 copied = {}
540 copied = {}
540 for f in added:
541 for f in added:
541 src = renamed(f)
542 src = renamed(f)
542 if src:
543 if src:
543 copied[f] = src
544 copied[f] = src
544 srcs = [x[1] for x in copied.items()]
545 srcs = [x[1] for x in copied.items()]
545
546
546 all = modified + added + removed
547 all = modified + added + removed
547 all.sort()
548 all.sort()
548 gone = {}
549 gone = {}
549
550
550 for f in all:
551 for f in all:
551 to = None
552 to = None
552 tn = None
553 tn = None
553 dodiff = True
554 dodiff = True
554 header = []
555 header = []
555 if f in man1:
556 if f in man1:
556 to = getfilectx(f, ctx1).data()
557 to = getfilectx(f, ctx1).data()
557 if f not in removed:
558 if f not in removed:
558 tn = getfilectx(f, ctx2).data()
559 tn = getfilectx(f, ctx2).data()
559 if opts.git:
560 if opts.git:
560 def gitmode(x):
561 def gitmode(x):
561 return x and '100755' or '100644'
562 return x and '100755' or '100644'
562 def addmodehdr(header, omode, nmode):
563 def addmodehdr(header, omode, nmode):
563 if omode != nmode:
564 if omode != nmode:
564 header.append('old mode %s\n' % omode)
565 header.append('old mode %s\n' % omode)
565 header.append('new mode %s\n' % nmode)
566 header.append('new mode %s\n' % nmode)
566
567
567 a, b = f, f
568 a, b = f, f
568 if f in added:
569 if f in added:
569 mode = gitmode(execf2(f))
570 mode = gitmode(execf2(f))
570 if f in copied:
571 if f in copied:
571 a = copied[f]
572 a = copied[f]
572 omode = gitmode(man1.execf(a))
573 omode = gitmode(man1.execf(a))
573 addmodehdr(header, omode, mode)
574 addmodehdr(header, omode, mode)
574 if a in removed and a not in gone:
575 if a in removed and a not in gone:
575 op = 'rename'
576 op = 'rename'
576 gone[a] = 1
577 gone[a] = 1
577 else:
578 else:
578 op = 'copy'
579 op = 'copy'
579 header.append('%s from %s\n' % (op, a))
580 header.append('%s from %s\n' % (op, a))
580 header.append('%s to %s\n' % (op, f))
581 header.append('%s to %s\n' % (op, f))
581 to = getfilectx(a, ctx1).data()
582 to = getfilectx(a, ctx1).data()
582 else:
583 else:
583 header.append('new file mode %s\n' % mode)
584 header.append('new file mode %s\n' % mode)
584 if util.binary(tn):
585 if util.binary(tn):
585 dodiff = 'binary'
586 dodiff = 'binary'
586 elif f in removed:
587 elif f in removed:
587 if f in srcs:
588 if f in srcs:
588 dodiff = False
589 dodiff = False
589 else:
590 else:
590 mode = gitmode(man1.execf(f))
591 mode = gitmode(man1.execf(f))
591 header.append('deleted file mode %s\n' % mode)
592 header.append('deleted file mode %s\n' % mode)
592 else:
593 else:
593 omode = gitmode(man1.execf(f))
594 omode = gitmode(man1.execf(f))
594 nmode = gitmode(execf2(f))
595 nmode = gitmode(execf2(f))
595 addmodehdr(header, omode, nmode)
596 addmodehdr(header, omode, nmode)
596 if util.binary(to) or util.binary(tn):
597 if util.binary(to) or util.binary(tn):
597 dodiff = 'binary'
598 dodiff = 'binary'
598 r = None
599 r = None
599 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
600 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
600 if dodiff:
601 if dodiff:
601 if dodiff == 'binary':
602 if dodiff == 'binary':
602 text = b85diff(fp, to, tn)
603 text = b85diff(fp, to, tn)
603 else:
604 else:
604 text = mdiff.unidiff(to, date1,
605 text = mdiff.unidiff(to, date1,
605 # ctx2 date may be dynamic
606 # ctx2 date may be dynamic
606 tn, util.datestr(ctx2.date()),
607 tn, util.datestr(ctx2.date()),
607 f, r, opts=opts)
608 f, r, opts=opts)
608 if text or len(header) > 1:
609 if text or len(header) > 1:
609 fp.write(''.join(header))
610 fp.write(''.join(header))
610 fp.write(text)
611 fp.write(text)
611
612
612 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
613 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
613 opts=None):
614 opts=None):
614 '''export changesets as hg patches.'''
615 '''export changesets as hg patches.'''
615
616
616 total = len(revs)
617 total = len(revs)
617 revwidth = max([len(str(rev)) for rev in revs])
618 revwidth = max([len(str(rev)) for rev in revs])
618
619
619 def single(rev, seqno, fp):
620 def single(rev, seqno, fp):
620 ctx = repo.changectx(rev)
621 ctx = repo.changectx(rev)
621 node = ctx.node()
622 node = ctx.node()
622 parents = [p.node() for p in ctx.parents() if p]
623 parents = [p.node() for p in ctx.parents() if p]
623 branch = ctx.branch()
624 branch = ctx.branch()
624 if switch_parent:
625 if switch_parent:
625 parents.reverse()
626 parents.reverse()
626 prev = (parents and parents[0]) or nullid
627 prev = (parents and parents[0]) or nullid
627
628
628 if not fp:
629 if not fp:
629 fp = cmdutil.make_file(repo, template, node, total=total,
630 fp = cmdutil.make_file(repo, template, node, total=total,
630 seqno=seqno, revwidth=revwidth)
631 seqno=seqno, revwidth=revwidth)
631 if fp != sys.stdout and hasattr(fp, 'name'):
632 if fp != sys.stdout and hasattr(fp, 'name'):
632 repo.ui.note("%s\n" % fp.name)
633 repo.ui.note("%s\n" % fp.name)
633
634
634 fp.write("# HG changeset patch\n")
635 fp.write("# HG changeset patch\n")
635 fp.write("# User %s\n" % ctx.user())
636 fp.write("# User %s\n" % ctx.user())
636 fp.write("# Date %d %d\n" % ctx.date())
637 fp.write("# Date %d %d\n" % ctx.date())
637 if branch and (branch != 'default'):
638 if branch and (branch != 'default'):
638 fp.write("# Branch %s\n" % branch)
639 fp.write("# Branch %s\n" % branch)
639 fp.write("# Node ID %s\n" % hex(node))
640 fp.write("# Node ID %s\n" % hex(node))
640 fp.write("# Parent %s\n" % hex(prev))
641 fp.write("# Parent %s\n" % hex(prev))
641 if len(parents) > 1:
642 if len(parents) > 1:
642 fp.write("# Parent %s\n" % hex(parents[1]))
643 fp.write("# Parent %s\n" % hex(parents[1]))
643 fp.write(ctx.description().rstrip())
644 fp.write(ctx.description().rstrip())
644 fp.write("\n\n")
645 fp.write("\n\n")
645
646
646 diff(repo, prev, node, fp=fp, opts=opts)
647 diff(repo, prev, node, fp=fp, opts=opts)
647 if fp not in (sys.stdout, repo.ui):
648 if fp not in (sys.stdout, repo.ui):
648 fp.close()
649 fp.close()
649
650
650 for seqno, rev in enumerate(revs):
651 for seqno, rev in enumerate(revs):
651 single(rev, seqno+1, fp)
652 single(rev, seqno+1, fp)
652
653
653 def diffstat(patchlines):
654 def diffstat(patchlines):
654 if not util.find_exe('diffstat'):
655 if not util.find_exe('diffstat'):
655 return
656 return
656 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
657 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
657 try:
658 try:
658 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
659 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
659 try:
660 try:
660 for line in patchlines: print >> p.tochild, line
661 for line in patchlines: print >> p.tochild, line
661 p.tochild.close()
662 p.tochild.close()
662 if p.wait(): return
663 if p.wait(): return
663 fp = os.fdopen(fd, 'r')
664 fp = os.fdopen(fd, 'r')
664 stat = []
665 stat = []
665 for line in fp: stat.append(line.lstrip())
666 for line in fp: stat.append(line.lstrip())
666 last = stat.pop()
667 last = stat.pop()
667 stat.insert(0, last)
668 stat.insert(0, last)
668 stat = ''.join(stat)
669 stat = ''.join(stat)
669 if stat.startswith('0 files'): raise ValueError
670 if stat.startswith('0 files'): raise ValueError
670 return stat
671 return stat
671 except: raise
672 except: raise
672 finally:
673 finally:
673 try: os.unlink(name)
674 try: os.unlink(name)
674 except: pass
675 except: pass
General Comments 0
You need to be logged in to leave comments. Login now