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