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