mercurial/ui.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43079 5209fc94b982
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    44 )
    44 )
    45 
    45 
    46 urlreq = util.urlreq
    46 urlreq = util.urlreq
    47 
    47 
    48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
    48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
    49 _keepalnum = ''.join(
    49 _keepalnum = b''.join(
    50     c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
    50     c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
    51 )
    51 )
    52 
    52 
    53 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
    53 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
    54 tweakrc = b"""
    54 tweakrc = b"""
    77 showfunc = 1
    77 showfunc = 1
    78 word-diff = 1
    78 word-diff = 1
    79 """
    79 """
    80 
    80 
    81 samplehgrcs = {
    81 samplehgrcs = {
    82     'user': b"""# example user config (see 'hg help config' for more info)
    82     b'user': b"""# example user config (see 'hg help config' for more info)
    83 [ui]
    83 [ui]
    84 # name and email, e.g.
    84 # name and email, e.g.
    85 # username = Jane Doe <jdoe@example.com>
    85 # username = Jane Doe <jdoe@example.com>
    86 username =
    86 username =
    87 
    87 
   104 #
   104 #
   105 # histedit =
   105 # histedit =
   106 # rebase =
   106 # rebase =
   107 # uncommit =
   107 # uncommit =
   108 """,
   108 """,
   109     'cloned': b"""# example repository config (see 'hg help config' for more info)
   109     b'cloned': b"""# example repository config (see 'hg help config' for more info)
   110 [paths]
   110 [paths]
   111 default = %s
   111 default = %s
   112 
   112 
   113 # path aliases to other clones of this repo in URLs or filesystem paths
   113 # path aliases to other clones of this repo in URLs or filesystem paths
   114 # (see 'hg help config.paths' for more info)
   114 # (see 'hg help config.paths' for more info)
   119 
   119 
   120 [ui]
   120 [ui]
   121 # name and email (local to this repository, optional), e.g.
   121 # name and email (local to this repository, optional), e.g.
   122 # username = Jane Doe <jdoe@example.com>
   122 # username = Jane Doe <jdoe@example.com>
   123 """,
   123 """,
   124     'local': b"""# example repository config (see 'hg help config' for more info)
   124     b'local': b"""# example repository config (see 'hg help config' for more info)
   125 [paths]
   125 [paths]
   126 # path aliases to other clones of this repo in URLs or filesystem paths
   126 # path aliases to other clones of this repo in URLs or filesystem paths
   127 # (see 'hg help config.paths' for more info)
   127 # (see 'hg help config.paths' for more info)
   128 #
   128 #
   129 # default         = http://example.com/hg/example-repo
   129 # default         = http://example.com/hg/example-repo
   133 
   133 
   134 [ui]
   134 [ui]
   135 # name and email (local to this repository, optional), e.g.
   135 # name and email (local to this repository, optional), e.g.
   136 # username = Jane Doe <jdoe@example.com>
   136 # username = Jane Doe <jdoe@example.com>
   137 """,
   137 """,
   138     'global': b"""# example system-wide hg config (see 'hg help config' for more info)
   138     b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
   139 
   139 
   140 [ui]
   140 [ui]
   141 # uncomment to disable color in command output
   141 # uncomment to disable color in command output
   142 # (see 'hg help color' for details)
   142 # (see 'hg help color' for details)
   143 # color = never
   143 # color = never
   283             self.environ = encoding.environ
   283             self.environ = encoding.environ
   284 
   284 
   285             self.httppasswordmgrdb = httppasswordmgrdbproxy()
   285             self.httppasswordmgrdb = httppasswordmgrdbproxy()
   286             self._blockedtimes = collections.defaultdict(int)
   286             self._blockedtimes = collections.defaultdict(int)
   287 
   287 
   288         allowed = self.configlist('experimental', 'exportableenviron')
   288         allowed = self.configlist(b'experimental', b'exportableenviron')
   289         if '*' in allowed:
   289         if b'*' in allowed:
   290             self._exportableenviron = self.environ
   290             self._exportableenviron = self.environ
   291         else:
   291         else:
   292             self._exportableenviron = {}
   292             self._exportableenviron = {}
   293             for k in allowed:
   293             for k in allowed:
   294                 if k in self.environ:
   294                 if k in self.environ:
   298     def load(cls):
   298     def load(cls):
   299         """Create a ui and load global and user configs"""
   299         """Create a ui and load global and user configs"""
   300         u = cls()
   300         u = cls()
   301         # we always trust global config files and environment variables
   301         # we always trust global config files and environment variables
   302         for t, f in rcutil.rccomponents():
   302         for t, f in rcutil.rccomponents():
   303             if t == 'path':
   303             if t == b'path':
   304                 u.readconfig(f, trust=True)
   304                 u.readconfig(f, trust=True)
   305             elif t == 'items':
   305             elif t == b'items':
   306                 sections = set()
   306                 sections = set()
   307                 for section, name, value, source in f:
   307                 for section, name, value, source in f:
   308                     # do not set u._ocfg
   308                     # do not set u._ocfg
   309                     # XXX clean this up once immutable config object is a thing
   309                     # XXX clean this up once immutable config object is a thing
   310                     u._tcfg.set(section, name, value, source)
   310                     u._tcfg.set(section, name, value, source)
   311                     u._ucfg.set(section, name, value, source)
   311                     u._ucfg.set(section, name, value, source)
   312                     sections.add(section)
   312                     sections.add(section)
   313                 for section in sections:
   313                 for section in sections:
   314                     u.fixconfig(section=section)
   314                     u.fixconfig(section=section)
   315             else:
   315             else:
   316                 raise error.ProgrammingError('unknown rctype: %s' % t)
   316                 raise error.ProgrammingError(b'unknown rctype: %s' % t)
   317         u._maybetweakdefaults()
   317         u._maybetweakdefaults()
   318         return u
   318         return u
   319 
   319 
   320     def _maybetweakdefaults(self):
   320     def _maybetweakdefaults(self):
   321         if not self.configbool('ui', 'tweakdefaults'):
   321         if not self.configbool(b'ui', b'tweakdefaults'):
   322             return
   322             return
   323         if self._tweaked or self.plain('tweakdefaults'):
   323         if self._tweaked or self.plain(b'tweakdefaults'):
   324             return
   324             return
   325 
   325 
   326         # Note: it is SUPER IMPORTANT that you set self._tweaked to
   326         # Note: it is SUPER IMPORTANT that you set self._tweaked to
   327         # True *before* any calls to setconfig(), otherwise you'll get
   327         # True *before* any calls to setconfig(), otherwise you'll get
   328         # infinite recursion between setconfig and this method.
   328         # infinite recursion between setconfig and this method.
   329         #
   329         #
   330         # TODO: We should extract an inner method in setconfig() to
   330         # TODO: We should extract an inner method in setconfig() to
   331         # avoid this weirdness.
   331         # avoid this weirdness.
   332         self._tweaked = True
   332         self._tweaked = True
   333         tmpcfg = config.config()
   333         tmpcfg = config.config()
   334         tmpcfg.parse('<tweakdefaults>', tweakrc)
   334         tmpcfg.parse(b'<tweakdefaults>', tweakrc)
   335         for section in tmpcfg:
   335         for section in tmpcfg:
   336             for name, value in tmpcfg.items(section):
   336             for name, value in tmpcfg.items(section):
   337                 if not self.hasconfig(section, name):
   337                 if not self.hasconfig(section, name):
   338                     self.setconfig(section, name, value, "<tweakdefaults>")
   338                     self.setconfig(section, name, value, b"<tweakdefaults>")
   339 
   339 
   340     def copy(self):
   340     def copy(self):
   341         return self.__class__(self)
   341         return self.__class__(self)
   342 
   342 
   343     def resetstate(self):
   343     def resetstate(self):
   351         # this is open-coded below - search for timeblockedsection to find them
   351         # this is open-coded below - search for timeblockedsection to find them
   352         starttime = util.timer()
   352         starttime = util.timer()
   353         try:
   353         try:
   354             yield
   354             yield
   355         finally:
   355         finally:
   356             self._blockedtimes[key + '_blocked'] += (
   356             self._blockedtimes[key + b'_blocked'] += (
   357                 util.timer() - starttime
   357                 util.timer() - starttime
   358             ) * 1000
   358             ) * 1000
   359 
   359 
   360     @contextlib.contextmanager
   360     @contextlib.contextmanager
   361     def uninterruptible(self):
   361     def uninterruptible(self):
   364         Most operations on a repository are safe to interrupt, but a
   364         Most operations on a repository are safe to interrupt, but a
   365         few are risky (for example repair.strip). This context manager
   365         few are risky (for example repair.strip). This context manager
   366         lets you advise Mercurial that something risky is happening so
   366         lets you advise Mercurial that something risky is happening so
   367         that control-C etc can be blocked if desired.
   367         that control-C etc can be blocked if desired.
   368         """
   368         """
   369         enabled = self.configbool('experimental', 'nointerrupt')
   369         enabled = self.configbool(b'experimental', b'nointerrupt')
   370         if enabled and self.configbool(
   370         if enabled and self.configbool(
   371             'experimental', 'nointerrupt-interactiveonly'
   371             b'experimental', b'nointerrupt-interactiveonly'
   372         ):
   372         ):
   373             enabled = self.interactive()
   373             enabled = self.interactive()
   374         if self._uninterruptible or not enabled:
   374         if self._uninterruptible or not enabled:
   375             # if nointerrupt support is turned off, the process isn't
   375             # if nointerrupt support is turned off, the process isn't
   376             # interactive, or we're already in an uninterruptible
   376             # interactive, or we're already in an uninterruptible
   377             # block, do nothing.
   377             # block, do nothing.
   378             yield
   378             yield
   379             return
   379             return
   380 
   380 
   381         def warn():
   381         def warn():
   382             self.warn(_("shutting down cleanly\n"))
   382             self.warn(_(b"shutting down cleanly\n"))
   383             self.warn(
   383             self.warn(
   384                 _("press ^C again to terminate immediately (dangerous)\n")
   384                 _(b"press ^C again to terminate immediately (dangerous)\n")
   385             )
   385             )
   386             return True
   386             return True
   387 
   387 
   388         with procutil.uninterruptible(warn):
   388         with procutil.uninterruptible(warn):
   389             try:
   389             try:
   399         st = util.fstat(fp)
   399         st = util.fstat(fp)
   400         if util.isowner(st):
   400         if util.isowner(st):
   401             return True
   401             return True
   402 
   402 
   403         tusers, tgroups = self._trustusers, self._trustgroups
   403         tusers, tgroups = self._trustusers, self._trustgroups
   404         if '*' in tusers or '*' in tgroups:
   404         if b'*' in tusers or b'*' in tgroups:
   405             return True
   405             return True
   406 
   406 
   407         user = util.username(st.st_uid)
   407         user = util.username(st.st_uid)
   408         group = util.groupname(st.st_gid)
   408         group = util.groupname(st.st_gid)
   409         if user in tusers or group in tgroups or user == util.username():
   409         if user in tusers or group in tgroups or user == util.username():
   410             return True
   410             return True
   411 
   411 
   412         if self._reportuntrusted:
   412         if self._reportuntrusted:
   413             self.warn(
   413             self.warn(
   414                 _('not trusting file %s from untrusted ' 'user %s, group %s\n')
   414                 _(
       
   415                     b'not trusting file %s from untrusted '
       
   416                     b'user %s, group %s\n'
       
   417                 )
   415                 % (f, user, group)
   418                 % (f, user, group)
   416             )
   419             )
   417         return False
   420         return False
   418 
   421 
   419     def readconfig(
   422     def readconfig(
   433             cfg.read(filename, fp, sections=sections, remap=remap)
   436             cfg.read(filename, fp, sections=sections, remap=remap)
   434             fp.close()
   437             fp.close()
   435         except error.ConfigError as inst:
   438         except error.ConfigError as inst:
   436             if trusted:
   439             if trusted:
   437                 raise
   440                 raise
   438             self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
   441             self.warn(_(b"ignored: %s\n") % stringutil.forcebytestr(inst))
   439 
   442 
   440         if self.plain():
   443         if self.plain():
   441             for k in (
   444             for k in (
   442                 'debug',
   445                 b'debug',
   443                 'fallbackencoding',
   446                 b'fallbackencoding',
   444                 'quiet',
   447                 b'quiet',
   445                 'slash',
   448                 b'slash',
   446                 'logtemplate',
   449                 b'logtemplate',
   447                 'message-output',
   450                 b'message-output',
   448                 'statuscopies',
   451                 b'statuscopies',
   449                 'style',
   452                 b'style',
   450                 'traceback',
   453                 b'traceback',
   451                 'verbose',
   454                 b'verbose',
   452             ):
   455             ):
   453                 if k in cfg['ui']:
   456                 if k in cfg[b'ui']:
   454                     del cfg['ui'][k]
   457                     del cfg[b'ui'][k]
   455             for k, v in cfg.items('defaults'):
   458             for k, v in cfg.items(b'defaults'):
   456                 del cfg['defaults'][k]
   459                 del cfg[b'defaults'][k]
   457             for k, v in cfg.items('commands'):
   460             for k, v in cfg.items(b'commands'):
   458                 del cfg['commands'][k]
   461                 del cfg[b'commands'][k]
   459         # Don't remove aliases from the configuration if in the exceptionlist
   462         # Don't remove aliases from the configuration if in the exceptionlist
   460         if self.plain('alias'):
   463         if self.plain(b'alias'):
   461             for k, v in cfg.items('alias'):
   464             for k, v in cfg.items(b'alias'):
   462                 del cfg['alias'][k]
   465                 del cfg[b'alias'][k]
   463         if self.plain('revsetalias'):
   466         if self.plain(b'revsetalias'):
   464             for k, v in cfg.items('revsetalias'):
   467             for k, v in cfg.items(b'revsetalias'):
   465                 del cfg['revsetalias'][k]
   468                 del cfg[b'revsetalias'][k]
   466         if self.plain('templatealias'):
   469         if self.plain(b'templatealias'):
   467             for k, v in cfg.items('templatealias'):
   470             for k, v in cfg.items(b'templatealias'):
   468                 del cfg['templatealias'][k]
   471                 del cfg[b'templatealias'][k]
   469 
   472 
   470         if trusted:
   473         if trusted:
   471             self._tcfg.update(cfg)
   474             self._tcfg.update(cfg)
   472             self._tcfg.update(self._ocfg)
   475             self._tcfg.update(self._ocfg)
   473         self._ucfg.update(cfg)
   476         self._ucfg.update(cfg)
   474         self._ucfg.update(self._ocfg)
   477         self._ucfg.update(self._ocfg)
   475 
   478 
   476         if root is None:
   479         if root is None:
   477             root = os.path.expanduser('~')
   480             root = os.path.expanduser(b'~')
   478         self.fixconfig(root=root)
   481         self.fixconfig(root=root)
   479 
   482 
   480     def fixconfig(self, root=None, section=None):
   483     def fixconfig(self, root=None, section=None):
   481         if section in (None, 'paths'):
   484         if section in (None, b'paths'):
   482             # expand vars and ~
   485             # expand vars and ~
   483             # translate paths relative to root (or home) into absolute paths
   486             # translate paths relative to root (or home) into absolute paths
   484             root = root or encoding.getcwd()
   487             root = root or encoding.getcwd()
   485             for c in self._tcfg, self._ucfg, self._ocfg:
   488             for c in self._tcfg, self._ucfg, self._ocfg:
   486                 for n, p in c.items('paths'):
   489                 for n, p in c.items(b'paths'):
   487                     # Ignore sub-options.
   490                     # Ignore sub-options.
   488                     if ':' in n:
   491                     if b':' in n:
   489                         continue
   492                         continue
   490                     if not p:
   493                     if not p:
   491                         continue
   494                         continue
   492                     if '%%' in p:
   495                     if b'%%' in p:
   493                         s = self.configsource('paths', n) or 'none'
   496                         s = self.configsource(b'paths', n) or b'none'
   494                         self.warn(
   497                         self.warn(
   495                             _("(deprecated '%%' in path %s=%s from %s)\n")
   498                             _(b"(deprecated '%%' in path %s=%s from %s)\n")
   496                             % (n, p, s)
   499                             % (n, p, s)
   497                         )
   500                         )
   498                         p = p.replace('%%', '%')
   501                         p = p.replace(b'%%', b'%')
   499                     p = util.expandpath(p)
   502                     p = util.expandpath(p)
   500                     if not util.hasscheme(p) and not os.path.isabs(p):
   503                     if not util.hasscheme(p) and not os.path.isabs(p):
   501                         p = os.path.normpath(os.path.join(root, p))
   504                         p = os.path.normpath(os.path.join(root, p))
   502                     c.set("paths", n, p)
   505                     c.set(b"paths", n, p)
   503 
   506 
   504         if section in (None, 'ui'):
   507         if section in (None, b'ui'):
   505             # update ui options
   508             # update ui options
   506             self._fmsgout, self._fmsgerr = _selectmsgdests(self)
   509             self._fmsgout, self._fmsgerr = _selectmsgdests(self)
   507             self.debugflag = self.configbool('ui', 'debug')
   510             self.debugflag = self.configbool(b'ui', b'debug')
   508             self.verbose = self.debugflag or self.configbool('ui', 'verbose')
   511             self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
   509             self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
   512             self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
   510             if self.verbose and self.quiet:
   513             if self.verbose and self.quiet:
   511                 self.quiet = self.verbose = False
   514                 self.quiet = self.verbose = False
   512             self._reportuntrusted = self.debugflag or self.configbool(
   515             self._reportuntrusted = self.debugflag or self.configbool(
   513                 "ui", "report_untrusted"
   516                 b"ui", b"report_untrusted"
   514             )
   517             )
   515             self.tracebackflag = self.configbool('ui', 'traceback')
   518             self.tracebackflag = self.configbool(b'ui', b'traceback')
   516             self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
   519             self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
   517 
   520 
   518         if section in (None, 'trusted'):
   521         if section in (None, b'trusted'):
   519             # update trust information
   522             # update trust information
   520             self._trustusers.update(self.configlist('trusted', 'users'))
   523             self._trustusers.update(self.configlist(b'trusted', b'users'))
   521             self._trustgroups.update(self.configlist('trusted', 'groups'))
   524             self._trustgroups.update(self.configlist(b'trusted', b'groups'))
   522 
   525 
   523         if section in (None, b'devel', b'ui') and self.debugflag:
   526         if section in (None, b'devel', b'ui') and self.debugflag:
   524             tracked = set()
   527             tracked = set()
   525             if self.configbool(b'devel', b'debug.extensions'):
   528             if self.configbool(b'devel', b'debug.extensions'):
   526                 tracked.add(b'extension')
   529                 tracked.add(b'extension')
   538     def restoreconfig(self, data):
   541     def restoreconfig(self, data):
   539         self._ocfg.restore(data[0])
   542         self._ocfg.restore(data[0])
   540         self._tcfg.restore(data[1])
   543         self._tcfg.restore(data[1])
   541         self._ucfg.restore(data[2])
   544         self._ucfg.restore(data[2])
   542 
   545 
   543     def setconfig(self, section, name, value, source=''):
   546     def setconfig(self, section, name, value, source=b''):
   544         for cfg in (self._ocfg, self._tcfg, self._ucfg):
   547         for cfg in (self._ocfg, self._tcfg, self._ucfg):
   545             cfg.set(section, name, value, source)
   548             cfg.set(section, name, value, source)
   546         self.fixconfig(section=section)
   549         self.fixconfig(section=section)
   547         self._maybetweakdefaults()
   550         self._maybetweakdefaults()
   548 
   551 
   571             if callable(item.default):
   574             if callable(item.default):
   572                 itemdefault = item.default()
   575                 itemdefault = item.default()
   573             else:
   576             else:
   574                 itemdefault = item.default
   577                 itemdefault = item.default
   575         else:
   578         else:
   576             msg = "accessing unregistered config item: '%s.%s'"
   579             msg = b"accessing unregistered config item: '%s.%s'"
   577             msg %= (section, name)
   580             msg %= (section, name)
   578             self.develwarn(msg, 2, 'warn-config-unknown')
   581             self.develwarn(msg, 2, b'warn-config-unknown')
   579 
   582 
   580         if default is _unset:
   583         if default is _unset:
   581             if item is None:
   584             if item is None:
   582                 value = default
   585                 value = default
   583             elif item.default is configitems.dynamicdefault:
   586             elif item.default is configitems.dynamicdefault:
   584                 value = None
   587                 value = None
   585                 msg = "config item requires an explicit default value: '%s.%s'"
   588                 msg = b"config item requires an explicit default value: '%s.%s'"
   586                 msg %= (section, name)
   589                 msg %= (section, name)
   587                 self.develwarn(msg, 2, 'warn-config-default')
   590                 self.develwarn(msg, 2, b'warn-config-default')
   588             else:
   591             else:
   589                 value = itemdefault
   592                 value = itemdefault
   590         elif (
   593         elif (
   591             item is not None
   594             item is not None
   592             and item.default is not configitems.dynamicdefault
   595             and item.default is not configitems.dynamicdefault
   593             and default != itemdefault
   596             and default != itemdefault
   594         ):
   597         ):
   595             msg = (
   598             msg = (
   596                 "specifying a mismatched default value for a registered "
   599                 b"specifying a mismatched default value for a registered "
   597                 "config item: '%s.%s' '%s'"
   600                 b"config item: '%s.%s' '%s'"
   598             )
   601             )
   599             msg %= (section, name, pycompat.bytestr(default))
   602             msg %= (section, name, pycompat.bytestr(default))
   600             self.develwarn(msg, 2, 'warn-config-default')
   603             self.develwarn(msg, 2, b'warn-config-default')
   601 
   604 
   602         for s, n in alternates:
   605         for s, n in alternates:
   603             candidate = self._data(untrusted).get(s, n, None)
   606             candidate = self._data(untrusted).get(s, n, None)
   604             if candidate is not None:
   607             if candidate is not None:
   605                 value = candidate
   608                 value = candidate
   608         if self.debugflag and not untrusted and self._reportuntrusted:
   611         if self.debugflag and not untrusted and self._reportuntrusted:
   609             for s, n in alternates:
   612             for s, n in alternates:
   610                 uvalue = self._ucfg.get(s, n)
   613                 uvalue = self._ucfg.get(s, n)
   611                 if uvalue is not None and uvalue != value:
   614                 if uvalue is not None and uvalue != value:
   612                     self.debug(
   615                     self.debug(
   613                         "ignoring untrusted configuration option "
   616                         b"ignoring untrusted configuration option "
   614                         "%s.%s = %s\n" % (s, n, uvalue)
   617                         b"%s.%s = %s\n" % (s, n, uvalue)
   615                     )
   618                     )
   616         return value
   619         return value
   617 
   620 
   618     def configsuboptions(self, section, name, default=_unset, untrusted=False):
   621     def configsuboptions(self, section, name, default=_unset, untrusted=False):
   619         """Get a config option and all sub-options.
   622         """Get a config option and all sub-options.
   626         is a dict of defined sub-options where keys and values are strings.
   629         is a dict of defined sub-options where keys and values are strings.
   627         """
   630         """
   628         main = self.config(section, name, default, untrusted=untrusted)
   631         main = self.config(section, name, default, untrusted=untrusted)
   629         data = self._data(untrusted)
   632         data = self._data(untrusted)
   630         sub = {}
   633         sub = {}
   631         prefix = '%s:' % name
   634         prefix = b'%s:' % name
   632         for k, v in data.items(section):
   635         for k, v in data.items(section):
   633             if k.startswith(prefix):
   636             if k.startswith(prefix):
   634                 sub[k[len(prefix) :]] = v
   637                 sub[k[len(prefix) :]] = v
   635 
   638 
   636         if self.debugflag and not untrusted and self._reportuntrusted:
   639         if self.debugflag and not untrusted and self._reportuntrusted:
   637             for k, v in sub.items():
   640             for k, v in sub.items():
   638                 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
   641                 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
   639                 if uvalue is not None and uvalue != v:
   642                 if uvalue is not None and uvalue != v:
   640                     self.debug(
   643                     self.debug(
   641                         'ignoring untrusted configuration option '
   644                         b'ignoring untrusted configuration option '
   642                         '%s:%s.%s = %s\n' % (section, name, k, uvalue)
   645                         b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
   643                     )
   646                     )
   644 
   647 
   645         return main, sub
   648         return main, sub
   646 
   649 
   647     def configpath(self, section, name, default=_unset, untrusted=False):
   650     def configpath(self, section, name, default=_unset, untrusted=False):
   648         'get a path config item, expanded relative to repo root or config file'
   651         b'get a path config item, expanded relative to repo root or config file'
   649         v = self.config(section, name, default, untrusted)
   652         v = self.config(section, name, default, untrusted)
   650         if v is None:
   653         if v is None:
   651             return None
   654             return None
   652         if not os.path.isabs(v) or "://" not in v:
   655         if not os.path.isabs(v) or b"://" not in v:
   653             src = self.configsource(section, name, untrusted)
   656             src = self.configsource(section, name, untrusted)
   654             if ':' in src:
   657             if b':' in src:
   655                 base = os.path.dirname(src.rsplit(':')[0])
   658                 base = os.path.dirname(src.rsplit(b':')[0])
   656                 v = os.path.join(base, os.path.expanduser(v))
   659                 v = os.path.join(base, os.path.expanduser(v))
   657         return v
   660         return v
   658 
   661 
   659     def configbool(self, section, name, default=_unset, untrusted=False):
   662     def configbool(self, section, name, default=_unset, untrusted=False):
   660         """parse a configuration element as a boolean
   663         """parse a configuration element as a boolean
   687         if isinstance(v, bool):
   690         if isinstance(v, bool):
   688             return v
   691             return v
   689         b = stringutil.parsebool(v)
   692         b = stringutil.parsebool(v)
   690         if b is None:
   693         if b is None:
   691             raise error.ConfigError(
   694             raise error.ConfigError(
   692                 _("%s.%s is not a boolean ('%s')") % (section, name, v)
   695                 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
   693             )
   696             )
   694         return b
   697         return b
   695 
   698 
   696     def configwith(
   699     def configwith(
   697         self, convert, section, name, default=_unset, desc=None, untrusted=False
   700         self, convert, section, name, default=_unset, desc=None, untrusted=False
   725             return convert(v)
   728             return convert(v)
   726         except (ValueError, error.ParseError):
   729         except (ValueError, error.ParseError):
   727             if desc is None:
   730             if desc is None:
   728                 desc = pycompat.sysbytes(convert.__name__)
   731                 desc = pycompat.sysbytes(convert.__name__)
   729             raise error.ConfigError(
   732             raise error.ConfigError(
   730                 _("%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
   733                 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
   731             )
   734             )
   732 
   735 
   733     def configint(self, section, name, default=_unset, untrusted=False):
   736     def configint(self, section, name, default=_unset, untrusted=False):
   734         """parse a configuration element as an integer
   737         """parse a configuration element as an integer
   735 
   738 
   748             ...
   751             ...
   749         ConfigError: foo.invalid is not a valid integer ('somevalue')
   752         ConfigError: foo.invalid is not a valid integer ('somevalue')
   750         """
   753         """
   751 
   754 
   752         return self.configwith(
   755         return self.configwith(
   753             int, section, name, default, 'integer', untrusted
   756             int, section, name, default, b'integer', untrusted
   754         )
   757         )
   755 
   758 
   756     def configbytes(self, section, name, default=_unset, untrusted=False):
   759     def configbytes(self, section, name, default=_unset, untrusted=False):
   757         """parse a configuration element as a quantity in bytes
   760         """parse a configuration element as a quantity in bytes
   758 
   761 
   784             return value
   787             return value
   785         try:
   788         try:
   786             return util.sizetoint(value)
   789             return util.sizetoint(value)
   787         except error.ParseError:
   790         except error.ParseError:
   788             raise error.ConfigError(
   791             raise error.ConfigError(
   789                 _("%s.%s is not a byte quantity ('%s')")
   792                 _(b"%s.%s is not a byte quantity ('%s')")
   790                 % (section, name, value)
   793                 % (section, name, value)
   791             )
   794             )
   792 
   795 
   793     def configlist(self, section, name, default=_unset, untrusted=False):
   796     def configlist(self, section, name, default=_unset, untrusted=False):
   794         """parse a configuration element as a list of comma/space separated
   797         """parse a configuration element as a list of comma/space separated
   802         >>> u.configlist(s, b'list2')
   805         >>> u.configlist(s, b'list2')
   803         ['this', 'is', 'a small', 'test']
   806         ['this', 'is', 'a small', 'test']
   804         """
   807         """
   805         # default is not always a list
   808         # default is not always a list
   806         v = self.configwith(
   809         v = self.configwith(
   807             config.parselist, section, name, default, 'list', untrusted
   810             config.parselist, section, name, default, b'list', untrusted
   808         )
   811         )
   809         if isinstance(v, bytes):
   812         if isinstance(v, bytes):
   810             return config.parselist(v)
   813             return config.parselist(v)
   811         elif v is None:
   814         elif v is None:
   812             return []
   815             return []
   820         >>> u.configdate(s, b'date')
   823         >>> u.configdate(s, b'date')
   821         (0, 0)
   824         (0, 0)
   822         """
   825         """
   823         if self.config(section, name, default, untrusted):
   826         if self.config(section, name, default, untrusted):
   824             return self.configwith(
   827             return self.configwith(
   825                 dateutil.parsedate, section, name, default, 'date', untrusted
   828                 dateutil.parsedate, section, name, default, b'date', untrusted
   826             )
   829             )
   827         if default is _unset:
   830         if default is _unset:
   828             return None
   831             return None
   829         return default
   832         return default
   830 
   833 
   847         return section in self._data(untrusted)
   850         return section in self._data(untrusted)
   848 
   851 
   849     def configitems(self, section, untrusted=False, ignoresub=False):
   852     def configitems(self, section, untrusted=False, ignoresub=False):
   850         items = self._data(untrusted).items(section)
   853         items = self._data(untrusted).items(section)
   851         if ignoresub:
   854         if ignoresub:
   852             items = [i for i in items if ':' not in i[0]]
   855             items = [i for i in items if b':' not in i[0]]
   853         if self.debugflag and not untrusted and self._reportuntrusted:
   856         if self.debugflag and not untrusted and self._reportuntrusted:
   854             for k, v in self._ucfg.items(section):
   857             for k, v in self._ucfg.items(section):
   855                 if self._tcfg.get(section, k) != v:
   858                 if self._tcfg.get(section, k) != v:
   856                     self.debug(
   859                     self.debug(
   857                         "ignoring untrusted configuration option "
   860                         b"ignoring untrusted configuration option "
   858                         "%s.%s = %s\n" % (section, k, v)
   861                         b"%s.%s = %s\n" % (section, k, v)
   859                     )
   862                     )
   860         return items
   863         return items
   861 
   864 
   862     def walkconfig(self, untrusted=False):
   865     def walkconfig(self, untrusted=False):
   863         cfg = self._data(untrusted)
   866         cfg = self._data(untrusted)
   880         - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
   883         - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
   881         - False if feature is disabled by default and not included in HGPLAIN
   884         - False if feature is disabled by default and not included in HGPLAIN
   882         - True otherwise
   885         - True otherwise
   883         '''
   886         '''
   884         if (
   887         if (
   885             'HGPLAIN' not in encoding.environ
   888             b'HGPLAIN' not in encoding.environ
   886             and 'HGPLAINEXCEPT' not in encoding.environ
   889             and b'HGPLAINEXCEPT' not in encoding.environ
   887         ):
   890         ):
   888             return False
   891             return False
   889         exceptions = (
   892         exceptions = (
   890             encoding.environ.get('HGPLAINEXCEPT', '').strip().split(',')
   893             encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
   891         )
   894         )
   892         # TODO: add support for HGPLAIN=+feature,-feature syntax
   895         # TODO: add support for HGPLAIN=+feature,-feature syntax
   893         if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
   896         if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
   894             exceptions.append('strictflags')
   897             b','
       
   898         ):
       
   899             exceptions.append(b'strictflags')
   895         if feature and exceptions:
   900         if feature and exceptions:
   896             return feature not in exceptions
   901             return feature not in exceptions
   897         return True
   902         return True
   898 
   903 
   899     def username(self, acceptempty=False):
   904     def username(self, acceptempty=False):
   904         If not found and acceptempty is True, returns None.
   909         If not found and acceptempty is True, returns None.
   905         If not found and ui.askusername is True, ask the user, else use
   910         If not found and ui.askusername is True, ask the user, else use
   906         ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
   911         ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
   907         If no username could be found, raise an Abort error.
   912         If no username could be found, raise an Abort error.
   908         """
   913         """
   909         user = encoding.environ.get("HGUSER")
   914         user = encoding.environ.get(b"HGUSER")
   910         if user is None:
   915         if user is None:
   911             user = self.config("ui", "username")
   916             user = self.config(b"ui", b"username")
   912             if user is not None:
   917             if user is not None:
   913                 user = os.path.expandvars(user)
   918                 user = os.path.expandvars(user)
   914         if user is None:
   919         if user is None:
   915             user = encoding.environ.get("EMAIL")
   920             user = encoding.environ.get(b"EMAIL")
   916         if user is None and acceptempty:
   921         if user is None and acceptempty:
   917             return user
   922             return user
   918         if user is None and self.configbool("ui", "askusername"):
   923         if user is None and self.configbool(b"ui", b"askusername"):
   919             user = self.prompt(_("enter a commit username:"), default=None)
   924             user = self.prompt(_(b"enter a commit username:"), default=None)
   920         if user is None and not self.interactive():
   925         if user is None and not self.interactive():
   921             try:
   926             try:
   922                 user = '%s@%s' % (
   927                 user = b'%s@%s' % (
   923                     procutil.getuser(),
   928                     procutil.getuser(),
   924                     encoding.strtolocal(socket.getfqdn()),
   929                     encoding.strtolocal(socket.getfqdn()),
   925                 )
   930                 )
   926                 self.warn(_("no username found, using '%s' instead\n") % user)
   931                 self.warn(_(b"no username found, using '%s' instead\n") % user)
   927             except KeyError:
   932             except KeyError:
   928                 pass
   933                 pass
   929         if not user:
   934         if not user:
   930             raise error.Abort(
   935             raise error.Abort(
   931                 _('no username supplied'),
   936                 _(b'no username supplied'),
   932                 hint=_("use 'hg config --edit' " 'to set your username'),
   937                 hint=_(b"use 'hg config --edit' " b'to set your username'),
   933             )
   938             )
   934         if "\n" in user:
   939         if b"\n" in user:
   935             raise error.Abort(
   940             raise error.Abort(
   936                 _("username %r contains a newline\n") % pycompat.bytestr(user)
   941                 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
   937             )
   942             )
   938         return user
   943         return user
   939 
   944 
   940     def shortuser(self, user):
   945     def shortuser(self, user):
   941         """Return a short representation of a user name or email address."""
   946         """Return a short representation of a user name or email address."""
  1028         if self._bufferstates:
  1033         if self._bufferstates:
  1029             self._bufferapplylabels = self._bufferstates[-1][2]
  1034             self._bufferapplylabels = self._bufferstates[-1][2]
  1030         else:
  1035         else:
  1031             self._bufferapplylabels = None
  1036             self._bufferapplylabels = None
  1032 
  1037 
  1033         return "".join(self._buffers.pop())
  1038         return b"".join(self._buffers.pop())
  1034 
  1039 
  1035     def _isbuffered(self, dest):
  1040     def _isbuffered(self, dest):
  1036         if dest is self._fout:
  1041         if dest is self._fout:
  1037             return bool(self._buffers)
  1042             return bool(self._buffers)
  1038         if dest is self._ferr:
  1043         if dest is self._ferr:
  1046         return self._colormode is None
  1051         return self._colormode is None
  1047 
  1052 
  1048     def canbatchlabeledwrites(self):
  1053     def canbatchlabeledwrites(self):
  1049         '''check if write calls with labels are batchable'''
  1054         '''check if write calls with labels are batchable'''
  1050         # Windows color printing is special, see ``write``.
  1055         # Windows color printing is special, see ``write``.
  1051         return self._colormode != 'win32'
  1056         return self._colormode != b'win32'
  1052 
  1057 
  1053     def write(self, *args, **opts):
  1058     def write(self, *args, **opts):
  1054         '''write args to output
  1059         '''write args to output
  1055 
  1060 
  1056         By default, this method simply writes to the buffer or stdout.
  1061         By default, this method simply writes to the buffer or stdout.
  1068         '''
  1073         '''
  1069         dest = self._fout
  1074         dest = self._fout
  1070 
  1075 
  1071         # inlined _write() for speed
  1076         # inlined _write() for speed
  1072         if self._buffers:
  1077         if self._buffers:
  1073             label = opts.get(r'label', '')
  1078             label = opts.get(r'label', b'')
  1074             if label and self._bufferapplylabels:
  1079             if label and self._bufferapplylabels:
  1075                 self._buffers[-1].extend(self.label(a, label) for a in args)
  1080                 self._buffers[-1].extend(self.label(a, label) for a in args)
  1076             else:
  1081             else:
  1077                 self._buffers[-1].extend(args)
  1082                 self._buffers[-1].extend(args)
  1078             return
  1083             return
  1082         msg = b''.join(args)
  1087         msg = b''.join(args)
  1083 
  1088 
  1084         # opencode timeblockedsection because this is a critical path
  1089         # opencode timeblockedsection because this is a critical path
  1085         starttime = util.timer()
  1090         starttime = util.timer()
  1086         try:
  1091         try:
  1087             if self._colormode == 'win32':
  1092             if self._colormode == b'win32':
  1088                 # windows color printing is its own can of crab, defer to
  1093                 # windows color printing is its own can of crab, defer to
  1089                 # the color module and that is it.
  1094                 # the color module and that is it.
  1090                 color.win32print(self, dest.write, msg, **opts)
  1095                 color.win32print(self, dest.write, msg, **opts)
  1091             else:
  1096             else:
  1092                 if self._colormode is not None:
  1097                 if self._colormode is not None:
  1093                     label = opts.get(r'label', '')
  1098                     label = opts.get(r'label', b'')
  1094                     msg = self.label(msg, label)
  1099                     msg = self.label(msg, label)
  1095                 dest.write(msg)
  1100                 dest.write(msg)
  1096         except IOError as err:
  1101         except IOError as err:
  1097             raise error.StdioError(err)
  1102             raise error.StdioError(err)
  1098         finally:
  1103         finally:
  1099             self._blockedtimes['stdio_blocked'] += (
  1104             self._blockedtimes[b'stdio_blocked'] += (
  1100                 util.timer() - starttime
  1105                 util.timer() - starttime
  1101             ) * 1000
  1106             ) * 1000
  1102 
  1107 
  1103     def write_err(self, *args, **opts):
  1108     def write_err(self, *args, **opts):
  1104         self._write(self._ferr, *args, **opts)
  1109         self._write(self._ferr, *args, **opts)
  1105 
  1110 
  1106     def _write(self, dest, *args, **opts):
  1111     def _write(self, dest, *args, **opts):
  1107         # update write() as well if you touch this code
  1112         # update write() as well if you touch this code
  1108         if self._isbuffered(dest):
  1113         if self._isbuffered(dest):
  1109             label = opts.get(r'label', '')
  1114             label = opts.get(r'label', b'')
  1110             if label and self._bufferapplylabels:
  1115             if label and self._bufferapplylabels:
  1111                 self._buffers[-1].extend(self.label(a, label) for a in args)
  1116                 self._buffers[-1].extend(self.label(a, label) for a in args)
  1112             else:
  1117             else:
  1113                 self._buffers[-1].extend(args)
  1118                 self._buffers[-1].extend(args)
  1114         else:
  1119         else:
  1126                 self._fout.flush()
  1131                 self._fout.flush()
  1127             if getattr(dest, 'structured', False):
  1132             if getattr(dest, 'structured', False):
  1128                 # channel for machine-readable output with metadata, where
  1133                 # channel for machine-readable output with metadata, where
  1129                 # no extra colorization is necessary.
  1134                 # no extra colorization is necessary.
  1130                 dest.write(msg, **opts)
  1135                 dest.write(msg, **opts)
  1131             elif self._colormode == 'win32':
  1136             elif self._colormode == b'win32':
  1132                 # windows color printing is its own can of crab, defer to
  1137                 # windows color printing is its own can of crab, defer to
  1133                 # the color module and that is it.
  1138                 # the color module and that is it.
  1134                 color.win32print(self, dest.write, msg, **opts)
  1139                 color.win32print(self, dest.write, msg, **opts)
  1135             else:
  1140             else:
  1136                 if self._colormode is not None:
  1141                 if self._colormode is not None:
  1137                     label = opts.get(r'label', '')
  1142                     label = opts.get(r'label', b'')
  1138                     msg = self.label(msg, label)
  1143                     msg = self.label(msg, label)
  1139                 dest.write(msg)
  1144                 dest.write(msg)
  1140             # stderr may be buffered under win32 when redirected to files,
  1145             # stderr may be buffered under win32 when redirected to files,
  1141             # including stdout.
  1146             # including stdout.
  1142             if dest is self._ferr and not getattr(self._ferr, 'closed', False):
  1147             if dest is self._ferr and not getattr(self._ferr, 'closed', False):
  1149             ):
  1154             ):
  1150                 # no way to report the error, so ignore it
  1155                 # no way to report the error, so ignore it
  1151                 return
  1156                 return
  1152             raise error.StdioError(err)
  1157             raise error.StdioError(err)
  1153         finally:
  1158         finally:
  1154             self._blockedtimes['stdio_blocked'] += (
  1159             self._blockedtimes[b'stdio_blocked'] += (
  1155                 util.timer() - starttime
  1160                 util.timer() - starttime
  1156             ) * 1000
  1161             ) * 1000
  1157 
  1162 
  1158     def _writemsg(self, dest, *args, **opts):
  1163     def _writemsg(self, dest, *args, **opts):
  1159         _writemsgwith(self._write, dest, *args, **opts)
  1164         _writemsgwith(self._write, dest, *args, **opts)
  1175                     self._ferr.flush()
  1180                     self._ferr.flush()
  1176                 except IOError as err:
  1181                 except IOError as err:
  1177                     if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
  1182                     if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
  1178                         raise error.StdioError(err)
  1183                         raise error.StdioError(err)
  1179         finally:
  1184         finally:
  1180             self._blockedtimes['stdio_blocked'] += (
  1185             self._blockedtimes[b'stdio_blocked'] += (
  1181                 util.timer() - starttime
  1186                 util.timer() - starttime
  1182             ) * 1000
  1187             ) * 1000
  1183 
  1188 
  1184     def _isatty(self, fh):
  1189     def _isatty(self, fh):
  1185         if self.configbool('ui', 'nontty'):
  1190         if self.configbool(b'ui', b'nontty'):
  1186             return False
  1191             return False
  1187         return procutil.isatty(fh)
  1192         return procutil.isatty(fh)
  1188 
  1193 
  1189     def protectfinout(self):
  1194     def protectfinout(self):
  1190         """Duplicate ui streams and redirect original if they are stdio
  1195         """Duplicate ui streams and redirect original if they are stdio
  1237         """
  1242         """
  1238         if self._disablepager or self.pageractive:
  1243         if self._disablepager or self.pageractive:
  1239             # how pager should do is already determined
  1244             # how pager should do is already determined
  1240             return
  1245             return
  1241 
  1246 
  1242         if not command.startswith('internal-always-') and (
  1247         if not command.startswith(b'internal-always-') and (
  1243             # explicit --pager=on (= 'internal-always-' prefix) should
  1248             # explicit --pager=on (= 'internal-always-' prefix) should
  1244             # take precedence over disabling factors below
  1249             # take precedence over disabling factors below
  1245             command in self.configlist('pager', 'ignore')
  1250             command in self.configlist(b'pager', b'ignore')
  1246             or not self.configbool('ui', 'paginate')
  1251             or not self.configbool(b'ui', b'paginate')
  1247             or not self.configbool('pager', 'attend-' + command, True)
  1252             or not self.configbool(b'pager', b'attend-' + command, True)
  1248             or encoding.environ.get('TERM') == 'dumb'
  1253             or encoding.environ.get(b'TERM') == b'dumb'
  1249             # TODO: if we want to allow HGPLAINEXCEPT=pager,
  1254             # TODO: if we want to allow HGPLAINEXCEPT=pager,
  1250             # formatted() will need some adjustment.
  1255             # formatted() will need some adjustment.
  1251             or not self.formatted()
  1256             or not self.formatted()
  1252             or self.plain()
  1257             or self.plain()
  1253             or self._buffers
  1258             or self._buffers
  1254             # TODO: expose debugger-enabled on the UI object
  1259             # TODO: expose debugger-enabled on the UI object
  1255             or '--debugger' in pycompat.sysargv
  1260             or b'--debugger' in pycompat.sysargv
  1256         ):
  1261         ):
  1257             # We only want to paginate if the ui appears to be
  1262             # We only want to paginate if the ui appears to be
  1258             # interactive, the user didn't say HGPLAIN or
  1263             # interactive, the user didn't say HGPLAIN or
  1259             # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
  1264             # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
  1260             return
  1265             return
  1261 
  1266 
  1262         pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
  1267         pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
  1263         if not pagercmd:
  1268         if not pagercmd:
  1264             return
  1269             return
  1265 
  1270 
  1266         pagerenv = {}
  1271         pagerenv = {}
  1267         for name, value in rcutil.defaultpagerenv().items():
  1272         for name, value in rcutil.defaultpagerenv().items():
  1268             if name not in encoding.environ:
  1273             if name not in encoding.environ:
  1269                 pagerenv[name] = value
  1274                 pagerenv[name] = value
  1270 
  1275 
  1271         self.debug(
  1276         self.debug(
  1272             'starting pager for command %s\n' % stringutil.pprint(command)
  1277             b'starting pager for command %s\n' % stringutil.pprint(command)
  1273         )
  1278         )
  1274         self.flush()
  1279         self.flush()
  1275 
  1280 
  1276         wasformatted = self.formatted()
  1281         wasformatted = self.formatted()
  1277         if util.safehasattr(signal, "SIGPIPE"):
  1282         if util.safehasattr(signal, b"SIGPIPE"):
  1278             signal.signal(signal.SIGPIPE, _catchterm)
  1283             signal.signal(signal.SIGPIPE, _catchterm)
  1279         if self._runpager(pagercmd, pagerenv):
  1284         if self._runpager(pagercmd, pagerenv):
  1280             self.pageractive = True
  1285             self.pageractive = True
  1281             # Preserve the formatted-ness of the UI. This is important
  1286             # Preserve the formatted-ness of the UI. This is important
  1282             # because we mess with stdout, which might confuse
  1287             # because we mess with stdout, which might confuse
  1283             # auto-detection of things being formatted.
  1288             # auto-detection of things being formatted.
  1284             self.setconfig('ui', 'formatted', wasformatted, 'pager')
  1289             self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
  1285             self.setconfig('ui', 'interactive', False, 'pager')
  1290             self.setconfig(b'ui', b'interactive', False, b'pager')
  1286 
  1291 
  1287             # If pagermode differs from color.mode, reconfigure color now that
  1292             # If pagermode differs from color.mode, reconfigure color now that
  1288             # pageractive is set.
  1293             # pageractive is set.
  1289             cm = self._colormode
  1294             cm = self._colormode
  1290             if cm != self.config('color', 'pagermode', cm):
  1295             if cm != self.config(b'color', b'pagermode', cm):
  1291                 color.setup(self)
  1296                 color.setup(self)
  1292         else:
  1297         else:
  1293             # If the pager can't be spawned in dispatch when --pager=on is
  1298             # If the pager can't be spawned in dispatch when --pager=on is
  1294             # given, don't try again when the command runs, to avoid a duplicate
  1299             # given, don't try again when the command runs, to avoid a duplicate
  1295             # warning about a missing pager command.
  1300             # warning about a missing pager command.
  1299         """Actually start the pager and set up file descriptors.
  1304         """Actually start the pager and set up file descriptors.
  1300 
  1305 
  1301         This is separate in part so that extensions (like chg) can
  1306         This is separate in part so that extensions (like chg) can
  1302         override how a pager is invoked.
  1307         override how a pager is invoked.
  1303         """
  1308         """
  1304         if command == 'cat':
  1309         if command == b'cat':
  1305             # Save ourselves some work.
  1310             # Save ourselves some work.
  1306             return False
  1311             return False
  1307         # If the command doesn't contain any of these characters, we
  1312         # If the command doesn't contain any of these characters, we
  1308         # assume it's a binary and exec it directly. This means for
  1313         # assume it's a binary and exec it directly. This means for
  1309         # simple pager command configurations, we can degrade
  1314         # simple pager command configurations, we can degrade
  1310         # gracefully and tell the user about their broken pager.
  1315         # gracefully and tell the user about their broken pager.
  1311         shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
  1316         shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
  1312 
  1317 
  1313         if pycompat.iswindows and not shell:
  1318         if pycompat.iswindows and not shell:
  1314             # Window's built-in `more` cannot be invoked with shell=False, but
  1319             # Window's built-in `more` cannot be invoked with shell=False, but
  1315             # its `more.com` can.  Hide this implementation detail from the
  1320             # its `more.com` can.  Hide this implementation detail from the
  1316             # user so we can also get sane bad PAGER behavior.  MSYS has
  1321             # user so we can also get sane bad PAGER behavior.  MSYS has
  1317             # `more.exe`, so do a cmd.exe style resolution of the executable to
  1322             # `more.exe`, so do a cmd.exe style resolution of the executable to
  1318             # determine which one to use.
  1323             # determine which one to use.
  1319             fullcmd = procutil.findexe(command)
  1324             fullcmd = procutil.findexe(command)
  1320             if not fullcmd:
  1325             if not fullcmd:
  1321                 self.warn(
  1326                 self.warn(
  1322                     _("missing pager command '%s', skipping pager\n") % command
  1327                     _(b"missing pager command '%s', skipping pager\n") % command
  1323                 )
  1328                 )
  1324                 return False
  1329                 return False
  1325 
  1330 
  1326             command = fullcmd
  1331             command = fullcmd
  1327 
  1332 
  1337                 env=procutil.tonativeenv(procutil.shellenviron(env)),
  1342                 env=procutil.tonativeenv(procutil.shellenviron(env)),
  1338             )
  1343             )
  1339         except OSError as e:
  1344         except OSError as e:
  1340             if e.errno == errno.ENOENT and not shell:
  1345             if e.errno == errno.ENOENT and not shell:
  1341                 self.warn(
  1346                 self.warn(
  1342                     _("missing pager command '%s', skipping pager\n") % command
  1347                     _(b"missing pager command '%s', skipping pager\n") % command
  1343                 )
  1348                 )
  1344                 return False
  1349                 return False
  1345             raise
  1350             raise
  1346 
  1351 
  1347         # back up original file descriptors
  1352         # back up original file descriptors
  1352         if self._isatty(procutil.stderr):
  1357         if self._isatty(procutil.stderr):
  1353             os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
  1358             os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
  1354 
  1359 
  1355         @self.atexit
  1360         @self.atexit
  1356         def killpager():
  1361         def killpager():
  1357             if util.safehasattr(signal, "SIGINT"):
  1362             if util.safehasattr(signal, b"SIGINT"):
  1358                 signal.signal(signal.SIGINT, signal.SIG_IGN)
  1363                 signal.signal(signal.SIGINT, signal.SIG_IGN)
  1359             # restore original fds, closing pager.stdin copies in the process
  1364             # restore original fds, closing pager.stdin copies in the process
  1360             os.dup2(stdoutfd, procutil.stdout.fileno())
  1365             os.dup2(stdoutfd, procutil.stdout.fileno())
  1361             os.dup2(stderrfd, procutil.stderr.fileno())
  1366             os.dup2(stderrfd, procutil.stderr.fileno())
  1362             pager.stdin.close()
  1367             pager.stdin.close()
  1395         ui.interface.histedit = text
  1400         ui.interface.histedit = text
  1396 
  1401 
  1397         Then histedit will use the text interface and chunkselector will use
  1402         Then histedit will use the text interface and chunkselector will use
  1398         the default curses interface (crecord at the moment).
  1403         the default curses interface (crecord at the moment).
  1399         """
  1404         """
  1400         alldefaults = frozenset(["text", "curses"])
  1405         alldefaults = frozenset([b"text", b"curses"])
  1401 
  1406 
  1402         featureinterfaces = {
  1407         featureinterfaces = {
  1403             "chunkselector": ["text", "curses",],
  1408             b"chunkselector": [b"text", b"curses",],
  1404             "histedit": ["text", "curses",],
  1409             b"histedit": [b"text", b"curses",],
  1405         }
  1410         }
  1406 
  1411 
  1407         # Feature-specific interface
  1412         # Feature-specific interface
  1408         if feature not in featureinterfaces.keys():
  1413         if feature not in featureinterfaces.keys():
  1409             # Programming error, not user error
  1414             # Programming error, not user error
  1410             raise ValueError("Unknown feature requested %s" % feature)
  1415             raise ValueError(b"Unknown feature requested %s" % feature)
  1411 
  1416 
  1412         availableinterfaces = frozenset(featureinterfaces[feature])
  1417         availableinterfaces = frozenset(featureinterfaces[feature])
  1413         if alldefaults > availableinterfaces:
  1418         if alldefaults > availableinterfaces:
  1414             # Programming error, not user error. We need a use case to
  1419             # Programming error, not user error. We need a use case to
  1415             # define the right thing to do here.
  1420             # define the right thing to do here.
  1416             raise ValueError(
  1421             raise ValueError(
  1417                 "Feature %s does not handle all default interfaces" % feature
  1422                 b"Feature %s does not handle all default interfaces" % feature
  1418             )
  1423             )
  1419 
  1424 
  1420         if self.plain() or encoding.environ.get('TERM') == 'dumb':
  1425         if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
  1421             return "text"
  1426             return b"text"
  1422 
  1427 
  1423         # Default interface for all the features
  1428         # Default interface for all the features
  1424         defaultinterface = "text"
  1429         defaultinterface = b"text"
  1425         i = self.config("ui", "interface")
  1430         i = self.config(b"ui", b"interface")
  1426         if i in alldefaults:
  1431         if i in alldefaults:
  1427             defaultinterface = i
  1432             defaultinterface = i
  1428 
  1433 
  1429         choseninterface = defaultinterface
  1434         choseninterface = defaultinterface
  1430         f = self.config("ui", "interface.%s" % feature)
  1435         f = self.config(b"ui", b"interface.%s" % feature)
  1431         if f in availableinterfaces:
  1436         if f in availableinterfaces:
  1432             choseninterface = f
  1437             choseninterface = f
  1433 
  1438 
  1434         if i is not None and defaultinterface != i:
  1439         if i is not None and defaultinterface != i:
  1435             if f is not None:
  1440             if f is not None:
  1436                 self.warn(_("invalid value for ui.interface: %s\n") % (i,))
  1441                 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
  1437             else:
  1442             else:
  1438                 self.warn(
  1443                 self.warn(
  1439                     _("invalid value for ui.interface: %s (using %s)\n")
  1444                     _(b"invalid value for ui.interface: %s (using %s)\n")
  1440                     % (i, choseninterface)
  1445                     % (i, choseninterface)
  1441                 )
  1446                 )
  1442         if f is not None and choseninterface != f:
  1447         if f is not None and choseninterface != f:
  1443             self.warn(
  1448             self.warn(
  1444                 _("invalid value for ui.interface.%s: %s (using %s)\n")
  1449                 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
  1445                 % (feature, f, choseninterface)
  1450                 % (feature, f, choseninterface)
  1446             )
  1451             )
  1447 
  1452 
  1448         return choseninterface
  1453         return choseninterface
  1449 
  1454 
  1459         configuration variable or - if it is unset - when `sys.stdin' points
  1464         configuration variable or - if it is unset - when `sys.stdin' points
  1460         to a terminal device.
  1465         to a terminal device.
  1461 
  1466 
  1462         This function refers to input only; for output, see `ui.formatted()'.
  1467         This function refers to input only; for output, see `ui.formatted()'.
  1463         '''
  1468         '''
  1464         i = self.configbool("ui", "interactive")
  1469         i = self.configbool(b"ui", b"interactive")
  1465         if i is None:
  1470         if i is None:
  1466             # some environments replace stdin without implementing isatty
  1471             # some environments replace stdin without implementing isatty
  1467             # usually those are non-interactive
  1472             # usually those are non-interactive
  1468             return self._isatty(self._fin)
  1473             return self._isatty(self._fin)
  1469 
  1474 
  1470         return i
  1475         return i
  1471 
  1476 
  1472     def termwidth(self):
  1477     def termwidth(self):
  1473         '''how wide is the terminal in columns?
  1478         '''how wide is the terminal in columns?
  1474         '''
  1479         '''
  1475         if 'COLUMNS' in encoding.environ:
  1480         if b'COLUMNS' in encoding.environ:
  1476             try:
  1481             try:
  1477                 return int(encoding.environ['COLUMNS'])
  1482                 return int(encoding.environ[b'COLUMNS'])
  1478             except ValueError:
  1483             except ValueError:
  1479                 pass
  1484                 pass
  1480         return scmutil.termsize(self)[0]
  1485         return scmutil.termsize(self)[0]
  1481 
  1486 
  1482     def formatted(self):
  1487     def formatted(self):
  1497         This function always returns false when in plain mode, see `ui.plain()'.
  1502         This function always returns false when in plain mode, see `ui.plain()'.
  1498         '''
  1503         '''
  1499         if self.plain():
  1504         if self.plain():
  1500             return False
  1505             return False
  1501 
  1506 
  1502         i = self.configbool("ui", "formatted")
  1507         i = self.configbool(b"ui", b"formatted")
  1503         if i is None:
  1508         if i is None:
  1504             # some environments replace stdout without implementing isatty
  1509             # some environments replace stdout without implementing isatty
  1505             # usually those are non-interactive
  1510             # usually those are non-interactive
  1506             return self._isatty(self._fout)
  1511             return self._isatty(self._fout)
  1507 
  1512 
  1508         return i
  1513         return i
  1509 
  1514 
  1510     def _readline(self, prompt=' ', promptopts=None):
  1515     def _readline(self, prompt=b' ', promptopts=None):
  1511         # Replacing stdin/stdout temporarily is a hard problem on Python 3
  1516         # Replacing stdin/stdout temporarily is a hard problem on Python 3
  1512         # because they have to be text streams with *no buffering*. Instead,
  1517         # because they have to be text streams with *no buffering*. Instead,
  1513         # we use rawinput() only if call_readline() will be invoked by
  1518         # we use rawinput() only if call_readline() will be invoked by
  1514         # PyOS_Readline(), so no I/O will be made at Python layer.
  1519         # PyOS_Readline(), so no I/O will be made at Python layer.
  1515         usereadline = (
  1520         usereadline = (
  1528                 readline.read_history_file
  1533                 readline.read_history_file
  1529                 # windows sometimes raises something other than ImportError
  1534                 # windows sometimes raises something other than ImportError
  1530             except Exception:
  1535             except Exception:
  1531                 usereadline = False
  1536                 usereadline = False
  1532 
  1537 
  1533         if self._colormode == 'win32' or not usereadline:
  1538         if self._colormode == b'win32' or not usereadline:
  1534             if not promptopts:
  1539             if not promptopts:
  1535                 promptopts = {}
  1540                 promptopts = {}
  1536             self._writemsgnobuf(
  1541             self._writemsgnobuf(
  1537                 self._fmsgout, prompt, type='prompt', **promptopts
  1542                 self._fmsgout, prompt, type=b'prompt', **promptopts
  1538             )
  1543             )
  1539             self.flush()
  1544             self.flush()
  1540             prompt = ' '
  1545             prompt = b' '
  1541         else:
  1546         else:
  1542             prompt = self.label(prompt, 'ui.prompt') + ' '
  1547             prompt = self.label(prompt, b'ui.prompt') + b' '
  1543 
  1548 
  1544         # prompt ' ' must exist; otherwise readline may delete entire line
  1549         # prompt ' ' must exist; otherwise readline may delete entire line
  1545         # - http://bugs.python.org/issue12833
  1550         # - http://bugs.python.org/issue12833
  1546         with self.timeblockedsection('stdio'):
  1551         with self.timeblockedsection(b'stdio'):
  1547             if usereadline:
  1552             if usereadline:
  1548                 line = encoding.strtolocal(pycompat.rawinput(prompt))
  1553                 line = encoding.strtolocal(pycompat.rawinput(prompt))
  1549                 # When stdin is in binary mode on Windows, it can cause
  1554                 # When stdin is in binary mode on Windows, it can cause
  1550                 # raw_input() to emit an extra trailing carriage return
  1555                 # raw_input() to emit an extra trailing carriage return
  1551                 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
  1556                 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
  1558                     raise EOFError
  1563                     raise EOFError
  1559                 line = line.rstrip(pycompat.oslinesep)
  1564                 line = line.rstrip(pycompat.oslinesep)
  1560 
  1565 
  1561         return line
  1566         return line
  1562 
  1567 
  1563     def prompt(self, msg, default="y"):
  1568     def prompt(self, msg, default=b"y"):
  1564         """Prompt user with msg, read response.
  1569         """Prompt user with msg, read response.
  1565         If ui is not interactive, the default is returned.
  1570         If ui is not interactive, the default is returned.
  1566         """
  1571         """
  1567         return self._prompt(msg, default=default)
  1572         return self._prompt(msg, default=default)
  1568 
  1573 
  1569     def _prompt(self, msg, **opts):
  1574     def _prompt(self, msg, **opts):
  1570         default = opts[r'default']
  1575         default = opts[r'default']
  1571         if not self.interactive():
  1576         if not self.interactive():
  1572             self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
  1577             self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
  1573             self._writemsg(
  1578             self._writemsg(
  1574                 self._fmsgout, default or '', "\n", type='promptecho'
  1579                 self._fmsgout, default or b'', b"\n", type=b'promptecho'
  1575             )
  1580             )
  1576             return default
  1581             return default
  1577         try:
  1582         try:
  1578             r = self._readline(prompt=msg, promptopts=opts)
  1583             r = self._readline(prompt=msg, promptopts=opts)
  1579             if not r:
  1584             if not r:
  1580                 r = default
  1585                 r = default
  1581             if self.configbool('ui', 'promptecho'):
  1586             if self.configbool(b'ui', b'promptecho'):
  1582                 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
  1587                 self._writemsg(self._fmsgout, r, b"\n", type=b'promptecho')
  1583             return r
  1588             return r
  1584         except EOFError:
  1589         except EOFError:
  1585             raise error.ResponseExpected()
  1590             raise error.ResponseExpected()
  1586 
  1591 
  1587     @staticmethod
  1592     @staticmethod
  1604         # prompt to start parsing. Sadly, we also can't rely on
  1609         # prompt to start parsing. Sadly, we also can't rely on
  1605         # choices containing spaces, ASCII, or basically anything
  1610         # choices containing spaces, ASCII, or basically anything
  1606         # except an ampersand followed by a character.
  1611         # except an ampersand followed by a character.
  1607         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
  1612         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
  1608         msg = m.group(1)
  1613         msg = m.group(1)
  1609         choices = [p.strip(' ') for p in m.group(2).split('$$')]
  1614         choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
  1610 
  1615 
  1611         def choicetuple(s):
  1616         def choicetuple(s):
  1612             ampidx = s.index('&')
  1617             ampidx = s.index(b'&')
  1613             return s[ampidx + 1 : ampidx + 2].lower(), s.replace('&', '', 1)
  1618             return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
  1614 
  1619 
  1615         return (msg, [choicetuple(s) for s in choices])
  1620         return (msg, [choicetuple(s) for s in choices])
  1616 
  1621 
  1617     def promptchoice(self, prompt, default=0):
  1622     def promptchoice(self, prompt, default=0):
  1618         """Prompt user with a message, read response, and ensure it matches
  1623         """Prompt user with a message, read response, and ensure it matches
  1630         while True:
  1635         while True:
  1631             r = self._prompt(msg, default=resps[default], choices=choices)
  1636             r = self._prompt(msg, default=resps[default], choices=choices)
  1632             if r.lower() in resps:
  1637             if r.lower() in resps:
  1633                 return resps.index(r.lower())
  1638                 return resps.index(r.lower())
  1634             # TODO: shouldn't it be a warning?
  1639             # TODO: shouldn't it be a warning?
  1635             self._writemsg(self._fmsgout, _("unrecognized response\n"))
  1640             self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
  1636 
  1641 
  1637     def getpass(self, prompt=None, default=None):
  1642     def getpass(self, prompt=None, default=None):
  1638         if not self.interactive():
  1643         if not self.interactive():
  1639             return default
  1644             return default
  1640         try:
  1645         try:
  1641             self._writemsg(
  1646             self._writemsg(
  1642                 self._fmsgerr,
  1647                 self._fmsgerr,
  1643                 prompt or _('password: '),
  1648                 prompt or _(b'password: '),
  1644                 type='prompt',
  1649                 type=b'prompt',
  1645                 password=True,
  1650                 password=True,
  1646             )
  1651             )
  1647             # disable getpass() only if explicitly specified. it's still valid
  1652             # disable getpass() only if explicitly specified. it's still valid
  1648             # to interact with tty even if fin is not a tty.
  1653             # to interact with tty even if fin is not a tty.
  1649             with self.timeblockedsection('stdio'):
  1654             with self.timeblockedsection(b'stdio'):
  1650                 if self.configbool('ui', 'nontty'):
  1655                 if self.configbool(b'ui', b'nontty'):
  1651                     l = self._fin.readline()
  1656                     l = self._fin.readline()
  1652                     if not l:
  1657                     if not l:
  1653                         raise EOFError
  1658                         raise EOFError
  1654                     return l.rstrip('\n')
  1659                     return l.rstrip(b'\n')
  1655                 else:
  1660                 else:
  1656                     return getpass.getpass(r'')
  1661                     return getpass.getpass(r'')
  1657         except EOFError:
  1662         except EOFError:
  1658             raise error.ResponseExpected()
  1663             raise error.ResponseExpected()
  1659 
  1664 
  1661         '''write status message to output (if ui.quiet is False)
  1666         '''write status message to output (if ui.quiet is False)
  1662 
  1667 
  1663         This adds an output label of "ui.status".
  1668         This adds an output label of "ui.status".
  1664         '''
  1669         '''
  1665         if not self.quiet:
  1670         if not self.quiet:
  1666             self._writemsg(self._fmsgout, type='status', *msg, **opts)
  1671             self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
  1667 
  1672 
  1668     def warn(self, *msg, **opts):
  1673     def warn(self, *msg, **opts):
  1669         '''write warning message to output (stderr)
  1674         '''write warning message to output (stderr)
  1670 
  1675 
  1671         This adds an output label of "ui.warning".
  1676         This adds an output label of "ui.warning".
  1672         '''
  1677         '''
  1673         self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
  1678         self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
  1674 
  1679 
  1675     def error(self, *msg, **opts):
  1680     def error(self, *msg, **opts):
  1676         '''write error message to output (stderr)
  1681         '''write error message to output (stderr)
  1677 
  1682 
  1678         This adds an output label of "ui.error".
  1683         This adds an output label of "ui.error".
  1679         '''
  1684         '''
  1680         self._writemsg(self._fmsgerr, type='error', *msg, **opts)
  1685         self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
  1681 
  1686 
  1682     def note(self, *msg, **opts):
  1687     def note(self, *msg, **opts):
  1683         '''write note to output (if ui.verbose is True)
  1688         '''write note to output (if ui.verbose is True)
  1684 
  1689 
  1685         This adds an output label of "ui.note".
  1690         This adds an output label of "ui.note".
  1686         '''
  1691         '''
  1687         if self.verbose:
  1692         if self.verbose:
  1688             self._writemsg(self._fmsgout, type='note', *msg, **opts)
  1693             self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
  1689 
  1694 
  1690     def debug(self, *msg, **opts):
  1695     def debug(self, *msg, **opts):
  1691         '''write debug message to output (if ui.debugflag is True)
  1696         '''write debug message to output (if ui.debugflag is True)
  1692 
  1697 
  1693         This adds an output label of "ui.debug".
  1698         This adds an output label of "ui.debug".
  1694         '''
  1699         '''
  1695         if self.debugflag:
  1700         if self.debugflag:
  1696             self._writemsg(self._fmsgout, type='debug', *msg, **opts)
  1701             self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
  1697             self.log(b'debug', b'%s', b''.join(msg))
  1702             self.log(b'debug', b'%s', b''.join(msg))
  1698 
  1703 
  1699     def edit(
  1704     def edit(
  1700         self,
  1705         self,
  1701         text,
  1706         text,
  1706         repopath=None,
  1711         repopath=None,
  1707         action=None,
  1712         action=None,
  1708     ):
  1713     ):
  1709         if action is None:
  1714         if action is None:
  1710             self.develwarn(
  1715             self.develwarn(
  1711                 'action is None but will soon be a required '
  1716                 b'action is None but will soon be a required '
  1712                 'parameter to ui.edit()'
  1717                 b'parameter to ui.edit()'
  1713             )
  1718             )
  1714         extra_defaults = {
  1719         extra_defaults = {
  1715             'prefix': 'editor',
  1720             b'prefix': b'editor',
  1716             'suffix': '.txt',
  1721             b'suffix': b'.txt',
  1717         }
  1722         }
  1718         if extra is not None:
  1723         if extra is not None:
  1719             if extra.get('suffix') is not None:
  1724             if extra.get(b'suffix') is not None:
  1720                 self.develwarn(
  1725                 self.develwarn(
  1721                     'extra.suffix is not None but will soon be '
  1726                     b'extra.suffix is not None but will soon be '
  1722                     'ignored by ui.edit()'
  1727                     b'ignored by ui.edit()'
  1723                 )
  1728                 )
  1724             extra_defaults.update(extra)
  1729             extra_defaults.update(extra)
  1725         extra = extra_defaults
  1730         extra = extra_defaults
  1726 
  1731 
  1727         if action == 'diff':
  1732         if action == b'diff':
  1728             suffix = '.diff'
  1733             suffix = b'.diff'
  1729         elif action:
  1734         elif action:
  1730             suffix = '.%s.hg.txt' % action
  1735             suffix = b'.%s.hg.txt' % action
  1731         else:
  1736         else:
  1732             suffix = extra['suffix']
  1737             suffix = extra[b'suffix']
  1733 
  1738 
  1734         rdir = None
  1739         rdir = None
  1735         if self.configbool('experimental', 'editortmpinhg'):
  1740         if self.configbool(b'experimental', b'editortmpinhg'):
  1736             rdir = repopath
  1741             rdir = repopath
  1737         (fd, name) = pycompat.mkstemp(
  1742         (fd, name) = pycompat.mkstemp(
  1738             prefix='hg-' + extra['prefix'] + '-', suffix=suffix, dir=rdir
  1743             prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
  1739         )
  1744         )
  1740         try:
  1745         try:
  1741             f = os.fdopen(fd, r'wb')
  1746             f = os.fdopen(fd, r'wb')
  1742             f.write(util.tonativeeol(text))
  1747             f.write(util.tonativeeol(text))
  1743             f.close()
  1748             f.close()
  1744 
  1749 
  1745             environ = {'HGUSER': user}
  1750             environ = {b'HGUSER': user}
  1746             if 'transplant_source' in extra:
  1751             if b'transplant_source' in extra:
  1747                 environ.update({'HGREVISION': hex(extra['transplant_source'])})
  1752                 environ.update(
  1748             for label in ('intermediate-source', 'source', 'rebase_source'):
  1753                     {b'HGREVISION': hex(extra[b'transplant_source'])}
       
  1754                 )
       
  1755             for label in (b'intermediate-source', b'source', b'rebase_source'):
  1749                 if label in extra:
  1756                 if label in extra:
  1750                     environ.update({'HGREVISION': extra[label]})
  1757                     environ.update({b'HGREVISION': extra[label]})
  1751                     break
  1758                     break
  1752             if editform:
  1759             if editform:
  1753                 environ.update({'HGEDITFORM': editform})
  1760                 environ.update({b'HGEDITFORM': editform})
  1754             if pending:
  1761             if pending:
  1755                 environ.update({'HG_PENDING': pending})
  1762                 environ.update({b'HG_PENDING': pending})
  1756 
  1763 
  1757             editor = self.geteditor()
  1764             editor = self.geteditor()
  1758 
  1765 
  1759             self.system(
  1766             self.system(
  1760                 "%s \"%s\"" % (editor, name),
  1767                 b"%s \"%s\"" % (editor, name),
  1761                 environ=environ,
  1768                 environ=environ,
  1762                 onerr=error.Abort,
  1769                 onerr=error.Abort,
  1763                 errprefix=_("edit failed"),
  1770                 errprefix=_(b"edit failed"),
  1764                 blockedtag='editor',
  1771                 blockedtag=b'editor',
  1765             )
  1772             )
  1766 
  1773 
  1767             f = open(name, r'rb')
  1774             f = open(name, r'rb')
  1768             t = util.fromnativeeol(f.read())
  1775             t = util.fromnativeeol(f.read())
  1769             f.close()
  1776             f.close()
  1789         '''
  1796         '''
  1790         if blockedtag is None:
  1797         if blockedtag is None:
  1791             # Long cmds tend to be because of an absolute path on cmd. Keep
  1798             # Long cmds tend to be because of an absolute path on cmd. Keep
  1792             # the tail end instead
  1799             # the tail end instead
  1793             cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
  1800             cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
  1794             blockedtag = 'unknown_system_' + cmdsuffix
  1801             blockedtag = b'unknown_system_' + cmdsuffix
  1795         out = self._fout
  1802         out = self._fout
  1796         if any(s[1] for s in self._bufferstates):
  1803         if any(s[1] for s in self._bufferstates):
  1797             out = self
  1804             out = self
  1798         with self.timeblockedsection(blockedtag):
  1805         with self.timeblockedsection(blockedtag):
  1799             rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
  1806             rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
  1800         if rc and onerr:
  1807         if rc and onerr:
  1801             errmsg = '%s %s' % (
  1808             errmsg = b'%s %s' % (
  1802                 os.path.basename(cmd.split(None, 1)[0]),
  1809                 os.path.basename(cmd.split(None, 1)[0]),
  1803                 procutil.explainexit(rc),
  1810                 procutil.explainexit(rc),
  1804             )
  1811             )
  1805             if errprefix:
  1812             if errprefix:
  1806                 errmsg = '%s: %s' % (errprefix, errmsg)
  1813                 errmsg = b'%s: %s' % (errprefix, errmsg)
  1807             raise onerr(errmsg)
  1814             raise onerr(errmsg)
  1808         return rc
  1815         return rc
  1809 
  1816 
  1810     def _runsystem(self, cmd, environ, cwd, out):
  1817     def _runsystem(self, cmd, environ, cwd, out):
  1811         """actually execute the given shell command (can be overridden by
  1818         """actually execute the given shell command (can be overridden by
  1826                 exctb = traceback.format_tb(exc[2])
  1833                 exctb = traceback.format_tb(exc[2])
  1827                 exconly = traceback.format_exception_only(cause[0], cause[1])
  1834                 exconly = traceback.format_exception_only(cause[0], cause[1])
  1828 
  1835 
  1829                 # exclude frame where 'exc' was chained and rethrown from exctb
  1836                 # exclude frame where 'exc' was chained and rethrown from exctb
  1830                 self.write_err(
  1837                 self.write_err(
  1831                     'Traceback (most recent call last):\n',
  1838                     b'Traceback (most recent call last):\n',
  1832                     ''.join(exctb[:-1]),
  1839                     b''.join(exctb[:-1]),
  1833                     ''.join(causetb),
  1840                     b''.join(causetb),
  1834                     ''.join(exconly),
  1841                     b''.join(exconly),
  1835                 )
  1842                 )
  1836             else:
  1843             else:
  1837                 output = traceback.format_exception(exc[0], exc[1], exc[2])
  1844                 output = traceback.format_exception(exc[0], exc[1], exc[2])
  1838                 self.write_err(encoding.strtolocal(r''.join(output)))
  1845                 self.write_err(encoding.strtolocal(r''.join(output)))
  1839         return self.tracebackflag or force
  1846         return self.tracebackflag or force
  1840 
  1847 
  1841     def geteditor(self):
  1848     def geteditor(self):
  1842         '''return editor to use'''
  1849         '''return editor to use'''
  1843         if pycompat.sysplatform == 'plan9':
  1850         if pycompat.sysplatform == b'plan9':
  1844             # vi is the MIPS instruction simulator on Plan 9. We
  1851             # vi is the MIPS instruction simulator on Plan 9. We
  1845             # instead default to E to plumb commit messages to
  1852             # instead default to E to plumb commit messages to
  1846             # avoid confusion.
  1853             # avoid confusion.
  1847             editor = 'E'
  1854             editor = b'E'
  1848         else:
  1855         else:
  1849             editor = 'vi'
  1856             editor = b'vi'
  1850         return encoding.environ.get("HGEDITOR") or self.config(
  1857         return encoding.environ.get(b"HGEDITOR") or self.config(
  1851             "ui", "editor", editor
  1858             b"ui", b"editor", editor
  1852         )
  1859         )
  1853 
  1860 
  1854     @util.propertycache
  1861     @util.propertycache
  1855     def _progbar(self):
  1862     def _progbar(self):
  1856         """setup the progbar singleton to the ui object"""
  1863         """setup the progbar singleton to the ui object"""
  1857         if (
  1864         if (
  1858             self.quiet
  1865             self.quiet
  1859             or self.debugflag
  1866             or self.debugflag
  1860             or self.configbool('progress', 'disable')
  1867             or self.configbool(b'progress', b'disable')
  1861             or not progress.shouldprint(self)
  1868             or not progress.shouldprint(self)
  1862         ):
  1869         ):
  1863             return None
  1870             return None
  1864         return getprogbar(self)
  1871         return getprogbar(self)
  1865 
  1872 
  1868         if not haveprogbar():  # nothing loaded yet
  1875         if not haveprogbar():  # nothing loaded yet
  1869             return
  1876             return
  1870         if self._progbar is not None and self._progbar.printed:
  1877         if self._progbar is not None and self._progbar.printed:
  1871             self._progbar.clear()
  1878             self._progbar.clear()
  1872 
  1879 
  1873     def progress(self, topic, pos, item="", unit="", total=None):
  1880     def progress(self, topic, pos, item=b"", unit=b"", total=None):
  1874         '''show a progress message
  1881         '''show a progress message
  1875 
  1882 
  1876         By default a textual progress bar will be displayed if an operation
  1883         By default a textual progress bar will be displayed if an operation
  1877         takes too long. 'topic' is the current operation, 'item' is a
  1884         takes too long. 'topic' is the current operation, 'item' is a
  1878         non-numeric marker of the current position (i.e. the currently
  1885         non-numeric marker of the current position (i.e. the currently
  1883         Multiple nested topics may be active at a time.
  1890         Multiple nested topics may be active at a time.
  1884 
  1891 
  1885         All topics should be marked closed by setting pos to None at
  1892         All topics should be marked closed by setting pos to None at
  1886         termination.
  1893         termination.
  1887         '''
  1894         '''
  1888         self.deprecwarn("use ui.makeprogress() instead of ui.progress()", "5.1")
  1895         self.deprecwarn(
       
  1896             b"use ui.makeprogress() instead of ui.progress()", b"5.1"
       
  1897         )
  1889         progress = self.makeprogress(topic, unit, total)
  1898         progress = self.makeprogress(topic, unit, total)
  1890         if pos is not None:
  1899         if pos is not None:
  1891             progress.update(pos, item=item)
  1900             progress.update(pos, item=item)
  1892         else:
  1901         else:
  1893             progress.complete()
  1902             progress.complete()
  1894 
  1903 
  1895     def makeprogress(self, topic, unit="", total=None):
  1904     def makeprogress(self, topic, unit=b"", total=None):
  1896         """Create a progress helper for the specified topic"""
  1905         """Create a progress helper for the specified topic"""
  1897         if getattr(self._fmsgerr, 'structured', False):
  1906         if getattr(self._fmsgerr, 'structured', False):
  1898             # channel for machine-readable output with metadata, just send
  1907             # channel for machine-readable output with metadata, just send
  1899             # raw information
  1908             # raw information
  1900             # TODO: consider porting some useful information (e.g. estimated
  1909             # TODO: consider porting some useful information (e.g. estimated
  1979         """issue a developer warning message
  1988         """issue a developer warning message
  1980 
  1989 
  1981         Use 'stacklevel' to report the offender some layers further up in the
  1990         Use 'stacklevel' to report the offender some layers further up in the
  1982         stack.
  1991         stack.
  1983         """
  1992         """
  1984         if not self.configbool('devel', 'all-warnings'):
  1993         if not self.configbool(b'devel', b'all-warnings'):
  1985             if config is None or not self.configbool('devel', config):
  1994             if config is None or not self.configbool(b'devel', config):
  1986                 return
  1995                 return
  1987         msg = 'devel-warn: ' + msg
  1996         msg = b'devel-warn: ' + msg
  1988         stacklevel += 1  # get in develwarn
  1997         stacklevel += 1  # get in develwarn
  1989         if self.tracebackflag:
  1998         if self.tracebackflag:
  1990             util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
  1999             util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
  1991             self.log(
  2000             self.log(
  1992                 'develwarn',
  2001                 b'develwarn',
  1993                 '%s at:\n%s' % (msg, ''.join(util.getstackframes(stacklevel))),
  2002                 b'%s at:\n%s'
       
  2003                 % (msg, b''.join(util.getstackframes(stacklevel))),
  1994             )
  2004             )
  1995         else:
  2005         else:
  1996             curframe = inspect.currentframe()
  2006             curframe = inspect.currentframe()
  1997             calframe = inspect.getouterframes(curframe, 2)
  2007             calframe = inspect.getouterframes(curframe, 2)
  1998             fname, lineno, fmsg = calframe[stacklevel][1:4]
  2008             fname, lineno, fmsg = calframe[stacklevel][1:4]
  1999             fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
  2009             fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
  2000             self.write_err('%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
  2010             self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
  2001             self.log(
  2011             self.log(
  2002                 'develwarn', '%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
  2012                 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
  2003             )
  2013             )
  2004             curframe = calframe = None  # avoid cycles
  2014             curframe = calframe = None  # avoid cycles
  2005 
  2015 
  2006     def deprecwarn(self, msg, version, stacklevel=2):
  2016     def deprecwarn(self, msg, version, stacklevel=2):
  2007         """issue a deprecation warning
  2017         """issue a deprecation warning
  2008 
  2018 
  2009         - msg: message explaining what is deprecated and how to upgrade,
  2019         - msg: message explaining what is deprecated and how to upgrade,
  2010         - version: last version where the API will be supported,
  2020         - version: last version where the API will be supported,
  2011         """
  2021         """
  2012         if not (
  2022         if not (
  2013             self.configbool('devel', 'all-warnings')
  2023             self.configbool(b'devel', b'all-warnings')
  2014             or self.configbool('devel', 'deprec-warn')
  2024             or self.configbool(b'devel', b'deprec-warn')
  2015         ):
  2025         ):
  2016             return
  2026             return
  2017         msg += (
  2027         msg += (
  2018             "\n(compatibility will be dropped after Mercurial-%s,"
  2028             b"\n(compatibility will be dropped after Mercurial-%s,"
  2019             " update your code.)"
  2029             b" update your code.)"
  2020         ) % version
  2030         ) % version
  2021         self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
  2031         self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
  2022 
  2032 
  2023     def exportableenviron(self):
  2033     def exportableenviron(self):
  2024         """The environment variables that are safe to export, e.g. through
  2034         """The environment variables that are safe to export, e.g. through
  2025         hgweb.
  2035         hgweb.
  2026         """
  2036         """
  2027         return self._exportableenviron
  2037         return self._exportableenviron
  2028 
  2038 
  2029     @contextlib.contextmanager
  2039     @contextlib.contextmanager
  2030     def configoverride(self, overrides, source=""):
  2040     def configoverride(self, overrides, source=b""):
  2031         """Context manager for temporary config overrides
  2041         """Context manager for temporary config overrides
  2032         `overrides` must be a dict of the following structure:
  2042         `overrides` must be a dict of the following structure:
  2033         {(section, name) : value}"""
  2043         {(section, name) : value}"""
  2034         backups = {}
  2044         backups = {}
  2035         try:
  2045         try:
  2040         finally:
  2050         finally:
  2041             for __, backup in backups.items():
  2051             for __, backup in backups.items():
  2042                 self.restoreconfig(backup)
  2052                 self.restoreconfig(backup)
  2043             # just restoring ui.quiet config to the previous value is not enough
  2053             # just restoring ui.quiet config to the previous value is not enough
  2044             # as it does not update ui.quiet class member
  2054             # as it does not update ui.quiet class member
  2045             if ('ui', 'quiet') in overrides:
  2055             if (b'ui', b'quiet') in overrides:
  2046                 self.fixconfig(section='ui')
  2056                 self.fixconfig(section=b'ui')
  2047 
  2057 
  2048 
  2058 
  2049 class paths(dict):
  2059 class paths(dict):
  2050     """Represents a collection of paths and their configs.
  2060     """Represents a collection of paths and their configs.
  2051 
  2061 
  2054     """
  2064     """
  2055 
  2065 
  2056     def __init__(self, ui):
  2066     def __init__(self, ui):
  2057         dict.__init__(self)
  2067         dict.__init__(self)
  2058 
  2068 
  2059         for name, loc in ui.configitems('paths', ignoresub=True):
  2069         for name, loc in ui.configitems(b'paths', ignoresub=True):
  2060             # No location is the same as not existing.
  2070             # No location is the same as not existing.
  2061             if not loc:
  2071             if not loc:
  2062                 continue
  2072                 continue
  2063             loc, sub = ui.configsuboptions('paths', name)
  2073             loc, sub = ui.configsuboptions(b'paths', name)
  2064             self[name] = path(ui, name, rawloc=loc, suboptions=sub)
  2074             self[name] = path(ui, name, rawloc=loc, suboptions=sub)
  2065 
  2075 
  2066     def getpath(self, name, default=None):
  2076     def getpath(self, name, default=None):
  2067         """Return a ``path`` from a string, falling back to default.
  2077         """Return a ``path`` from a string, falling back to default.
  2068 
  2078 
  2096             # Try to resolve as a local path or URI.
  2106             # Try to resolve as a local path or URI.
  2097             try:
  2107             try:
  2098                 # We don't pass sub-options in, so no need to pass ui instance.
  2108                 # We don't pass sub-options in, so no need to pass ui instance.
  2099                 return path(None, None, rawloc=name)
  2109                 return path(None, None, rawloc=name)
  2100             except ValueError:
  2110             except ValueError:
  2101                 raise error.RepoError(_('repository %s does not exist') % name)
  2111                 raise error.RepoError(_(b'repository %s does not exist') % name)
  2102 
  2112 
  2103 
  2113 
  2104 _pathsuboptions = {}
  2114 _pathsuboptions = {}
  2105 
  2115 
  2106 
  2116 
  2124         return func
  2134         return func
  2125 
  2135 
  2126     return register
  2136     return register
  2127 
  2137 
  2128 
  2138 
  2129 @pathsuboption('pushurl', 'pushloc')
  2139 @pathsuboption(b'pushurl', b'pushloc')
  2130 def pushurlpathoption(ui, path, value):
  2140 def pushurlpathoption(ui, path, value):
  2131     u = util.url(value)
  2141     u = util.url(value)
  2132     # Actually require a URL.
  2142     # Actually require a URL.
  2133     if not u.scheme:
  2143     if not u.scheme:
  2134         ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
  2144         ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
  2135         return None
  2145         return None
  2136 
  2146 
  2137     # Don't support the #foo syntax in the push URL to declare branch to
  2147     # Don't support the #foo syntax in the push URL to declare branch to
  2138     # push.
  2148     # push.
  2139     if u.fragment:
  2149     if u.fragment:
  2140         ui.warn(
  2150         ui.warn(
  2141             _('("#fragment" in paths.%s:pushurl not supported; ' 'ignoring)\n')
  2151             _(
       
  2152                 b'("#fragment" in paths.%s:pushurl not supported; '
       
  2153                 b'ignoring)\n'
       
  2154             )
  2142             % path.name
  2155             % path.name
  2143         )
  2156         )
  2144         u.fragment = None
  2157         u.fragment = None
  2145 
  2158 
  2146     return bytes(u)
  2159     return bytes(u)
  2147 
  2160 
  2148 
  2161 
  2149 @pathsuboption('pushrev', 'pushrev')
  2162 @pathsuboption(b'pushrev', b'pushrev')
  2150 def pushrevpathoption(ui, path, value):
  2163 def pushrevpathoption(ui, path, value):
  2151     return value
  2164     return value
  2152 
  2165 
  2153 
  2166 
  2154 class path(object):
  2167 class path(object):
  2165         If ``name`` is not defined, we require that the location be a) a local
  2178         If ``name`` is not defined, we require that the location be a) a local
  2166         filesystem path with a .hg directory or b) a URL. If not,
  2179         filesystem path with a .hg directory or b) a URL. If not,
  2167         ``ValueError`` is raised.
  2180         ``ValueError`` is raised.
  2168         """
  2181         """
  2169         if not rawloc:
  2182         if not rawloc:
  2170             raise ValueError('rawloc must be defined')
  2183             raise ValueError(b'rawloc must be defined')
  2171 
  2184 
  2172         # Locations may define branches via syntax <base>#<branch>.
  2185         # Locations may define branches via syntax <base>#<branch>.
  2173         u = util.url(rawloc)
  2186         u = util.url(rawloc)
  2174         branch = None
  2187         branch = None
  2175         if u.fragment:
  2188         if u.fragment:
  2179         self.url = u
  2192         self.url = u
  2180         self.branch = branch
  2193         self.branch = branch
  2181 
  2194 
  2182         self.name = name
  2195         self.name = name
  2183         self.rawloc = rawloc
  2196         self.rawloc = rawloc
  2184         self.loc = '%s' % u
  2197         self.loc = b'%s' % u
  2185 
  2198 
  2186         # When given a raw location but not a symbolic name, validate the
  2199         # When given a raw location but not a symbolic name, validate the
  2187         # location is valid.
  2200         # location is valid.
  2188         if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
  2201         if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
  2189             raise ValueError(
  2202             raise ValueError(
  2190                 'location is not a URL or path to a local ' 'repo: %s' % rawloc
  2203                 b'location is not a URL or path to a local '
       
  2204                 b'repo: %s' % rawloc
  2191             )
  2205             )
  2192 
  2206 
  2193         suboptions = suboptions or {}
  2207         suboptions = suboptions or {}
  2194 
  2208 
  2195         # Now process the sub-options. If a sub-option is registered, its
  2209         # Now process the sub-options. If a sub-option is registered, its
  2207         """Returns True if the given path is a potentially valid repository.
  2221         """Returns True if the given path is a potentially valid repository.
  2208         This is its own function so that extensions can change the definition of
  2222         This is its own function so that extensions can change the definition of
  2209         'valid' in this case (like when pulling from a git repo into a hg
  2223         'valid' in this case (like when pulling from a git repo into a hg
  2210         one)."""
  2224         one)."""
  2211         try:
  2225         try:
  2212             return os.path.isdir(os.path.join(path, '.hg'))
  2226             return os.path.isdir(os.path.join(path, b'.hg'))
  2213         # Python 2 may return TypeError. Python 3, ValueError.
  2227         # Python 2 may return TypeError. Python 3, ValueError.
  2214         except (TypeError, ValueError):
  2228         except (TypeError, ValueError):
  2215             return False
  2229             return False
  2216 
  2230 
  2217     @property
  2231     @property
  2268     The specified message type is translated to 'ui.<type>' label if the dest
  2282     The specified message type is translated to 'ui.<type>' label if the dest
  2269     isn't a structured channel, so that the message will be colorized.
  2283     isn't a structured channel, so that the message will be colorized.
  2270     """
  2284     """
  2271     # TODO: maybe change 'type' to a mandatory option
  2285     # TODO: maybe change 'type' to a mandatory option
  2272     if r'type' in opts and not getattr(dest, 'structured', False):
  2286     if r'type' in opts and not getattr(dest, 'structured', False):
  2273         opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
  2287         opts[r'label'] = opts.get(r'label', b'') + b' ui.%s' % opts.pop(r'type')
  2274     write(dest, *args, **opts)
  2288     write(dest, *args, **opts)