##// END OF EJS Templates
profiling: make profiling functions context managers (API)...
Gregory Szorc -
r29783:5d44197c default
parent child Browse files
Show More
@@ -1,981 +1,982 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import shlex
16 import shlex
17 import signal
17 import signal
18 import socket
18 import socket
19 import sys
19 import sys
20 import time
20 import time
21 import traceback
21 import traceback
22
22
23
23
24 from .i18n import _
24 from .i18n import _
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
28 commands,
28 commands,
29 demandimport,
29 demandimport,
30 encoding,
30 encoding,
31 error,
31 error,
32 extensions,
32 extensions,
33 fancyopts,
33 fancyopts,
34 fileset,
34 fileset,
35 hg,
35 hg,
36 hook,
36 hook,
37 profiling,
37 profiling,
38 revset,
38 revset,
39 templatefilters,
39 templatefilters,
40 templatekw,
40 templatekw,
41 templater,
41 templater,
42 ui as uimod,
42 ui as uimod,
43 util,
43 util,
44 )
44 )
45
45
46 class request(object):
46 class request(object):
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
48 ferr=None):
48 ferr=None):
49 self.args = args
49 self.args = args
50 self.ui = ui
50 self.ui = ui
51 self.repo = repo
51 self.repo = repo
52
52
53 # input/output/error streams
53 # input/output/error streams
54 self.fin = fin
54 self.fin = fin
55 self.fout = fout
55 self.fout = fout
56 self.ferr = ferr
56 self.ferr = ferr
57
57
58 def run():
58 def run():
59 "run the command in sys.argv"
59 "run the command in sys.argv"
60 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
60 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
61
61
62 def _getsimilar(symbols, value):
62 def _getsimilar(symbols, value):
63 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
63 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
64 # The cutoff for similarity here is pretty arbitrary. It should
64 # The cutoff for similarity here is pretty arbitrary. It should
65 # probably be investigated and tweaked.
65 # probably be investigated and tweaked.
66 return [s for s in symbols if sim(s) > 0.6]
66 return [s for s in symbols if sim(s) > 0.6]
67
67
68 def _reportsimilar(write, similar):
68 def _reportsimilar(write, similar):
69 if len(similar) == 1:
69 if len(similar) == 1:
70 write(_("(did you mean %s?)\n") % similar[0])
70 write(_("(did you mean %s?)\n") % similar[0])
71 elif similar:
71 elif similar:
72 ss = ", ".join(sorted(similar))
72 ss = ", ".join(sorted(similar))
73 write(_("(did you mean one of %s?)\n") % ss)
73 write(_("(did you mean one of %s?)\n") % ss)
74
74
75 def _formatparse(write, inst):
75 def _formatparse(write, inst):
76 similar = []
76 similar = []
77 if isinstance(inst, error.UnknownIdentifier):
77 if isinstance(inst, error.UnknownIdentifier):
78 # make sure to check fileset first, as revset can invoke fileset
78 # make sure to check fileset first, as revset can invoke fileset
79 similar = _getsimilar(inst.symbols, inst.function)
79 similar = _getsimilar(inst.symbols, inst.function)
80 if len(inst.args) > 1:
80 if len(inst.args) > 1:
81 write(_("hg: parse error at %s: %s\n") %
81 write(_("hg: parse error at %s: %s\n") %
82 (inst.args[1], inst.args[0]))
82 (inst.args[1], inst.args[0]))
83 if (inst.args[0][0] == ' '):
83 if (inst.args[0][0] == ' '):
84 write(_("unexpected leading whitespace\n"))
84 write(_("unexpected leading whitespace\n"))
85 else:
85 else:
86 write(_("hg: parse error: %s\n") % inst.args[0])
86 write(_("hg: parse error: %s\n") % inst.args[0])
87 _reportsimilar(write, similar)
87 _reportsimilar(write, similar)
88 if inst.hint:
88 if inst.hint:
89 write(_("(%s)\n") % inst.hint)
89 write(_("(%s)\n") % inst.hint)
90
90
91 def dispatch(req):
91 def dispatch(req):
92 "run the command specified in req.args"
92 "run the command specified in req.args"
93 if req.ferr:
93 if req.ferr:
94 ferr = req.ferr
94 ferr = req.ferr
95 elif req.ui:
95 elif req.ui:
96 ferr = req.ui.ferr
96 ferr = req.ui.ferr
97 else:
97 else:
98 ferr = sys.stderr
98 ferr = sys.stderr
99
99
100 try:
100 try:
101 if not req.ui:
101 if not req.ui:
102 req.ui = uimod.ui()
102 req.ui = uimod.ui()
103 if '--traceback' in req.args:
103 if '--traceback' in req.args:
104 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
104 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
105
105
106 # set ui streams from the request
106 # set ui streams from the request
107 if req.fin:
107 if req.fin:
108 req.ui.fin = req.fin
108 req.ui.fin = req.fin
109 if req.fout:
109 if req.fout:
110 req.ui.fout = req.fout
110 req.ui.fout = req.fout
111 if req.ferr:
111 if req.ferr:
112 req.ui.ferr = req.ferr
112 req.ui.ferr = req.ferr
113 except error.Abort as inst:
113 except error.Abort as inst:
114 ferr.write(_("abort: %s\n") % inst)
114 ferr.write(_("abort: %s\n") % inst)
115 if inst.hint:
115 if inst.hint:
116 ferr.write(_("(%s)\n") % inst.hint)
116 ferr.write(_("(%s)\n") % inst.hint)
117 return -1
117 return -1
118 except error.ParseError as inst:
118 except error.ParseError as inst:
119 _formatparse(ferr.write, inst)
119 _formatparse(ferr.write, inst)
120 return -1
120 return -1
121
121
122 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
122 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
123 starttime = time.time()
123 starttime = time.time()
124 ret = None
124 ret = None
125 try:
125 try:
126 ret = _runcatch(req)
126 ret = _runcatch(req)
127 except KeyboardInterrupt:
127 except KeyboardInterrupt:
128 try:
128 try:
129 req.ui.warn(_("interrupted!\n"))
129 req.ui.warn(_("interrupted!\n"))
130 except IOError as inst:
130 except IOError as inst:
131 if inst.errno != errno.EPIPE:
131 if inst.errno != errno.EPIPE:
132 raise
132 raise
133 ret = -1
133 ret = -1
134 finally:
134 finally:
135 duration = time.time() - starttime
135 duration = time.time() - starttime
136 req.ui.flush()
136 req.ui.flush()
137 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
137 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
138 msg, ret or 0, duration)
138 msg, ret or 0, duration)
139 return ret
139 return ret
140
140
141 def _runcatch(req):
141 def _runcatch(req):
142 def catchterm(*args):
142 def catchterm(*args):
143 raise error.SignalInterrupt
143 raise error.SignalInterrupt
144
144
145 ui = req.ui
145 ui = req.ui
146 try:
146 try:
147 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
147 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
148 num = getattr(signal, name, None)
148 num = getattr(signal, name, None)
149 if num:
149 if num:
150 signal.signal(num, catchterm)
150 signal.signal(num, catchterm)
151 except ValueError:
151 except ValueError:
152 pass # happens if called in a thread
152 pass # happens if called in a thread
153
153
154 def _runcatchfunc():
154 def _runcatchfunc():
155 try:
155 try:
156 debugger = 'pdb'
156 debugger = 'pdb'
157 debugtrace = {
157 debugtrace = {
158 'pdb' : pdb.set_trace
158 'pdb' : pdb.set_trace
159 }
159 }
160 debugmortem = {
160 debugmortem = {
161 'pdb' : pdb.post_mortem
161 'pdb' : pdb.post_mortem
162 }
162 }
163
163
164 # read --config before doing anything else
164 # read --config before doing anything else
165 # (e.g. to change trust settings for reading .hg/hgrc)
165 # (e.g. to change trust settings for reading .hg/hgrc)
166 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
166 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
167
167
168 if req.repo:
168 if req.repo:
169 # copy configs that were passed on the cmdline (--config) to
169 # copy configs that were passed on the cmdline (--config) to
170 # the repo ui
170 # the repo ui
171 for sec, name, val in cfgs:
171 for sec, name, val in cfgs:
172 req.repo.ui.setconfig(sec, name, val, source='--config')
172 req.repo.ui.setconfig(sec, name, val, source='--config')
173
173
174 # developer config: ui.debugger
174 # developer config: ui.debugger
175 debugger = ui.config("ui", "debugger")
175 debugger = ui.config("ui", "debugger")
176 debugmod = pdb
176 debugmod = pdb
177 if not debugger or ui.plain():
177 if not debugger or ui.plain():
178 # if we are in HGPLAIN mode, then disable custom debugging
178 # if we are in HGPLAIN mode, then disable custom debugging
179 debugger = 'pdb'
179 debugger = 'pdb'
180 elif '--debugger' in req.args:
180 elif '--debugger' in req.args:
181 # This import can be slow for fancy debuggers, so only
181 # This import can be slow for fancy debuggers, so only
182 # do it when absolutely necessary, i.e. when actual
182 # do it when absolutely necessary, i.e. when actual
183 # debugging has been requested
183 # debugging has been requested
184 with demandimport.deactivated():
184 with demandimport.deactivated():
185 try:
185 try:
186 debugmod = __import__(debugger)
186 debugmod = __import__(debugger)
187 except ImportError:
187 except ImportError:
188 pass # Leave debugmod = pdb
188 pass # Leave debugmod = pdb
189
189
190 debugtrace[debugger] = debugmod.set_trace
190 debugtrace[debugger] = debugmod.set_trace
191 debugmortem[debugger] = debugmod.post_mortem
191 debugmortem[debugger] = debugmod.post_mortem
192
192
193 # enter the debugger before command execution
193 # enter the debugger before command execution
194 if '--debugger' in req.args:
194 if '--debugger' in req.args:
195 ui.warn(_("entering debugger - "
195 ui.warn(_("entering debugger - "
196 "type c to continue starting hg or h for help\n"))
196 "type c to continue starting hg or h for help\n"))
197
197
198 if (debugger != 'pdb' and
198 if (debugger != 'pdb' and
199 debugtrace[debugger] == debugtrace['pdb']):
199 debugtrace[debugger] == debugtrace['pdb']):
200 ui.warn(_("%s debugger specified "
200 ui.warn(_("%s debugger specified "
201 "but its module was not found\n") % debugger)
201 "but its module was not found\n") % debugger)
202 with demandimport.deactivated():
202 with demandimport.deactivated():
203 debugtrace[debugger]()
203 debugtrace[debugger]()
204 try:
204 try:
205 return _dispatch(req)
205 return _dispatch(req)
206 finally:
206 finally:
207 ui.flush()
207 ui.flush()
208 except: # re-raises
208 except: # re-raises
209 # enter the debugger when we hit an exception
209 # enter the debugger when we hit an exception
210 if '--debugger' in req.args:
210 if '--debugger' in req.args:
211 traceback.print_exc()
211 traceback.print_exc()
212 debugmortem[debugger](sys.exc_info()[2])
212 debugmortem[debugger](sys.exc_info()[2])
213 ui.traceback()
213 ui.traceback()
214 raise
214 raise
215
215
216 return callcatch(ui, _runcatchfunc)
216 return callcatch(ui, _runcatchfunc)
217
217
218 def callcatch(ui, func):
218 def callcatch(ui, func):
219 """call func() with global exception handling
219 """call func() with global exception handling
220
220
221 return func() if no exception happens. otherwise do some error handling
221 return func() if no exception happens. otherwise do some error handling
222 and return an exit code accordingly.
222 and return an exit code accordingly.
223 """
223 """
224 try:
224 try:
225 return func()
225 return func()
226 # Global exception handling, alphabetically
226 # Global exception handling, alphabetically
227 # Mercurial-specific first, followed by built-in and library exceptions
227 # Mercurial-specific first, followed by built-in and library exceptions
228 except error.AmbiguousCommand as inst:
228 except error.AmbiguousCommand as inst:
229 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
229 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
230 (inst.args[0], " ".join(inst.args[1])))
230 (inst.args[0], " ".join(inst.args[1])))
231 except error.ParseError as inst:
231 except error.ParseError as inst:
232 _formatparse(ui.warn, inst)
232 _formatparse(ui.warn, inst)
233 return -1
233 return -1
234 except error.LockHeld as inst:
234 except error.LockHeld as inst:
235 if inst.errno == errno.ETIMEDOUT:
235 if inst.errno == errno.ETIMEDOUT:
236 reason = _('timed out waiting for lock held by %s') % inst.locker
236 reason = _('timed out waiting for lock held by %s') % inst.locker
237 else:
237 else:
238 reason = _('lock held by %s') % inst.locker
238 reason = _('lock held by %s') % inst.locker
239 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
239 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
240 except error.LockUnavailable as inst:
240 except error.LockUnavailable as inst:
241 ui.warn(_("abort: could not lock %s: %s\n") %
241 ui.warn(_("abort: could not lock %s: %s\n") %
242 (inst.desc or inst.filename, inst.strerror))
242 (inst.desc or inst.filename, inst.strerror))
243 except error.CommandError as inst:
243 except error.CommandError as inst:
244 if inst.args[0]:
244 if inst.args[0]:
245 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
245 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
246 commands.help_(ui, inst.args[0], full=False, command=True)
246 commands.help_(ui, inst.args[0], full=False, command=True)
247 else:
247 else:
248 ui.warn(_("hg: %s\n") % inst.args[1])
248 ui.warn(_("hg: %s\n") % inst.args[1])
249 commands.help_(ui, 'shortlist')
249 commands.help_(ui, 'shortlist')
250 except error.OutOfBandError as inst:
250 except error.OutOfBandError as inst:
251 if inst.args:
251 if inst.args:
252 msg = _("abort: remote error:\n")
252 msg = _("abort: remote error:\n")
253 else:
253 else:
254 msg = _("abort: remote error\n")
254 msg = _("abort: remote error\n")
255 ui.warn(msg)
255 ui.warn(msg)
256 if inst.args:
256 if inst.args:
257 ui.warn(''.join(inst.args))
257 ui.warn(''.join(inst.args))
258 if inst.hint:
258 if inst.hint:
259 ui.warn('(%s)\n' % inst.hint)
259 ui.warn('(%s)\n' % inst.hint)
260 except error.RepoError as inst:
260 except error.RepoError as inst:
261 ui.warn(_("abort: %s!\n") % inst)
261 ui.warn(_("abort: %s!\n") % inst)
262 if inst.hint:
262 if inst.hint:
263 ui.warn(_("(%s)\n") % inst.hint)
263 ui.warn(_("(%s)\n") % inst.hint)
264 except error.ResponseError as inst:
264 except error.ResponseError as inst:
265 ui.warn(_("abort: %s") % inst.args[0])
265 ui.warn(_("abort: %s") % inst.args[0])
266 if not isinstance(inst.args[1], basestring):
266 if not isinstance(inst.args[1], basestring):
267 ui.warn(" %r\n" % (inst.args[1],))
267 ui.warn(" %r\n" % (inst.args[1],))
268 elif not inst.args[1]:
268 elif not inst.args[1]:
269 ui.warn(_(" empty string\n"))
269 ui.warn(_(" empty string\n"))
270 else:
270 else:
271 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
271 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
272 except error.CensoredNodeError as inst:
272 except error.CensoredNodeError as inst:
273 ui.warn(_("abort: file censored %s!\n") % inst)
273 ui.warn(_("abort: file censored %s!\n") % inst)
274 except error.RevlogError as inst:
274 except error.RevlogError as inst:
275 ui.warn(_("abort: %s!\n") % inst)
275 ui.warn(_("abort: %s!\n") % inst)
276 except error.SignalInterrupt:
276 except error.SignalInterrupt:
277 ui.warn(_("killed!\n"))
277 ui.warn(_("killed!\n"))
278 except error.UnknownCommand as inst:
278 except error.UnknownCommand as inst:
279 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
279 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
280 try:
280 try:
281 # check if the command is in a disabled extension
281 # check if the command is in a disabled extension
282 # (but don't check for extensions themselves)
282 # (but don't check for extensions themselves)
283 commands.help_(ui, inst.args[0], unknowncmd=True)
283 commands.help_(ui, inst.args[0], unknowncmd=True)
284 except (error.UnknownCommand, error.Abort):
284 except (error.UnknownCommand, error.Abort):
285 suggested = False
285 suggested = False
286 if len(inst.args) == 2:
286 if len(inst.args) == 2:
287 sim = _getsimilar(inst.args[1], inst.args[0])
287 sim = _getsimilar(inst.args[1], inst.args[0])
288 if sim:
288 if sim:
289 _reportsimilar(ui.warn, sim)
289 _reportsimilar(ui.warn, sim)
290 suggested = True
290 suggested = True
291 if not suggested:
291 if not suggested:
292 commands.help_(ui, 'shortlist')
292 commands.help_(ui, 'shortlist')
293 except error.InterventionRequired as inst:
293 except error.InterventionRequired as inst:
294 ui.warn("%s\n" % inst)
294 ui.warn("%s\n" % inst)
295 if inst.hint:
295 if inst.hint:
296 ui.warn(_("(%s)\n") % inst.hint)
296 ui.warn(_("(%s)\n") % inst.hint)
297 return 1
297 return 1
298 except error.Abort as inst:
298 except error.Abort as inst:
299 ui.warn(_("abort: %s\n") % inst)
299 ui.warn(_("abort: %s\n") % inst)
300 if inst.hint:
300 if inst.hint:
301 ui.warn(_("(%s)\n") % inst.hint)
301 ui.warn(_("(%s)\n") % inst.hint)
302 except ImportError as inst:
302 except ImportError as inst:
303 ui.warn(_("abort: %s!\n") % inst)
303 ui.warn(_("abort: %s!\n") % inst)
304 m = str(inst).split()[-1]
304 m = str(inst).split()[-1]
305 if m in "mpatch bdiff".split():
305 if m in "mpatch bdiff".split():
306 ui.warn(_("(did you forget to compile extensions?)\n"))
306 ui.warn(_("(did you forget to compile extensions?)\n"))
307 elif m in "zlib".split():
307 elif m in "zlib".split():
308 ui.warn(_("(is your Python install correct?)\n"))
308 ui.warn(_("(is your Python install correct?)\n"))
309 except IOError as inst:
309 except IOError as inst:
310 if util.safehasattr(inst, "code"):
310 if util.safehasattr(inst, "code"):
311 ui.warn(_("abort: %s\n") % inst)
311 ui.warn(_("abort: %s\n") % inst)
312 elif util.safehasattr(inst, "reason"):
312 elif util.safehasattr(inst, "reason"):
313 try: # usually it is in the form (errno, strerror)
313 try: # usually it is in the form (errno, strerror)
314 reason = inst.reason.args[1]
314 reason = inst.reason.args[1]
315 except (AttributeError, IndexError):
315 except (AttributeError, IndexError):
316 # it might be anything, for example a string
316 # it might be anything, for example a string
317 reason = inst.reason
317 reason = inst.reason
318 if isinstance(reason, unicode):
318 if isinstance(reason, unicode):
319 # SSLError of Python 2.7.9 contains a unicode
319 # SSLError of Python 2.7.9 contains a unicode
320 reason = reason.encode(encoding.encoding, 'replace')
320 reason = reason.encode(encoding.encoding, 'replace')
321 ui.warn(_("abort: error: %s\n") % reason)
321 ui.warn(_("abort: error: %s\n") % reason)
322 elif (util.safehasattr(inst, "args")
322 elif (util.safehasattr(inst, "args")
323 and inst.args and inst.args[0] == errno.EPIPE):
323 and inst.args and inst.args[0] == errno.EPIPE):
324 pass
324 pass
325 elif getattr(inst, "strerror", None):
325 elif getattr(inst, "strerror", None):
326 if getattr(inst, "filename", None):
326 if getattr(inst, "filename", None):
327 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
327 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
328 else:
328 else:
329 ui.warn(_("abort: %s\n") % inst.strerror)
329 ui.warn(_("abort: %s\n") % inst.strerror)
330 else:
330 else:
331 raise
331 raise
332 except OSError as inst:
332 except OSError as inst:
333 if getattr(inst, "filename", None) is not None:
333 if getattr(inst, "filename", None) is not None:
334 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
334 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
335 else:
335 else:
336 ui.warn(_("abort: %s\n") % inst.strerror)
336 ui.warn(_("abort: %s\n") % inst.strerror)
337 except KeyboardInterrupt:
337 except KeyboardInterrupt:
338 raise
338 raise
339 except MemoryError:
339 except MemoryError:
340 ui.warn(_("abort: out of memory\n"))
340 ui.warn(_("abort: out of memory\n"))
341 except SystemExit as inst:
341 except SystemExit as inst:
342 # Commands shouldn't sys.exit directly, but give a return code.
342 # Commands shouldn't sys.exit directly, but give a return code.
343 # Just in case catch this and and pass exit code to caller.
343 # Just in case catch this and and pass exit code to caller.
344 return inst.code
344 return inst.code
345 except socket.error as inst:
345 except socket.error as inst:
346 ui.warn(_("abort: %s\n") % inst.args[-1])
346 ui.warn(_("abort: %s\n") % inst.args[-1])
347 except: # perhaps re-raises
347 except: # perhaps re-raises
348 if not handlecommandexception(ui):
348 if not handlecommandexception(ui):
349 raise
349 raise
350
350
351 return -1
351 return -1
352
352
353 def aliasargs(fn, givenargs):
353 def aliasargs(fn, givenargs):
354 args = getattr(fn, 'args', [])
354 args = getattr(fn, 'args', [])
355 if args:
355 if args:
356 cmd = ' '.join(map(util.shellquote, args))
356 cmd = ' '.join(map(util.shellquote, args))
357
357
358 nums = []
358 nums = []
359 def replacer(m):
359 def replacer(m):
360 num = int(m.group(1)) - 1
360 num = int(m.group(1)) - 1
361 nums.append(num)
361 nums.append(num)
362 if num < len(givenargs):
362 if num < len(givenargs):
363 return givenargs[num]
363 return givenargs[num]
364 raise error.Abort(_('too few arguments for command alias'))
364 raise error.Abort(_('too few arguments for command alias'))
365 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
365 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
366 givenargs = [x for i, x in enumerate(givenargs)
366 givenargs = [x for i, x in enumerate(givenargs)
367 if i not in nums]
367 if i not in nums]
368 args = shlex.split(cmd)
368 args = shlex.split(cmd)
369 return args + givenargs
369 return args + givenargs
370
370
371 def aliasinterpolate(name, args, cmd):
371 def aliasinterpolate(name, args, cmd):
372 '''interpolate args into cmd for shell aliases
372 '''interpolate args into cmd for shell aliases
373
373
374 This also handles $0, $@ and "$@".
374 This also handles $0, $@ and "$@".
375 '''
375 '''
376 # util.interpolate can't deal with "$@" (with quotes) because it's only
376 # util.interpolate can't deal with "$@" (with quotes) because it's only
377 # built to match prefix + patterns.
377 # built to match prefix + patterns.
378 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
378 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
379 replacemap['$0'] = name
379 replacemap['$0'] = name
380 replacemap['$$'] = '$'
380 replacemap['$$'] = '$'
381 replacemap['$@'] = ' '.join(args)
381 replacemap['$@'] = ' '.join(args)
382 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
382 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
383 # parameters, separated out into words. Emulate the same behavior here by
383 # parameters, separated out into words. Emulate the same behavior here by
384 # quoting the arguments individually. POSIX shells will then typically
384 # quoting the arguments individually. POSIX shells will then typically
385 # tokenize each argument into exactly one word.
385 # tokenize each argument into exactly one word.
386 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
386 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
387 # escape '\$' for regex
387 # escape '\$' for regex
388 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
388 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
389 r = re.compile(regex)
389 r = re.compile(regex)
390 return r.sub(lambda x: replacemap[x.group()], cmd)
390 return r.sub(lambda x: replacemap[x.group()], cmd)
391
391
392 class cmdalias(object):
392 class cmdalias(object):
393 def __init__(self, name, definition, cmdtable, source):
393 def __init__(self, name, definition, cmdtable, source):
394 self.name = self.cmd = name
394 self.name = self.cmd = name
395 self.cmdname = ''
395 self.cmdname = ''
396 self.definition = definition
396 self.definition = definition
397 self.fn = None
397 self.fn = None
398 self.givenargs = []
398 self.givenargs = []
399 self.opts = []
399 self.opts = []
400 self.help = ''
400 self.help = ''
401 self.badalias = None
401 self.badalias = None
402 self.unknowncmd = False
402 self.unknowncmd = False
403 self.source = source
403 self.source = source
404
404
405 try:
405 try:
406 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
406 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
407 for alias, e in cmdtable.iteritems():
407 for alias, e in cmdtable.iteritems():
408 if e is entry:
408 if e is entry:
409 self.cmd = alias
409 self.cmd = alias
410 break
410 break
411 self.shadows = True
411 self.shadows = True
412 except error.UnknownCommand:
412 except error.UnknownCommand:
413 self.shadows = False
413 self.shadows = False
414
414
415 if not self.definition:
415 if not self.definition:
416 self.badalias = _("no definition for alias '%s'") % self.name
416 self.badalias = _("no definition for alias '%s'") % self.name
417 return
417 return
418
418
419 if self.definition.startswith('!'):
419 if self.definition.startswith('!'):
420 self.shell = True
420 self.shell = True
421 def fn(ui, *args):
421 def fn(ui, *args):
422 env = {'HG_ARGS': ' '.join((self.name,) + args)}
422 env = {'HG_ARGS': ' '.join((self.name,) + args)}
423 def _checkvar(m):
423 def _checkvar(m):
424 if m.groups()[0] == '$':
424 if m.groups()[0] == '$':
425 return m.group()
425 return m.group()
426 elif int(m.groups()[0]) <= len(args):
426 elif int(m.groups()[0]) <= len(args):
427 return m.group()
427 return m.group()
428 else:
428 else:
429 ui.debug("No argument found for substitution "
429 ui.debug("No argument found for substitution "
430 "of %i variable in alias '%s' definition."
430 "of %i variable in alias '%s' definition."
431 % (int(m.groups()[0]), self.name))
431 % (int(m.groups()[0]), self.name))
432 return ''
432 return ''
433 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
433 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
434 cmd = aliasinterpolate(self.name, args, cmd)
434 cmd = aliasinterpolate(self.name, args, cmd)
435 return ui.system(cmd, environ=env)
435 return ui.system(cmd, environ=env)
436 self.fn = fn
436 self.fn = fn
437 return
437 return
438
438
439 try:
439 try:
440 args = shlex.split(self.definition)
440 args = shlex.split(self.definition)
441 except ValueError as inst:
441 except ValueError as inst:
442 self.badalias = (_("error in definition for alias '%s': %s")
442 self.badalias = (_("error in definition for alias '%s': %s")
443 % (self.name, inst))
443 % (self.name, inst))
444 return
444 return
445 self.cmdname = cmd = args.pop(0)
445 self.cmdname = cmd = args.pop(0)
446 self.givenargs = args
446 self.givenargs = args
447
447
448 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
448 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
449 if _earlygetopt([invalidarg], args):
449 if _earlygetopt([invalidarg], args):
450 self.badalias = (_("error in definition for alias '%s': %s may "
450 self.badalias = (_("error in definition for alias '%s': %s may "
451 "only be given on the command line")
451 "only be given on the command line")
452 % (self.name, invalidarg))
452 % (self.name, invalidarg))
453 return
453 return
454
454
455 try:
455 try:
456 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
456 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
457 if len(tableentry) > 2:
457 if len(tableentry) > 2:
458 self.fn, self.opts, self.help = tableentry
458 self.fn, self.opts, self.help = tableentry
459 else:
459 else:
460 self.fn, self.opts = tableentry
460 self.fn, self.opts = tableentry
461
461
462 if self.help.startswith("hg " + cmd):
462 if self.help.startswith("hg " + cmd):
463 # drop prefix in old-style help lines so hg shows the alias
463 # drop prefix in old-style help lines so hg shows the alias
464 self.help = self.help[4 + len(cmd):]
464 self.help = self.help[4 + len(cmd):]
465 self.__doc__ = self.fn.__doc__
465 self.__doc__ = self.fn.__doc__
466
466
467 except error.UnknownCommand:
467 except error.UnknownCommand:
468 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
468 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
469 % (self.name, cmd))
469 % (self.name, cmd))
470 self.unknowncmd = True
470 self.unknowncmd = True
471 except error.AmbiguousCommand:
471 except error.AmbiguousCommand:
472 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
472 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
473 % (self.name, cmd))
473 % (self.name, cmd))
474
474
475 @property
475 @property
476 def args(self):
476 def args(self):
477 args = map(util.expandpath, self.givenargs)
477 args = map(util.expandpath, self.givenargs)
478 return aliasargs(self.fn, args)
478 return aliasargs(self.fn, args)
479
479
480 def __getattr__(self, name):
480 def __getattr__(self, name):
481 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
481 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
482 if name not in adefaults:
482 if name not in adefaults:
483 raise AttributeError(name)
483 raise AttributeError(name)
484 if self.badalias or util.safehasattr(self, 'shell'):
484 if self.badalias or util.safehasattr(self, 'shell'):
485 return adefaults[name]
485 return adefaults[name]
486 return getattr(self.fn, name)
486 return getattr(self.fn, name)
487
487
488 def __call__(self, ui, *args, **opts):
488 def __call__(self, ui, *args, **opts):
489 if self.badalias:
489 if self.badalias:
490 hint = None
490 hint = None
491 if self.unknowncmd:
491 if self.unknowncmd:
492 try:
492 try:
493 # check if the command is in a disabled extension
493 # check if the command is in a disabled extension
494 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
494 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
495 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
495 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
496 except error.UnknownCommand:
496 except error.UnknownCommand:
497 pass
497 pass
498 raise error.Abort(self.badalias, hint=hint)
498 raise error.Abort(self.badalias, hint=hint)
499 if self.shadows:
499 if self.shadows:
500 ui.debug("alias '%s' shadows command '%s'\n" %
500 ui.debug("alias '%s' shadows command '%s'\n" %
501 (self.name, self.cmdname))
501 (self.name, self.cmdname))
502
502
503 if util.safehasattr(self, 'shell'):
503 if util.safehasattr(self, 'shell'):
504 return self.fn(ui, *args, **opts)
504 return self.fn(ui, *args, **opts)
505 else:
505 else:
506 try:
506 try:
507 return util.checksignature(self.fn)(ui, *args, **opts)
507 return util.checksignature(self.fn)(ui, *args, **opts)
508 except error.SignatureError:
508 except error.SignatureError:
509 args = ' '.join([self.cmdname] + self.args)
509 args = ' '.join([self.cmdname] + self.args)
510 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
510 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
511 raise
511 raise
512
512
513 def addaliases(ui, cmdtable):
513 def addaliases(ui, cmdtable):
514 # aliases are processed after extensions have been loaded, so they
514 # aliases are processed after extensions have been loaded, so they
515 # may use extension commands. Aliases can also use other alias definitions,
515 # may use extension commands. Aliases can also use other alias definitions,
516 # but only if they have been defined prior to the current definition.
516 # but only if they have been defined prior to the current definition.
517 for alias, definition in ui.configitems('alias'):
517 for alias, definition in ui.configitems('alias'):
518 source = ui.configsource('alias', alias)
518 source = ui.configsource('alias', alias)
519 aliasdef = cmdalias(alias, definition, cmdtable, source)
519 aliasdef = cmdalias(alias, definition, cmdtable, source)
520
520
521 try:
521 try:
522 olddef = cmdtable[aliasdef.cmd][0]
522 olddef = cmdtable[aliasdef.cmd][0]
523 if olddef.definition == aliasdef.definition:
523 if olddef.definition == aliasdef.definition:
524 continue
524 continue
525 except (KeyError, AttributeError):
525 except (KeyError, AttributeError):
526 # definition might not exist or it might not be a cmdalias
526 # definition might not exist or it might not be a cmdalias
527 pass
527 pass
528
528
529 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
529 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
530
530
531 def _parse(ui, args):
531 def _parse(ui, args):
532 options = {}
532 options = {}
533 cmdoptions = {}
533 cmdoptions = {}
534
534
535 try:
535 try:
536 args = fancyopts.fancyopts(args, commands.globalopts, options)
536 args = fancyopts.fancyopts(args, commands.globalopts, options)
537 except fancyopts.getopt.GetoptError as inst:
537 except fancyopts.getopt.GetoptError as inst:
538 raise error.CommandError(None, inst)
538 raise error.CommandError(None, inst)
539
539
540 if args:
540 if args:
541 cmd, args = args[0], args[1:]
541 cmd, args = args[0], args[1:]
542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
543 ui.configbool("ui", "strict"))
543 ui.configbool("ui", "strict"))
544 cmd = aliases[0]
544 cmd = aliases[0]
545 args = aliasargs(entry[0], args)
545 args = aliasargs(entry[0], args)
546 defaults = ui.config("defaults", cmd)
546 defaults = ui.config("defaults", cmd)
547 if defaults:
547 if defaults:
548 args = map(util.expandpath, shlex.split(defaults)) + args
548 args = map(util.expandpath, shlex.split(defaults)) + args
549 c = list(entry[1])
549 c = list(entry[1])
550 else:
550 else:
551 cmd = None
551 cmd = None
552 c = []
552 c = []
553
553
554 # combine global options into local
554 # combine global options into local
555 for o in commands.globalopts:
555 for o in commands.globalopts:
556 c.append((o[0], o[1], options[o[1]], o[3]))
556 c.append((o[0], o[1], options[o[1]], o[3]))
557
557
558 try:
558 try:
559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
560 except fancyopts.getopt.GetoptError as inst:
560 except fancyopts.getopt.GetoptError as inst:
561 raise error.CommandError(cmd, inst)
561 raise error.CommandError(cmd, inst)
562
562
563 # separate global options back out
563 # separate global options back out
564 for o in commands.globalopts:
564 for o in commands.globalopts:
565 n = o[1]
565 n = o[1]
566 options[n] = cmdoptions[n]
566 options[n] = cmdoptions[n]
567 del cmdoptions[n]
567 del cmdoptions[n]
568
568
569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
570
570
571 def _parseconfig(ui, config):
571 def _parseconfig(ui, config):
572 """parse the --config options from the command line"""
572 """parse the --config options from the command line"""
573 configs = []
573 configs = []
574
574
575 for cfg in config:
575 for cfg in config:
576 try:
576 try:
577 name, value = [cfgelem.strip()
577 name, value = [cfgelem.strip()
578 for cfgelem in cfg.split('=', 1)]
578 for cfgelem in cfg.split('=', 1)]
579 section, name = name.split('.', 1)
579 section, name = name.split('.', 1)
580 if not section or not name:
580 if not section or not name:
581 raise IndexError
581 raise IndexError
582 ui.setconfig(section, name, value, '--config')
582 ui.setconfig(section, name, value, '--config')
583 configs.append((section, name, value))
583 configs.append((section, name, value))
584 except (IndexError, ValueError):
584 except (IndexError, ValueError):
585 raise error.Abort(_('malformed --config option: %r '
585 raise error.Abort(_('malformed --config option: %r '
586 '(use --config section.name=value)') % cfg)
586 '(use --config section.name=value)') % cfg)
587
587
588 return configs
588 return configs
589
589
590 def _earlygetopt(aliases, args):
590 def _earlygetopt(aliases, args):
591 """Return list of values for an option (or aliases).
591 """Return list of values for an option (or aliases).
592
592
593 The values are listed in the order they appear in args.
593 The values are listed in the order they appear in args.
594 The options and values are removed from args.
594 The options and values are removed from args.
595
595
596 >>> args = ['x', '--cwd', 'foo', 'y']
596 >>> args = ['x', '--cwd', 'foo', 'y']
597 >>> _earlygetopt(['--cwd'], args), args
597 >>> _earlygetopt(['--cwd'], args), args
598 (['foo'], ['x', 'y'])
598 (['foo'], ['x', 'y'])
599
599
600 >>> args = ['x', '--cwd=bar', 'y']
600 >>> args = ['x', '--cwd=bar', 'y']
601 >>> _earlygetopt(['--cwd'], args), args
601 >>> _earlygetopt(['--cwd'], args), args
602 (['bar'], ['x', 'y'])
602 (['bar'], ['x', 'y'])
603
603
604 >>> args = ['x', '-R', 'foo', 'y']
604 >>> args = ['x', '-R', 'foo', 'y']
605 >>> _earlygetopt(['-R'], args), args
605 >>> _earlygetopt(['-R'], args), args
606 (['foo'], ['x', 'y'])
606 (['foo'], ['x', 'y'])
607
607
608 >>> args = ['x', '-Rbar', 'y']
608 >>> args = ['x', '-Rbar', 'y']
609 >>> _earlygetopt(['-R'], args), args
609 >>> _earlygetopt(['-R'], args), args
610 (['bar'], ['x', 'y'])
610 (['bar'], ['x', 'y'])
611 """
611 """
612 try:
612 try:
613 argcount = args.index("--")
613 argcount = args.index("--")
614 except ValueError:
614 except ValueError:
615 argcount = len(args)
615 argcount = len(args)
616 shortopts = [opt for opt in aliases if len(opt) == 2]
616 shortopts = [opt for opt in aliases if len(opt) == 2]
617 values = []
617 values = []
618 pos = 0
618 pos = 0
619 while pos < argcount:
619 while pos < argcount:
620 fullarg = arg = args[pos]
620 fullarg = arg = args[pos]
621 equals = arg.find('=')
621 equals = arg.find('=')
622 if equals > -1:
622 if equals > -1:
623 arg = arg[:equals]
623 arg = arg[:equals]
624 if arg in aliases:
624 if arg in aliases:
625 del args[pos]
625 del args[pos]
626 if equals > -1:
626 if equals > -1:
627 values.append(fullarg[equals + 1:])
627 values.append(fullarg[equals + 1:])
628 argcount -= 1
628 argcount -= 1
629 else:
629 else:
630 if pos + 1 >= argcount:
630 if pos + 1 >= argcount:
631 # ignore and let getopt report an error if there is no value
631 # ignore and let getopt report an error if there is no value
632 break
632 break
633 values.append(args.pop(pos))
633 values.append(args.pop(pos))
634 argcount -= 2
634 argcount -= 2
635 elif arg[:2] in shortopts:
635 elif arg[:2] in shortopts:
636 # short option can have no following space, e.g. hg log -Rfoo
636 # short option can have no following space, e.g. hg log -Rfoo
637 values.append(args.pop(pos)[2:])
637 values.append(args.pop(pos)[2:])
638 argcount -= 1
638 argcount -= 1
639 else:
639 else:
640 pos += 1
640 pos += 1
641 return values
641 return values
642
642
643 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
643 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
644 # run pre-hook, and abort if it fails
644 # run pre-hook, and abort if it fails
645 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
645 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
646 pats=cmdpats, opts=cmdoptions)
646 pats=cmdpats, opts=cmdoptions)
647 try:
647 try:
648 ret = _runcommand(ui, options, cmd, d)
648 ret = _runcommand(ui, options, cmd, d)
649 # run post-hook, passing command result
649 # run post-hook, passing command result
650 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
650 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
651 result=ret, pats=cmdpats, opts=cmdoptions)
651 result=ret, pats=cmdpats, opts=cmdoptions)
652 except Exception:
652 except Exception:
653 # run failure hook and re-raise
653 # run failure hook and re-raise
654 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
654 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
655 pats=cmdpats, opts=cmdoptions)
655 pats=cmdpats, opts=cmdoptions)
656 raise
656 raise
657 return ret
657 return ret
658
658
659 def _getlocal(ui, rpath, wd=None):
659 def _getlocal(ui, rpath, wd=None):
660 """Return (path, local ui object) for the given target path.
660 """Return (path, local ui object) for the given target path.
661
661
662 Takes paths in [cwd]/.hg/hgrc into account."
662 Takes paths in [cwd]/.hg/hgrc into account."
663 """
663 """
664 if wd is None:
664 if wd is None:
665 try:
665 try:
666 wd = os.getcwd()
666 wd = os.getcwd()
667 except OSError as e:
667 except OSError as e:
668 raise error.Abort(_("error getting current working directory: %s") %
668 raise error.Abort(_("error getting current working directory: %s") %
669 e.strerror)
669 e.strerror)
670 path = cmdutil.findrepo(wd) or ""
670 path = cmdutil.findrepo(wd) or ""
671 if not path:
671 if not path:
672 lui = ui
672 lui = ui
673 else:
673 else:
674 lui = ui.copy()
674 lui = ui.copy()
675 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
675 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
676
676
677 if rpath and rpath[-1]:
677 if rpath and rpath[-1]:
678 path = lui.expandpath(rpath[-1])
678 path = lui.expandpath(rpath[-1])
679 lui = ui.copy()
679 lui = ui.copy()
680 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
680 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
681
681
682 return path, lui
682 return path, lui
683
683
684 def _checkshellalias(lui, ui, args):
684 def _checkshellalias(lui, ui, args):
685 """Return the function to run the shell alias, if it is required"""
685 """Return the function to run the shell alias, if it is required"""
686 options = {}
686 options = {}
687
687
688 try:
688 try:
689 args = fancyopts.fancyopts(args, commands.globalopts, options)
689 args = fancyopts.fancyopts(args, commands.globalopts, options)
690 except fancyopts.getopt.GetoptError:
690 except fancyopts.getopt.GetoptError:
691 return
691 return
692
692
693 if not args:
693 if not args:
694 return
694 return
695
695
696 cmdtable = commands.table
696 cmdtable = commands.table
697
697
698 cmd = args[0]
698 cmd = args[0]
699 try:
699 try:
700 strict = ui.configbool("ui", "strict")
700 strict = ui.configbool("ui", "strict")
701 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
701 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
702 except (error.AmbiguousCommand, error.UnknownCommand):
702 except (error.AmbiguousCommand, error.UnknownCommand):
703 return
703 return
704
704
705 cmd = aliases[0]
705 cmd = aliases[0]
706 fn = entry[0]
706 fn = entry[0]
707
707
708 if cmd and util.safehasattr(fn, 'shell'):
708 if cmd and util.safehasattr(fn, 'shell'):
709 d = lambda: fn(ui, *args[1:])
709 d = lambda: fn(ui, *args[1:])
710 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
710 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
711 [], {})
711 [], {})
712
712
713 def _cmdattr(ui, cmd, func, attr):
713 def _cmdattr(ui, cmd, func, attr):
714 try:
714 try:
715 return getattr(func, attr)
715 return getattr(func, attr)
716 except AttributeError:
716 except AttributeError:
717 ui.deprecwarn("missing attribute '%s', use @command decorator "
717 ui.deprecwarn("missing attribute '%s', use @command decorator "
718 "to register '%s'" % (attr, cmd), '3.8')
718 "to register '%s'" % (attr, cmd), '3.8')
719 return False
719 return False
720
720
721 _loaded = set()
721 _loaded = set()
722
722
723 # list of (objname, loadermod, loadername) tuple:
723 # list of (objname, loadermod, loadername) tuple:
724 # - objname is the name of an object in extension module, from which
724 # - objname is the name of an object in extension module, from which
725 # extra information is loaded
725 # extra information is loaded
726 # - loadermod is the module where loader is placed
726 # - loadermod is the module where loader is placed
727 # - loadername is the name of the function, which takes (ui, extensionname,
727 # - loadername is the name of the function, which takes (ui, extensionname,
728 # extraobj) arguments
728 # extraobj) arguments
729 extraloaders = [
729 extraloaders = [
730 ('cmdtable', commands, 'loadcmdtable'),
730 ('cmdtable', commands, 'loadcmdtable'),
731 ('filesetpredicate', fileset, 'loadpredicate'),
731 ('filesetpredicate', fileset, 'loadpredicate'),
732 ('revsetpredicate', revset, 'loadpredicate'),
732 ('revsetpredicate', revset, 'loadpredicate'),
733 ('templatefilter', templatefilters, 'loadfilter'),
733 ('templatefilter', templatefilters, 'loadfilter'),
734 ('templatefunc', templater, 'loadfunction'),
734 ('templatefunc', templater, 'loadfunction'),
735 ('templatekeyword', templatekw, 'loadkeyword'),
735 ('templatekeyword', templatekw, 'loadkeyword'),
736 ]
736 ]
737
737
738 def _dispatch(req):
738 def _dispatch(req):
739 args = req.args
739 args = req.args
740 ui = req.ui
740 ui = req.ui
741
741
742 # check for cwd
742 # check for cwd
743 cwd = _earlygetopt(['--cwd'], args)
743 cwd = _earlygetopt(['--cwd'], args)
744 if cwd:
744 if cwd:
745 os.chdir(cwd[-1])
745 os.chdir(cwd[-1])
746
746
747 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
747 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
748 path, lui = _getlocal(ui, rpath)
748 path, lui = _getlocal(ui, rpath)
749
749
750 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
750 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
751 # reposetup. Programs like TortoiseHg will call _dispatch several
751 # reposetup. Programs like TortoiseHg will call _dispatch several
752 # times so we keep track of configured extensions in _loaded.
752 # times so we keep track of configured extensions in _loaded.
753 extensions.loadall(lui)
753 extensions.loadall(lui)
754 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
754 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
755 # Propagate any changes to lui.__class__ by extensions
755 # Propagate any changes to lui.__class__ by extensions
756 ui.__class__ = lui.__class__
756 ui.__class__ = lui.__class__
757
757
758 # (uisetup and extsetup are handled in extensions.loadall)
758 # (uisetup and extsetup are handled in extensions.loadall)
759
759
760 for name, module in exts:
760 for name, module in exts:
761 for objname, loadermod, loadername in extraloaders:
761 for objname, loadermod, loadername in extraloaders:
762 extraobj = getattr(module, objname, None)
762 extraobj = getattr(module, objname, None)
763 if extraobj is not None:
763 if extraobj is not None:
764 getattr(loadermod, loadername)(ui, name, extraobj)
764 getattr(loadermod, loadername)(ui, name, extraobj)
765 _loaded.add(name)
765 _loaded.add(name)
766
766
767 # (reposetup is handled in hg.repository)
767 # (reposetup is handled in hg.repository)
768
768
769 addaliases(lui, commands.table)
769 addaliases(lui, commands.table)
770
770
771 # All aliases and commands are completely defined, now.
771 # All aliases and commands are completely defined, now.
772 # Check abbreviation/ambiguity of shell alias.
772 # Check abbreviation/ambiguity of shell alias.
773 shellaliasfn = _checkshellalias(lui, ui, args)
773 shellaliasfn = _checkshellalias(lui, ui, args)
774 if shellaliasfn:
774 if shellaliasfn:
775 return shellaliasfn()
775 return shellaliasfn()
776
776
777 # check for fallback encoding
777 # check for fallback encoding
778 fallback = lui.config('ui', 'fallbackencoding')
778 fallback = lui.config('ui', 'fallbackencoding')
779 if fallback:
779 if fallback:
780 encoding.fallbackencoding = fallback
780 encoding.fallbackencoding = fallback
781
781
782 fullargs = args
782 fullargs = args
783 cmd, func, args, options, cmdoptions = _parse(lui, args)
783 cmd, func, args, options, cmdoptions = _parse(lui, args)
784
784
785 if options["config"]:
785 if options["config"]:
786 raise error.Abort(_("option --config may not be abbreviated!"))
786 raise error.Abort(_("option --config may not be abbreviated!"))
787 if options["cwd"]:
787 if options["cwd"]:
788 raise error.Abort(_("option --cwd may not be abbreviated!"))
788 raise error.Abort(_("option --cwd may not be abbreviated!"))
789 if options["repository"]:
789 if options["repository"]:
790 raise error.Abort(_(
790 raise error.Abort(_(
791 "option -R has to be separated from other options (e.g. not -qR) "
791 "option -R has to be separated from other options (e.g. not -qR) "
792 "and --repository may only be abbreviated as --repo!"))
792 "and --repository may only be abbreviated as --repo!"))
793
793
794 if options["encoding"]:
794 if options["encoding"]:
795 encoding.encoding = options["encoding"]
795 encoding.encoding = options["encoding"]
796 if options["encodingmode"]:
796 if options["encodingmode"]:
797 encoding.encodingmode = options["encodingmode"]
797 encoding.encodingmode = options["encodingmode"]
798 if options["time"]:
798 if options["time"]:
799 def get_times():
799 def get_times():
800 t = os.times()
800 t = os.times()
801 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
801 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
802 t = (t[0], t[1], t[2], t[3], time.clock())
802 t = (t[0], t[1], t[2], t[3], time.clock())
803 return t
803 return t
804 s = get_times()
804 s = get_times()
805 def print_time():
805 def print_time():
806 t = get_times()
806 t = get_times()
807 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
807 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
808 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
808 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
809 atexit.register(print_time)
809 atexit.register(print_time)
810
810
811 uis = set([ui, lui])
811 uis = set([ui, lui])
812
812
813 if req.repo:
813 if req.repo:
814 uis.add(req.repo.ui)
814 uis.add(req.repo.ui)
815
815
816 if options['verbose'] or options['debug'] or options['quiet']:
816 if options['verbose'] or options['debug'] or options['quiet']:
817 for opt in ('verbose', 'debug', 'quiet'):
817 for opt in ('verbose', 'debug', 'quiet'):
818 val = str(bool(options[opt]))
818 val = str(bool(options[opt]))
819 for ui_ in uis:
819 for ui_ in uis:
820 ui_.setconfig('ui', opt, val, '--' + opt)
820 ui_.setconfig('ui', opt, val, '--' + opt)
821
821
822 if options['profile']:
822 if options['profile']:
823 for ui_ in uis:
823 for ui_ in uis:
824 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
824 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
825
825
826 if options['traceback']:
826 if options['traceback']:
827 for ui_ in uis:
827 for ui_ in uis:
828 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
828 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
829
829
830 if options['noninteractive']:
830 if options['noninteractive']:
831 for ui_ in uis:
831 for ui_ in uis:
832 ui_.setconfig('ui', 'interactive', 'off', '-y')
832 ui_.setconfig('ui', 'interactive', 'off', '-y')
833
833
834 if cmdoptions.get('insecure', False):
834 if cmdoptions.get('insecure', False):
835 for ui_ in uis:
835 for ui_ in uis:
836 ui_.insecureconnections = True
836 ui_.insecureconnections = True
837
837
838 if options['version']:
838 if options['version']:
839 return commands.version_(ui)
839 return commands.version_(ui)
840 if options['help']:
840 if options['help']:
841 return commands.help_(ui, cmd, command=cmd is not None)
841 return commands.help_(ui, cmd, command=cmd is not None)
842 elif not cmd:
842 elif not cmd:
843 return commands.help_(ui, 'shortlist')
843 return commands.help_(ui, 'shortlist')
844
844
845 repo = None
845 repo = None
846 cmdpats = args[:]
846 cmdpats = args[:]
847 if not _cmdattr(ui, cmd, func, 'norepo'):
847 if not _cmdattr(ui, cmd, func, 'norepo'):
848 # use the repo from the request only if we don't have -R
848 # use the repo from the request only if we don't have -R
849 if not rpath and not cwd:
849 if not rpath and not cwd:
850 repo = req.repo
850 repo = req.repo
851
851
852 if repo:
852 if repo:
853 # set the descriptors of the repo ui to those of ui
853 # set the descriptors of the repo ui to those of ui
854 repo.ui.fin = ui.fin
854 repo.ui.fin = ui.fin
855 repo.ui.fout = ui.fout
855 repo.ui.fout = ui.fout
856 repo.ui.ferr = ui.ferr
856 repo.ui.ferr = ui.ferr
857 else:
857 else:
858 try:
858 try:
859 repo = hg.repository(ui, path=path)
859 repo = hg.repository(ui, path=path)
860 if not repo.local():
860 if not repo.local():
861 raise error.Abort(_("repository '%s' is not local") % path)
861 raise error.Abort(_("repository '%s' is not local") % path)
862 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
862 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
863 except error.RequirementError:
863 except error.RequirementError:
864 raise
864 raise
865 except error.RepoError:
865 except error.RepoError:
866 if rpath and rpath[-1]: # invalid -R path
866 if rpath and rpath[-1]: # invalid -R path
867 raise
867 raise
868 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
868 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
869 if (_cmdattr(ui, cmd, func, 'inferrepo') and
869 if (_cmdattr(ui, cmd, func, 'inferrepo') and
870 args and not path):
870 args and not path):
871 # try to infer -R from command args
871 # try to infer -R from command args
872 repos = map(cmdutil.findrepo, args)
872 repos = map(cmdutil.findrepo, args)
873 guess = repos[0]
873 guess = repos[0]
874 if guess and repos.count(guess) == len(repos):
874 if guess and repos.count(guess) == len(repos):
875 req.args = ['--repository', guess] + fullargs
875 req.args = ['--repository', guess] + fullargs
876 return _dispatch(req)
876 return _dispatch(req)
877 if not path:
877 if not path:
878 raise error.RepoError(_("no repository found in '%s'"
878 raise error.RepoError(_("no repository found in '%s'"
879 " (.hg not found)")
879 " (.hg not found)")
880 % os.getcwd())
880 % os.getcwd())
881 raise
881 raise
882 if repo:
882 if repo:
883 ui = repo.ui
883 ui = repo.ui
884 if options['hidden']:
884 if options['hidden']:
885 repo = repo.unfiltered()
885 repo = repo.unfiltered()
886 args.insert(0, repo)
886 args.insert(0, repo)
887 elif rpath:
887 elif rpath:
888 ui.warn(_("warning: --repository ignored\n"))
888 ui.warn(_("warning: --repository ignored\n"))
889
889
890 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
890 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
891 ui.log("command", '%s\n', msg)
891 ui.log("command", '%s\n', msg)
892 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
892 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
893 try:
893 try:
894 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
894 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
895 cmdpats, cmdoptions)
895 cmdpats, cmdoptions)
896 finally:
896 finally:
897 if repo and repo != req.repo:
897 if repo and repo != req.repo:
898 repo.close()
898 repo.close()
899
899
900 def _runcommand(ui, options, cmd, cmdfunc):
900 def _runcommand(ui, options, cmd, cmdfunc):
901 """Enables the profiler if applicable.
901 """Enables the profiler if applicable.
902
902
903 ``profiling.enabled`` - boolean config that enables or disables profiling
903 ``profiling.enabled`` - boolean config that enables or disables profiling
904 """
904 """
905 def checkargs():
905 def checkargs():
906 try:
906 try:
907 return cmdfunc()
907 return cmdfunc()
908 except error.SignatureError:
908 except error.SignatureError:
909 raise error.CommandError(cmd, _("invalid arguments"))
909 raise error.CommandError(cmd, _("invalid arguments"))
910
910
911 if ui.configbool('profiling', 'enabled'):
911 if ui.configbool('profiling', 'enabled'):
912 return profiling.profile(ui, checkargs)
912 with profiling.profile(ui):
913 return checkargs()
913 else:
914 else:
914 return checkargs()
915 return checkargs()
915
916
916 def _exceptionwarning(ui):
917 def _exceptionwarning(ui):
917 """Produce a warning message for the current active exception"""
918 """Produce a warning message for the current active exception"""
918
919
919 # For compatibility checking, we discard the portion of the hg
920 # For compatibility checking, we discard the portion of the hg
920 # version after the + on the assumption that if a "normal
921 # version after the + on the assumption that if a "normal
921 # user" is running a build with a + in it the packager
922 # user" is running a build with a + in it the packager
922 # probably built from fairly close to a tag and anyone with a
923 # probably built from fairly close to a tag and anyone with a
923 # 'make local' copy of hg (where the version number can be out
924 # 'make local' copy of hg (where the version number can be out
924 # of date) will be clueful enough to notice the implausible
925 # of date) will be clueful enough to notice the implausible
925 # version number and try updating.
926 # version number and try updating.
926 ct = util.versiontuple(n=2)
927 ct = util.versiontuple(n=2)
927 worst = None, ct, ''
928 worst = None, ct, ''
928 if ui.config('ui', 'supportcontact', None) is None:
929 if ui.config('ui', 'supportcontact', None) is None:
929 for name, mod in extensions.extensions():
930 for name, mod in extensions.extensions():
930 testedwith = getattr(mod, 'testedwith', '')
931 testedwith = getattr(mod, 'testedwith', '')
931 report = getattr(mod, 'buglink', _('the extension author.'))
932 report = getattr(mod, 'buglink', _('the extension author.'))
932 if not testedwith.strip():
933 if not testedwith.strip():
933 # We found an untested extension. It's likely the culprit.
934 # We found an untested extension. It's likely the culprit.
934 worst = name, 'unknown', report
935 worst = name, 'unknown', report
935 break
936 break
936
937
937 # Never blame on extensions bundled with Mercurial.
938 # Never blame on extensions bundled with Mercurial.
938 if testedwith == 'internal':
939 if testedwith == 'internal':
939 continue
940 continue
940
941
941 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
942 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
942 if ct in tested:
943 if ct in tested:
943 continue
944 continue
944
945
945 lower = [t for t in tested if t < ct]
946 lower = [t for t in tested if t < ct]
946 nearest = max(lower or tested)
947 nearest = max(lower or tested)
947 if worst[0] is None or nearest < worst[1]:
948 if worst[0] is None or nearest < worst[1]:
948 worst = name, nearest, report
949 worst = name, nearest, report
949 if worst[0] is not None:
950 if worst[0] is not None:
950 name, testedwith, report = worst
951 name, testedwith, report = worst
951 if not isinstance(testedwith, str):
952 if not isinstance(testedwith, str):
952 testedwith = '.'.join([str(c) for c in testedwith])
953 testedwith = '.'.join([str(c) for c in testedwith])
953 warning = (_('** Unknown exception encountered with '
954 warning = (_('** Unknown exception encountered with '
954 'possibly-broken third-party extension %s\n'
955 'possibly-broken third-party extension %s\n'
955 '** which supports versions %s of Mercurial.\n'
956 '** which supports versions %s of Mercurial.\n'
956 '** Please disable %s and try your action again.\n'
957 '** Please disable %s and try your action again.\n'
957 '** If that fixes the bug please report it to %s\n')
958 '** If that fixes the bug please report it to %s\n')
958 % (name, testedwith, name, report))
959 % (name, testedwith, name, report))
959 else:
960 else:
960 bugtracker = ui.config('ui', 'supportcontact', None)
961 bugtracker = ui.config('ui', 'supportcontact', None)
961 if bugtracker is None:
962 if bugtracker is None:
962 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
963 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
963 warning = (_("** unknown exception encountered, "
964 warning = (_("** unknown exception encountered, "
964 "please report by visiting\n** ") + bugtracker + '\n')
965 "please report by visiting\n** ") + bugtracker + '\n')
965 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
966 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
966 (_("** Mercurial Distributed SCM (version %s)\n") %
967 (_("** Mercurial Distributed SCM (version %s)\n") %
967 util.version()) +
968 util.version()) +
968 (_("** Extensions loaded: %s\n") %
969 (_("** Extensions loaded: %s\n") %
969 ", ".join([x[0] for x in extensions.extensions()])))
970 ", ".join([x[0] for x in extensions.extensions()])))
970 return warning
971 return warning
971
972
972 def handlecommandexception(ui):
973 def handlecommandexception(ui):
973 """Produce a warning message for broken commands
974 """Produce a warning message for broken commands
974
975
975 Called when handling an exception; the exception is reraised if
976 Called when handling an exception; the exception is reraised if
976 this function returns False, ignored otherwise.
977 this function returns False, ignored otherwise.
977 """
978 """
978 warning = _exceptionwarning(ui)
979 warning = _exceptionwarning(ui)
979 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
980 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
980 ui.warn(warning)
981 ui.warn(warning)
981 return False # re-raise the exception
982 return False # re-raise the exception
@@ -1,132 +1,145 b''
1 # profiling.py - profiling functions
1 # profiling.py - profiling functions
2 #
2 #
3 # Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2016 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import contextlib
10 import os
11 import os
11 import sys
12 import sys
12 import time
13 import time
13
14
14 from .i18n import _
15 from .i18n import _
15 from . import (
16 from . import (
16 error,
17 error,
17 util,
18 util,
18 )
19 )
19
20
20 def lsprofile(ui, func, fp):
21 @contextlib.contextmanager
22 def lsprofile(ui, fp):
21 format = ui.config('profiling', 'format', default='text')
23 format = ui.config('profiling', 'format', default='text')
22 field = ui.config('profiling', 'sort', default='inlinetime')
24 field = ui.config('profiling', 'sort', default='inlinetime')
23 limit = ui.configint('profiling', 'limit', default=30)
25 limit = ui.configint('profiling', 'limit', default=30)
24 climit = ui.configint('profiling', 'nested', default=0)
26 climit = ui.configint('profiling', 'nested', default=0)
25
27
26 if format not in ['text', 'kcachegrind']:
28 if format not in ['text', 'kcachegrind']:
27 ui.warn(_("unrecognized profiling format '%s'"
29 ui.warn(_("unrecognized profiling format '%s'"
28 " - Ignored\n") % format)
30 " - Ignored\n") % format)
29 format = 'text'
31 format = 'text'
30
32
31 try:
33 try:
32 from . import lsprof
34 from . import lsprof
33 except ImportError:
35 except ImportError:
34 raise error.Abort(_(
36 raise error.Abort(_(
35 'lsprof not available - install from '
37 'lsprof not available - install from '
36 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
38 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
37 p = lsprof.Profiler()
39 p = lsprof.Profiler()
38 p.enable(subcalls=True)
40 p.enable(subcalls=True)
39 try:
41 try:
40 return func()
42 yield
41 finally:
43 finally:
42 p.disable()
44 p.disable()
43
45
44 if format == 'kcachegrind':
46 if format == 'kcachegrind':
45 from . import lsprofcalltree
47 from . import lsprofcalltree
46 calltree = lsprofcalltree.KCacheGrind(p)
48 calltree = lsprofcalltree.KCacheGrind(p)
47 calltree.output(fp)
49 calltree.output(fp)
48 else:
50 else:
49 # format == 'text'
51 # format == 'text'
50 stats = lsprof.Stats(p.getstats())
52 stats = lsprof.Stats(p.getstats())
51 stats.sort(field)
53 stats.sort(field)
52 stats.pprint(limit=limit, file=fp, climit=climit)
54 stats.pprint(limit=limit, file=fp, climit=climit)
53
55
54 def flameprofile(ui, func, fp):
56 @contextlib.contextmanager
57 def flameprofile(ui, fp):
55 try:
58 try:
56 from flamegraph import flamegraph
59 from flamegraph import flamegraph
57 except ImportError:
60 except ImportError:
58 raise error.Abort(_(
61 raise error.Abort(_(
59 'flamegraph not available - install from '
62 'flamegraph not available - install from '
60 'https://github.com/evanhempel/python-flamegraph'))
63 'https://github.com/evanhempel/python-flamegraph'))
61 # developer config: profiling.freq
64 # developer config: profiling.freq
62 freq = ui.configint('profiling', 'freq', default=1000)
65 freq = ui.configint('profiling', 'freq', default=1000)
63 filter_ = None
66 filter_ = None
64 collapse_recursion = True
67 collapse_recursion = True
65 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
68 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
66 filter_, collapse_recursion)
69 filter_, collapse_recursion)
67 start_time = time.clock()
70 start_time = time.clock()
68 try:
71 try:
69 thread.start()
72 thread.start()
70 func()
73 yield
71 finally:
74 finally:
72 thread.stop()
75 thread.stop()
73 thread.join()
76 thread.join()
74 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
77 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
75 time.clock() - start_time, thread.num_frames(),
78 time.clock() - start_time, thread.num_frames(),
76 thread.num_frames(unique=True)))
79 thread.num_frames(unique=True)))
77
80
78 def statprofile(ui, func, fp):
81 @contextlib.contextmanager
82 def statprofile(ui, fp):
79 try:
83 try:
80 import statprof
84 import statprof
81 except ImportError:
85 except ImportError:
82 raise error.Abort(_(
86 raise error.Abort(_(
83 'statprof not available - install using "easy_install statprof"'))
87 'statprof not available - install using "easy_install statprof"'))
84
88
85 freq = ui.configint('profiling', 'freq', default=1000)
89 freq = ui.configint('profiling', 'freq', default=1000)
86 if freq > 0:
90 if freq > 0:
87 statprof.reset(freq)
91 statprof.reset(freq)
88 else:
92 else:
89 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
93 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
90
94
91 statprof.start()
95 statprof.start()
92 try:
96 try:
93 return func()
97 yield
94 finally:
98 finally:
95 statprof.stop()
99 statprof.stop()
96 statprof.display(fp)
100 statprof.display(fp)
97
101
98 def profile(ui, fn):
102 @contextlib.contextmanager
99 """Profile a function call."""
103 def profile(ui):
104 """Start profiling.
105
106 Profiling is active when the context manager is active. When the context
107 manager exits, profiling results will be written to the configured output.
108 """
100 profiler = os.getenv('HGPROF')
109 profiler = os.getenv('HGPROF')
101 if profiler is None:
110 if profiler is None:
102 profiler = ui.config('profiling', 'type', default='ls')
111 profiler = ui.config('profiling', 'type', default='ls')
103 if profiler not in ('ls', 'stat', 'flame'):
112 if profiler not in ('ls', 'stat', 'flame'):
104 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
113 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
105 profiler = 'ls'
114 profiler = 'ls'
106
115
107 output = ui.config('profiling', 'output')
116 output = ui.config('profiling', 'output')
108
117
109 if output == 'blackbox':
118 if output == 'blackbox':
110 fp = util.stringio()
119 fp = util.stringio()
111 elif output:
120 elif output:
112 path = ui.expandpath(output)
121 path = ui.expandpath(output)
113 fp = open(path, 'wb')
122 fp = open(path, 'wb')
114 else:
123 else:
115 fp = sys.stderr
124 fp = sys.stderr
116
125
117 try:
126 try:
118 if profiler == 'ls':
127 if profiler == 'ls':
119 return lsprofile(ui, fn, fp)
128 proffn = lsprofile
120 elif profiler == 'flame':
129 elif profiler == 'flame':
121 return flameprofile(ui, fn, fp)
130 proffn = flameprofile
122 else:
131 else:
123 return statprofile(ui, fn, fp)
132 proffn = statprofile
133
134 with proffn(ui, fp):
135 yield
136
124 finally:
137 finally:
125 if output:
138 if output:
126 if output == 'blackbox':
139 if output == 'blackbox':
127 val = 'Profile:\n%s' % fp.getvalue()
140 val = 'Profile:\n%s' % fp.getvalue()
128 # ui.log treats the input as a format string,
141 # ui.log treats the input as a format string,
129 # so we need to escape any % signs.
142 # so we need to escape any % signs.
130 val = val.replace('%', '%%')
143 val = val.replace('%', '%%')
131 ui.log('profile', val)
144 ui.log('profile', val)
132 fp.close()
145 fp.close()
General Comments 0
You need to be logged in to leave comments. Login now