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