mercurial/ui.py
changeset 49797 0449fb7729d7
parent 49795 f1e820cda2f5
child 49798 a51328ba33ca
equal deleted inserted replaced
49796:98e7be1ed6c5 49797:0449fb7729d7
    18 import subprocess
    18 import subprocess
    19 import sys
    19 import sys
    20 import traceback
    20 import traceback
    21 
    21 
    22 from typing import (
    22 from typing import (
       
    23     Any,
       
    24     Callable,
    23     Dict,
    25     Dict,
    24     List,
    26     List,
       
    27     NoReturn,
    25     Optional,
    28     Optional,
    26     Tuple,
    29     Tuple,
       
    30     Type,
       
    31     TypeVar,
    27     Union,
    32     Union,
    28     cast,
    33     cast,
    29 )
    34 )
    30 
    35 
    31 from .i18n import _
    36 from .i18n import _
    55     resourceutil,
    60     resourceutil,
    56     stringutil,
    61     stringutil,
    57     urlutil,
    62     urlutil,
    58 )
    63 )
    59 
    64 
       
    65 _ConfigItems = Dict[Tuple[bytes, bytes], object]  # {(section, name) : value}
    60 # The **opts args of the various write() methods can be basically anything, but
    66 # The **opts args of the various write() methods can be basically anything, but
    61 # there's no way to express it as "anything but str".  So type it to be the
    67 # there's no way to express it as "anything but str".  So type it to be the
    62 # handful of known types that are used.
    68 # handful of known types that are used.
    63 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
    69 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
    64 _PromptChoice = Tuple[bytes, bytes]
    70 _PromptChoice = Tuple[bytes, bytes]
       
    71 _Tui = TypeVar('_Tui', bound="ui")
    65 
    72 
    66 urlreq = util.urlreq
    73 urlreq = util.urlreq
    67 
    74 
    68 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
    75 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
    69 _keepalnum = b''.join(
    76 _keepalnum: bytes = b''.join(
    70     c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
    77     c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
    71 )
    78 )
    72 
    79 
    73 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
    80 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
    74 tweakrc = b"""
    81 tweakrc: bytes = b"""
    75 [ui]
    82 [ui]
    76 # The rollback command is dangerous. As a rule, don't use it.
    83 # The rollback command is dangerous. As a rule, don't use it.
    77 rollback = False
    84 rollback = False
    78 # Make `hg status` report copy information
    85 # Make `hg status` report copy information
    79 statuscopies = yes
    86 statuscopies = yes
    96 git = 1
   103 git = 1
    97 showfunc = 1
   104 showfunc = 1
    98 word-diff = 1
   105 word-diff = 1
    99 """
   106 """
   100 
   107 
   101 samplehgrcs = {
   108 samplehgrcs: Dict[bytes, bytes] = {
   102     b'user': b"""# example user config (see 'hg help config' for more info)
   109     b'user': b"""# example user config (see 'hg help config' for more info)
   103 [ui]
   110 [ui]
   104 # name and email, e.g.
   111 # name and email, e.g.
   105 # username = Jane Doe <jdoe@example.com>
   112 # username = Jane Doe <jdoe@example.com>
   106 username =
   113 username =
   185 
   192 
   186 
   193 
   187 class httppasswordmgrdbproxy:
   194 class httppasswordmgrdbproxy:
   188     """Delays loading urllib2 until it's needed."""
   195     """Delays loading urllib2 until it's needed."""
   189 
   196 
   190     def __init__(self):
   197     def __init__(self) -> None:
   191         self._mgr = None
   198         self._mgr = None
   192 
   199 
   193     def _get_mgr(self):
   200     def _get_mgr(self):
   194         if self._mgr is None:
   201         if self._mgr is None:
   195             self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
   202             self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
   208         return _maybebytesurl(
   215         return _maybebytesurl(
   209             mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
   216             mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
   210         )
   217         )
   211 
   218 
   212 
   219 
   213 def _catchterm(*args):
   220 def _catchterm(*args) -> NoReturn:
   214     raise error.SignalInterrupt
   221     raise error.SignalInterrupt
   215 
   222 
   216 
   223 
   217 # unique object used to detect no default value has been provided when
   224 # unique object used to detect no default value has been provided when
   218 # retrieving configuration value.
   225 # retrieving configuration value.
   219 _unset = object()
   226 _unset = object()
   220 
   227 
   221 # _reqexithandlers: callbacks run at the end of a request
   228 # _reqexithandlers: callbacks run at the end of a request
   222 _reqexithandlers = []
   229 _reqexithandlers: List = []
   223 
   230 
   224 
   231 
   225 class ui:
   232 class ui:
   226     def __init__(self, src=None):
   233     def __init__(self, src: Optional["ui"] = None) -> None:
   227         """Create a fresh new ui object if no src given
   234         """Create a fresh new ui object if no src given
   228 
   235 
   229         Use uimod.ui.load() to create a ui which knows global and user configs.
   236         Use uimod.ui.load() to create a ui which knows global and user configs.
   230         In most cases, you should use ui.copy() to create a copy of an existing
   237         In most cases, you should use ui.copy() to create a copy of an existing
   231         ui object.
   238         ui object.
   316             self._exportableenviron = {}
   323             self._exportableenviron = {}
   317             for k in allowed:
   324             for k in allowed:
   318                 if k in self.environ:
   325                 if k in self.environ:
   319                     self._exportableenviron[k] = self.environ[k]
   326                     self._exportableenviron[k] = self.environ[k]
   320 
   327 
   321     def _new_source(self):
   328     def _new_source(self) -> None:
   322         self._ocfg.new_source()
   329         self._ocfg.new_source()
   323         self._tcfg.new_source()
   330         self._tcfg.new_source()
   324         self._ucfg.new_source()
   331         self._ucfg.new_source()
   325 
   332 
   326     @classmethod
   333     @classmethod
   327     def load(cls):
   334     def load(cls: Type[_Tui]) -> _Tui:
   328         """Create a ui and load global and user configs"""
   335         """Create a ui and load global and user configs"""
   329         u = cls()
   336         u = cls()
   330         # we always trust global config files and environment variables
   337         # we always trust global config files and environment variables
   331         for t, f in rcutil.rccomponents():
   338         for t, f in rcutil.rccomponents():
   332             if t == b'path':
   339             if t == b'path':
   348                 raise error.ProgrammingError(b'unknown rctype: %s' % t)
   355                 raise error.ProgrammingError(b'unknown rctype: %s' % t)
   349         u._maybetweakdefaults()
   356         u._maybetweakdefaults()
   350         u._new_source()  # anything after that is a different level
   357         u._new_source()  # anything after that is a different level
   351         return u
   358         return u
   352 
   359 
   353     def _maybetweakdefaults(self):
   360     def _maybetweakdefaults(self) -> None:
   354         if not self.configbool(b'ui', b'tweakdefaults'):
   361         if not self.configbool(b'ui', b'tweakdefaults'):
   355             return
   362             return
   356         if self._tweaked or self.plain(b'tweakdefaults'):
   363         if self._tweaked or self.plain(b'tweakdefaults'):
   357             return
   364             return
   358 
   365 
   368         for section in tmpcfg:
   375         for section in tmpcfg:
   369             for name, value in tmpcfg.items(section):
   376             for name, value in tmpcfg.items(section):
   370                 if not self.hasconfig(section, name):
   377                 if not self.hasconfig(section, name):
   371                     self.setconfig(section, name, value, b"<tweakdefaults>")
   378                     self.setconfig(section, name, value, b"<tweakdefaults>")
   372 
   379 
   373     def copy(self):
   380     def copy(self: _Tui) -> _Tui:
   374         return self.__class__(self)
   381         return self.__class__(self)
   375 
   382 
   376     def resetstate(self):
   383     def resetstate(self) -> None:
   377         """Clear internal state that shouldn't persist across commands"""
   384         """Clear internal state that shouldn't persist across commands"""
   378         if self._progbar:
   385         if self._progbar:
   379             self._progbar.resetstate()  # reset last-print time of progress bar
   386             self._progbar.resetstate()  # reset last-print time of progress bar
   380         self.httppasswordmgrdb = httppasswordmgrdbproxy()
   387         self.httppasswordmgrdb = httppasswordmgrdbproxy()
   381 
   388 
   382     @contextlib.contextmanager
   389     @contextlib.contextmanager
   383     def timeblockedsection(self, key):
   390     def timeblockedsection(self, key: bytes):
   384         # this is open-coded below - search for timeblockedsection to find them
   391         # this is open-coded below - search for timeblockedsection to find them
   385         starttime = util.timer()
   392         starttime = util.timer()
   386         try:
   393         try:
   387             yield
   394             yield
   388         finally:
   395         finally:
   423                 self._uninterruptible = True
   430                 self._uninterruptible = True
   424                 yield
   431                 yield
   425             finally:
   432             finally:
   426                 self._uninterruptible = False
   433                 self._uninterruptible = False
   427 
   434 
   428     def formatter(self, topic, opts):
   435     def formatter(self, topic: bytes, opts):
   429         return formatter.formatter(self, self, topic, opts)
   436         return formatter.formatter(self, self, topic, opts)
   430 
   437 
   431     def _trusted(self, fp, f):
   438     def _trusted(self, fp, f: bytes) -> bool:
   432         st = util.fstat(fp)
   439         st = util.fstat(fp)
   433         if util.isowner(st):
   440         if util.isowner(st):
   434             return True
   441             return True
   435 
   442 
   436         tusers, tgroups = self._trustusers, self._trustgroups
   443         tusers, tgroups = self._trustusers, self._trustgroups
   452             )
   459             )
   453         return False
   460         return False
   454 
   461 
   455     def read_resource_config(
   462     def read_resource_config(
   456         self, name, root=None, trust=False, sections=None, remap=None
   463         self, name, root=None, trust=False, sections=None, remap=None
   457     ):
   464     ) -> None:
   458         try:
   465         try:
   459             fp = resourceutil.open_resource(name[0], name[1])
   466             fp = resourceutil.open_resource(name[0], name[1])
   460         except IOError:
   467         except IOError:
   461             if not sections:  # ignore unless we were looking for something
   468             if not sections:  # ignore unless we were looking for something
   462                 return
   469                 return
   466             b'resource:%s.%s' % name, fp, root, trust, sections, remap
   473             b'resource:%s.%s' % name, fp, root, trust, sections, remap
   467         )
   474         )
   468 
   475 
   469     def readconfig(
   476     def readconfig(
   470         self, filename, root=None, trust=False, sections=None, remap=None
   477         self, filename, root=None, trust=False, sections=None, remap=None
   471     ):
   478     ) -> None:
   472         try:
   479         try:
   473             fp = open(filename, 'rb')
   480             fp = open(filename, 'rb')
   474         except IOError:
   481         except IOError:
   475             if not sections:  # ignore unless we were looking for something
   482             if not sections:  # ignore unless we were looking for something
   476                 return
   483                 return
   478 
   485 
   479         self._readconfig(filename, fp, root, trust, sections, remap)
   486         self._readconfig(filename, fp, root, trust, sections, remap)
   480 
   487 
   481     def _readconfig(
   488     def _readconfig(
   482         self, filename, fp, root=None, trust=False, sections=None, remap=None
   489         self, filename, fp, root=None, trust=False, sections=None, remap=None
   483     ):
   490     ) -> None:
   484         with fp:
   491         with fp:
   485             cfg = config.config()
   492             cfg = config.config()
   486             trusted = sections or trust or self._trusted(fp, filename)
   493             trusted = sections or trust or self._trusted(fp, filename)
   487 
   494 
   488             try:
   495             try:
   494                     _(b'ignored %s: %s\n') % (inst.location, inst.message)
   501                     _(b'ignored %s: %s\n') % (inst.location, inst.message)
   495                 )
   502                 )
   496 
   503 
   497         self._applyconfig(cfg, trusted, root)
   504         self._applyconfig(cfg, trusted, root)
   498 
   505 
   499     def applyconfig(self, configitems, source=b"", root=None):
   506     def applyconfig(
       
   507         self, configitems: _ConfigItems, source=b"", root=None
       
   508     ) -> None:
   500         """Add configitems from a non-file source.  Unlike with ``setconfig()``,
   509         """Add configitems from a non-file source.  Unlike with ``setconfig()``,
   501         they can be overridden by subsequent config file reads.  The items are
   510         they can be overridden by subsequent config file reads.  The items are
   502         in the same format as ``configoverride()``, namely a dict of the
   511         in the same format as ``configoverride()``, namely a dict of the
   503         following structures: {(section, name) : value}
   512         following structures: {(section, name) : value}
   504 
   513 
   510         for (section, name), value in configitems.items():
   519         for (section, name), value in configitems.items():
   511             cfg.set(section, name, value, source)
   520             cfg.set(section, name, value, source)
   512 
   521 
   513         self._applyconfig(cfg, True, root)
   522         self._applyconfig(cfg, True, root)
   514 
   523 
   515     def _applyconfig(self, cfg, trusted, root):
   524     def _applyconfig(self, cfg, trusted, root) -> None:
   516         if self.plain():
   525         if self.plain():
   517             for k in (
   526             for k in (
   518                 b'debug',
   527                 b'debug',
   519                 b'fallbackencoding',
   528                 b'fallbackencoding',
   520                 b'quiet',
   529                 b'quiet',
   553 
   562 
   554         if root is None:
   563         if root is None:
   555             root = os.path.expanduser(b'~')
   564             root = os.path.expanduser(b'~')
   556         self.fixconfig(root=root)
   565         self.fixconfig(root=root)
   557 
   566 
   558     def fixconfig(self, root=None, section=None):
   567     def fixconfig(self, root=None, section=None) -> None:
   559         if section in (None, b'paths'):
   568         if section in (None, b'paths'):
   560             # expand vars and ~
   569             # expand vars and ~
   561             # translate paths relative to root (or home) into absolute paths
   570             # translate paths relative to root (or home) into absolute paths
   562             root = root or encoding.getcwd()
   571             root = root or encoding.getcwd()
   563             for c in self._tcfg, self._ucfg, self._ocfg:
   572             for c in self._tcfg, self._ucfg, self._ocfg:
   616             self._ocfg.backup(section, item),
   625             self._ocfg.backup(section, item),
   617             self._tcfg.backup(section, item),
   626             self._tcfg.backup(section, item),
   618             self._ucfg.backup(section, item),
   627             self._ucfg.backup(section, item),
   619         )
   628         )
   620 
   629 
   621     def restoreconfig(self, data):
   630     def restoreconfig(self, data) -> None:
   622         self._ocfg.restore(data[0])
   631         self._ocfg.restore(data[0])
   623         self._tcfg.restore(data[1])
   632         self._tcfg.restore(data[1])
   624         self._ucfg.restore(data[2])
   633         self._ucfg.restore(data[2])
   625 
   634 
   626     def setconfig(self, section, name, value, source=b''):
   635     def setconfig(self, section, name, value, source=b'') -> None:
   627         for cfg in (self._ocfg, self._tcfg, self._ucfg):
   636         for cfg in (self._ocfg, self._tcfg, self._ucfg):
   628             cfg.set(section, name, value, source)
   637             cfg.set(section, name, value, source)
   629         self.fixconfig(section=section)
   638         self.fixconfig(section=section)
   630         self._maybetweakdefaults()
   639         self._maybetweakdefaults()
   631 
   640 
  1007         cfg = self._data(untrusted)
  1016         cfg = self._data(untrusted)
  1008         for section in cfg.sections():
  1017         for section in cfg.sections():
  1009             for name, value in self.configitems(section, untrusted):
  1018             for name, value in self.configitems(section, untrusted):
  1010                 yield section, name, value
  1019                 yield section, name, value
  1011 
  1020 
  1012     def plain(self, feature=None):
  1021     def plain(self, feature: Optional[bytes] = None) -> bool:
  1013         """is plain mode active?
  1022         """is plain mode active?
  1014 
  1023 
  1015         Plain mode means that all configuration variables which affect
  1024         Plain mode means that all configuration variables which affect
  1016         the behavior and output of Mercurial should be
  1025         the behavior and output of Mercurial should be
  1017         ignored. Additionally, the output should be stable,
  1026         ignored. Additionally, the output should be stable,
  1081             raise error.Abort(
  1090             raise error.Abort(
  1082                 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
  1091                 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
  1083             )
  1092             )
  1084         return user
  1093         return user
  1085 
  1094 
  1086     def shortuser(self, user):
  1095     def shortuser(self, user: bytes) -> bytes:
  1087         """Return a short representation of a user name or email address."""
  1096         """Return a short representation of a user name or email address."""
  1088         if not self.verbose:
  1097         if not self.verbose:
  1089             user = stringutil.shortuser(user)
  1098             user = stringutil.shortuser(user)
  1090         return user
  1099         return user
  1091 
  1100 
  1159     def fmsg(self, f):
  1168     def fmsg(self, f):
  1160         self._fmsg = f
  1169         self._fmsg = f
  1161         self._fmsgout, self._fmsgerr = _selectmsgdests(self)
  1170         self._fmsgout, self._fmsgerr = _selectmsgdests(self)
  1162 
  1171 
  1163     @contextlib.contextmanager
  1172     @contextlib.contextmanager
  1164     def silent(self, error=False, subproc=False, labeled=False):
  1173     def silent(
       
  1174         self, error: bool = False, subproc: bool = False, labeled: bool = False
       
  1175     ):
  1165         self.pushbuffer(error=error, subproc=subproc, labeled=labeled)
  1176         self.pushbuffer(error=error, subproc=subproc, labeled=labeled)
  1166         try:
  1177         try:
  1167             yield
  1178             yield
  1168         finally:
  1179         finally:
  1169             self.popbuffer()
  1180             self.popbuffer()
  1170 
  1181 
  1171     def pushbuffer(self, error=False, subproc=False, labeled=False):
  1182     def pushbuffer(
       
  1183         self, error: bool = False, subproc: bool = False, labeled: bool = False
       
  1184     ) -> None:
  1172         """install a buffer to capture standard output of the ui object
  1185         """install a buffer to capture standard output of the ui object
  1173 
  1186 
  1174         If error is True, the error output will be captured too.
  1187         If error is True, the error output will be captured too.
  1175 
  1188 
  1176         If subproc is True, output from subprocesses (typically hooks) will be
  1189         If subproc is True, output from subprocesses (typically hooks) will be
  1185         """
  1198         """
  1186         self._buffers.append([])
  1199         self._buffers.append([])
  1187         self._bufferstates.append((error, subproc, labeled))
  1200         self._bufferstates.append((error, subproc, labeled))
  1188         self._bufferapplylabels = labeled
  1201         self._bufferapplylabels = labeled
  1189 
  1202 
  1190     def popbuffer(self):
  1203     def popbuffer(self) -> bytes:
  1191         '''pop the last buffer and return the buffered output'''
  1204         '''pop the last buffer and return the buffered output'''
  1192         self._bufferstates.pop()
  1205         self._bufferstates.pop()
  1193         if self._bufferstates:
  1206         if self._bufferstates:
  1194             self._bufferapplylabels = self._bufferstates[-1][2]
  1207             self._bufferapplylabels = self._bufferstates[-1][2]
  1195         else:
  1208         else:
  1196             self._bufferapplylabels = None
  1209             self._bufferapplylabels = None
  1197 
  1210 
  1198         return b"".join(self._buffers.pop())
  1211         return b"".join(self._buffers.pop())
  1199 
  1212 
  1200     def _isbuffered(self, dest):
  1213     def _isbuffered(self, dest) -> bool:
  1201         if dest is self._fout:
  1214         if dest is self._fout:
  1202             return bool(self._buffers)
  1215             return bool(self._buffers)
  1203         if dest is self._ferr:
  1216         if dest is self._ferr:
  1204             return bool(self._bufferstates and self._bufferstates[-1][0])
  1217             return bool(self._bufferstates and self._bufferstates[-1][0])
  1205         return False
  1218         return False
  1206 
  1219 
  1207     def canwritewithoutlabels(self):
  1220     def canwritewithoutlabels(self) -> bool:
  1208         '''check if write skips the label'''
  1221         '''check if write skips the label'''
  1209         if self._buffers and not self._bufferapplylabels:
  1222         if self._buffers and not self._bufferapplylabels:
  1210             return True
  1223             return True
  1211         return self._colormode is None
  1224         return self._colormode is None
  1212 
  1225 
  1213     def canbatchlabeledwrites(self):
  1226     def canbatchlabeledwrites(self) -> bool:
  1214         '''check if write calls with labels are batchable'''
  1227         '''check if write calls with labels are batchable'''
  1215         # Windows color printing is special, see ``write``.
  1228         # Windows color printing is special, see ``write``.
  1216         return self._colormode != b'win32'
  1229         return self._colormode != b'win32'
  1217 
  1230 
  1218     def write(self, *args: bytes, **opts: _MsgOpts) -> None:
  1231     def write(self, *args: bytes, **opts: _MsgOpts) -> None:
  1367         finally:
  1380         finally:
  1368             self._blockedtimes[b'stdio_blocked'] += (
  1381             self._blockedtimes[b'stdio_blocked'] += (
  1369                 util.timer() - starttime
  1382                 util.timer() - starttime
  1370             ) * 1000
  1383             ) * 1000
  1371 
  1384 
  1372     def _isatty(self, fh):
  1385     def _isatty(self, fh) -> bool:
  1373         if self.configbool(b'ui', b'nontty'):
  1386         if self.configbool(b'ui', b'nontty'):
  1374             return False
  1387             return False
  1375         return procutil.isatty(fh)
  1388         return procutil.isatty(fh)
  1376 
  1389 
  1377     def protectfinout(self):
  1390     def protectfinout(self):
  1405         try:
  1418         try:
  1406             yield fin, fout
  1419             yield fin, fout
  1407         finally:
  1420         finally:
  1408             self.restorefinout(fin, fout)
  1421             self.restorefinout(fin, fout)
  1409 
  1422 
  1410     def disablepager(self):
  1423     def disablepager(self) -> None:
  1411         self._disablepager = True
  1424         self._disablepager = True
  1412 
  1425 
  1413     def pager(self, command):
  1426     def pager(self, command: bytes) -> None:
  1414         """Start a pager for subsequent command output.
  1427         """Start a pager for subsequent command output.
  1415 
  1428 
  1416         Commands which produce a long stream of output should call
  1429         Commands which produce a long stream of output should call
  1417         this function to activate the user's preferred pagination
  1430         this function to activate the user's preferred pagination
  1418         mechanism (which may be no pager). Calling this function
  1431         mechanism (which may be no pager). Calling this function
  1489             # If the pager can't be spawned in dispatch when --pager=on is
  1502             # If the pager can't be spawned in dispatch when --pager=on is
  1490             # given, don't try again when the command runs, to avoid a duplicate
  1503             # given, don't try again when the command runs, to avoid a duplicate
  1491             # warning about a missing pager command.
  1504             # warning about a missing pager command.
  1492             self.disablepager()
  1505             self.disablepager()
  1493 
  1506 
  1494     def _runpager(self, command, env=None):
  1507     def _runpager(self, command: bytes, env=None) -> bool:
  1495         """Actually start the pager and set up file descriptors.
  1508         """Actually start the pager and set up file descriptors.
  1496 
  1509 
  1497         This is separate in part so that extensions (like chg) can
  1510         This is separate in part so that extensions (like chg) can
  1498         override how a pager is invoked.
  1511         override how a pager is invoked.
  1499         """
  1512         """
  1569 
  1582 
  1570         Handlers do not stay registered across request boundaries."""
  1583         Handlers do not stay registered across request boundaries."""
  1571         self._exithandlers.append((func, args, kwargs))
  1584         self._exithandlers.append((func, args, kwargs))
  1572         return func
  1585         return func
  1573 
  1586 
  1574     def interface(self, feature):
  1587     def interface(self, feature: bytes) -> bytes:
  1575         """what interface to use for interactive console features?
  1588         """what interface to use for interactive console features?
  1576 
  1589 
  1577         The interface is controlled by the value of `ui.interface` but also by
  1590         The interface is controlled by the value of `ui.interface` but also by
  1578         the value of feature-specific configuration. For example:
  1591         the value of feature-specific configuration. For example:
  1579 
  1592 
  1624 
  1637 
  1625         # Default interface for all the features
  1638         # Default interface for all the features
  1626         defaultinterface = b"text"
  1639         defaultinterface = b"text"
  1627         i = self.config(b"ui", b"interface")
  1640         i = self.config(b"ui", b"interface")
  1628         if i in alldefaults:
  1641         if i in alldefaults:
  1629             defaultinterface = i
  1642             defaultinterface = cast(bytes, i)  # cast to help pytype
  1630 
  1643 
  1631         choseninterface = defaultinterface
  1644         choseninterface: bytes = defaultinterface
  1632         f = self.config(b"ui", b"interface.%s" % feature)
  1645         f = self.config(b"ui", b"interface.%s" % feature)
  1633         if f in availableinterfaces:
  1646         if f in availableinterfaces:
  1634             choseninterface = f
  1647             choseninterface = cast(bytes, f)  # cast to help pytype
  1635 
  1648 
  1636         if i is not None and defaultinterface != i:
  1649         if i is not None and defaultinterface != i:
  1637             if f is not None:
  1650             if f is not None:
  1638                 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
  1651                 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
  1639             else:
  1652             else:
  1669             # usually those are non-interactive
  1682             # usually those are non-interactive
  1670             return self._isatty(self._fin)
  1683             return self._isatty(self._fin)
  1671 
  1684 
  1672         return i
  1685         return i
  1673 
  1686 
  1674     def termwidth(self):
  1687     def termwidth(self) -> int:
  1675         """how wide is the terminal in columns?"""
  1688         """how wide is the terminal in columns?"""
  1676         if b'COLUMNS' in encoding.environ:
  1689         if b'COLUMNS' in encoding.environ:
  1677             try:
  1690             try:
  1678                 return int(encoding.environ[b'COLUMNS'])
  1691                 return int(encoding.environ[b'COLUMNS'])
  1679             except ValueError:
  1692             except ValueError:
  1916     warnnoi18n = warn
  1929     warnnoi18n = warn
  1917     writenoi18n = write
  1930     writenoi18n = write
  1918 
  1931 
  1919     def edit(
  1932     def edit(
  1920         self,
  1933         self,
  1921         text,
  1934         text: bytes,
  1922         user,
  1935         user: bytes,
  1923         extra=None,
  1936         extra: Optional[Dict[bytes, Any]] = None,  # TODO: value type of bytes?
  1924         editform=None,
  1937         editform=None,
  1925         pending=None,
  1938         pending=None,
  1926         repopath=None,
  1939         repopath: Optional[bytes] = None,
  1927         action=None,
  1940         action: Optional[bytes] = None,
  1928     ):
  1941     ) -> bytes:
  1929         if action is None:
  1942         if action is None:
  1930             self.develwarn(
  1943             self.develwarn(
  1931                 b'action is None but will soon be a required '
  1944                 b'action is None but will soon be a required '
  1932                 b'parameter to ui.edit()'
  1945                 b'parameter to ui.edit()'
  1933             )
  1946             )
  1992 
  2005 
  1993         return t
  2006         return t
  1994 
  2007 
  1995     def system(
  2008     def system(
  1996         self,
  2009         self,
  1997         cmd,
  2010         cmd: bytes,
  1998         environ=None,
  2011         environ=None,
  1999         cwd=None,
  2012         cwd: Optional[bytes] = None,
  2000         onerr=None,
  2013         onerr: Optional[Callable[[bytes], Exception]] = None,
  2001         errprefix=None,
  2014         errprefix: Optional[bytes] = None,
  2002         blockedtag=None,
  2015         blockedtag: Optional[bytes] = None,
  2003     ):
  2016     ) -> int:
  2004         """execute shell command with appropriate output stream. command
  2017         """execute shell command with appropriate output stream. command
  2005         output will be redirected if fout is not stdout.
  2018         output will be redirected if fout is not stdout.
  2006 
  2019 
  2007         if command fails and onerr is None, return status, else raise onerr
  2020         if command fails and onerr is None, return status, else raise onerr
  2008         object as exception.
  2021         object as exception.
  2025             if errprefix:
  2038             if errprefix:
  2026                 errmsg = b'%s: %s' % (errprefix, errmsg)
  2039                 errmsg = b'%s: %s' % (errprefix, errmsg)
  2027             raise onerr(errmsg)
  2040             raise onerr(errmsg)
  2028         return rc
  2041         return rc
  2029 
  2042 
  2030     def _runsystem(self, cmd, environ, cwd, out):
  2043     def _runsystem(self, cmd: bytes, environ, cwd: Optional[bytes], out) -> int:
  2031         """actually execute the given shell command (can be overridden by
  2044         """actually execute the given shell command (can be overridden by
  2032         extensions like chg)"""
  2045         extensions like chg)"""
  2033         return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
  2046         return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
  2034 
  2047 
  2035     def traceback(self, exc=None, force=False):
  2048     def traceback(self, exc=None, force: bool = False):
  2036         """print exception traceback if traceback printing enabled or forced.
  2049         """print exception traceback if traceback printing enabled or forced.
  2037         only to call in exception handler. returns true if traceback
  2050         only to call in exception handler. returns true if traceback
  2038         printed."""
  2051         printed."""
  2039         if self.tracebackflag or force:
  2052         if self.tracebackflag or force:
  2040             if exc is None:
  2053             if exc is None:
  2128 
  2141 
  2129     def getlogger(self, name):
  2142     def getlogger(self, name):
  2130         """Returns a logger of the given name; or None if not registered"""
  2143         """Returns a logger of the given name; or None if not registered"""
  2131         return self._loggers.get(name)
  2144         return self._loggers.get(name)
  2132 
  2145 
  2133     def setlogger(self, name, logger):
  2146     def setlogger(self, name, logger) -> None:
  2134         """Install logger which can be identified later by the given name
  2147         """Install logger which can be identified later by the given name
  2135 
  2148 
  2136         More than one loggers can be registered. Use extension or module
  2149         More than one loggers can be registered. Use extension or module
  2137         name to uniquely identify the logger instance.
  2150         name to uniquely identify the logger instance.
  2138         """
  2151         """
  2139         self._loggers[name] = logger
  2152         self._loggers[name] = logger
  2140 
  2153 
  2141     def log(self, event, msgfmt, *msgargs, **opts):
  2154     def log(self, event, msgfmt, *msgargs, **opts) -> None:
  2142         """hook for logging facility extensions
  2155         """hook for logging facility extensions
  2143 
  2156 
  2144         event should be a readily-identifiable subsystem, which will
  2157         event should be a readily-identifiable subsystem, which will
  2145         allow filtering.
  2158         allow filtering.
  2146 
  2159 
  2237         hgweb.
  2250         hgweb.
  2238         """
  2251         """
  2239         return self._exportableenviron
  2252         return self._exportableenviron
  2240 
  2253 
  2241     @contextlib.contextmanager
  2254     @contextlib.contextmanager
  2242     def configoverride(self, overrides, source=b""):
  2255     def configoverride(self, overrides: _ConfigItems, source: bytes = b""):
  2243         """Context manager for temporary config overrides
  2256         """Context manager for temporary config overrides
  2244         `overrides` must be a dict of the following structure:
  2257         `overrides` must be a dict of the following structure:
  2245         {(section, name) : value}"""
  2258         {(section, name) : value}"""
  2246         backups = {}
  2259         backups = {}
  2247         try:
  2260         try:
  2255             # just restoring ui.quiet config to the previous value is not enough
  2268             # just restoring ui.quiet config to the previous value is not enough
  2256             # as it does not update ui.quiet class member
  2269             # as it does not update ui.quiet class member
  2257             if (b'ui', b'quiet') in overrides:
  2270             if (b'ui', b'quiet') in overrides:
  2258                 self.fixconfig(section=b'ui')
  2271                 self.fixconfig(section=b'ui')
  2259 
  2272 
  2260     def estimatememory(self):
  2273     def estimatememory(self) -> Optional[int]:
  2261         """Provide an estimate for the available system memory in Bytes.
  2274         """Provide an estimate for the available system memory in Bytes.
  2262 
  2275 
  2263         This can be overriden via ui.available-memory. It returns None, if
  2276         This can be overriden via ui.available-memory. It returns None, if
  2264         no estimate can be computed.
  2277         no estimate can be computed.
  2265         """
  2278         """
  2290 
  2303 
  2291 def haveprogbar() -> bool:
  2304 def haveprogbar() -> bool:
  2292     return _progresssingleton is not None
  2305     return _progresssingleton is not None
  2293 
  2306 
  2294 
  2307 
  2295 def _selectmsgdests(ui):
  2308 def _selectmsgdests(ui: ui):
  2296     name = ui.config(b'ui', b'message-output')
  2309     name = ui.config(b'ui', b'message-output')
  2297     if name == b'channel':
  2310     if name == b'channel':
  2298         if ui.fmsg:
  2311         if ui.fmsg:
  2299             return ui.fmsg, ui.fmsg
  2312             return ui.fmsg, ui.fmsg
  2300         else:
  2313         else: