mercurial/dispatch.py
changeset 43076 2372284d9457
parent 41193 dd97354b8891
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    46 from .utils import (
    46 from .utils import (
    47     procutil,
    47     procutil,
    48     stringutil,
    48     stringutil,
    49 )
    49 )
    50 
    50 
       
    51 
    51 class request(object):
    52 class request(object):
    52     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
    53     def __init__(
    53                  ferr=None, fmsg=None, prereposetups=None):
    54         self,
       
    55         args,
       
    56         ui=None,
       
    57         repo=None,
       
    58         fin=None,
       
    59         fout=None,
       
    60         ferr=None,
       
    61         fmsg=None,
       
    62         prereposetups=None,
       
    63     ):
    54         self.args = args
    64         self.args = args
    55         self.ui = ui
    65         self.ui = ui
    56         self.repo = repo
    66         self.repo = repo
    57 
    67 
    58         # input/output/error streams
    68         # input/output/error streams
    78         try:
    88         try:
    79             while handlers:
    89             while handlers:
    80                 func, args, kwargs = handlers.pop()
    90                 func, args, kwargs = handlers.pop()
    81                 try:
    91                 try:
    82                     func(*args, **kwargs)
    92                     func(*args, **kwargs)
    83                 except: # re-raises below
    93                 except:  # re-raises below
    84                     if exc is None:
    94                     if exc is None:
    85                         exc = sys.exc_info()[1]
    95                         exc = sys.exc_info()[1]
    86                     self.ui.warn(('error in exit handlers:\n'))
    96                     self.ui.warn('error in exit handlers:\n')
    87                     self.ui.traceback(force=True)
    97                     self.ui.traceback(force=True)
    88         finally:
    98         finally:
    89             if exc is not None:
    99             if exc is not None:
    90                 raise exc
   100                 raise exc
       
   101 
    91 
   102 
    92 def run():
   103 def run():
    93     "run the command in sys.argv"
   104     "run the command in sys.argv"
    94     initstdio()
   105     initstdio()
    95     with tracing.log('parse args into request'):
   106     with tracing.log('parse args into request'):
   110             status = -1
   121             status = -1
   111 
   122 
   112     if util.safehasattr(req.ui, 'ferr'):
   123     if util.safehasattr(req.ui, 'ferr'):
   113         try:
   124         try:
   114             if err is not None and err.errno != errno.EPIPE:
   125             if err is not None and err.errno != errno.EPIPE:
   115                 req.ui.ferr.write('abort: %s\n' %
   126                 req.ui.ferr.write(
   116                                   encoding.strtolocal(err.strerror))
   127                     'abort: %s\n' % encoding.strtolocal(err.strerror)
       
   128                 )
   117             req.ui.ferr.flush()
   129             req.ui.ferr.flush()
   118         # 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)
   119         # change the status code and move on.
   131         # change the status code and move on.
   120         except IOError:
   132         except IOError:
   121             status = -1
   133             status = -1
   122 
   134 
   123     _silencestdio()
   135     _silencestdio()
   124     sys.exit(status & 255)
   136     sys.exit(status & 255)
   125 
   137 
       
   138 
   126 if pycompat.ispy3:
   139 if pycompat.ispy3:
       
   140 
   127     def initstdio():
   141     def initstdio():
   128         pass
   142         pass
   129 
   143 
   130     def _silencestdio():
   144     def _silencestdio():
   131         for fp in (sys.stdout, sys.stderr):
   145         for fp in (sys.stdout, sys.stderr):
   141             # and its close() actually closes the underlying file descriptor.
   155             # and its close() actually closes the underlying file descriptor.
   142             try:
   156             try:
   143                 fp.close()
   157                 fp.close()
   144             except IOError:
   158             except IOError:
   145                 pass
   159                 pass
       
   160 
       
   161 
   146 else:
   162 else:
       
   163 
   147     def initstdio():
   164     def initstdio():
   148         for fp in (sys.stdin, sys.stdout, sys.stderr):
   165         for fp in (sys.stdin, sys.stdout, sys.stderr):
   149             procutil.setbinary(fp)
   166             procutil.setbinary(fp)
   150 
   167 
   151     def _silencestdio():
   168     def _silencestdio():
   152         pass
   169         pass
       
   170 
   153 
   171 
   154 def _getsimilar(symbols, value):
   172 def _getsimilar(symbols, value):
   155     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
   173     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
   156     # The cutoff for similarity here is pretty arbitrary. It should
   174     # The cutoff for similarity here is pretty arbitrary. It should
   157     # probably be investigated and tweaked.
   175     # probably be investigated and tweaked.
   158     return [s for s in symbols if sim(s) > 0.6]
   176     return [s for s in symbols if sim(s) > 0.6]
       
   177 
   159 
   178 
   160 def _reportsimilar(write, similar):
   179 def _reportsimilar(write, similar):
   161     if len(similar) == 1:
   180     if len(similar) == 1:
   162         write(_("(did you mean %s?)\n") % similar[0])
   181         write(_("(did you mean %s?)\n") % similar[0])
   163     elif similar:
   182     elif similar:
   164         ss = ", ".join(sorted(similar))
   183         ss = ", ".join(sorted(similar))
   165         write(_("(did you mean one of %s?)\n") % ss)
   184         write(_("(did you mean one of %s?)\n") % ss)
   166 
   185 
       
   186 
   167 def _formatparse(write, inst):
   187 def _formatparse(write, inst):
   168     similar = []
   188     similar = []
   169     if isinstance(inst, error.UnknownIdentifier):
   189     if isinstance(inst, error.UnknownIdentifier):
   170         # make sure to check fileset first, as revset can invoke fileset
   190         # make sure to check fileset first, as revset can invoke fileset
   171         similar = _getsimilar(inst.symbols, inst.function)
   191         similar = _getsimilar(inst.symbols, inst.function)
   172     if len(inst.args) > 1:
   192     if len(inst.args) > 1:
   173         write(_("hg: parse error at %s: %s\n") %
   193         write(
   174               (pycompat.bytestr(inst.args[1]), inst.args[0]))
   194             _("hg: parse error at %s: %s\n")
       
   195             % (pycompat.bytestr(inst.args[1]), inst.args[0])
       
   196         )
   175         if inst.args[0].startswith(' '):
   197         if inst.args[0].startswith(' '):
   176             write(_("unexpected leading whitespace\n"))
   198             write(_("unexpected leading whitespace\n"))
   177     else:
   199     else:
   178         write(_("hg: parse error: %s\n") % inst.args[0])
   200         write(_("hg: parse error: %s\n") % inst.args[0])
   179         _reportsimilar(write, similar)
   201         _reportsimilar(write, similar)
   180     if inst.hint:
   202     if inst.hint:
   181         write(_("(%s)\n") % inst.hint)
   203         write(_("(%s)\n") % inst.hint)
   182 
   204 
       
   205 
   183 def _formatargs(args):
   206 def _formatargs(args):
   184     return ' '.join(procutil.shellquote(a) for a in args)
   207     return ' '.join(procutil.shellquote(a) for a in args)
       
   208 
   185 
   209 
   186 def dispatch(req):
   210 def dispatch(req):
   187     """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"""
   188     with tracing.log('dispatch.dispatch'):
   212     with tracing.log('dispatch.dispatch'):
   189         if req.ferr:
   213         if req.ferr:
   246         finally:
   270         finally:
   247             duration = util.timer() - starttime
   271             duration = util.timer() - starttime
   248             req.ui.flush()
   272             req.ui.flush()
   249             if req.ui.logblockedtimes:
   273             if req.ui.logblockedtimes:
   250                 req.ui._blockedtimes['command_duration'] = duration * 1000
   274                 req.ui._blockedtimes['command_duration'] = duration * 1000
   251                 req.ui.log('uiblocked', 'ui blocked ms\n',
   275                 req.ui.log(
   252                            **pycompat.strkwargs(req.ui._blockedtimes))
   276                     'uiblocked',
       
   277                     'ui blocked ms\n',
       
   278                     **pycompat.strkwargs(req.ui._blockedtimes)
       
   279                 )
   253             return_code = ret & 255
   280             return_code = ret & 255
   254             req.ui.log(
   281             req.ui.log(
   255                 "commandfinish",
   282                 "commandfinish",
   256                 "%s exited %d after %0.2f seconds\n",
   283                 "%s exited %d after %0.2f seconds\n",
   257                 msg,
   284                 msg,
   261                 duration=duration,
   288                 duration=duration,
   262                 canonical_command=req.canonical_command,
   289                 canonical_command=req.canonical_command,
   263             )
   290             )
   264             try:
   291             try:
   265                 req._runexithandlers()
   292                 req._runexithandlers()
   266             except: # exiting, so no re-raises
   293             except:  # exiting, so no re-raises
   267                 ret = ret or -1
   294                 ret = ret or -1
   268         return ret
   295         return ret
   269 
   296 
       
   297 
   270 def _runcatch(req):
   298 def _runcatch(req):
   271     with tracing.log('dispatch._runcatch'):
   299     with tracing.log('dispatch._runcatch'):
       
   300 
   272         def catchterm(*args):
   301         def catchterm(*args):
   273             raise error.SignalInterrupt
   302             raise error.SignalInterrupt
   274 
   303 
   275         ui = req.ui
   304         ui = req.ui
   276         try:
   305         try:
   277             for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
   306             for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
   278                 num = getattr(signal, name, None)
   307                 num = getattr(signal, name, None)
   279                 if num:
   308                 if num:
   280                     signal.signal(num, catchterm)
   309                     signal.signal(num, catchterm)
   281         except ValueError:
   310         except ValueError:
   282             pass # happens if called in a thread
   311             pass  # happens if called in a thread
   283 
   312 
   284         def _runcatchfunc():
   313         def _runcatchfunc():
   285             realcmd = None
   314             realcmd = None
   286             try:
   315             try:
   287                 cmdargs = fancyopts.fancyopts(
   316                 cmdargs = fancyopts.fancyopts(
   288                     req.args[:], commands.globalopts, {})
   317                     req.args[:], commands.globalopts, {}
       
   318                 )
   289                 cmd = cmdargs[0]
   319                 cmd = cmdargs[0]
   290                 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
   320                 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
   291                 realcmd = aliases[0]
   321                 realcmd = aliases[0]
   292             except (error.UnknownCommand, error.AmbiguousCommand,
   322             except (
   293                     IndexError, getopt.GetoptError):
   323                 error.UnknownCommand,
       
   324                 error.AmbiguousCommand,
       
   325                 IndexError,
       
   326                 getopt.GetoptError,
       
   327             ):
   294                 # Don't handle this here. We know the command is
   328                 # Don't handle this here. We know the command is
   295                 # invalid, but all we're worried about for now is that
   329                 # invalid, but all we're worried about for now is that
   296                 # it's not a command that server operators expect to
   330                 # it's not a command that server operators expect to
   297                 # be safe to offer to users in a sandbox.
   331                 # be safe to offer to users in a sandbox.
   298                 pass
   332                 pass
   303                 # restrict to exactly that set of arguments, and prohibit
   337                 # restrict to exactly that set of arguments, and prohibit
   304                 # any repo name that starts with '--' to prevent
   338                 # any repo name that starts with '--' to prevent
   305                 # shenanigans wherein a user does something like pass
   339                 # shenanigans wherein a user does something like pass
   306                 # --debugger or --config=ui.debugger=1 as a repo
   340                 # --debugger or --config=ui.debugger=1 as a repo
   307                 # name. This used to actually run the debugger.
   341                 # name. This used to actually run the debugger.
   308                 if (len(req.args) != 4 or
   342                 if (
   309                     req.args[0] != '-R' or
   343                     len(req.args) != 4
   310                     req.args[1].startswith('--') or
   344                     or req.args[0] != '-R'
   311                     req.args[2] != 'serve' or
   345                     or req.args[1].startswith('--')
   312                     req.args[3] != '--stdio'):
   346                     or req.args[2] != 'serve'
       
   347                     or req.args[3] != '--stdio'
       
   348                 ):
   313                     raise error.Abort(
   349                     raise error.Abort(
   314                         _('potentially unsafe serve --stdio invocation: %s') %
   350                         _('potentially unsafe serve --stdio invocation: %s')
   315                         (stringutil.pprint(req.args),))
   351                         % (stringutil.pprint(req.args),)
       
   352                     )
   316 
   353 
   317             try:
   354             try:
   318                 debugger = 'pdb'
   355                 debugger = 'pdb'
   319                 debugtrace = {
   356                 debugtrace = {'pdb': pdb.set_trace}
   320                     'pdb': pdb.set_trace
   357                 debugmortem = {'pdb': pdb.post_mortem}
   321                 }
       
   322                 debugmortem = {
       
   323                     'pdb': pdb.post_mortem
       
   324                 }
       
   325 
   358 
   326                 # read --config before doing anything else
   359                 # read --config before doing anything else
   327                 # (e.g. to change trust settings for reading .hg/hgrc)
   360                 # (e.g. to change trust settings for reading .hg/hgrc)
   328                 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
   361                 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
   329 
   362 
   345                     # debugging has been requested
   378                     # debugging has been requested
   346                     with demandimport.deactivated():
   379                     with demandimport.deactivated():
   347                         try:
   380                         try:
   348                             debugmod = __import__(debugger)
   381                             debugmod = __import__(debugger)
   349                         except ImportError:
   382                         except ImportError:
   350                             pass # Leave debugmod = pdb
   383                             pass  # Leave debugmod = pdb
   351 
   384 
   352                 debugtrace[debugger] = debugmod.set_trace
   385                 debugtrace[debugger] = debugmod.set_trace
   353                 debugmortem[debugger] = debugmod.post_mortem
   386                 debugmortem[debugger] = debugmod.post_mortem
   354 
   387 
   355                 # enter the debugger before command execution
   388                 # enter the debugger before command execution
   356                 if req.earlyoptions['debugger']:
   389                 if req.earlyoptions['debugger']:
   357                     ui.warn(_("entering debugger - "
   390                     ui.warn(
   358                             "type c to continue starting hg or h for help\n"))
   391                         _(
   359 
   392                             "entering debugger - "
   360                     if (debugger != 'pdb' and
   393                             "type c to continue starting hg or h for help\n"
   361                         debugtrace[debugger] == debugtrace['pdb']):
   394                         )
   362                         ui.warn(_("%s debugger specified "
   395                     )
   363                                   "but its module was not found\n") % debugger)
   396 
       
   397                     if (
       
   398                         debugger != 'pdb'
       
   399                         and debugtrace[debugger] == debugtrace['pdb']
       
   400                     ):
       
   401                         ui.warn(
       
   402                             _(
       
   403                                 "%s debugger specified "
       
   404                                 "but its module was not found\n"
       
   405                             )
       
   406                             % debugger
       
   407                         )
   364                     with demandimport.deactivated():
   408                     with demandimport.deactivated():
   365                         debugtrace[debugger]()
   409                         debugtrace[debugger]()
   366                 try:
   410                 try:
   367                     return _dispatch(req)
   411                     return _dispatch(req)
   368                 finally:
   412                 finally:
   369                     ui.flush()
   413                     ui.flush()
   370             except: # re-raises
   414             except:  # re-raises
   371                 # enter the debugger when we hit an exception
   415                 # enter the debugger when we hit an exception
   372                 if req.earlyoptions['debugger']:
   416                 if req.earlyoptions['debugger']:
   373                     traceback.print_exc()
   417                     traceback.print_exc()
   374                     debugmortem[debugger](sys.exc_info()[2])
   418                     debugmortem[debugger](sys.exc_info()[2])
   375                 raise
   419                 raise
       
   420 
   376         return _callcatch(ui, _runcatchfunc)
   421         return _callcatch(ui, _runcatchfunc)
       
   422 
   377 
   423 
   378 def _callcatch(ui, func):
   424 def _callcatch(ui, func):
   379     """like scmutil.callcatch but handles more high-level exceptions about
   425     """like scmutil.callcatch but handles more high-level exceptions about
   380     config parsing and commands. besides, use handlecommandexception to handle
   426     config parsing and commands. besides, use handlecommandexception to handle
   381     uncaught exceptions.
   427     uncaught exceptions.
   382     """
   428     """
   383     try:
   429     try:
   384         return scmutil.callcatch(ui, func)
   430         return scmutil.callcatch(ui, func)
   385     except error.AmbiguousCommand as inst:
   431     except error.AmbiguousCommand as inst:
   386         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
   432         ui.warn(
   387                 (inst.args[0], " ".join(inst.args[1])))
   433             _("hg: command '%s' is ambiguous:\n    %s\n")
       
   434             % (inst.args[0], " ".join(inst.args[1]))
       
   435         )
   388     except error.CommandError as inst:
   436     except error.CommandError as inst:
   389         if inst.args[0]:
   437         if inst.args[0]:
   390             ui.pager('help')
   438             ui.pager('help')
   391             msgbytes = pycompat.bytestr(inst.args[1])
   439             msgbytes = pycompat.bytestr(inst.args[1])
   392             ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
   440             ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
   400     except error.UnknownCommand as inst:
   448     except error.UnknownCommand as inst:
   401         nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
   449         nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
   402         try:
   450         try:
   403             # check if the command is in a disabled extension
   451             # check if the command is in a disabled extension
   404             # (but don't check for extensions themselves)
   452             # (but don't check for extensions themselves)
   405             formatted = help.formattedhelp(ui, commands, inst.args[0],
   453             formatted = help.formattedhelp(
   406                                            unknowncmd=True)
   454                 ui, commands, inst.args[0], unknowncmd=True
       
   455             )
   407             ui.warn(nocmdmsg)
   456             ui.warn(nocmdmsg)
   408             ui.write(formatted)
   457             ui.write(formatted)
   409         except (error.UnknownCommand, error.Abort):
   458         except (error.UnknownCommand, error.Abort):
   410             suggested = False
   459             suggested = False
   411             if len(inst.args) == 2:
   460             if len(inst.args) == 2:
   425         if not handlecommandexception(ui):
   474         if not handlecommandexception(ui):
   426             raise
   475             raise
   427 
   476 
   428     return -1
   477     return -1
   429 
   478 
       
   479 
   430 def aliasargs(fn, givenargs):
   480 def aliasargs(fn, givenargs):
   431     args = []
   481     args = []
   432     # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
   482     # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
   433     if not util.safehasattr(fn, '_origfunc'):
   483     if not util.safehasattr(fn, '_origfunc'):
   434         args = getattr(fn, 'args', args)
   484         args = getattr(fn, 'args', args)
   435     if args:
   485     if args:
   436         cmd = ' '.join(map(procutil.shellquote, args))
   486         cmd = ' '.join(map(procutil.shellquote, args))
   437 
   487 
   438         nums = []
   488         nums = []
       
   489 
   439         def replacer(m):
   490         def replacer(m):
   440             num = int(m.group(1)) - 1
   491             num = int(m.group(1)) - 1
   441             nums.append(num)
   492             nums.append(num)
   442             if num < len(givenargs):
   493             if num < len(givenargs):
   443                 return givenargs[num]
   494                 return givenargs[num]
   444             raise error.Abort(_('too few arguments for command alias'))
   495             raise error.Abort(_('too few arguments for command alias'))
       
   496 
   445         cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
   497         cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
   446         givenargs = [x for i, x in enumerate(givenargs)
   498         givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
   447                      if i not in nums]
       
   448         args = pycompat.shlexsplit(cmd)
   499         args = pycompat.shlexsplit(cmd)
   449     return args + givenargs
   500     return args + givenargs
       
   501 
   450 
   502 
   451 def aliasinterpolate(name, args, cmd):
   503 def aliasinterpolate(name, args, cmd):
   452     '''interpolate args into cmd for shell aliases
   504     '''interpolate args into cmd for shell aliases
   453 
   505 
   454     This also handles $0, $@ and "$@".
   506     This also handles $0, $@ and "$@".
   467     # escape '\$' for regex
   519     # escape '\$' for regex
   468     regex = '|'.join(replacemap.keys()).replace('$', br'\$')
   520     regex = '|'.join(replacemap.keys()).replace('$', br'\$')
   469     r = re.compile(regex)
   521     r = re.compile(regex)
   470     return r.sub(lambda x: replacemap[x.group()], cmd)
   522     return r.sub(lambda x: replacemap[x.group()], cmd)
   471 
   523 
       
   524 
   472 class cmdalias(object):
   525 class cmdalias(object):
   473     def __init__(self, ui, name, definition, cmdtable, source):
   526     def __init__(self, ui, name, definition, cmdtable, source):
   474         self.name = self.cmd = name
   527         self.name = self.cmd = name
   475         self.cmdname = ''
   528         self.cmdname = ''
   476         self.definition = definition
   529         self.definition = definition
   497             return
   550             return
   498 
   551 
   499         if self.definition.startswith('!'):
   552         if self.definition.startswith('!'):
   500             shdef = self.definition[1:]
   553             shdef = self.definition[1:]
   501             self.shell = True
   554             self.shell = True
       
   555 
   502             def fn(ui, *args):
   556             def fn(ui, *args):
   503                 env = {'HG_ARGS': ' '.join((self.name,) + args)}
   557                 env = {'HG_ARGS': ' '.join((self.name,) + args)}
       
   558 
   504                 def _checkvar(m):
   559                 def _checkvar(m):
   505                     if m.groups()[0] == '$':
   560                     if m.groups()[0] == '$':
   506                         return m.group()
   561                         return m.group()
   507                     elif int(m.groups()[0]) <= len(args):
   562                     elif int(m.groups()[0]) <= len(args):
   508                         return m.group()
   563                         return m.group()
   509                     else:
   564                     else:
   510                         ui.debug("No argument found for substitution "
   565                         ui.debug(
   511                                  "of %i variable in alias '%s' definition.\n"
   566                             "No argument found for substitution "
   512                                  % (int(m.groups()[0]), self.name))
   567                             "of %i variable in alias '%s' definition.\n"
       
   568                             % (int(m.groups()[0]), self.name)
       
   569                         )
   513                         return ''
   570                         return ''
       
   571 
   514                 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
   572                 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
   515                 cmd = aliasinterpolate(self.name, args, cmd)
   573                 cmd = aliasinterpolate(self.name, args, cmd)
   516                 return ui.system(cmd, environ=env,
   574                 return ui.system(
   517                                  blockedtag='alias_%s' % self.name)
   575                     cmd, environ=env, blockedtag='alias_%s' % self.name
       
   576                 )
       
   577 
   518             self.fn = fn
   578             self.fn = fn
   519             self.alias = True
   579             self.alias = True
   520             self._populatehelp(ui, name, shdef, self.fn)
   580             self._populatehelp(ui, name, shdef, self.fn)
   521             return
   581             return
   522 
   582 
   523         try:
   583         try:
   524             args = pycompat.shlexsplit(self.definition)
   584             args = pycompat.shlexsplit(self.definition)
   525         except ValueError as inst:
   585         except ValueError as inst:
   526             self.badalias = (_("error in definition for alias '%s': %s")
   586             self.badalias = _("error in definition for alias '%s': %s") % (
   527                              % (self.name, stringutil.forcebytestr(inst)))
   587                 self.name,
       
   588                 stringutil.forcebytestr(inst),
       
   589             )
   528             return
   590             return
   529         earlyopts, args = _earlysplitopts(args)
   591         earlyopts, args = _earlysplitopts(args)
   530         if earlyopts:
   592         if earlyopts:
   531             self.badalias = (_("error in definition for alias '%s': %s may "
   593             self.badalias = _(
   532                                "only be given on the command line")
   594                 "error in definition for alias '%s': %s may "
   533                              % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
   595                 "only be given on the command line"
   534                                                     [0])))
   596             ) % (self.name, '/'.join(pycompat.ziplist(*earlyopts)[0]))
   535             return
   597             return
   536         self.cmdname = cmd = args.pop(0)
   598         self.cmdname = cmd = args.pop(0)
   537         self.givenargs = args
   599         self.givenargs = args
   538 
   600 
   539         try:
   601         try:
   546 
   608 
   547             self.alias = True
   609             self.alias = True
   548             self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
   610             self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
   549 
   611 
   550         except error.UnknownCommand:
   612         except error.UnknownCommand:
   551             self.badalias = (_("alias '%s' resolves to unknown command '%s'")
   613             self.badalias = _("alias '%s' resolves to unknown command '%s'") % (
   552                              % (self.name, cmd))
   614                 self.name,
       
   615                 cmd,
       
   616             )
   553             self.unknowncmd = True
   617             self.unknowncmd = True
   554         except error.AmbiguousCommand:
   618         except error.AmbiguousCommand:
   555             self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
   619             self.badalias = _(
   556                              % (self.name, cmd))
   620                 "alias '%s' resolves to ambiguous command '%s'"
       
   621             ) % (self.name, cmd)
   557 
   622 
   558     def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
   623     def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
   559         # confine strings to be passed to i18n.gettext()
   624         # confine strings to be passed to i18n.gettext()
   560         cfg = {}
   625         cfg = {}
   561         for k in ('doc', 'help', 'category'):
   626         for k in ('doc', 'help', 'category'):
   562             v = ui.config('alias', '%s:%s' % (name, k), None)
   627             v = ui.config('alias', '%s:%s' % (name, k), None)
   563             if v is None:
   628             if v is None:
   564                 continue
   629                 continue
   565             if not encoding.isasciistr(v):
   630             if not encoding.isasciistr(v):
   566                 self.badalias = (_("non-ASCII character in alias definition "
   631                 self.badalias = _(
   567                                    "'%s:%s'") % (name, k))
   632                     "non-ASCII character in alias definition " "'%s:%s'"
       
   633                 ) % (name, k)
   568                 return
   634                 return
   569             cfg[k] = v
   635             cfg[k] = v
   570 
   636 
   571         self.help = cfg.get('help', defaulthelp or '')
   637         self.help = cfg.get('help', defaulthelp or '')
   572         if self.help and self.help.startswith("hg " + cmd):
   638         if self.help and self.help.startswith("hg " + cmd):
   573             # drop prefix in old-style help lines so hg shows the alias
   639             # drop prefix in old-style help lines so hg shows the alias
   574             self.help = self.help[4 + len(cmd):]
   640             self.help = self.help[4 + len(cmd) :]
   575 
   641 
   576         self.owndoc = 'doc' in cfg
   642         self.owndoc = 'doc' in cfg
   577         doc = cfg.get('doc', pycompat.getdoc(fn))
   643         doc = cfg.get('doc', pycompat.getdoc(fn))
   578         if doc is not None:
   644         if doc is not None:
   579             doc = pycompat.sysstr(doc)
   645             doc = pycompat.sysstr(doc)
   585     def args(self):
   651     def args(self):
   586         args = pycompat.maplist(util.expandpath, self.givenargs)
   652         args = pycompat.maplist(util.expandpath, self.givenargs)
   587         return aliasargs(self.fn, args)
   653         return aliasargs(self.fn, args)
   588 
   654 
   589     def __getattr__(self, name):
   655     def __getattr__(self, name):
   590         adefaults = {r'norepo': True, r'intents': set(),
   656         adefaults = {
   591                      r'optionalrepo': False, r'inferrepo': False}
   657             r'norepo': True,
       
   658             r'intents': set(),
       
   659             r'optionalrepo': False,
       
   660             r'inferrepo': False,
       
   661         }
   592         if name not in adefaults:
   662         if name not in adefaults:
   593             raise AttributeError(name)
   663             raise AttributeError(name)
   594         if self.badalias or util.safehasattr(self, 'shell'):
   664         if self.badalias or util.safehasattr(self, 'shell'):
   595             return adefaults[name]
   665             return adefaults[name]
   596         return getattr(self.fn, name)
   666         return getattr(self.fn, name)
   605                     hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
   675                     hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
   606                 except error.UnknownCommand:
   676                 except error.UnknownCommand:
   607                     pass
   677                     pass
   608             raise error.Abort(self.badalias, hint=hint)
   678             raise error.Abort(self.badalias, hint=hint)
   609         if self.shadows:
   679         if self.shadows:
   610             ui.debug("alias '%s' shadows command '%s'\n" %
   680             ui.debug(
   611                      (self.name, self.cmdname))
   681                 "alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
   612 
   682             )
   613         ui.log('commandalias', "alias '%s' expands to '%s'\n",
   683 
   614                self.name, self.definition)
   684         ui.log(
       
   685             'commandalias',
       
   686             "alias '%s' expands to '%s'\n",
       
   687             self.name,
       
   688             self.definition,
       
   689         )
   615         if util.safehasattr(self, 'shell'):
   690         if util.safehasattr(self, 'shell'):
   616             return self.fn(ui, *args, **opts)
   691             return self.fn(ui, *args, **opts)
   617         else:
   692         else:
   618             try:
   693             try:
   619                 return util.checksignature(self.fn)(ui, *args, **opts)
   694                 return util.checksignature(self.fn)(ui, *args, **opts)
   620             except error.SignatureError:
   695             except error.SignatureError:
   621                 args = ' '.join([self.cmdname] + self.args)
   696                 args = ' '.join([self.cmdname] + self.args)
   622                 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
   697                 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
   623                 raise
   698                 raise
   624 
   699 
       
   700 
   625 class lazyaliasentry(object):
   701 class lazyaliasentry(object):
   626     """like a typical command entry (func, opts, help), but is lazy"""
   702     """like a typical command entry (func, opts, help), but is lazy"""
   627 
   703 
   628     def __init__(self, ui, name, definition, cmdtable, source):
   704     def __init__(self, ui, name, definition, cmdtable, source):
   629         self.ui = ui
   705         self.ui = ui
   633         self.source = source
   709         self.source = source
   634         self.alias = True
   710         self.alias = True
   635 
   711 
   636     @util.propertycache
   712     @util.propertycache
   637     def _aliasdef(self):
   713     def _aliasdef(self):
   638         return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
   714         return cmdalias(
   639                         self.source)
   715             self.ui, self.name, self.definition, self.cmdtable, self.source
       
   716         )
   640 
   717 
   641     def __getitem__(self, n):
   718     def __getitem__(self, n):
   642         aliasdef = self._aliasdef
   719         aliasdef = self._aliasdef
   643         if n == 0:
   720         if n == 0:
   644             return aliasdef
   721             return aliasdef
   654             yield self[i]
   731             yield self[i]
   655 
   732 
   656     def __len__(self):
   733     def __len__(self):
   657         return 3
   734         return 3
   658 
   735 
       
   736 
   659 def addaliases(ui, cmdtable):
   737 def addaliases(ui, cmdtable):
   660     # aliases are processed after extensions have been loaded, so they
   738     # aliases are processed after extensions have been loaded, so they
   661     # may use extension commands. Aliases can also use other alias definitions,
   739     # may use extension commands. Aliases can also use other alias definitions,
   662     # but only if they have been defined prior to the current definition.
   740     # but only if they have been defined prior to the current definition.
   663     for alias, definition in ui.configitems('alias', ignoresub=True):
   741     for alias, definition in ui.configitems('alias', ignoresub=True):
   670 
   748 
   671         source = ui.configsource('alias', alias)
   749         source = ui.configsource('alias', alias)
   672         entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
   750         entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
   673         cmdtable[alias] = entry
   751         cmdtable[alias] = entry
   674 
   752 
       
   753 
   675 def _parse(ui, args):
   754 def _parse(ui, args):
   676     options = {}
   755     options = {}
   677     cmdoptions = {}
   756     cmdoptions = {}
   678 
   757 
   679     try:
   758     try:
   681     except getopt.GetoptError as inst:
   760     except getopt.GetoptError as inst:
   682         raise error.CommandError(None, stringutil.forcebytestr(inst))
   761         raise error.CommandError(None, stringutil.forcebytestr(inst))
   683 
   762 
   684     if args:
   763     if args:
   685         cmd, args = args[0], args[1:]
   764         cmd, args = args[0], args[1:]
   686         aliases, entry = cmdutil.findcmd(cmd, commands.table,
   765         aliases, entry = cmdutil.findcmd(
   687                                          ui.configbool("ui", "strict"))
   766             cmd, commands.table, ui.configbool("ui", "strict")
       
   767         )
   688         cmd = aliases[0]
   768         cmd = aliases[0]
   689         args = aliasargs(entry[0], args)
   769         args = aliasargs(entry[0], args)
   690         defaults = ui.config("defaults", cmd)
   770         defaults = ui.config("defaults", cmd)
   691         if defaults:
   771         if defaults:
   692             args = pycompat.maplist(
   772             args = (
   693                 util.expandpath, pycompat.shlexsplit(defaults)) + args
   773                 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
       
   774                 + args
       
   775             )
   694         c = list(entry[1])
   776         c = list(entry[1])
   695     else:
   777     else:
   696         cmd = None
   778         cmd = None
   697         c = []
   779         c = []
   698 
   780 
   711         options[n] = cmdoptions[n]
   793         options[n] = cmdoptions[n]
   712         del cmdoptions[n]
   794         del cmdoptions[n]
   713 
   795 
   714     return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
   796     return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
   715 
   797 
       
   798 
   716 def _parseconfig(ui, config):
   799 def _parseconfig(ui, config):
   717     """parse the --config options from the command line"""
   800     """parse the --config options from the command line"""
   718     configs = []
   801     configs = []
   719 
   802 
   720     for cfg in config:
   803     for cfg in config:
   721         try:
   804         try:
   722             name, value = [cfgelem.strip()
   805             name, value = [cfgelem.strip() for cfgelem in cfg.split('=', 1)]
   723                            for cfgelem in cfg.split('=', 1)]
       
   724             section, name = name.split('.', 1)
   806             section, name = name.split('.', 1)
   725             if not section or not name:
   807             if not section or not name:
   726                 raise IndexError
   808                 raise IndexError
   727             ui.setconfig(section, name, value, '--config')
   809             ui.setconfig(section, name, value, '--config')
   728             configs.append((section, name, value))
   810             configs.append((section, name, value))
   729         except (IndexError, ValueError):
   811         except (IndexError, ValueError):
   730             raise error.Abort(_('malformed --config option: %r '
   812             raise error.Abort(
   731                                 '(use --config section.name=value)')
   813                 _(
   732                               % pycompat.bytestr(cfg))
   814                     'malformed --config option: %r '
       
   815                     '(use --config section.name=value)'
       
   816                 )
       
   817                 % pycompat.bytestr(cfg)
       
   818             )
   733 
   819 
   734     return configs
   820     return configs
       
   821 
   735 
   822 
   736 def _earlyparseopts(ui, args):
   823 def _earlyparseopts(ui, args):
   737     options = {}
   824     options = {}
   738     fancyopts.fancyopts(args, commands.globalopts, options,
   825     fancyopts.fancyopts(
   739                         gnu=not ui.plain('strictflags'), early=True,
   826         args,
   740                         optaliases={'repository': ['repo']})
   827         commands.globalopts,
       
   828         options,
       
   829         gnu=not ui.plain('strictflags'),
       
   830         early=True,
       
   831         optaliases={'repository': ['repo']},
       
   832     )
   741     return options
   833     return options
       
   834 
   742 
   835 
   743 def _earlysplitopts(args):
   836 def _earlysplitopts(args):
   744     """Split args into a list of possible early options and remainder args"""
   837     """Split args into a list of possible early options and remainder args"""
   745     shortoptions = 'R:'
   838     shortoptions = 'R:'
   746     # TODO: perhaps 'debugger' should be included
   839     # TODO: perhaps 'debugger' should be included
   747     longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
   840     longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
   748     return fancyopts.earlygetopt(args, shortoptions, longoptions,
   841     return fancyopts.earlygetopt(
   749                                  gnu=True, keepsep=True)
   842         args, shortoptions, longoptions, gnu=True, keepsep=True
       
   843     )
       
   844 
   750 
   845 
   751 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
   846 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
   752     # run pre-hook, and abort if it fails
   847     # run pre-hook, and abort if it fails
   753     hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
   848     hook.hook(
   754               pats=cmdpats, opts=cmdoptions)
   849         lui,
       
   850         repo,
       
   851         "pre-%s" % cmd,
       
   852         True,
       
   853         args=" ".join(fullargs),
       
   854         pats=cmdpats,
       
   855         opts=cmdoptions,
       
   856     )
   755     try:
   857     try:
   756         ret = _runcommand(ui, options, cmd, d)
   858         ret = _runcommand(ui, options, cmd, d)
   757         # run post-hook, passing command result
   859         # run post-hook, passing command result
   758         hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
   860         hook.hook(
   759                   result=ret, pats=cmdpats, opts=cmdoptions)
   861             lui,
       
   862             repo,
       
   863             "post-%s" % cmd,
       
   864             False,
       
   865             args=" ".join(fullargs),
       
   866             result=ret,
       
   867             pats=cmdpats,
       
   868             opts=cmdoptions,
       
   869         )
   760     except Exception:
   870     except Exception:
   761         # run failure hook and re-raise
   871         # run failure hook and re-raise
   762         hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
   872         hook.hook(
   763                   pats=cmdpats, opts=cmdoptions)
   873             lui,
       
   874             repo,
       
   875             "fail-%s" % cmd,
       
   876             False,
       
   877             args=" ".join(fullargs),
       
   878             pats=cmdpats,
       
   879             opts=cmdoptions,
       
   880         )
   764         raise
   881         raise
   765     return ret
   882     return ret
       
   883 
   766 
   884 
   767 def _getlocal(ui, rpath, wd=None):
   885 def _getlocal(ui, rpath, wd=None):
   768     """Return (path, local ui object) for the given target path.
   886     """Return (path, local ui object) for the given target path.
   769 
   887 
   770     Takes paths in [cwd]/.hg/hgrc into account."
   888     Takes paths in [cwd]/.hg/hgrc into account."
   771     """
   889     """
   772     if wd is None:
   890     if wd is None:
   773         try:
   891         try:
   774             wd = encoding.getcwd()
   892             wd = encoding.getcwd()
   775         except OSError as e:
   893         except OSError as e:
   776             raise error.Abort(_("error getting current working directory: %s") %
   894             raise error.Abort(
   777                               encoding.strtolocal(e.strerror))
   895                 _("error getting current working directory: %s")
       
   896                 % encoding.strtolocal(e.strerror)
       
   897             )
   778     path = cmdutil.findrepo(wd) or ""
   898     path = cmdutil.findrepo(wd) or ""
   779     if not path:
   899     if not path:
   780         lui = ui
   900         lui = ui
   781     else:
   901     else:
   782         lui = ui.copy()
   902         lui = ui.copy()
   787         lui = ui.copy()
   907         lui = ui.copy()
   788         lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
   908         lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
   789 
   909 
   790     return path, lui
   910     return path, lui
   791 
   911 
       
   912 
   792 def _checkshellalias(lui, ui, args):
   913 def _checkshellalias(lui, ui, args):
   793     """Return the function to run the shell alias, if it is required"""
   914     """Return the function to run the shell alias, if it is required"""
   794     options = {}
   915     options = {}
   795 
   916 
   796     try:
   917     try:
   815 
   936 
   816     if cmd and util.safehasattr(fn, 'shell'):
   937     if cmd and util.safehasattr(fn, 'shell'):
   817         # shell alias shouldn't receive early options which are consumed by hg
   938         # shell alias shouldn't receive early options which are consumed by hg
   818         _earlyopts, args = _earlysplitopts(args)
   939         _earlyopts, args = _earlysplitopts(args)
   819         d = lambda: fn(ui, *args[1:])
   940         d = lambda: fn(ui, *args[1:])
   820         return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
   941         return lambda: runcommand(
   821                                   [], {})
   942             lui, None, cmd, args[:1], ui, options, d, [], {}
       
   943         )
       
   944 
   822 
   945 
   823 def _dispatch(req):
   946 def _dispatch(req):
   824     args = req.args
   947     args = req.args
   825     ui = req.ui
   948     ui = req.ui
   826 
   949 
   835     uis = {ui, lui}
   958     uis = {ui, lui}
   836 
   959 
   837     if req.repo:
   960     if req.repo:
   838         uis.add(req.repo.ui)
   961         uis.add(req.repo.ui)
   839 
   962 
   840     if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
   963     if (
   841             or req.earlyoptions['quiet']):
   964         req.earlyoptions['verbose']
       
   965         or req.earlyoptions['debug']
       
   966         or req.earlyoptions['quiet']
       
   967     ):
   842         for opt in ('verbose', 'debug', 'quiet'):
   968         for opt in ('verbose', 'debug', 'quiet'):
   843             val = pycompat.bytestr(bool(req.earlyoptions[opt]))
   969             val = pycompat.bytestr(bool(req.earlyoptions[opt]))
   844             for ui_ in uis:
   970             for ui_ in uis:
   845                 ui_.setconfig('ui', opt, val, '--' + opt)
   971                 ui_.setconfig('ui', opt, val, '--' + opt)
   846 
   972 
   885         if options["config"] != req.earlyoptions["config"]:
  1011         if options["config"] != req.earlyoptions["config"]:
   886             raise error.Abort(_("option --config may not be abbreviated!"))
  1012             raise error.Abort(_("option --config may not be abbreviated!"))
   887         if options["cwd"] != req.earlyoptions["cwd"]:
  1013         if options["cwd"] != req.earlyoptions["cwd"]:
   888             raise error.Abort(_("option --cwd may not be abbreviated!"))
  1014             raise error.Abort(_("option --cwd may not be abbreviated!"))
   889         if options["repository"] != req.earlyoptions["repository"]:
  1015         if options["repository"] != req.earlyoptions["repository"]:
   890             raise error.Abort(_(
  1016             raise error.Abort(
   891                 "option -R has to be separated from other options (e.g. not "
  1017                 _(
   892                 "-qR) and --repository may only be abbreviated as --repo!"))
  1018                     "option -R has to be separated from other options (e.g. not "
       
  1019                     "-qR) and --repository may only be abbreviated as --repo!"
       
  1020                 )
       
  1021             )
   893         if options["debugger"] != req.earlyoptions["debugger"]:
  1022         if options["debugger"] != req.earlyoptions["debugger"]:
   894             raise error.Abort(_("option --debugger may not be abbreviated!"))
  1023             raise error.Abort(_("option --debugger may not be abbreviated!"))
   895         # don't validate --profile/--traceback, which can be enabled from now
  1024         # don't validate --profile/--traceback, which can be enabled from now
   896 
  1025 
   897         if options["encoding"]:
  1026         if options["encoding"]:
   898             encoding.encoding = options["encoding"]
  1027             encoding.encoding = options["encoding"]
   899         if options["encodingmode"]:
  1028         if options["encodingmode"]:
   900             encoding.encodingmode = options["encodingmode"]
  1029             encoding.encodingmode = options["encodingmode"]
   901         if options["time"]:
  1030         if options["time"]:
       
  1031 
   902             def get_times():
  1032             def get_times():
   903                 t = os.times()
  1033                 t = os.times()
   904                 if t[4] == 0.0:
  1034                 if t[4] == 0.0:
   905                     # Windows leaves this as zero, so use time.clock()
  1035                     # Windows leaves this as zero, so use time.clock()
   906                     t = (t[0], t[1], t[2], t[3], time.clock())
  1036                     t = (t[0], t[1], t[2], t[3], time.clock())
   907                 return t
  1037                 return t
       
  1038 
   908             s = get_times()
  1039             s = get_times()
       
  1040 
   909             def print_time():
  1041             def print_time():
   910                 t = get_times()
  1042                 t = get_times()
   911                 ui.warn(
  1043                 ui.warn(
   912                     _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
  1044                     _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
   913                     (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
  1045                     % (
       
  1046                         t[4] - s[4],
       
  1047                         t[0] - s[0],
       
  1048                         t[2] - s[2],
       
  1049                         t[1] - s[1],
       
  1050                         t[3] - s[3],
       
  1051                     )
       
  1052                 )
       
  1053 
   914             ui.atexit(print_time)
  1054             ui.atexit(print_time)
   915         if options["profile"]:
  1055         if options["profile"]:
   916             profiler.start()
  1056             profiler.start()
   917 
  1057 
   918         # if abbreviated version of this were used, take them in account, now
  1058         # if abbreviated version of this were used, take them in account, now
   975                 repo.ui.fout = ui.fout
  1115                 repo.ui.fout = ui.fout
   976                 repo.ui.ferr = ui.ferr
  1116                 repo.ui.ferr = ui.ferr
   977                 repo.ui.fmsg = ui.fmsg
  1117                 repo.ui.fmsg = ui.fmsg
   978             else:
  1118             else:
   979                 try:
  1119                 try:
   980                     repo = hg.repository(ui, path=path,
  1120                     repo = hg.repository(
   981                                          presetupfuncs=req.prereposetups,
  1121                         ui,
   982                                          intents=func.intents)
  1122                         path=path,
       
  1123                         presetupfuncs=req.prereposetups,
       
  1124                         intents=func.intents,
       
  1125                     )
   983                     if not repo.local():
  1126                     if not repo.local():
   984                         raise error.Abort(_("repository '%s' is not local")
  1127                         raise error.Abort(
   985                                           % path)
  1128                             _("repository '%s' is not local") % path
   986                     repo.ui.setconfig("bundle", "mainreporoot", repo.root,
  1129                         )
   987                                       'repo')
  1130                     repo.ui.setconfig(
       
  1131                         "bundle", "mainreporoot", repo.root, 'repo'
       
  1132                     )
   988                 except error.RequirementError:
  1133                 except error.RequirementError:
   989                     raise
  1134                     raise
   990                 except error.RepoError:
  1135                 except error.RepoError:
   991                     if rpath: # invalid -R path
  1136                     if rpath:  # invalid -R path
   992                         raise
  1137                         raise
   993                     if not func.optionalrepo:
  1138                     if not func.optionalrepo:
   994                         if func.inferrepo and args and not path:
  1139                         if func.inferrepo and args and not path:
   995                             # try to infer -R from command args
  1140                             # try to infer -R from command args
   996                             repos = pycompat.maplist(cmdutil.findrepo, args)
  1141                             repos = pycompat.maplist(cmdutil.findrepo, args)
   998                             if guess and repos.count(guess) == len(repos):
  1143                             if guess and repos.count(guess) == len(repos):
   999                                 req.args = ['--repository', guess] + fullargs
  1144                                 req.args = ['--repository', guess] + fullargs
  1000                                 req.earlyoptions['repository'] = guess
  1145                                 req.earlyoptions['repository'] = guess
  1001                                 return _dispatch(req)
  1146                                 return _dispatch(req)
  1002                         if not path:
  1147                         if not path:
  1003                             raise error.RepoError(_("no repository found in"
  1148                             raise error.RepoError(
  1004                                                     " '%s' (.hg not found)")
  1149                                 _(
  1005                                                   % encoding.getcwd())
  1150                                     "no repository found in"
       
  1151                                     " '%s' (.hg not found)"
       
  1152                                 )
       
  1153                                 % encoding.getcwd()
       
  1154                             )
  1006                         raise
  1155                         raise
  1007             if repo:
  1156             if repo:
  1008                 ui = repo.ui
  1157                 ui = repo.ui
  1009                 if options['hidden']:
  1158                 if options['hidden']:
  1010                     repo = repo.unfiltered()
  1159                     repo = repo.unfiltered()
  1015         msg = _formatargs(fullargs)
  1164         msg = _formatargs(fullargs)
  1016         ui.log("command", '%s\n', msg)
  1165         ui.log("command", '%s\n', msg)
  1017         strcmdopt = pycompat.strkwargs(cmdoptions)
  1166         strcmdopt = pycompat.strkwargs(cmdoptions)
  1018         d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
  1167         d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
  1019         try:
  1168         try:
  1020             return runcommand(lui, repo, cmd, fullargs, ui, options, d,
  1169             return runcommand(
  1021                               cmdpats, cmdoptions)
  1170                 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
       
  1171             )
  1022         finally:
  1172         finally:
  1023             if repo and repo != req.repo:
  1173             if repo and repo != req.repo:
  1024                 repo.close()
  1174                 repo.close()
       
  1175 
  1025 
  1176 
  1026 def _runcommand(ui, options, cmd, cmdfunc):
  1177 def _runcommand(ui, options, cmd, cmdfunc):
  1027     """Run a command function, possibly with profiling enabled."""
  1178     """Run a command function, possibly with profiling enabled."""
  1028     try:
  1179     try:
  1029         with tracing.log("Running %s command" % cmd):
  1180         with tracing.log("Running %s command" % cmd):
  1030             return cmdfunc()
  1181             return cmdfunc()
  1031     except error.SignatureError:
  1182     except error.SignatureError:
  1032         raise error.CommandError(cmd, _('invalid arguments'))
  1183         raise error.CommandError(cmd, _('invalid arguments'))
       
  1184 
  1033 
  1185 
  1034 def _exceptionwarning(ui):
  1186 def _exceptionwarning(ui):
  1035     """Produce a warning message for the current active exception"""
  1187     """Produce a warning message for the current active exception"""
  1036 
  1188 
  1037     # For compatibility checking, we discard the portion of the hg
  1189     # For compatibility checking, we discard the portion of the hg
  1067             if worst[0] is None or nearest < worst[1]:
  1219             if worst[0] is None or nearest < worst[1]:
  1068                 worst = name, nearest, report
  1220                 worst = name, nearest, report
  1069     if worst[0] is not None:
  1221     if worst[0] is not None:
  1070         name, testedwith, report = worst
  1222         name, testedwith, report = worst
  1071         if not isinstance(testedwith, (bytes, str)):
  1223         if not isinstance(testedwith, (bytes, str)):
  1072             testedwith = '.'.join([stringutil.forcebytestr(c)
  1224             testedwith = '.'.join(
  1073                                    for c in testedwith])
  1225                 [stringutil.forcebytestr(c) for c in testedwith]
  1074         warning = (_('** Unknown exception encountered with '
  1226             )
  1075                      'possibly-broken third-party extension %s\n'
  1227         warning = _(
  1076                      '** which supports versions %s of Mercurial.\n'
  1228             '** Unknown exception encountered with '
  1077                      '** Please disable %s and try your action again.\n'
  1229             'possibly-broken third-party extension %s\n'
  1078                      '** If that fixes the bug please report it to %s\n')
  1230             '** which supports versions %s of Mercurial.\n'
  1079                    % (name, testedwith, name, stringutil.forcebytestr(report)))
  1231             '** Please disable %s and try your action again.\n'
       
  1232             '** If that fixes the bug please report it to %s\n'
       
  1233         ) % (name, testedwith, name, stringutil.forcebytestr(report))
  1080     else:
  1234     else:
  1081         bugtracker = ui.config('ui', 'supportcontact')
  1235         bugtracker = ui.config('ui', 'supportcontact')
  1082         if bugtracker is None:
  1236         if bugtracker is None:
  1083             bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
  1237             bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
  1084         warning = (_("** unknown exception encountered, "
  1238         warning = (
  1085                      "please report by visiting\n** ") + bugtracker + '\n')
  1239             _(
       
  1240                 "** unknown exception encountered, "
       
  1241                 "please report by visiting\n** "
       
  1242             )
       
  1243             + bugtracker
       
  1244             + '\n'
       
  1245         )
  1086     sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
  1246     sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
  1087     warning += ((_("** Python %s\n") % sysversion) +
  1247     warning += (
  1088                 (_("** Mercurial Distributed SCM (version %s)\n") %
  1248         (_("** Python %s\n") % sysversion)
  1089                  util.version()) +
  1249         + (_("** Mercurial Distributed SCM (version %s)\n") % util.version())
  1090                 (_("** Extensions loaded: %s\n") %
  1250         + (
  1091                  ", ".join([x[0] for x in extensions.extensions()])))
  1251             _("** Extensions loaded: %s\n")
       
  1252             % ", ".join([x[0] for x in extensions.extensions()])
       
  1253         )
       
  1254     )
  1092     return warning
  1255     return warning
       
  1256 
  1093 
  1257 
  1094 def handlecommandexception(ui):
  1258 def handlecommandexception(ui):
  1095     """Produce a warning message for broken commands
  1259     """Produce a warning message for broken commands
  1096 
  1260 
  1097     Called when handling an exception; the exception is reraised if
  1261     Called when handling an exception; the exception is reraised if
  1098     this function returns False, ignored otherwise.
  1262     this function returns False, ignored otherwise.
  1099     """
  1263     """
  1100     warning = _exceptionwarning(ui)
  1264     warning = _exceptionwarning(ui)
  1101     ui.log("commandexception", "%s\n%s\n", warning,
  1265     ui.log(
  1102            pycompat.sysbytes(traceback.format_exc()))
  1266         "commandexception",
       
  1267         "%s\n%s\n",
       
  1268         warning,
       
  1269         pycompat.sysbytes(traceback.format_exc()),
       
  1270     )
  1103     ui.warn(warning)
  1271     ui.warn(warning)
  1104     return False  # re-raise the exception
  1272     return False  # re-raise the exception