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