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