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