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