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