mercurial/dispatch.py
branchstable
changeset 32054 616e788321cc
parent 32050 77eaf9539499
parent 32044 cde72a195f32
child 32111 1208b74841ff
child 32383 f928d53b687c
equal deleted inserted replaced
32053:52902059edc7 32054:616e788321cc
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 from __future__ import absolute_import, print_function
     8 from __future__ import absolute_import, print_function
     9 
     9 
    10 import atexit
       
    11 import difflib
    10 import difflib
    12 import errno
    11 import errno
    13 import getopt
    12 import getopt
    14 import os
    13 import os
    15 import pdb
    14 import pdb
    31     encoding,
    30     encoding,
    32     error,
    31     error,
    33     extensions,
    32     extensions,
    34     fancyopts,
    33     fancyopts,
    35     fileset,
    34     fileset,
       
    35     help,
    36     hg,
    36     hg,
    37     hook,
    37     hook,
    38     profiling,
    38     profiling,
    39     pycompat,
    39     pycompat,
    40     revset,
    40     revset,
    56         # input/output/error streams
    56         # input/output/error streams
    57         self.fin = fin
    57         self.fin = fin
    58         self.fout = fout
    58         self.fout = fout
    59         self.ferr = ferr
    59         self.ferr = ferr
    60 
    60 
       
    61     def _runexithandlers(self):
       
    62         exc = None
       
    63         handlers = self.ui._exithandlers
       
    64         try:
       
    65             while handlers:
       
    66                 func, args, kwargs = handlers.pop()
       
    67                 try:
       
    68                     func(*args, **kwargs)
       
    69                 except: # re-raises below
       
    70                     if exc is None:
       
    71                         exc = sys.exc_info()[1]
       
    72                     self.ui.warn(('error in exit handlers:\n'))
       
    73                     self.ui.traceback(force=True)
       
    74         finally:
       
    75             if exc is not None:
       
    76                 raise exc
       
    77 
    61 def run():
    78 def run():
    62     "run the command in sys.argv"
    79     "run the command in sys.argv"
    63     sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
    80     req = request(pycompat.sysargv[1:])
       
    81     err = None
       
    82     try:
       
    83         status = (dispatch(req) or 0) & 255
       
    84     except error.StdioError as err:
       
    85         status = -1
       
    86     if util.safehasattr(req.ui, 'fout'):
       
    87         try:
       
    88             req.ui.fout.close()
       
    89         except IOError as err:
       
    90             status = -1
       
    91     if util.safehasattr(req.ui, 'ferr'):
       
    92         if err is not None and err.errno != errno.EPIPE:
       
    93             req.ui.ferr.write('abort: %s\n' % err.strerror)
       
    94         req.ui.ferr.close()
       
    95     sys.exit(status & 255)
    64 
    96 
    65 def _getsimilar(symbols, value):
    97 def _getsimilar(symbols, value):
    66     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
    98     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
    67     # The cutoff for similarity here is pretty arbitrary. It should
    99     # The cutoff for similarity here is pretty arbitrary. It should
    68     # probably be investigated and tweaked.
   100     # probably be investigated and tweaked.
    89         write(_("hg: parse error: %s\n") % inst.args[0])
   121         write(_("hg: parse error: %s\n") % inst.args[0])
    90         _reportsimilar(write, similar)
   122         _reportsimilar(write, similar)
    91     if inst.hint:
   123     if inst.hint:
    92         write(_("(%s)\n") % inst.hint)
   124         write(_("(%s)\n") % inst.hint)
    93 
   125 
       
   126 def _formatargs(args):
       
   127     return ' '.join(util.shellquote(a) for a in args)
       
   128 
    94 def dispatch(req):
   129 def dispatch(req):
    95     "run the command specified in req.args"
   130     "run the command specified in req.args"
    96     if req.ferr:
   131     if req.ferr:
    97         ferr = req.ferr
   132         ferr = req.ferr
    98     elif req.ui:
   133     elif req.ui:
   120         return -1
   155         return -1
   121     except error.ParseError as inst:
   156     except error.ParseError as inst:
   122         _formatparse(ferr.write, inst)
   157         _formatparse(ferr.write, inst)
   123         return -1
   158         return -1
   124 
   159 
   125     msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
   160     msg = _formatargs(req.args)
   126     starttime = time.time()
   161     starttime = util.timer()
   127     ret = None
   162     ret = None
   128     try:
   163     try:
   129         ret = _runcatch(req)
   164         ret = _runcatch(req)
   130     except KeyboardInterrupt:
   165     except KeyboardInterrupt:
   131         try:
   166         try:
   132             req.ui.warn(_("interrupted!\n"))
   167             req.ui.warn(_("interrupted!\n"))
       
   168         except error.SignalInterrupt:
       
   169             # maybe pager would quit without consuming all the output, and
       
   170             # SIGPIPE was raised. we cannot print anything in this case.
       
   171             pass
   133         except IOError as inst:
   172         except IOError as inst:
   134             if inst.errno != errno.EPIPE:
   173             if inst.errno != errno.EPIPE:
   135                 raise
   174                 raise
   136         ret = -1
   175         ret = -1
   137     finally:
   176     finally:
   138         duration = time.time() - starttime
   177         duration = util.timer() - starttime
   139         req.ui.flush()
   178         req.ui.flush()
       
   179         if req.ui.logblockedtimes:
       
   180             req.ui._blockedtimes['command_duration'] = duration * 1000
       
   181             req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
   140         req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
   182         req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
   141                    msg, ret or 0, duration)
   183                    msg, ret or 0, duration)
       
   184         try:
       
   185             req._runexithandlers()
       
   186         except: # exiting, so no re-raises
       
   187             ret = ret or -1
   142     return ret
   188     return ret
   143 
   189 
   144 def _runcatch(req):
   190 def _runcatch(req):
   145     def catchterm(*args):
   191     def catchterm(*args):
   146         raise error.SignalInterrupt
   192         raise error.SignalInterrupt
   242         except: # re-raises
   288         except: # re-raises
   243             # enter the debugger when we hit an exception
   289             # enter the debugger when we hit an exception
   244             if '--debugger' in req.args:
   290             if '--debugger' in req.args:
   245                 traceback.print_exc()
   291                 traceback.print_exc()
   246                 debugmortem[debugger](sys.exc_info()[2])
   292                 debugmortem[debugger](sys.exc_info()[2])
   247             ui.traceback()
       
   248             raise
   293             raise
   249 
   294 
   250     return callcatch(ui, _runcatchfunc)
   295     return _callcatch(ui, _runcatchfunc)
   251 
   296 
   252 def callcatch(ui, func):
   297 def _callcatch(ui, func):
   253     """like scmutil.callcatch but handles more high-level exceptions about
   298     """like scmutil.callcatch but handles more high-level exceptions about
   254     config parsing and commands. besides, use handlecommandexception to handle
   299     config parsing and commands. besides, use handlecommandexception to handle
   255     uncaught exceptions.
   300     uncaught exceptions.
   256     """
   301     """
   257     try:
   302     try:
   259     except error.AmbiguousCommand as inst:
   304     except error.AmbiguousCommand as inst:
   260         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
   305         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
   261                 (inst.args[0], " ".join(inst.args[1])))
   306                 (inst.args[0], " ".join(inst.args[1])))
   262     except error.CommandError as inst:
   307     except error.CommandError as inst:
   263         if inst.args[0]:
   308         if inst.args[0]:
       
   309             ui.pager('help')
   264             ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
   310             ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
   265             commands.help_(ui, inst.args[0], full=False, command=True)
   311             commands.help_(ui, inst.args[0], full=False, command=True)
   266         else:
   312         else:
       
   313             ui.pager('help')
   267             ui.warn(_("hg: %s\n") % inst.args[1])
   314             ui.warn(_("hg: %s\n") % inst.args[1])
   268             commands.help_(ui, 'shortlist')
   315             commands.help_(ui, 'shortlist')
   269     except error.ParseError as inst:
   316     except error.ParseError as inst:
   270         _formatparse(ui.warn, inst)
   317         _formatparse(ui.warn, inst)
   271         return -1
   318         return -1
   272     except error.UnknownCommand as inst:
   319     except error.UnknownCommand as inst:
   273         ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
   320         nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
   274         try:
   321         try:
   275             # check if the command is in a disabled extension
   322             # check if the command is in a disabled extension
   276             # (but don't check for extensions themselves)
   323             # (but don't check for extensions themselves)
   277             commands.help_(ui, inst.args[0], unknowncmd=True)
   324             formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
       
   325             ui.warn(nocmdmsg)
       
   326             ui.write(formatted)
   278         except (error.UnknownCommand, error.Abort):
   327         except (error.UnknownCommand, error.Abort):
   279             suggested = False
   328             suggested = False
   280             if len(inst.args) == 2:
   329             if len(inst.args) == 2:
   281                 sim = _getsimilar(inst.args[1], inst.args[0])
   330                 sim = _getsimilar(inst.args[1], inst.args[0])
   282                 if sim:
   331                 if sim:
       
   332                     ui.warn(nocmdmsg)
   283                     _reportsimilar(ui.warn, sim)
   333                     _reportsimilar(ui.warn, sim)
   284                     suggested = True
   334                     suggested = True
   285             if not suggested:
   335             if not suggested:
       
   336                 ui.pager('help')
       
   337                 ui.warn(nocmdmsg)
   286                 commands.help_(ui, 'shortlist')
   338                 commands.help_(ui, 'shortlist')
   287     except IOError:
   339     except IOError:
   288         raise
   340         raise
   289     except KeyboardInterrupt:
   341     except KeyboardInterrupt:
   290         raise
   342         raise
   304             num = int(m.group(1)) - 1
   356             num = int(m.group(1)) - 1
   305             nums.append(num)
   357             nums.append(num)
   306             if num < len(givenargs):
   358             if num < len(givenargs):
   307                 return givenargs[num]
   359                 return givenargs[num]
   308             raise error.Abort(_('too few arguments for command alias'))
   360             raise error.Abort(_('too few arguments for command alias'))
   309         cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
   361         cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
   310         givenargs = [x for i, x in enumerate(givenargs)
   362         givenargs = [x for i, x in enumerate(givenargs)
   311                      if i not in nums]
   363                      if i not in nums]
   312         args = pycompat.shlexsplit(cmd)
   364         args = pycompat.shlexsplit(cmd)
   313     return args + givenargs
   365     return args + givenargs
   314 
   366 
   374                                  "of %i variable in alias '%s' definition."
   426                                  "of %i variable in alias '%s' definition."
   375                                  % (int(m.groups()[0]), self.name))
   427                                  % (int(m.groups()[0]), self.name))
   376                         return ''
   428                         return ''
   377                 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
   429                 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
   378                 cmd = aliasinterpolate(self.name, args, cmd)
   430                 cmd = aliasinterpolate(self.name, args, cmd)
   379                 return ui.system(cmd, environ=env)
   431                 return ui.system(cmd, environ=env,
       
   432                                  blockedtag='alias_%s' % self.name)
   380             self.fn = fn
   433             self.fn = fn
   381             return
   434             return
   382 
   435 
   383         try:
   436         try:
   384             args = pycompat.shlexsplit(self.definition)
   437             args = pycompat.shlexsplit(self.definition)
   416             self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
   469             self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
   417                              % (self.name, cmd))
   470                              % (self.name, cmd))
   418 
   471 
   419     @property
   472     @property
   420     def args(self):
   473     def args(self):
   421         args = map(util.expandpath, self.givenargs)
   474         args = pycompat.maplist(util.expandpath, self.givenargs)
   422         return aliasargs(self.fn, args)
   475         return aliasargs(self.fn, args)
   423 
   476 
   424     def __getattr__(self, name):
   477     def __getattr__(self, name):
   425         adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
   478         adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
   426         if name not in adefaults:
   479         if name not in adefaults:
   489                                          ui.configbool("ui", "strict"))
   542                                          ui.configbool("ui", "strict"))
   490         cmd = aliases[0]
   543         cmd = aliases[0]
   491         args = aliasargs(entry[0], args)
   544         args = aliasargs(entry[0], args)
   492         defaults = ui.config("defaults", cmd)
   545         defaults = ui.config("defaults", cmd)
   493         if defaults:
   546         if defaults:
   494             args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
   547             args = pycompat.maplist(
       
   548                 util.expandpath, pycompat.shlexsplit(defaults)) + args
   495         c = list(entry[1])
   549         c = list(entry[1])
   496     else:
   550     else:
   497         cmd = None
   551         cmd = None
   498         c = []
   552         c = []
   499 
   553 
   684         os.chdir(cwd[-1])
   738         os.chdir(cwd[-1])
   685 
   739 
   686     rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
   740     rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
   687     path, lui = _getlocal(ui, rpath)
   741     path, lui = _getlocal(ui, rpath)
   688 
   742 
   689     # Configure extensions in phases: uisetup, extsetup, cmdtable, and
       
   690     # reposetup. Programs like TortoiseHg will call _dispatch several
       
   691     # times so we keep track of configured extensions in _loaded.
       
   692     extensions.loadall(lui)
       
   693     exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
       
   694     # Propagate any changes to lui.__class__ by extensions
       
   695     ui.__class__ = lui.__class__
       
   696 
       
   697     # (uisetup and extsetup are handled in extensions.loadall)
       
   698 
       
   699     for name, module in exts:
       
   700         for objname, loadermod, loadername in extraloaders:
       
   701             extraobj = getattr(module, objname, None)
       
   702             if extraobj is not None:
       
   703                 getattr(loadermod, loadername)(ui, name, extraobj)
       
   704         _loaded.add(name)
       
   705 
       
   706     # (reposetup is handled in hg.repository)
       
   707 
       
   708     # Side-effect of accessing is debugcommands module is guaranteed to be
   743     # Side-effect of accessing is debugcommands module is guaranteed to be
   709     # imported and commands.table is populated.
   744     # imported and commands.table is populated.
   710     debugcommands.command
   745     debugcommands.command
   711 
   746 
   712     addaliases(lui, commands.table)
       
   713 
       
   714     # All aliases and commands are completely defined, now.
       
   715     # Check abbreviation/ambiguity of shell alias.
       
   716     shellaliasfn = _checkshellalias(lui, ui, args)
       
   717     if shellaliasfn:
       
   718         with profiling.maybeprofile(lui):
       
   719             return shellaliasfn()
       
   720 
       
   721     # check for fallback encoding
       
   722     fallback = lui.config('ui', 'fallbackencoding')
       
   723     if fallback:
       
   724         encoding.fallbackencoding = fallback
       
   725 
       
   726     fullargs = args
       
   727     cmd, func, args, options, cmdoptions = _parse(lui, args)
       
   728 
       
   729     if options["config"]:
       
   730         raise error.Abort(_("option --config may not be abbreviated!"))
       
   731     if options["cwd"]:
       
   732         raise error.Abort(_("option --cwd may not be abbreviated!"))
       
   733     if options["repository"]:
       
   734         raise error.Abort(_(
       
   735             "option -R has to be separated from other options (e.g. not -qR) "
       
   736             "and --repository may only be abbreviated as --repo!"))
       
   737 
       
   738     if options["encoding"]:
       
   739         encoding.encoding = options["encoding"]
       
   740     if options["encodingmode"]:
       
   741         encoding.encodingmode = options["encodingmode"]
       
   742     if options["time"]:
       
   743         def get_times():
       
   744             t = os.times()
       
   745             if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
       
   746                 t = (t[0], t[1], t[2], t[3], time.clock())
       
   747             return t
       
   748         s = get_times()
       
   749         def print_time():
       
   750             t = get_times()
       
   751             ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
       
   752                 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
       
   753         atexit.register(print_time)
       
   754 
       
   755     uis = set([ui, lui])
   747     uis = set([ui, lui])
   756 
   748 
   757     if req.repo:
   749     if req.repo:
   758         uis.add(req.repo.ui)
   750         uis.add(req.repo.ui)
   759 
   751 
   760     if options['verbose'] or options['debug'] or options['quiet']:
   752     if '--profile' in args:
   761         for opt in ('verbose', 'debug', 'quiet'):
       
   762             val = str(bool(options[opt]))
       
   763             for ui_ in uis:
       
   764                 ui_.setconfig('ui', opt, val, '--' + opt)
       
   765 
       
   766     if options['profile']:
       
   767         for ui_ in uis:
   753         for ui_ in uis:
   768             ui_.setconfig('profiling', 'enabled', 'true', '--profile')
   754             ui_.setconfig('profiling', 'enabled', 'true', '--profile')
   769 
   755 
   770     if options['traceback']:
   756     with profiling.maybeprofile(lui):
       
   757         # Configure extensions in phases: uisetup, extsetup, cmdtable, and
       
   758         # reposetup. Programs like TortoiseHg will call _dispatch several
       
   759         # times so we keep track of configured extensions in _loaded.
       
   760         extensions.loadall(lui)
       
   761         exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
       
   762         # Propagate any changes to lui.__class__ by extensions
       
   763         ui.__class__ = lui.__class__
       
   764 
       
   765         # (uisetup and extsetup are handled in extensions.loadall)
       
   766 
       
   767         for name, module in exts:
       
   768             for objname, loadermod, loadername in extraloaders:
       
   769                 extraobj = getattr(module, objname, None)
       
   770                 if extraobj is not None:
       
   771                     getattr(loadermod, loadername)(ui, name, extraobj)
       
   772             _loaded.add(name)
       
   773 
       
   774         # (reposetup is handled in hg.repository)
       
   775 
       
   776         addaliases(lui, commands.table)
       
   777 
       
   778         # All aliases and commands are completely defined, now.
       
   779         # Check abbreviation/ambiguity of shell alias.
       
   780         shellaliasfn = _checkshellalias(lui, ui, args)
       
   781         if shellaliasfn:
       
   782             return shellaliasfn()
       
   783 
       
   784         # check for fallback encoding
       
   785         fallback = lui.config('ui', 'fallbackencoding')
       
   786         if fallback:
       
   787             encoding.fallbackencoding = fallback
       
   788 
       
   789         fullargs = args
       
   790         cmd, func, args, options, cmdoptions = _parse(lui, args)
       
   791 
       
   792         if options["config"]:
       
   793             raise error.Abort(_("option --config may not be abbreviated!"))
       
   794         if options["cwd"]:
       
   795             raise error.Abort(_("option --cwd may not be abbreviated!"))
       
   796         if options["repository"]:
       
   797             raise error.Abort(_(
       
   798                 "option -R has to be separated from other options (e.g. not "
       
   799                 "-qR) and --repository may only be abbreviated as --repo!"))
       
   800 
       
   801         if options["encoding"]:
       
   802             encoding.encoding = options["encoding"]
       
   803         if options["encodingmode"]:
       
   804             encoding.encodingmode = options["encodingmode"]
       
   805         if options["time"]:
       
   806             def get_times():
       
   807                 t = os.times()
       
   808                 if t[4] == 0.0:
       
   809                     # Windows leaves this as zero, so use time.clock()
       
   810                     t = (t[0], t[1], t[2], t[3], time.clock())
       
   811                 return t
       
   812             s = get_times()
       
   813             def print_time():
       
   814                 t = get_times()
       
   815                 ui.warn(
       
   816                     _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
       
   817                     (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
       
   818             ui.atexit(print_time)
       
   819 
       
   820         if options['verbose'] or options['debug'] or options['quiet']:
       
   821             for opt in ('verbose', 'debug', 'quiet'):
       
   822                 val = str(bool(options[opt]))
       
   823                 if pycompat.ispy3:
       
   824                     val = val.encode('ascii')
       
   825                 for ui_ in uis:
       
   826                     ui_.setconfig('ui', opt, val, '--' + opt)
       
   827 
       
   828         if options['traceback']:
       
   829             for ui_ in uis:
       
   830                 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
       
   831 
       
   832         if options['noninteractive']:
       
   833             for ui_ in uis:
       
   834                 ui_.setconfig('ui', 'interactive', 'off', '-y')
       
   835 
       
   836         if util.parsebool(options['pager']):
       
   837             ui.pager('internal-always-' + cmd)
       
   838         elif options['pager'] != 'auto':
       
   839             ui.disablepager()
       
   840 
       
   841         if cmdoptions.get('insecure', False):
       
   842             for ui_ in uis:
       
   843                 ui_.insecureconnections = True
       
   844 
       
   845         # setup color handling
       
   846         coloropt = options['color']
   771         for ui_ in uis:
   847         for ui_ in uis:
   772             ui_.setconfig('ui', 'traceback', 'on', '--traceback')
   848             if coloropt:
   773 
   849                 ui_.setconfig('ui', 'color', coloropt, '--color')
   774     if options['noninteractive']:
   850             color.setup(ui_)
   775         for ui_ in uis:
   851 
   776             ui_.setconfig('ui', 'interactive', 'off', '-y')
   852         if options['version']:
   777 
   853             return commands.version_(ui)
   778     if cmdoptions.get('insecure', False):
   854         if options['help']:
   779         for ui_ in uis:
   855             return commands.help_(ui, cmd, command=cmd is not None)
   780             ui_.insecureconnections = True
   856         elif not cmd:
   781 
   857             return commands.help_(ui, 'shortlist')
   782     if options['version']:
   858 
   783         return commands.version_(ui)
       
   784     if options['help']:
       
   785         return commands.help_(ui, cmd, command=cmd is not None)
       
   786     elif not cmd:
       
   787         return commands.help_(ui, 'shortlist')
       
   788 
       
   789     with profiling.maybeprofile(lui):
       
   790         repo = None
   859         repo = None
   791         cmdpats = args[:]
   860         cmdpats = args[:]
   792         if not func.norepo:
   861         if not func.norepo:
   793             # use the repo from the request only if we don't have -R
   862             # use the repo from the request only if we don't have -R
   794             if not rpath and not cwd:
   863             if not rpath and not cwd:
   831                     repo = repo.unfiltered()
   900                     repo = repo.unfiltered()
   832             args.insert(0, repo)
   901             args.insert(0, repo)
   833         elif rpath:
   902         elif rpath:
   834             ui.warn(_("warning: --repository ignored\n"))
   903             ui.warn(_("warning: --repository ignored\n"))
   835 
   904 
   836         msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
   905         msg = _formatargs(fullargs)
   837         ui.log("command", '%s\n', msg)
   906         ui.log("command", '%s\n', msg)
   838         strcmdopt = pycompat.strkwargs(cmdoptions)
   907         strcmdopt = pycompat.strkwargs(cmdoptions)
   839         d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
   908         d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
   840         try:
   909         try:
   841             return runcommand(lui, repo, cmd, fullargs, ui, options, d,
   910             return runcommand(lui, repo, cmd, fullargs, ui, options, d,
   864     ct = util.versiontuple(n=2)
   933     ct = util.versiontuple(n=2)
   865     worst = None, ct, ''
   934     worst = None, ct, ''
   866     if ui.config('ui', 'supportcontact', None) is None:
   935     if ui.config('ui', 'supportcontact', None) is None:
   867         for name, mod in extensions.extensions():
   936         for name, mod in extensions.extensions():
   868             testedwith = getattr(mod, 'testedwith', '')
   937             testedwith = getattr(mod, 'testedwith', '')
       
   938             if pycompat.ispy3 and isinstance(testedwith, str):
       
   939                 testedwith = testedwith.encode(u'utf-8')
   869             report = getattr(mod, 'buglink', _('the extension author.'))
   940             report = getattr(mod, 'buglink', _('the extension author.'))
   870             if not testedwith.strip():
   941             if not testedwith.strip():
   871                 # We found an untested extension. It's likely the culprit.
   942                 # We found an untested extension. It's likely the culprit.
   872                 worst = name, 'unknown', report
   943                 worst = name, 'unknown', report
   873                 break
   944                 break
   884             nearest = max(lower or tested)
   955             nearest = max(lower or tested)
   885             if worst[0] is None or nearest < worst[1]:
   956             if worst[0] is None or nearest < worst[1]:
   886                 worst = name, nearest, report
   957                 worst = name, nearest, report
   887     if worst[0] is not None:
   958     if worst[0] is not None:
   888         name, testedwith, report = worst
   959         name, testedwith, report = worst
   889         if not isinstance(testedwith, str):
   960         if not isinstance(testedwith, (bytes, str)):
   890             testedwith = '.'.join([str(c) for c in testedwith])
   961             testedwith = '.'.join([str(c) for c in testedwith])
   891         warning = (_('** Unknown exception encountered with '
   962         warning = (_('** Unknown exception encountered with '
   892                      'possibly-broken third-party extension %s\n'
   963                      'possibly-broken third-party extension %s\n'
   893                      '** which supports versions %s of Mercurial.\n'
   964                      '** which supports versions %s of Mercurial.\n'
   894                      '** Please disable %s and try your action again.\n'
   965                      '** Please disable %s and try your action again.\n'
   898         bugtracker = ui.config('ui', 'supportcontact', None)
   969         bugtracker = ui.config('ui', 'supportcontact', None)
   899         if bugtracker is None:
   970         if bugtracker is None:
   900             bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
   971             bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
   901         warning = (_("** unknown exception encountered, "
   972         warning = (_("** unknown exception encountered, "
   902                      "please report by visiting\n** ") + bugtracker + '\n')
   973                      "please report by visiting\n** ") + bugtracker + '\n')
   903     warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
   974     if pycompat.ispy3:
       
   975         sysversion = sys.version.encode(u'utf-8')
       
   976     else:
       
   977         sysversion = sys.version
       
   978     sysversion = sysversion.replace('\n', '')
       
   979     warning += ((_("** Python %s\n") % sysversion) +
   904                 (_("** Mercurial Distributed SCM (version %s)\n") %
   980                 (_("** Mercurial Distributed SCM (version %s)\n") %
   905                  util.version()) +
   981                  util.version()) +
   906                 (_("** Extensions loaded: %s\n") %
   982                 (_("** Extensions loaded: %s\n") %
   907                  ", ".join([x[0] for x in extensions.extensions()])))
   983                  ", ".join([x[0] for x in extensions.extensions()])))
   908     return warning
   984     return warning