##// END OF EJS Templates
py3: replace "if ispy3" by pycompat.sysbytes() or util.forcebytestr()
Yuya Nishihara -
r35917:d5457d94 default
parent child Browse files
Show More
@@ -1,1000 +1,996
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(zip(*earlyopts)[0])))
480 % (self.name, '/'.join(zip(*earlyopts)[0])))
481 return
481 return
482 self.cmdname = cmd = args.pop(0)
482 self.cmdname = cmd = args.pop(0)
483 self.givenargs = args
483 self.givenargs = args
484
484
485 try:
485 try:
486 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
486 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
487 if len(tableentry) > 2:
487 if len(tableentry) > 2:
488 self.fn, self.opts, self.help = tableentry
488 self.fn, self.opts, self.help = tableentry
489 else:
489 else:
490 self.fn, self.opts = tableentry
490 self.fn, self.opts = tableentry
491
491
492 if self.help.startswith("hg " + cmd):
492 if self.help.startswith("hg " + cmd):
493 # drop prefix in old-style help lines so hg shows the alias
493 # drop prefix in old-style help lines so hg shows the alias
494 self.help = self.help[4 + len(cmd):]
494 self.help = self.help[4 + len(cmd):]
495 self.__doc__ = self.fn.__doc__
495 self.__doc__ = self.fn.__doc__
496
496
497 except error.UnknownCommand:
497 except error.UnknownCommand:
498 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
498 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
499 % (self.name, cmd))
499 % (self.name, cmd))
500 self.unknowncmd = True
500 self.unknowncmd = True
501 except error.AmbiguousCommand:
501 except error.AmbiguousCommand:
502 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
502 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
503 % (self.name, cmd))
503 % (self.name, cmd))
504
504
505 @property
505 @property
506 def args(self):
506 def args(self):
507 args = pycompat.maplist(util.expandpath, self.givenargs)
507 args = pycompat.maplist(util.expandpath, self.givenargs)
508 return aliasargs(self.fn, args)
508 return aliasargs(self.fn, args)
509
509
510 def __getattr__(self, name):
510 def __getattr__(self, name):
511 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
511 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
512 r'optionalrepo': False, r'inferrepo': False}
512 r'optionalrepo': False, r'inferrepo': False}
513 if name not in adefaults:
513 if name not in adefaults:
514 raise AttributeError(name)
514 raise AttributeError(name)
515 if self.badalias or util.safehasattr(self, 'shell'):
515 if self.badalias or util.safehasattr(self, 'shell'):
516 return adefaults[name]
516 return adefaults[name]
517 return getattr(self.fn, name)
517 return getattr(self.fn, name)
518
518
519 def __call__(self, ui, *args, **opts):
519 def __call__(self, ui, *args, **opts):
520 if self.badalias:
520 if self.badalias:
521 hint = None
521 hint = None
522 if self.unknowncmd:
522 if self.unknowncmd:
523 try:
523 try:
524 # check if the command is in a disabled extension
524 # check if the command is in a disabled extension
525 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
525 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
526 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
526 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
527 except error.UnknownCommand:
527 except error.UnknownCommand:
528 pass
528 pass
529 raise error.Abort(self.badalias, hint=hint)
529 raise error.Abort(self.badalias, hint=hint)
530 if self.shadows:
530 if self.shadows:
531 ui.debug("alias '%s' shadows command '%s'\n" %
531 ui.debug("alias '%s' shadows command '%s'\n" %
532 (self.name, self.cmdname))
532 (self.name, self.cmdname))
533
533
534 ui.log('commandalias', "alias '%s' expands to '%s'\n",
534 ui.log('commandalias', "alias '%s' expands to '%s'\n",
535 self.name, self.definition)
535 self.name, self.definition)
536 if util.safehasattr(self, 'shell'):
536 if util.safehasattr(self, 'shell'):
537 return self.fn(ui, *args, **opts)
537 return self.fn(ui, *args, **opts)
538 else:
538 else:
539 try:
539 try:
540 return util.checksignature(self.fn)(ui, *args, **opts)
540 return util.checksignature(self.fn)(ui, *args, **opts)
541 except error.SignatureError:
541 except error.SignatureError:
542 args = ' '.join([self.cmdname] + self.args)
542 args = ' '.join([self.cmdname] + self.args)
543 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
543 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
544 raise
544 raise
545
545
546 class lazyaliasentry(object):
546 class lazyaliasentry(object):
547 """like a typical command entry (func, opts, help), but is lazy"""
547 """like a typical command entry (func, opts, help), but is lazy"""
548
548
549 def __init__(self, name, definition, cmdtable, source):
549 def __init__(self, name, definition, cmdtable, source):
550 self.name = name
550 self.name = name
551 self.definition = definition
551 self.definition = definition
552 self.cmdtable = cmdtable.copy()
552 self.cmdtable = cmdtable.copy()
553 self.source = source
553 self.source = source
554
554
555 @util.propertycache
555 @util.propertycache
556 def _aliasdef(self):
556 def _aliasdef(self):
557 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
557 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
558
558
559 def __getitem__(self, n):
559 def __getitem__(self, n):
560 aliasdef = self._aliasdef
560 aliasdef = self._aliasdef
561 if n == 0:
561 if n == 0:
562 return aliasdef
562 return aliasdef
563 elif n == 1:
563 elif n == 1:
564 return aliasdef.opts
564 return aliasdef.opts
565 elif n == 2:
565 elif n == 2:
566 return aliasdef.help
566 return aliasdef.help
567 else:
567 else:
568 raise IndexError
568 raise IndexError
569
569
570 def __iter__(self):
570 def __iter__(self):
571 for i in range(3):
571 for i in range(3):
572 yield self[i]
572 yield self[i]
573
573
574 def __len__(self):
574 def __len__(self):
575 return 3
575 return 3
576
576
577 def addaliases(ui, cmdtable):
577 def addaliases(ui, cmdtable):
578 # aliases are processed after extensions have been loaded, so they
578 # aliases are processed after extensions have been loaded, so they
579 # may use extension commands. Aliases can also use other alias definitions,
579 # may use extension commands. Aliases can also use other alias definitions,
580 # but only if they have been defined prior to the current definition.
580 # but only if they have been defined prior to the current definition.
581 for alias, definition in ui.configitems('alias'):
581 for alias, definition in ui.configitems('alias'):
582 try:
582 try:
583 if cmdtable[alias].definition == definition:
583 if cmdtable[alias].definition == definition:
584 continue
584 continue
585 except (KeyError, AttributeError):
585 except (KeyError, AttributeError):
586 # definition might not exist or it might not be a cmdalias
586 # definition might not exist or it might not be a cmdalias
587 pass
587 pass
588
588
589 source = ui.configsource('alias', alias)
589 source = ui.configsource('alias', alias)
590 entry = lazyaliasentry(alias, definition, cmdtable, source)
590 entry = lazyaliasentry(alias, definition, cmdtable, source)
591 cmdtable[alias] = entry
591 cmdtable[alias] = entry
592
592
593 def _parse(ui, args):
593 def _parse(ui, args):
594 options = {}
594 options = {}
595 cmdoptions = {}
595 cmdoptions = {}
596
596
597 try:
597 try:
598 args = fancyopts.fancyopts(args, commands.globalopts, options)
598 args = fancyopts.fancyopts(args, commands.globalopts, options)
599 except getopt.GetoptError as inst:
599 except getopt.GetoptError as inst:
600 raise error.CommandError(None, inst)
600 raise error.CommandError(None, inst)
601
601
602 if args:
602 if args:
603 cmd, args = args[0], args[1:]
603 cmd, args = args[0], args[1:]
604 aliases, entry = cmdutil.findcmd(cmd, commands.table,
604 aliases, entry = cmdutil.findcmd(cmd, commands.table,
605 ui.configbool("ui", "strict"))
605 ui.configbool("ui", "strict"))
606 cmd = aliases[0]
606 cmd = aliases[0]
607 args = aliasargs(entry[0], args)
607 args = aliasargs(entry[0], args)
608 defaults = ui.config("defaults", cmd)
608 defaults = ui.config("defaults", cmd)
609 if defaults:
609 if defaults:
610 args = pycompat.maplist(
610 args = pycompat.maplist(
611 util.expandpath, pycompat.shlexsplit(defaults)) + args
611 util.expandpath, pycompat.shlexsplit(defaults)) + args
612 c = list(entry[1])
612 c = list(entry[1])
613 else:
613 else:
614 cmd = None
614 cmd = None
615 c = []
615 c = []
616
616
617 # combine global options into local
617 # combine global options into local
618 for o in commands.globalopts:
618 for o in commands.globalopts:
619 c.append((o[0], o[1], options[o[1]], o[3]))
619 c.append((o[0], o[1], options[o[1]], o[3]))
620
620
621 try:
621 try:
622 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
622 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
623 except getopt.GetoptError as inst:
623 except getopt.GetoptError as inst:
624 raise error.CommandError(cmd, inst)
624 raise error.CommandError(cmd, inst)
625
625
626 # separate global options back out
626 # separate global options back out
627 for o in commands.globalopts:
627 for o in commands.globalopts:
628 n = o[1]
628 n = o[1]
629 options[n] = cmdoptions[n]
629 options[n] = cmdoptions[n]
630 del cmdoptions[n]
630 del cmdoptions[n]
631
631
632 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
632 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
633
633
634 def _parseconfig(ui, config):
634 def _parseconfig(ui, config):
635 """parse the --config options from the command line"""
635 """parse the --config options from the command line"""
636 configs = []
636 configs = []
637
637
638 for cfg in config:
638 for cfg in config:
639 try:
639 try:
640 name, value = [cfgelem.strip()
640 name, value = [cfgelem.strip()
641 for cfgelem in cfg.split('=', 1)]
641 for cfgelem in cfg.split('=', 1)]
642 section, name = name.split('.', 1)
642 section, name = name.split('.', 1)
643 if not section or not name:
643 if not section or not name:
644 raise IndexError
644 raise IndexError
645 ui.setconfig(section, name, value, '--config')
645 ui.setconfig(section, name, value, '--config')
646 configs.append((section, name, value))
646 configs.append((section, name, value))
647 except (IndexError, ValueError):
647 except (IndexError, ValueError):
648 raise error.Abort(_('malformed --config option: %r '
648 raise error.Abort(_('malformed --config option: %r '
649 '(use --config section.name=value)') % cfg)
649 '(use --config section.name=value)') % cfg)
650
650
651 return configs
651 return configs
652
652
653 def _earlyparseopts(ui, args):
653 def _earlyparseopts(ui, args):
654 options = {}
654 options = {}
655 fancyopts.fancyopts(args, commands.globalopts, options,
655 fancyopts.fancyopts(args, commands.globalopts, options,
656 gnu=not ui.plain('strictflags'), early=True,
656 gnu=not ui.plain('strictflags'), early=True,
657 optaliases={'repository': ['repo']})
657 optaliases={'repository': ['repo']})
658 return options
658 return options
659
659
660 def _earlysplitopts(args):
660 def _earlysplitopts(args):
661 """Split args into a list of possible early options and remainder args"""
661 """Split args into a list of possible early options and remainder args"""
662 shortoptions = 'R:'
662 shortoptions = 'R:'
663 # TODO: perhaps 'debugger' should be included
663 # TODO: perhaps 'debugger' should be included
664 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
664 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
665 return fancyopts.earlygetopt(args, shortoptions, longoptions,
665 return fancyopts.earlygetopt(args, shortoptions, longoptions,
666 gnu=True, keepsep=True)
666 gnu=True, keepsep=True)
667
667
668 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
668 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
669 # run pre-hook, and abort if it fails
669 # run pre-hook, and abort if it fails
670 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
670 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
671 pats=cmdpats, opts=cmdoptions)
671 pats=cmdpats, opts=cmdoptions)
672 try:
672 try:
673 ret = _runcommand(ui, options, cmd, d)
673 ret = _runcommand(ui, options, cmd, d)
674 # run post-hook, passing command result
674 # run post-hook, passing command result
675 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
675 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
676 result=ret, pats=cmdpats, opts=cmdoptions)
676 result=ret, pats=cmdpats, opts=cmdoptions)
677 except Exception:
677 except Exception:
678 # run failure hook and re-raise
678 # run failure hook and re-raise
679 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
679 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
680 pats=cmdpats, opts=cmdoptions)
680 pats=cmdpats, opts=cmdoptions)
681 raise
681 raise
682 return ret
682 return ret
683
683
684 def _getlocal(ui, rpath, wd=None):
684 def _getlocal(ui, rpath, wd=None):
685 """Return (path, local ui object) for the given target path.
685 """Return (path, local ui object) for the given target path.
686
686
687 Takes paths in [cwd]/.hg/hgrc into account."
687 Takes paths in [cwd]/.hg/hgrc into account."
688 """
688 """
689 if wd is None:
689 if wd is None:
690 try:
690 try:
691 wd = pycompat.getcwd()
691 wd = pycompat.getcwd()
692 except OSError as e:
692 except OSError as e:
693 raise error.Abort(_("error getting current working directory: %s") %
693 raise error.Abort(_("error getting current working directory: %s") %
694 encoding.strtolocal(e.strerror))
694 encoding.strtolocal(e.strerror))
695 path = cmdutil.findrepo(wd) or ""
695 path = cmdutil.findrepo(wd) or ""
696 if not path:
696 if not path:
697 lui = ui
697 lui = ui
698 else:
698 else:
699 lui = ui.copy()
699 lui = ui.copy()
700 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
700 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
701
701
702 if rpath:
702 if rpath:
703 path = lui.expandpath(rpath)
703 path = lui.expandpath(rpath)
704 lui = ui.copy()
704 lui = ui.copy()
705 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
705 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
706
706
707 return path, lui
707 return path, lui
708
708
709 def _checkshellalias(lui, ui, args):
709 def _checkshellalias(lui, ui, args):
710 """Return the function to run the shell alias, if it is required"""
710 """Return the function to run the shell alias, if it is required"""
711 options = {}
711 options = {}
712
712
713 try:
713 try:
714 args = fancyopts.fancyopts(args, commands.globalopts, options)
714 args = fancyopts.fancyopts(args, commands.globalopts, options)
715 except getopt.GetoptError:
715 except getopt.GetoptError:
716 return
716 return
717
717
718 if not args:
718 if not args:
719 return
719 return
720
720
721 cmdtable = commands.table
721 cmdtable = commands.table
722
722
723 cmd = args[0]
723 cmd = args[0]
724 try:
724 try:
725 strict = ui.configbool("ui", "strict")
725 strict = ui.configbool("ui", "strict")
726 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
726 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
727 except (error.AmbiguousCommand, error.UnknownCommand):
727 except (error.AmbiguousCommand, error.UnknownCommand):
728 return
728 return
729
729
730 cmd = aliases[0]
730 cmd = aliases[0]
731 fn = entry[0]
731 fn = entry[0]
732
732
733 if cmd and util.safehasattr(fn, 'shell'):
733 if cmd and util.safehasattr(fn, 'shell'):
734 # shell alias shouldn't receive early options which are consumed by hg
734 # shell alias shouldn't receive early options which are consumed by hg
735 _earlyopts, args = _earlysplitopts(args)
735 _earlyopts, args = _earlysplitopts(args)
736 d = lambda: fn(ui, *args[1:])
736 d = lambda: fn(ui, *args[1:])
737 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
737 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
738 [], {})
738 [], {})
739
739
740 def _dispatch(req):
740 def _dispatch(req):
741 args = req.args
741 args = req.args
742 ui = req.ui
742 ui = req.ui
743
743
744 # check for cwd
744 # check for cwd
745 cwd = req.earlyoptions['cwd']
745 cwd = req.earlyoptions['cwd']
746 if cwd:
746 if cwd:
747 os.chdir(cwd)
747 os.chdir(cwd)
748
748
749 rpath = req.earlyoptions['repository']
749 rpath = req.earlyoptions['repository']
750 path, lui = _getlocal(ui, rpath)
750 path, lui = _getlocal(ui, rpath)
751
751
752 uis = {ui, lui}
752 uis = {ui, lui}
753
753
754 if req.repo:
754 if req.repo:
755 uis.add(req.repo.ui)
755 uis.add(req.repo.ui)
756
756
757 if req.earlyoptions['profile']:
757 if req.earlyoptions['profile']:
758 for ui_ in uis:
758 for ui_ in uis:
759 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
759 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
760
760
761 profile = lui.configbool('profiling', 'enabled')
761 profile = lui.configbool('profiling', 'enabled')
762 with profiling.profile(lui, enabled=profile) as profiler:
762 with profiling.profile(lui, enabled=profile) as profiler:
763 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
763 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
764 # reposetup
764 # reposetup
765 extensions.loadall(lui)
765 extensions.loadall(lui)
766 # Propagate any changes to lui.__class__ by extensions
766 # Propagate any changes to lui.__class__ by extensions
767 ui.__class__ = lui.__class__
767 ui.__class__ = lui.__class__
768
768
769 # (uisetup and extsetup are handled in extensions.loadall)
769 # (uisetup and extsetup are handled in extensions.loadall)
770
770
771 # (reposetup is handled in hg.repository)
771 # (reposetup is handled in hg.repository)
772
772
773 addaliases(lui, commands.table)
773 addaliases(lui, commands.table)
774
774
775 # All aliases and commands are completely defined, now.
775 # All aliases and commands are completely defined, now.
776 # Check abbreviation/ambiguity of shell alias.
776 # Check abbreviation/ambiguity of shell alias.
777 shellaliasfn = _checkshellalias(lui, ui, args)
777 shellaliasfn = _checkshellalias(lui, ui, args)
778 if shellaliasfn:
778 if shellaliasfn:
779 return shellaliasfn()
779 return shellaliasfn()
780
780
781 # check for fallback encoding
781 # check for fallback encoding
782 fallback = lui.config('ui', 'fallbackencoding')
782 fallback = lui.config('ui', 'fallbackencoding')
783 if fallback:
783 if fallback:
784 encoding.fallbackencoding = fallback
784 encoding.fallbackencoding = fallback
785
785
786 fullargs = args
786 fullargs = args
787 cmd, func, args, options, cmdoptions = _parse(lui, args)
787 cmd, func, args, options, cmdoptions = _parse(lui, args)
788
788
789 if options["config"] != req.earlyoptions["config"]:
789 if options["config"] != req.earlyoptions["config"]:
790 raise error.Abort(_("option --config may not be abbreviated!"))
790 raise error.Abort(_("option --config may not be abbreviated!"))
791 if options["cwd"] != req.earlyoptions["cwd"]:
791 if options["cwd"] != req.earlyoptions["cwd"]:
792 raise error.Abort(_("option --cwd may not be abbreviated!"))
792 raise error.Abort(_("option --cwd may not be abbreviated!"))
793 if options["repository"] != req.earlyoptions["repository"]:
793 if options["repository"] != req.earlyoptions["repository"]:
794 raise error.Abort(_(
794 raise error.Abort(_(
795 "option -R has to be separated from other options (e.g. not "
795 "option -R has to be separated from other options (e.g. not "
796 "-qR) and --repository may only be abbreviated as --repo!"))
796 "-qR) and --repository may only be abbreviated as --repo!"))
797 if options["debugger"] != req.earlyoptions["debugger"]:
797 if options["debugger"] != req.earlyoptions["debugger"]:
798 raise error.Abort(_("option --debugger may not be abbreviated!"))
798 raise error.Abort(_("option --debugger may not be abbreviated!"))
799 # don't validate --profile/--traceback, which can be enabled from now
799 # don't validate --profile/--traceback, which can be enabled from now
800
800
801 if options["encoding"]:
801 if options["encoding"]:
802 encoding.encoding = options["encoding"]
802 encoding.encoding = options["encoding"]
803 if options["encodingmode"]:
803 if options["encodingmode"]:
804 encoding.encodingmode = options["encodingmode"]
804 encoding.encodingmode = options["encodingmode"]
805 if options["time"]:
805 if options["time"]:
806 def get_times():
806 def get_times():
807 t = os.times()
807 t = os.times()
808 if t[4] == 0.0:
808 if t[4] == 0.0:
809 # Windows leaves this as zero, so use time.clock()
809 # Windows leaves this as zero, so use time.clock()
810 t = (t[0], t[1], t[2], t[3], time.clock())
810 t = (t[0], t[1], t[2], t[3], time.clock())
811 return t
811 return t
812 s = get_times()
812 s = get_times()
813 def print_time():
813 def print_time():
814 t = get_times()
814 t = get_times()
815 ui.warn(
815 ui.warn(
816 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
816 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
817 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
817 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
818 ui.atexit(print_time)
818 ui.atexit(print_time)
819 if options["profile"]:
819 if options["profile"]:
820 profiler.start()
820 profiler.start()
821
821
822 if options['verbose'] or options['debug'] or options['quiet']:
822 if options['verbose'] or options['debug'] or options['quiet']:
823 for opt in ('verbose', 'debug', 'quiet'):
823 for opt in ('verbose', 'debug', 'quiet'):
824 val = pycompat.bytestr(bool(options[opt]))
824 val = pycompat.bytestr(bool(options[opt]))
825 for ui_ in uis:
825 for ui_ in uis:
826 ui_.setconfig('ui', opt, val, '--' + opt)
826 ui_.setconfig('ui', opt, val, '--' + opt)
827
827
828 if options['traceback']:
828 if options['traceback']:
829 for ui_ in uis:
829 for ui_ in uis:
830 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
830 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
831
831
832 if options['noninteractive']:
832 if options['noninteractive']:
833 for ui_ in uis:
833 for ui_ in uis:
834 ui_.setconfig('ui', 'interactive', 'off', '-y')
834 ui_.setconfig('ui', 'interactive', 'off', '-y')
835
835
836 if cmdoptions.get('insecure', False):
836 if cmdoptions.get('insecure', False):
837 for ui_ in uis:
837 for ui_ in uis:
838 ui_.insecureconnections = True
838 ui_.insecureconnections = True
839
839
840 # setup color handling before pager, because setting up pager
840 # setup color handling before pager, because setting up pager
841 # might cause incorrect console information
841 # might cause incorrect console information
842 coloropt = options['color']
842 coloropt = options['color']
843 for ui_ in uis:
843 for ui_ in uis:
844 if coloropt:
844 if coloropt:
845 ui_.setconfig('ui', 'color', coloropt, '--color')
845 ui_.setconfig('ui', 'color', coloropt, '--color')
846 color.setup(ui_)
846 color.setup(ui_)
847
847
848 if util.parsebool(options['pager']):
848 if util.parsebool(options['pager']):
849 # ui.pager() expects 'internal-always-' prefix in this case
849 # ui.pager() expects 'internal-always-' prefix in this case
850 ui.pager('internal-always-' + cmd)
850 ui.pager('internal-always-' + cmd)
851 elif options['pager'] != 'auto':
851 elif options['pager'] != 'auto':
852 for ui_ in uis:
852 for ui_ in uis:
853 ui_.disablepager()
853 ui_.disablepager()
854
854
855 if options['version']:
855 if options['version']:
856 return commands.version_(ui)
856 return commands.version_(ui)
857 if options['help']:
857 if options['help']:
858 return commands.help_(ui, cmd, command=cmd is not None)
858 return commands.help_(ui, cmd, command=cmd is not None)
859 elif not cmd:
859 elif not cmd:
860 return commands.help_(ui, 'shortlist')
860 return commands.help_(ui, 'shortlist')
861
861
862 repo = None
862 repo = None
863 cmdpats = args[:]
863 cmdpats = args[:]
864 if not func.norepo:
864 if not func.norepo:
865 # use the repo from the request only if we don't have -R
865 # use the repo from the request only if we don't have -R
866 if not rpath and not cwd:
866 if not rpath and not cwd:
867 repo = req.repo
867 repo = req.repo
868
868
869 if repo:
869 if repo:
870 # set the descriptors of the repo ui to those of ui
870 # set the descriptors of the repo ui to those of ui
871 repo.ui.fin = ui.fin
871 repo.ui.fin = ui.fin
872 repo.ui.fout = ui.fout
872 repo.ui.fout = ui.fout
873 repo.ui.ferr = ui.ferr
873 repo.ui.ferr = ui.ferr
874 else:
874 else:
875 try:
875 try:
876 repo = hg.repository(ui, path=path,
876 repo = hg.repository(ui, path=path,
877 presetupfuncs=req.prereposetups)
877 presetupfuncs=req.prereposetups)
878 if not repo.local():
878 if not repo.local():
879 raise error.Abort(_("repository '%s' is not local")
879 raise error.Abort(_("repository '%s' is not local")
880 % path)
880 % path)
881 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
881 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
882 'repo')
882 'repo')
883 except error.RequirementError:
883 except error.RequirementError:
884 raise
884 raise
885 except error.RepoError:
885 except error.RepoError:
886 if rpath: # invalid -R path
886 if rpath: # invalid -R path
887 raise
887 raise
888 if not func.optionalrepo:
888 if not func.optionalrepo:
889 if func.inferrepo and args and not path:
889 if func.inferrepo and args and not path:
890 # try to infer -R from command args
890 # try to infer -R from command args
891 repos = pycompat.maplist(cmdutil.findrepo, args)
891 repos = pycompat.maplist(cmdutil.findrepo, args)
892 guess = repos[0]
892 guess = repos[0]
893 if guess and repos.count(guess) == len(repos):
893 if guess and repos.count(guess) == len(repos):
894 req.args = ['--repository', guess] + fullargs
894 req.args = ['--repository', guess] + fullargs
895 req.earlyoptions['repository'] = guess
895 req.earlyoptions['repository'] = guess
896 return _dispatch(req)
896 return _dispatch(req)
897 if not path:
897 if not path:
898 raise error.RepoError(_("no repository found in"
898 raise error.RepoError(_("no repository found in"
899 " '%s' (.hg not found)")
899 " '%s' (.hg not found)")
900 % pycompat.getcwd())
900 % pycompat.getcwd())
901 raise
901 raise
902 if repo:
902 if repo:
903 ui = repo.ui
903 ui = repo.ui
904 if options['hidden']:
904 if options['hidden']:
905 repo = repo.unfiltered()
905 repo = repo.unfiltered()
906 args.insert(0, repo)
906 args.insert(0, repo)
907 elif rpath:
907 elif rpath:
908 ui.warn(_("warning: --repository ignored\n"))
908 ui.warn(_("warning: --repository ignored\n"))
909
909
910 msg = _formatargs(fullargs)
910 msg = _formatargs(fullargs)
911 ui.log("command", '%s\n', msg)
911 ui.log("command", '%s\n', msg)
912 strcmdopt = pycompat.strkwargs(cmdoptions)
912 strcmdopt = pycompat.strkwargs(cmdoptions)
913 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
913 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
914 try:
914 try:
915 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
915 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
916 cmdpats, cmdoptions)
916 cmdpats, cmdoptions)
917 finally:
917 finally:
918 if repo and repo != req.repo:
918 if repo and repo != req.repo:
919 repo.close()
919 repo.close()
920
920
921 def _runcommand(ui, options, cmd, cmdfunc):
921 def _runcommand(ui, options, cmd, cmdfunc):
922 """Run a command function, possibly with profiling enabled."""
922 """Run a command function, possibly with profiling enabled."""
923 try:
923 try:
924 return cmdfunc()
924 return cmdfunc()
925 except error.SignatureError:
925 except error.SignatureError:
926 raise error.CommandError(cmd, _('invalid arguments'))
926 raise error.CommandError(cmd, _('invalid arguments'))
927
927
928 def _exceptionwarning(ui):
928 def _exceptionwarning(ui):
929 """Produce a warning message for the current active exception"""
929 """Produce a warning message for the current active exception"""
930
930
931 # For compatibility checking, we discard the portion of the hg
931 # For compatibility checking, we discard the portion of the hg
932 # version after the + on the assumption that if a "normal
932 # version after the + on the assumption that if a "normal
933 # user" is running a build with a + in it the packager
933 # user" is running a build with a + in it the packager
934 # probably built from fairly close to a tag and anyone with a
934 # probably built from fairly close to a tag and anyone with a
935 # 'make local' copy of hg (where the version number can be out
935 # 'make local' copy of hg (where the version number can be out
936 # of date) will be clueful enough to notice the implausible
936 # of date) will be clueful enough to notice the implausible
937 # version number and try updating.
937 # version number and try updating.
938 ct = util.versiontuple(n=2)
938 ct = util.versiontuple(n=2)
939 worst = None, ct, ''
939 worst = None, ct, ''
940 if ui.config('ui', 'supportcontact') is None:
940 if ui.config('ui', 'supportcontact') is None:
941 for name, mod in extensions.extensions():
941 for name, mod in extensions.extensions():
942 testedwith = getattr(mod, 'testedwith', '')
942 # 'testedwith' should be bytes, but not all extensions are ported
943 if pycompat.ispy3 and isinstance(testedwith, str):
943 # to py3 and we don't want UnicodeException because of that.
944 testedwith = testedwith.encode(u'utf-8')
944 testedwith = util.forcebytestr(getattr(mod, 'testedwith', ''))
945 report = getattr(mod, 'buglink', _('the extension author.'))
945 report = getattr(mod, 'buglink', _('the extension author.'))
946 if not testedwith.strip():
946 if not testedwith.strip():
947 # We found an untested extension. It's likely the culprit.
947 # We found an untested extension. It's likely the culprit.
948 worst = name, 'unknown', report
948 worst = name, 'unknown', report
949 break
949 break
950
950
951 # Never blame on extensions bundled with Mercurial.
951 # Never blame on extensions bundled with Mercurial.
952 if extensions.ismoduleinternal(mod):
952 if extensions.ismoduleinternal(mod):
953 continue
953 continue
954
954
955 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
955 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
956 if ct in tested:
956 if ct in tested:
957 continue
957 continue
958
958
959 lower = [t for t in tested if t < ct]
959 lower = [t for t in tested if t < ct]
960 nearest = max(lower or tested)
960 nearest = max(lower or tested)
961 if worst[0] is None or nearest < worst[1]:
961 if worst[0] is None or nearest < worst[1]:
962 worst = name, nearest, report
962 worst = name, nearest, report
963 if worst[0] is not None:
963 if worst[0] is not None:
964 name, testedwith, report = worst
964 name, testedwith, report = worst
965 if not isinstance(testedwith, (bytes, str)):
965 if not isinstance(testedwith, (bytes, str)):
966 testedwith = '.'.join([str(c) for c in testedwith])
966 testedwith = '.'.join([str(c) for c in testedwith])
967 warning = (_('** Unknown exception encountered with '
967 warning = (_('** Unknown exception encountered with '
968 'possibly-broken third-party extension %s\n'
968 'possibly-broken third-party extension %s\n'
969 '** which supports versions %s of Mercurial.\n'
969 '** which supports versions %s of Mercurial.\n'
970 '** Please disable %s and try your action again.\n'
970 '** Please disable %s and try your action again.\n'
971 '** If that fixes the bug please report it to %s\n')
971 '** If that fixes the bug please report it to %s\n')
972 % (name, testedwith, name, report))
972 % (name, testedwith, name, report))
973 else:
973 else:
974 bugtracker = ui.config('ui', 'supportcontact')
974 bugtracker = ui.config('ui', 'supportcontact')
975 if bugtracker is None:
975 if bugtracker is None:
976 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
976 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
977 warning = (_("** unknown exception encountered, "
977 warning = (_("** unknown exception encountered, "
978 "please report by visiting\n** ") + bugtracker + '\n')
978 "please report by visiting\n** ") + bugtracker + '\n')
979 if pycompat.ispy3:
979 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
980 sysversion = sys.version.encode(u'utf-8')
981 else:
982 sysversion = sys.version
983 sysversion = sysversion.replace('\n', '')
984 warning += ((_("** Python %s\n") % sysversion) +
980 warning += ((_("** Python %s\n") % sysversion) +
985 (_("** Mercurial Distributed SCM (version %s)\n") %
981 (_("** Mercurial Distributed SCM (version %s)\n") %
986 util.version()) +
982 util.version()) +
987 (_("** Extensions loaded: %s\n") %
983 (_("** Extensions loaded: %s\n") %
988 ", ".join([x[0] for x in extensions.extensions()])))
984 ", ".join([x[0] for x in extensions.extensions()])))
989 return warning
985 return warning
990
986
991 def handlecommandexception(ui):
987 def handlecommandexception(ui):
992 """Produce a warning message for broken commands
988 """Produce a warning message for broken commands
993
989
994 Called when handling an exception; the exception is reraised if
990 Called when handling an exception; the exception is reraised if
995 this function returns False, ignored otherwise.
991 this function returns False, ignored otherwise.
996 """
992 """
997 warning = _exceptionwarning(ui)
993 warning = _exceptionwarning(ui)
998 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
994 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
999 ui.warn(warning)
995 ui.warn(warning)
1000 return False # re-raise the exception
996 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now