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