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