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