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