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