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