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