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