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