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