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