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