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