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