mercurial/util.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   149 
   149 
   150 
   150 
   151 # python 2.6 still have deprecation warning enabled by default. We do not want
   151 # python 2.6 still have deprecation warning enabled by default. We do not want
   152 # to display anything to standard user so detect if we are running test and
   152 # to display anything to standard user so detect if we are running test and
   153 # only use python deprecation warning in this case.
   153 # only use python deprecation warning in this case.
   154 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
   154 _dowarn = bool(encoding.environ.get(b'HGEMITWARNINGS'))
   155 if _dowarn:
   155 if _dowarn:
   156     # explicitly unfilter our warning for python 2.7
   156     # explicitly unfilter our warning for python 2.7
   157     #
   157     #
   158     # The option of setting PYTHONWARNINGS in the test runner was investigated.
   158     # The option of setting PYTHONWARNINGS in the test runner was investigated.
   159     # However, module name set through PYTHONWARNINGS was exactly matched, so
   159     # However, module name set through PYTHONWARNINGS was exactly matched, so
   184 
   184 
   185     This is a noop outside of tests, use 'ui.deprecwarn' when possible.
   185     This is a noop outside of tests, use 'ui.deprecwarn' when possible.
   186     """
   186     """
   187     if _dowarn:
   187     if _dowarn:
   188         msg += (
   188         msg += (
   189             "\n(compatibility will be dropped after Mercurial-%s,"
   189             b"\n(compatibility will be dropped after Mercurial-%s,"
   190             " update your code.)"
   190             b" update your code.)"
   191         ) % version
   191         ) % version
   192         warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
   192         warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
   193 
   193 
   194 
   194 
   195 DIGESTS = {
   195 DIGESTS = {
   196     'md5': hashlib.md5,
   196     b'md5': hashlib.md5,
   197     'sha1': hashlib.sha1,
   197     b'sha1': hashlib.sha1,
   198     'sha512': hashlib.sha512,
   198     b'sha512': hashlib.sha512,
   199 }
   199 }
   200 # List of digest types from strongest to weakest
   200 # List of digest types from strongest to weakest
   201 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
   201 DIGESTS_BY_STRENGTH = [b'sha512', b'sha1', b'md5']
   202 
   202 
   203 for k in DIGESTS_BY_STRENGTH:
   203 for k in DIGESTS_BY_STRENGTH:
   204     assert k in DIGESTS
   204     assert k in DIGESTS
   205 
   205 
   206 
   206 
   219     '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
   219     '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
   220     >>> digester.preferred([b'md5', b'sha1'])
   220     >>> digester.preferred([b'md5', b'sha1'])
   221     'sha1'
   221     'sha1'
   222     """
   222     """
   223 
   223 
   224     def __init__(self, digests, s=''):
   224     def __init__(self, digests, s=b''):
   225         self._hashes = {}
   225         self._hashes = {}
   226         for k in digests:
   226         for k in digests:
   227             if k not in DIGESTS:
   227             if k not in DIGESTS:
   228                 raise error.Abort(_('unknown digest type: %s') % k)
   228                 raise error.Abort(_(b'unknown digest type: %s') % k)
   229             self._hashes[k] = DIGESTS[k]()
   229             self._hashes[k] = DIGESTS[k]()
   230         if s:
   230         if s:
   231             self.update(s)
   231             self.update(s)
   232 
   232 
   233     def update(self, data):
   233     def update(self, data):
   234         for h in self._hashes.values():
   234         for h in self._hashes.values():
   235             h.update(data)
   235             h.update(data)
   236 
   236 
   237     def __getitem__(self, key):
   237     def __getitem__(self, key):
   238         if key not in DIGESTS:
   238         if key not in DIGESTS:
   239             raise error.Abort(_('unknown digest type: %s') % k)
   239             raise error.Abort(_(b'unknown digest type: %s') % k)
   240         return nodemod.hex(self._hashes[key].digest())
   240         return nodemod.hex(self._hashes[key].digest())
   241 
   241 
   242     def __iter__(self):
   242     def __iter__(self):
   243         return iter(self._hashes)
   243         return iter(self._hashes)
   244 
   244 
   275         return content
   275         return content
   276 
   276 
   277     def validate(self):
   277     def validate(self):
   278         if self._size != self._got:
   278         if self._size != self._got:
   279             raise error.Abort(
   279             raise error.Abort(
   280                 _('size mismatch: expected %d, got %d')
   280                 _(b'size mismatch: expected %d, got %d')
   281                 % (self._size, self._got)
   281                 % (self._size, self._got)
   282             )
   282             )
   283         for k, v in self._digests.items():
   283         for k, v in self._digests.items():
   284             if v != self._digester[k]:
   284             if v != self._digester[k]:
   285                 # i18n: first parameter is a digest name
   285                 # i18n: first parameter is a digest name
   286                 raise error.Abort(
   286                 raise error.Abort(
   287                     _('%s mismatch: expected %s, got %s')
   287                     _(b'%s mismatch: expected %s, got %s')
   288                     % (k, v, self._digester[k])
   288                     % (k, v, self._digester[k])
   289                 )
   289                 )
   290 
   290 
   291 
   291 
   292 try:
   292 try:
   361 
   361 
   362     def readline(self, *args, **kwargs):
   362     def readline(self, *args, **kwargs):
   363         if len(self._buffer) > 1:
   363         if len(self._buffer) > 1:
   364             # this should not happen because both read and readline end with a
   364             # this should not happen because both read and readline end with a
   365             # _frombuffer call that collapse it.
   365             # _frombuffer call that collapse it.
   366             self._buffer = [''.join(self._buffer)]
   366             self._buffer = [b''.join(self._buffer)]
   367             self._lenbuf = len(self._buffer[0])
   367             self._lenbuf = len(self._buffer[0])
   368         lfi = -1
   368         lfi = -1
   369         if self._buffer:
   369         if self._buffer:
   370             lfi = self._buffer[-1].find('\n')
   370             lfi = self._buffer[-1].find(b'\n')
   371         while (not self._eof) and lfi < 0:
   371         while (not self._eof) and lfi < 0:
   372             self._fillbuffer()
   372             self._fillbuffer()
   373             if self._buffer:
   373             if self._buffer:
   374                 lfi = self._buffer[-1].find('\n')
   374                 lfi = self._buffer[-1].find(b'\n')
   375         size = lfi + 1
   375         size = lfi + 1
   376         if lfi < 0:  # end of file
   376         if lfi < 0:  # end of file
   377             size = self._lenbuf
   377             size = self._lenbuf
   378         elif len(self._buffer) > 1:
   378         elif len(self._buffer) > 1:
   379             # we need to take previous chunks into account
   379             # we need to take previous chunks into account
   383     def _frombuffer(self, size):
   383     def _frombuffer(self, size):
   384         """return at most 'size' data from the buffer
   384         """return at most 'size' data from the buffer
   385 
   385 
   386         The data are removed from the buffer."""
   386         The data are removed from the buffer."""
   387         if size == 0 or not self._buffer:
   387         if size == 0 or not self._buffer:
   388             return ''
   388             return b''
   389         buf = self._buffer[0]
   389         buf = self._buffer[0]
   390         if len(self._buffer) > 1:
   390         if len(self._buffer) > 1:
   391             buf = ''.join(self._buffer)
   391             buf = b''.join(self._buffer)
   392 
   392 
   393         data = buf[:size]
   393         data = buf[:size]
   394         buf = buf[len(data) :]
   394         buf = buf[len(data) :]
   395         if buf:
   395         if buf:
   396             self._buffer = [buf]
   396             self._buffer = [buf]
   418         return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
   418         return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
   419     except ValueError:
   419     except ValueError:
   420         # Empty files cannot be mmapped, but mmapread should still work.  Check
   420         # Empty files cannot be mmapped, but mmapread should still work.  Check
   421         # if the file is empty, and if so, return an empty buffer.
   421         # if the file is empty, and if so, return an empty buffer.
   422         if os.fstat(fd).st_size == 0:
   422         if os.fstat(fd).st_size == 0:
   423             return ''
   423             return b''
   424         raise
   424         raise
   425 
   425 
   426 
   426 
   427 class fileobjectproxy(object):
   427 class fileobjectproxy(object):
   428     """A proxy around file objects that tells a watcher when events occur.
   428     """A proxy around file objects that tells a watcher when events occur.
   785 
   785 
   786 class baseproxyobserver(object):
   786 class baseproxyobserver(object):
   787     def _writedata(self, data):
   787     def _writedata(self, data):
   788         if not self.logdata:
   788         if not self.logdata:
   789             if self.logdataapis:
   789             if self.logdataapis:
   790                 self.fh.write('\n')
   790                 self.fh.write(b'\n')
   791                 self.fh.flush()
   791                 self.fh.flush()
   792             return
   792             return
   793 
   793 
   794         # Simple case writes all data on a single line.
   794         # Simple case writes all data on a single line.
   795         if b'\n' not in data:
   795         if b'\n' not in data:
   796             if self.logdataapis:
   796             if self.logdataapis:
   797                 self.fh.write(': %s\n' % stringutil.escapestr(data))
   797                 self.fh.write(b': %s\n' % stringutil.escapestr(data))
   798             else:
   798             else:
   799                 self.fh.write(
   799                 self.fh.write(
   800                     '%s>     %s\n' % (self.name, stringutil.escapestr(data))
   800                     b'%s>     %s\n' % (self.name, stringutil.escapestr(data))
   801                 )
   801                 )
   802             self.fh.flush()
   802             self.fh.flush()
   803             return
   803             return
   804 
   804 
   805         # Data with newlines is written to multiple lines.
   805         # Data with newlines is written to multiple lines.
   806         if self.logdataapis:
   806         if self.logdataapis:
   807             self.fh.write(':\n')
   807             self.fh.write(b':\n')
   808 
   808 
   809         lines = data.splitlines(True)
   809         lines = data.splitlines(True)
   810         for line in lines:
   810         for line in lines:
   811             self.fh.write(
   811             self.fh.write(
   812                 '%s>     %s\n' % (self.name, stringutil.escapestr(line))
   812                 b'%s>     %s\n' % (self.name, stringutil.escapestr(line))
   813             )
   813             )
   814         self.fh.flush()
   814         self.fh.flush()
   815 
   815 
   816 
   816 
   817 class fileobjectobserver(baseproxyobserver):
   817 class fileobjectobserver(baseproxyobserver):
   830     def read(self, res, size=-1):
   830     def read(self, res, size=-1):
   831         if not self.reads:
   831         if not self.reads:
   832             return
   832             return
   833         # Python 3 can return None from reads at EOF instead of empty strings.
   833         # Python 3 can return None from reads at EOF instead of empty strings.
   834         if res is None:
   834         if res is None:
   835             res = ''
   835             res = b''
   836 
   836 
   837         if size == -1 and res == '':
   837         if size == -1 and res == b'':
   838             # Suppress pointless read(-1) calls that return
   838             # Suppress pointless read(-1) calls that return
   839             # nothing. These happen _a lot_ on Python 3, and there
   839             # nothing. These happen _a lot_ on Python 3, and there
   840             # doesn't seem to be a better workaround to have matching
   840             # doesn't seem to be a better workaround to have matching
   841             # Python 2 and 3 behavior. :(
   841             # Python 2 and 3 behavior. :(
   842             return
   842             return
   843 
   843 
   844         if self.logdataapis:
   844         if self.logdataapis:
   845             self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
   845             self.fh.write(b'%s> read(%d) -> %d' % (self.name, size, len(res)))
   846 
   846 
   847         self._writedata(res)
   847         self._writedata(res)
   848 
   848 
   849     def readline(self, res, limit=-1):
   849     def readline(self, res, limit=-1):
   850         if not self.reads:
   850         if not self.reads:
   851             return
   851             return
   852 
   852 
   853         if self.logdataapis:
   853         if self.logdataapis:
   854             self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
   854             self.fh.write(b'%s> readline() -> %d' % (self.name, len(res)))
   855 
   855 
   856         self._writedata(res)
   856         self._writedata(res)
   857 
   857 
   858     def readinto(self, res, dest):
   858     def readinto(self, res, dest):
   859         if not self.reads:
   859         if not self.reads:
   860             return
   860             return
   861 
   861 
   862         if self.logdataapis:
   862         if self.logdataapis:
   863             self.fh.write(
   863             self.fh.write(
   864                 '%s> readinto(%d) -> %r' % (self.name, len(dest), res)
   864                 b'%s> readinto(%d) -> %r' % (self.name, len(dest), res)
   865             )
   865             )
   866 
   866 
   867         data = dest[0:res] if res is not None else b''
   867         data = dest[0:res] if res is not None else b''
   868 
   868 
   869         # _writedata() uses "in" operator and is confused by memoryview because
   869         # _writedata() uses "in" operator and is confused by memoryview because
   881         # returns the integer bytes written.
   881         # returns the integer bytes written.
   882         if res is None and data:
   882         if res is None and data:
   883             res = len(data)
   883             res = len(data)
   884 
   884 
   885         if self.logdataapis:
   885         if self.logdataapis:
   886             self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
   886             self.fh.write(b'%s> write(%d) -> %r' % (self.name, len(data), res))
   887 
   887 
   888         self._writedata(data)
   888         self._writedata(data)
   889 
   889 
   890     def flush(self, res):
   890     def flush(self, res):
   891         if not self.writes:
   891         if not self.writes:
   892             return
   892             return
   893 
   893 
   894         self.fh.write('%s> flush() -> %r\n' % (self.name, res))
   894         self.fh.write(b'%s> flush() -> %r\n' % (self.name, res))
   895 
   895 
   896     # For observedbufferedinputpipe.
   896     # For observedbufferedinputpipe.
   897     def bufferedread(self, res, size):
   897     def bufferedread(self, res, size):
   898         if not self.reads:
   898         if not self.reads:
   899             return
   899             return
   900 
   900 
   901         if self.logdataapis:
   901         if self.logdataapis:
   902             self.fh.write(
   902             self.fh.write(
   903                 '%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
   903                 b'%s> bufferedread(%d) -> %d' % (self.name, size, len(res))
   904             )
   904             )
   905 
   905 
   906         self._writedata(res)
   906         self._writedata(res)
   907 
   907 
   908     def bufferedreadline(self, res):
   908     def bufferedreadline(self, res):
   909         if not self.reads:
   909         if not self.reads:
   910             return
   910             return
   911 
   911 
   912         if self.logdataapis:
   912         if self.logdataapis:
   913             self.fh.write(
   913             self.fh.write(
   914                 '%s> bufferedreadline() -> %d' % (self.name, len(res))
   914                 b'%s> bufferedreadline() -> %d' % (self.name, len(res))
   915             )
   915             )
   916 
   916 
   917         self._writedata(res)
   917         self._writedata(res)
   918 
   918 
   919 
   919 
   956 
   956 
   957     def makefile(self, res, mode=None, bufsize=None):
   957     def makefile(self, res, mode=None, bufsize=None):
   958         if not self.states:
   958         if not self.states:
   959             return
   959             return
   960 
   960 
   961         self.fh.write('%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
   961         self.fh.write(b'%s> makefile(%r, %r)\n' % (self.name, mode, bufsize))
   962 
   962 
   963     def recv(self, res, size, flags=0):
   963     def recv(self, res, size, flags=0):
   964         if not self.reads:
   964         if not self.reads:
   965             return
   965             return
   966 
   966 
   967         if self.logdataapis:
   967         if self.logdataapis:
   968             self.fh.write(
   968             self.fh.write(
   969                 '%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
   969                 b'%s> recv(%d, %d) -> %d' % (self.name, size, flags, len(res))
   970             )
   970             )
   971         self._writedata(res)
   971         self._writedata(res)
   972 
   972 
   973     def recvfrom(self, res, size, flags=0):
   973     def recvfrom(self, res, size, flags=0):
   974         if not self.reads:
   974         if not self.reads:
   975             return
   975             return
   976 
   976 
   977         if self.logdataapis:
   977         if self.logdataapis:
   978             self.fh.write(
   978             self.fh.write(
   979                 '%s> recvfrom(%d, %d) -> %d'
   979                 b'%s> recvfrom(%d, %d) -> %d'
   980                 % (self.name, size, flags, len(res[0]))
   980                 % (self.name, size, flags, len(res[0]))
   981             )
   981             )
   982 
   982 
   983         self._writedata(res[0])
   983         self._writedata(res[0])
   984 
   984 
   986         if not self.reads:
   986         if not self.reads:
   987             return
   987             return
   988 
   988 
   989         if self.logdataapis:
   989         if self.logdataapis:
   990             self.fh.write(
   990             self.fh.write(
   991                 '%s> recvfrom_into(%d, %d) -> %d'
   991                 b'%s> recvfrom_into(%d, %d) -> %d'
   992                 % (self.name, size, flags, res[0])
   992                 % (self.name, size, flags, res[0])
   993             )
   993             )
   994 
   994 
   995         self._writedata(buf[0 : res[0]])
   995         self._writedata(buf[0 : res[0]])
   996 
   996 
   998         if not self.reads:
   998         if not self.reads:
   999             return
   999             return
  1000 
  1000 
  1001         if self.logdataapis:
  1001         if self.logdataapis:
  1002             self.fh.write(
  1002             self.fh.write(
  1003                 '%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
  1003                 b'%s> recv_into(%d, %d) -> %d' % (self.name, size, flags, res)
  1004             )
  1004             )
  1005 
  1005 
  1006         self._writedata(buf[0:res])
  1006         self._writedata(buf[0:res])
  1007 
  1007 
  1008     def send(self, res, data, flags=0):
  1008     def send(self, res, data, flags=0):
  1009         if not self.writes:
  1009         if not self.writes:
  1010             return
  1010             return
  1011 
  1011 
  1012         self.fh.write(
  1012         self.fh.write(
  1013             '%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
  1013             b'%s> send(%d, %d) -> %d' % (self.name, len(data), flags, len(res))
  1014         )
  1014         )
  1015         self._writedata(data)
  1015         self._writedata(data)
  1016 
  1016 
  1017     def sendall(self, res, data, flags=0):
  1017     def sendall(self, res, data, flags=0):
  1018         if not self.writes:
  1018         if not self.writes:
  1019             return
  1019             return
  1020 
  1020 
  1021         if self.logdataapis:
  1021         if self.logdataapis:
  1022             # Returns None on success. So don't bother reporting return value.
  1022             # Returns None on success. So don't bother reporting return value.
  1023             self.fh.write('%s> sendall(%d, %d)' % (self.name, len(data), flags))
  1023             self.fh.write(
       
  1024                 b'%s> sendall(%d, %d)' % (self.name, len(data), flags)
       
  1025             )
  1024 
  1026 
  1025         self._writedata(data)
  1027         self._writedata(data)
  1026 
  1028 
  1027     def sendto(self, res, data, flagsoraddress, address=None):
  1029     def sendto(self, res, data, flagsoraddress, address=None):
  1028         if not self.writes:
  1030         if not self.writes:
  1033         else:
  1035         else:
  1034             flags = 0
  1036             flags = 0
  1035 
  1037 
  1036         if self.logdataapis:
  1038         if self.logdataapis:
  1037             self.fh.write(
  1039             self.fh.write(
  1038                 '%s> sendto(%d, %d, %r) -> %d'
  1040                 b'%s> sendto(%d, %d, %r) -> %d'
  1039                 % (self.name, len(data), flags, address, res)
  1041                 % (self.name, len(data), flags, address, res)
  1040             )
  1042             )
  1041 
  1043 
  1042         self._writedata(data)
  1044         self._writedata(data)
  1043 
  1045 
  1044     def setblocking(self, res, flag):
  1046     def setblocking(self, res, flag):
  1045         if not self.states:
  1047         if not self.states:
  1046             return
  1048             return
  1047 
  1049 
  1048         self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
  1050         self.fh.write(b'%s> setblocking(%r)\n' % (self.name, flag))
  1049 
  1051 
  1050     def settimeout(self, res, value):
  1052     def settimeout(self, res, value):
  1051         if not self.states:
  1053         if not self.states:
  1052             return
  1054             return
  1053 
  1055 
  1054         self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
  1056         self.fh.write(b'%s> settimeout(%r)\n' % (self.name, value))
  1055 
  1057 
  1056     def gettimeout(self, res):
  1058     def gettimeout(self, res):
  1057         if not self.states:
  1059         if not self.states:
  1058             return
  1060             return
  1059 
  1061 
  1060         self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
  1062         self.fh.write(b'%s> gettimeout() -> %f\n' % (self.name, res))
  1061 
  1063 
  1062     def setsockopt(self, res, level, optname, value):
  1064     def setsockopt(self, res, level, optname, value):
  1063         if not self.states:
  1065         if not self.states:
  1064             return
  1066             return
  1065 
  1067 
  1066         self.fh.write(
  1068         self.fh.write(
  1067             '%s> setsockopt(%r, %r, %r) -> %r\n'
  1069             b'%s> setsockopt(%r, %r, %r) -> %r\n'
  1068             % (self.name, level, optname, value, res)
  1070             % (self.name, level, optname, value, res)
  1069         )
  1071         )
  1070 
  1072 
  1071 
  1073 
  1072 def makeloggingsocket(
  1074 def makeloggingsocket(
  1098     try:
  1100     try:
  1099         from . import __version__
  1101         from . import __version__
  1100 
  1102 
  1101         return __version__.version
  1103         return __version__.version
  1102     except ImportError:
  1104     except ImportError:
  1103         return 'unknown'
  1105         return b'unknown'
  1104 
  1106 
  1105 
  1107 
  1106 def versiontuple(v=None, n=4):
  1108 def versiontuple(v=None, n=4):
  1107     """Parses a Mercurial version string into an N-tuple.
  1109     """Parses a Mercurial version string into an N-tuple.
  1108 
  1110 
  1160     """
  1162     """
  1161     if not v:
  1163     if not v:
  1162         v = version()
  1164         v = version()
  1163     m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
  1165     m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
  1164     if not m:
  1166     if not m:
  1165         vparts, extra = '', v
  1167         vparts, extra = b'', v
  1166     elif m.group(2):
  1168     elif m.group(2):
  1167         vparts, extra = m.groups()
  1169         vparts, extra = m.groups()
  1168     else:
  1170     else:
  1169         vparts, extra = m.group(1), None
  1171         vparts, extra = m.group(1), None
  1170 
  1172 
  1171     vints = []
  1173     vints = []
  1172     for i in vparts.split('.'):
  1174     for i in vparts.split(b'.'):
  1173         try:
  1175         try:
  1174             vints.append(int(i))
  1176             vints.append(int(i))
  1175         except ValueError:
  1177         except ValueError:
  1176             break
  1178             break
  1177     # (3, 6) -> (3, 6, None)
  1179     # (3, 6) -> (3, 6, None)
  1742                 nmin = 1 << log2(blen)
  1744                 nmin = 1 << log2(blen)
  1743                 if nmin > min:
  1745                 if nmin > min:
  1744                     min = nmin
  1746                     min = nmin
  1745                 if min > max:
  1747                 if min > max:
  1746                     min = max
  1748                     min = max
  1747             yield ''.join(buf)
  1749             yield b''.join(buf)
  1748             blen = 0
  1750             blen = 0
  1749             buf = []
  1751             buf = []
  1750     if buf:
  1752     if buf:
  1751         yield ''.join(buf)
  1753         yield b''.join(buf)
  1752 
  1754 
  1753 
  1755 
  1754 def always(fn):
  1756 def always(fn):
  1755     return True
  1757     return True
  1756 
  1758 
  1804     if not n1:
  1806     if not n1:
  1805         return localpath(n2)
  1807         return localpath(n2)
  1806     if os.path.isabs(n1):
  1808     if os.path.isabs(n1):
  1807         if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
  1809         if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
  1808             return os.path.join(root, localpath(n2))
  1810             return os.path.join(root, localpath(n2))
  1809         n2 = '/'.join((pconvert(root), n2))
  1811         n2 = b'/'.join((pconvert(root), n2))
  1810     a, b = splitpath(n1), n2.split('/')
  1812     a, b = splitpath(n1), n2.split(b'/')
  1811     a.reverse()
  1813     a.reverse()
  1812     b.reverse()
  1814     b.reverse()
  1813     while a and b and a[-1] == b[-1]:
  1815     while a and b and a[-1] == b[-1]:
  1814         a.pop()
  1816         a.pop()
  1815         b.pop()
  1817         b.pop()
  1816     b.reverse()
  1818     b.reverse()
  1817     return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
  1819     return pycompat.ossep.join(([b'..'] * len(a)) + b) or b'.'
  1818 
  1820 
  1819 
  1821 
  1820 # the location of data files matching the source code
  1822 # the location of data files matching the source code
  1821 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
  1823 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != b'macosx_app':
  1822     # executable version (py2exe) doesn't support __file__
  1824     # executable version (py2exe) doesn't support __file__
  1823     datapath = os.path.dirname(pycompat.sysexecutable)
  1825     datapath = os.path.dirname(pycompat.sysexecutable)
  1824 else:
  1826 else:
  1825     datapath = os.path.dirname(pycompat.fsencode(__file__))
  1827     datapath = os.path.dirname(pycompat.fsencode(__file__))
  1826 
  1828 
  1841     return check
  1843     return check
  1842 
  1844 
  1843 
  1845 
  1844 # a whilelist of known filesystems where hardlink works reliably
  1846 # a whilelist of known filesystems where hardlink works reliably
  1845 _hardlinkfswhitelist = {
  1847 _hardlinkfswhitelist = {
  1846     'apfs',
  1848     b'apfs',
  1847     'btrfs',
  1849     b'btrfs',
  1848     'ext2',
  1850     b'ext2',
  1849     'ext3',
  1851     b'ext3',
  1850     'ext4',
  1852     b'ext4',
  1851     'hfs',
  1853     b'hfs',
  1852     'jfs',
  1854     b'jfs',
  1853     'NTFS',
  1855     b'NTFS',
  1854     'reiserfs',
  1856     b'reiserfs',
  1855     'tmpfs',
  1857     b'tmpfs',
  1856     'ufs',
  1858     b'ufs',
  1857     'xfs',
  1859     b'xfs',
  1858     'zfs',
  1860     b'zfs',
  1859 }
  1861 }
  1860 
  1862 
  1861 
  1863 
  1862 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
  1864 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
  1863     '''copy a file, preserving mode and optionally other stat info like
  1865     '''copy a file, preserving mode and optionally other stat info like
  1918     """Copy a directory tree using hardlinks if possible."""
  1920     """Copy a directory tree using hardlinks if possible."""
  1919     num = 0
  1921     num = 0
  1920 
  1922 
  1921     def settopic():
  1923     def settopic():
  1922         if progress:
  1924         if progress:
  1923             progress.topic = _('linking') if hardlink else _('copying')
  1925             progress.topic = _(b'linking') if hardlink else _(b'copying')
  1924 
  1926 
  1925     if os.path.isdir(src):
  1927     if os.path.isdir(src):
  1926         if hardlink is None:
  1928         if hardlink is None:
  1927             hardlink = (
  1929             hardlink = (
  1928                 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
  1930                 os.stat(src).st_dev == os.stat(os.path.dirname(dst)).st_dev
  1956 
  1958 
  1957     return hardlink, num
  1959     return hardlink, num
  1958 
  1960 
  1959 
  1961 
  1960 _winreservednames = {
  1962 _winreservednames = {
  1961     'con',
  1963     b'con',
  1962     'prn',
  1964     b'prn',
  1963     'aux',
  1965     b'aux',
  1964     'nul',
  1966     b'nul',
  1965     'com1',
  1967     b'com1',
  1966     'com2',
  1968     b'com2',
  1967     'com3',
  1969     b'com3',
  1968     'com4',
  1970     b'com4',
  1969     'com5',
  1971     b'com5',
  1970     'com6',
  1972     b'com6',
  1971     'com7',
  1973     b'com7',
  1972     'com8',
  1974     b'com8',
  1973     'com9',
  1975     b'com9',
  1974     'lpt1',
  1976     b'lpt1',
  1975     'lpt2',
  1977     b'lpt2',
  1976     'lpt3',
  1978     b'lpt3',
  1977     'lpt4',
  1979     b'lpt4',
  1978     'lpt5',
  1980     b'lpt5',
  1979     'lpt6',
  1981     b'lpt6',
  1980     'lpt7',
  1982     b'lpt7',
  1981     'lpt8',
  1983     b'lpt8',
  1982     'lpt9',
  1984     b'lpt9',
  1983 }
  1985 }
  1984 _winreservedchars = ':*?"<>|'
  1986 _winreservedchars = b':*?"<>|'
  1985 
  1987 
  1986 
  1988 
  1987 def checkwinfilename(path):
  1989 def checkwinfilename(path):
  1988     r'''Check that the base-relative path is a valid filename on Windows.
  1990     r'''Check that the base-relative path is a valid filename on Windows.
  1989     Returns None if the path is ok, or a UI string describing the problem.
  1991     Returns None if the path is ok, or a UI string describing the problem.
  2006     >>> checkwinfilename(b"foo\\")
  2008     >>> checkwinfilename(b"foo\\")
  2007     "filename ends with '\\', which is invalid on Windows"
  2009     "filename ends with '\\', which is invalid on Windows"
  2008     >>> checkwinfilename(b"foo\\/bar")
  2010     >>> checkwinfilename(b"foo\\/bar")
  2009     "directory name ends with '\\', which is invalid on Windows"
  2011     "directory name ends with '\\', which is invalid on Windows"
  2010     '''
  2012     '''
  2011     if path.endswith('\\'):
  2013     if path.endswith(b'\\'):
  2012         return _("filename ends with '\\', which is invalid on Windows")
  2014         return _(b"filename ends with '\\', which is invalid on Windows")
  2013     if '\\/' in path:
  2015     if b'\\/' in path:
  2014         return _("directory name ends with '\\', which is invalid on Windows")
  2016         return _(b"directory name ends with '\\', which is invalid on Windows")
  2015     for n in path.replace('\\', '/').split('/'):
  2017     for n in path.replace(b'\\', b'/').split(b'/'):
  2016         if not n:
  2018         if not n:
  2017             continue
  2019             continue
  2018         for c in _filenamebytestr(n):
  2020         for c in _filenamebytestr(n):
  2019             if c in _winreservedchars:
  2021             if c in _winreservedchars:
  2020                 return (
  2022                 return (
  2021                     _("filename contains '%s', which is reserved " "on Windows")
  2023                     _(
       
  2024                         b"filename contains '%s', which is reserved "
       
  2025                         b"on Windows"
       
  2026                     )
  2022                     % c
  2027                     % c
  2023                 )
  2028                 )
  2024             if ord(c) <= 31:
  2029             if ord(c) <= 31:
  2025                 return _(
  2030                 return _(
  2026                     "filename contains '%s', which is invalid " "on Windows"
  2031                     b"filename contains '%s', which is invalid " b"on Windows"
  2027                 ) % stringutil.escapestr(c)
  2032                 ) % stringutil.escapestr(c)
  2028         base = n.split('.')[0]
  2033         base = n.split(b'.')[0]
  2029         if base and base.lower() in _winreservednames:
  2034         if base and base.lower() in _winreservednames:
  2030             return (
  2035             return (
  2031                 _("filename contains '%s', which is reserved " "on Windows")
  2036                 _(b"filename contains '%s', which is reserved " b"on Windows")
  2032                 % base
  2037                 % base
  2033             )
  2038             )
  2034         t = n[-1:]
  2039         t = n[-1:]
  2035         if t in '. ' and n not in '..':
  2040         if t in b'. ' and n not in b'..':
  2036             return (
  2041             return (
  2037                 _("filename ends with '%s', which is not allowed " "on Windows")
  2042                 _(
       
  2043                     b"filename ends with '%s', which is not allowed "
       
  2044                     b"on Windows"
       
  2045                 )
  2038                 % t
  2046                 % t
  2039             )
  2047             )
  2040 
  2048 
  2041 
  2049 
  2042 if pycompat.iswindows:
  2050 if pycompat.iswindows:
  2076     except OSError as why:
  2084     except OSError as why:
  2077         if why.errno not in (errno.EINVAL, errno.ENOSYS):
  2085         if why.errno not in (errno.EINVAL, errno.ENOSYS):
  2078             raise
  2086             raise
  2079     except AttributeError:  # no symlink in os
  2087     except AttributeError:  # no symlink in os
  2080         pass
  2088         pass
  2081     with posixfile(pathname, 'rb') as fp:
  2089     with posixfile(pathname, b'rb') as fp:
  2082         return fp.read()
  2090         return fp.read()
  2083 
  2091 
  2084 
  2092 
  2085 def fstat(fp):
  2093 def fstat(fp):
  2086     '''stat file object that may not have fileno method.'''
  2094     '''stat file object that may not have fileno method.'''
  2128 class _re(object):
  2136 class _re(object):
  2129     def _checkre2(self):
  2137     def _checkre2(self):
  2130         global _re2
  2138         global _re2
  2131         try:
  2139         try:
  2132             # check if match works, see issue3964
  2140             # check if match works, see issue3964
  2133             _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
  2141             _re2 = bool(re2.match(r'\[([^\[]+)\]', b'[ui]'))
  2134         except ImportError:
  2142         except ImportError:
  2135             _re2 = False
  2143             _re2 = False
  2136 
  2144 
  2137     def compile(self, pat, flags=0):
  2145     def compile(self, pat, flags=0):
  2138         '''Compile a regular expression, using re2 if possible
  2146         '''Compile a regular expression, using re2 if possible
  2142         IGNORECASE and MULTILINE.'''
  2150         IGNORECASE and MULTILINE.'''
  2143         if _re2 is None:
  2151         if _re2 is None:
  2144             self._checkre2()
  2152             self._checkre2()
  2145         if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
  2153         if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
  2146             if flags & remod.IGNORECASE:
  2154             if flags & remod.IGNORECASE:
  2147                 pat = '(?i)' + pat
  2155                 pat = b'(?i)' + pat
  2148             if flags & remod.MULTILINE:
  2156             if flags & remod.MULTILINE:
  2149                 pat = '(?m)' + pat
  2157                 pat = b'(?m)' + pat
  2150             try:
  2158             try:
  2151                 return re2.compile(pat)
  2159                 return re2.compile(pat)
  2152             except re2.error:
  2160             except re2.error:
  2153                 pass
  2161                 pass
  2154         return remod.compile(pat, flags)
  2162         return remod.compile(pat, flags)
  2190 
  2198 
  2191     seps = pycompat.ossep
  2199     seps = pycompat.ossep
  2192     if pycompat.osaltsep:
  2200     if pycompat.osaltsep:
  2193         seps = seps + pycompat.osaltsep
  2201         seps = seps + pycompat.osaltsep
  2194     # Protect backslashes. This gets silly very quickly.
  2202     # Protect backslashes. This gets silly very quickly.
  2195     seps.replace('\\', '\\\\')
  2203     seps.replace(b'\\', b'\\\\')
  2196     pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
  2204     pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
  2197     dir = os.path.normpath(root)
  2205     dir = os.path.normpath(root)
  2198     result = []
  2206     result = []
  2199     for part, sep in pattern.findall(name):
  2207     for part, sep in pattern.findall(name):
  2200         if sep:
  2208         if sep:
  2213             found = contents.get(part)
  2221             found = contents.get(part)
  2214 
  2222 
  2215         result.append(found or part)
  2223         result.append(found or part)
  2216         dir = os.path.join(dir, part)
  2224         dir = os.path.join(dir, part)
  2217 
  2225 
  2218     return ''.join(result)
  2226     return b''.join(result)
  2219 
  2227 
  2220 
  2228 
  2221 def checknlink(testfile):
  2229 def checknlink(testfile):
  2222     '''check whether hardlink count reporting works properly'''
  2230     '''check whether hardlink count reporting works properly'''
  2223 
  2231 
  2224     # testfile may be open, so we need a separate file for checking to
  2232     # testfile may be open, so we need a separate file for checking to
  2225     # work around issue2543 (or testfile may get lost on Samba shares)
  2233     # work around issue2543 (or testfile may get lost on Samba shares)
  2226     f1, f2, fp = None, None, None
  2234     f1, f2, fp = None, None, None
  2227     try:
  2235     try:
  2228         fd, f1 = pycompat.mkstemp(
  2236         fd, f1 = pycompat.mkstemp(
  2229             prefix='.%s-' % os.path.basename(testfile),
  2237             prefix=b'.%s-' % os.path.basename(testfile),
  2230             suffix='1~',
  2238             suffix=b'1~',
  2231             dir=os.path.dirname(testfile),
  2239             dir=os.path.dirname(testfile),
  2232         )
  2240         )
  2233         os.close(fd)
  2241         os.close(fd)
  2234         f2 = '%s2~' % f1[:-2]
  2242         f2 = b'%s2~' % f1[:-2]
  2235 
  2243 
  2236         oslink(f1, f2)
  2244         oslink(f1, f2)
  2237         # nlinks() may behave differently for files on Windows shares if
  2245         # nlinks() may behave differently for files on Windows shares if
  2238         # the file is open.
  2246         # the file is open.
  2239         fp = posixfile(f2)
  2247         fp = posixfile(f2)
  2278     can use emptyok=True as an optimization.
  2286     can use emptyok=True as an optimization.
  2279 
  2287 
  2280     Returns the name of the temporary file.
  2288     Returns the name of the temporary file.
  2281     """
  2289     """
  2282     d, fn = os.path.split(name)
  2290     d, fn = os.path.split(name)
  2283     fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
  2291     fd, temp = pycompat.mkstemp(prefix=b'.%s-' % fn, suffix=b'~', dir=d)
  2284     os.close(fd)
  2292     os.close(fd)
  2285     # Temporary files are created with mode 0600, which is usually not
  2293     # Temporary files are created with mode 0600, which is usually not
  2286     # what we want.  If the original file already exists, just copy
  2294     # what we want.  If the original file already exists, just copy
  2287     # its mode.  Otherwise, manually obey umask.
  2295     # its mode.  Otherwise, manually obey umask.
  2288     copymode(name, temp, createmode, enforcewritable)
  2296     copymode(name, temp, createmode, enforcewritable)
  2289 
  2297 
  2290     if emptyok:
  2298     if emptyok:
  2291         return temp
  2299         return temp
  2292     try:
  2300     try:
  2293         try:
  2301         try:
  2294             ifp = posixfile(name, "rb")
  2302             ifp = posixfile(name, b"rb")
  2295         except IOError as inst:
  2303         except IOError as inst:
  2296             if inst.errno == errno.ENOENT:
  2304             if inst.errno == errno.ENOENT:
  2297                 return temp
  2305                 return temp
  2298             if not getattr(inst, 'filename', None):
  2306             if not getattr(inst, 'filename', None):
  2299                 inst.filename = name
  2307                 inst.filename = name
  2300             raise
  2308             raise
  2301         ofp = posixfile(temp, "wb")
  2309         ofp = posixfile(temp, b"wb")
  2302         for chunk in filechunkiter(ifp):
  2310         for chunk in filechunkiter(ifp):
  2303             ofp.write(chunk)
  2311             ofp.write(chunk)
  2304         ifp.close()
  2312         ifp.close()
  2305         ofp.close()
  2313         ofp.close()
  2306     except:  # re-raises
  2314     except:  # re-raises
  2430     checkambig argument of constructor is used with filestat, and is
  2438     checkambig argument of constructor is used with filestat, and is
  2431     useful only if target file is guarded by any lock (e.g. repo.lock
  2439     useful only if target file is guarded by any lock (e.g. repo.lock
  2432     or repo.wlock).
  2440     or repo.wlock).
  2433     '''
  2441     '''
  2434 
  2442 
  2435     def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
  2443     def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
  2436         self.__name = name  # permanent name
  2444         self.__name = name  # permanent name
  2437         self._tempname = mktempcopy(
  2445         self._tempname = mktempcopy(
  2438             name,
  2446             name,
  2439             emptyok=('w' in mode),
  2447             emptyok=(b'w' in mode),
  2440             createmode=createmode,
  2448             createmode=createmode,
  2441             enforcewritable=('w' in mode),
  2449             enforcewritable=(b'w' in mode),
  2442         )
  2450         )
  2443 
  2451 
  2444         self._fp = posixfile(self._tempname, mode)
  2452         self._fp = posixfile(self._tempname, mode)
  2445         self._checkambig = checkambig
  2453         self._checkambig = checkambig
  2446 
  2454 
  2539     if mode is not None:
  2547     if mode is not None:
  2540         os.chmod(name, mode)
  2548         os.chmod(name, mode)
  2541 
  2549 
  2542 
  2550 
  2543 def readfile(path):
  2551 def readfile(path):
  2544     with open(path, 'rb') as fp:
  2552     with open(path, b'rb') as fp:
  2545         return fp.read()
  2553         return fp.read()
  2546 
  2554 
  2547 
  2555 
  2548 def writefile(path, text):
  2556 def writefile(path, text):
  2549     with open(path, 'wb') as fp:
  2557     with open(path, b'wb') as fp:
  2550         fp.write(text)
  2558         fp.write(text)
  2551 
  2559 
  2552 
  2560 
  2553 def appendfile(path, text):
  2561 def appendfile(path, text):
  2554     with open(path, 'ab') as fp:
  2562     with open(path, b'ab') as fp:
  2555         fp.write(text)
  2563         fp.write(text)
  2556 
  2564 
  2557 
  2565 
  2558 class chunkbuffer(object):
  2566 class chunkbuffer(object):
  2559     """Allow arbitrary sized chunks of data to be efficiently read from an
  2567     """Allow arbitrary sized chunks of data to be efficiently read from an
  2581         """Read L bytes of data from the iterator of chunks of data.
  2589         """Read L bytes of data from the iterator of chunks of data.
  2582         Returns less than L bytes if the iterator runs dry.
  2590         Returns less than L bytes if the iterator runs dry.
  2583 
  2591 
  2584         If size parameter is omitted, read everything"""
  2592         If size parameter is omitted, read everything"""
  2585         if l is None:
  2593         if l is None:
  2586             return ''.join(self.iter)
  2594             return b''.join(self.iter)
  2587 
  2595 
  2588         left = l
  2596         left = l
  2589         buf = []
  2597         buf = []
  2590         queue = self._queue
  2598         queue = self._queue
  2591         while left > 0:
  2599         while left > 0:
  2633             else:
  2641             else:
  2634                 buf.append(chunk[offset : offset + left])
  2642                 buf.append(chunk[offset : offset + left])
  2635                 self._chunkoffset += left
  2643                 self._chunkoffset += left
  2636                 left -= chunkremaining
  2644                 left -= chunkremaining
  2637 
  2645 
  2638         return ''.join(buf)
  2646         return b''.join(buf)
  2639 
  2647 
  2640 
  2648 
  2641 def filechunkiter(f, size=131072, limit=None):
  2649 def filechunkiter(f, size=131072, limit=None):
  2642     """Create a generator that produces the data in the file size
  2650     """Create a generator that produces the data in the file size
  2643     (default 131072) bytes at a time, up to optional limit (default is
  2651     (default 131072) bytes at a time, up to optional limit (default is
  2725     Traceback (most recent call last):
  2733     Traceback (most recent call last):
  2726         ...
  2734         ...
  2727     ParseError: fromline must be strictly positive
  2735     ParseError: fromline must be strictly positive
  2728     """
  2736     """
  2729     if toline - fromline < 0:
  2737     if toline - fromline < 0:
  2730         raise error.ParseError(_("line range must be positive"))
  2738         raise error.ParseError(_(b"line range must be positive"))
  2731     if fromline < 1:
  2739     if fromline < 1:
  2732         raise error.ParseError(_("fromline must be strictly positive"))
  2740         raise error.ParseError(_(b"fromline must be strictly positive"))
  2733     return fromline - 1, toline
  2741     return fromline - 1, toline
  2734 
  2742 
  2735 
  2743 
  2736 bytecount = unitcountfn(
  2744 bytecount = unitcountfn(
  2737     (100, 1 << 30, _('%.0f GB')),
  2745     (100, 1 << 30, _(b'%.0f GB')),
  2738     (10, 1 << 30, _('%.1f GB')),
  2746     (10, 1 << 30, _(b'%.1f GB')),
  2739     (1, 1 << 30, _('%.2f GB')),
  2747     (1, 1 << 30, _(b'%.2f GB')),
  2740     (100, 1 << 20, _('%.0f MB')),
  2748     (100, 1 << 20, _(b'%.0f MB')),
  2741     (10, 1 << 20, _('%.1f MB')),
  2749     (10, 1 << 20, _(b'%.1f MB')),
  2742     (1, 1 << 20, _('%.2f MB')),
  2750     (1, 1 << 20, _(b'%.2f MB')),
  2743     (100, 1 << 10, _('%.0f KB')),
  2751     (100, 1 << 10, _(b'%.0f KB')),
  2744     (10, 1 << 10, _('%.1f KB')),
  2752     (10, 1 << 10, _(b'%.1f KB')),
  2745     (1, 1 << 10, _('%.2f KB')),
  2753     (1, 1 << 10, _(b'%.2f KB')),
  2746     (1, 1, _('%.0f bytes')),
  2754     (1, 1, _(b'%.0f bytes')),
  2747 )
  2755 )
  2748 
  2756 
  2749 
  2757 
  2750 class transformingwriter(object):
  2758 class transformingwriter(object):
  2751     """Writable file wrapper to transform data by function"""
  2759     """Writable file wrapper to transform data by function"""
  2769 # stray CR is an error.
  2777 # stray CR is an error.
  2770 _eolre = remod.compile(br'\r*\n')
  2778 _eolre = remod.compile(br'\r*\n')
  2771 
  2779 
  2772 
  2780 
  2773 def tolf(s):
  2781 def tolf(s):
  2774     return _eolre.sub('\n', s)
  2782     return _eolre.sub(b'\n', s)
  2775 
  2783 
  2776 
  2784 
  2777 def tocrlf(s):
  2785 def tocrlf(s):
  2778     return _eolre.sub('\r\n', s)
  2786     return _eolre.sub(b'\r\n', s)
  2779 
  2787 
  2780 
  2788 
  2781 def _crlfwriter(fp):
  2789 def _crlfwriter(fp):
  2782     return transformingwriter(fp, tocrlf)
  2790     return transformingwriter(fp, tocrlf)
  2783 
  2791 
  2784 
  2792 
  2785 if pycompat.oslinesep == '\r\n':
  2793 if pycompat.oslinesep == b'\r\n':
  2786     tonativeeol = tocrlf
  2794     tonativeeol = tocrlf
  2787     fromnativeeol = tolf
  2795     fromnativeeol = tolf
  2788     nativeeolwriter = _crlfwriter
  2796     nativeeolwriter = _crlfwriter
  2789 else:
  2797 else:
  2790     tonativeeol = pycompat.identity
  2798     tonativeeol = pycompat.identity
  2791     fromnativeeol = pycompat.identity
  2799     fromnativeeol = pycompat.identity
  2792     nativeeolwriter = pycompat.identity
  2800     nativeeolwriter = pycompat.identity
  2793 
  2801 
  2794 if pyplatform.python_implementation() == 'CPython' and sys.version_info < (
  2802 if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
  2795     3,
  2803     3,
  2796     0,
  2804     0,
  2797 ):
  2805 ):
  2798     # There is an issue in CPython that some IO methods do not handle EINTR
  2806     # There is an issue in CPython that some IO methods do not handle EINTR
  2799     # correctly. The following table shows what CPython version (and functions)
  2807     # correctly. The following table shows what CPython version (and functions)
  2820     # files approximately as "fast" files and use the fast (unsafe) code path,
  2828     # files approximately as "fast" files and use the fast (unsafe) code path,
  2821     # to minimize the performance impact.
  2829     # to minimize the performance impact.
  2822     if sys.version_info >= (2, 7, 4):
  2830     if sys.version_info >= (2, 7, 4):
  2823         # fp.readline deals with EINTR correctly, use it as a workaround.
  2831         # fp.readline deals with EINTR correctly, use it as a workaround.
  2824         def _safeiterfile(fp):
  2832         def _safeiterfile(fp):
  2825             return iter(fp.readline, '')
  2833             return iter(fp.readline, b'')
  2826 
  2834 
  2827     else:
  2835     else:
  2828         # fp.read* are broken too, manually deal with EINTR in a stupid way.
  2836         # fp.read* are broken too, manually deal with EINTR in a stupid way.
  2829         # note: this may block longer than necessary because of bufsize.
  2837         # note: this may block longer than necessary because of bufsize.
  2830         def _safeiterfile(fp, bufsize=4096):
  2838         def _safeiterfile(fp, bufsize=4096):
  2831             fd = fp.fileno()
  2839             fd = fp.fileno()
  2832             line = ''
  2840             line = b''
  2833             while True:
  2841             while True:
  2834                 try:
  2842                 try:
  2835                     buf = os.read(fd, bufsize)
  2843                     buf = os.read(fd, bufsize)
  2836                 except OSError as ex:
  2844                 except OSError as ex:
  2837                     # os.read only raises EINTR before any data is read
  2845                     # os.read only raises EINTR before any data is read
  2838                     if ex.errno == errno.EINTR:
  2846                     if ex.errno == errno.EINTR:
  2839                         continue
  2847                         continue
  2840                     else:
  2848                     else:
  2841                         raise
  2849                         raise
  2842                 line += buf
  2850                 line += buf
  2843                 if '\n' in buf:
  2851                 if b'\n' in buf:
  2844                     splitted = line.splitlines(True)
  2852                     splitted = line.splitlines(True)
  2845                     line = ''
  2853                     line = b''
  2846                     for l in splitted:
  2854                     for l in splitted:
  2847                         if l[-1] == '\n':
  2855                         if l[-1] == b'\n':
  2848                             yield l
  2856                             yield l
  2849                         else:
  2857                         else:
  2850                             line = l
  2858                             line = l
  2851                 if not buf:
  2859                 if not buf:
  2852                     break
  2860                     break
  2891 
  2899 
  2892     escape_prefix is an optional flag that allows using doubled prefix for
  2900     escape_prefix is an optional flag that allows using doubled prefix for
  2893     its escaping.
  2901     its escaping.
  2894     """
  2902     """
  2895     fn = fn or (lambda s: s)
  2903     fn = fn or (lambda s: s)
  2896     patterns = '|'.join(mapping.keys())
  2904     patterns = b'|'.join(mapping.keys())
  2897     if escape_prefix:
  2905     if escape_prefix:
  2898         patterns += '|' + prefix
  2906         patterns += b'|' + prefix
  2899         if len(prefix) > 1:
  2907         if len(prefix) > 1:
  2900             prefix_char = prefix[1:]
  2908             prefix_char = prefix[1:]
  2901         else:
  2909         else:
  2902             prefix_char = prefix
  2910             prefix_char = prefix
  2903         mapping[prefix_char] = prefix_char
  2911         mapping[prefix_char] = prefix_char
  2919 
  2927 
  2920     try:
  2928     try:
  2921         return socket.getservbyname(pycompat.sysstr(port))
  2929         return socket.getservbyname(pycompat.sysstr(port))
  2922     except socket.error:
  2930     except socket.error:
  2923         raise error.Abort(
  2931         raise error.Abort(
  2924             _("no port number associated with service '%s'") % port
  2932             _(b"no port number associated with service '%s'") % port
  2925         )
  2933         )
  2926 
  2934 
  2927 
  2935 
  2928 class url(object):
  2936 class url(object):
  2929     r"""Reliable URL parser.
  2937     r"""Reliable URL parser.
  2997 
  3005 
  2998     >>> url(b'http:')
  3006     >>> url(b'http:')
  2999     <url scheme: 'http'>
  3007     <url scheme: 'http'>
  3000     """
  3008     """
  3001 
  3009 
  3002     _safechars = "!~*'()+"
  3010     _safechars = b"!~*'()+"
  3003     _safepchars = "/!~*'()+:\\"
  3011     _safepchars = b"/!~*'()+:\\"
  3004     _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
  3012     _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
  3005 
  3013 
  3006     def __init__(self, path, parsequery=True, parsefragment=True):
  3014     def __init__(self, path, parsequery=True, parsefragment=True):
  3007         # We slowly chomp away at path until we have only the path left
  3015         # We slowly chomp away at path until we have only the path left
  3008         self.scheme = self.user = self.passwd = self.host = None
  3016         self.scheme = self.user = self.passwd = self.host = None
  3009         self.port = self.path = self.query = self.fragment = None
  3017         self.port = self.path = self.query = self.fragment = None
  3010         self._localpath = True
  3018         self._localpath = True
  3011         self._hostport = ''
  3019         self._hostport = b''
  3012         self._origpath = path
  3020         self._origpath = path
  3013 
  3021 
  3014         if parsefragment and '#' in path:
  3022         if parsefragment and b'#' in path:
  3015             path, self.fragment = path.split('#', 1)
  3023             path, self.fragment = path.split(b'#', 1)
  3016 
  3024 
  3017         # special case for Windows drive letters and UNC paths
  3025         # special case for Windows drive letters and UNC paths
  3018         if hasdriveletter(path) or path.startswith('\\\\'):
  3026         if hasdriveletter(path) or path.startswith(b'\\\\'):
  3019             self.path = path
  3027             self.path = path
  3020             return
  3028             return
  3021 
  3029 
  3022         # For compatibility reasons, we can't handle bundle paths as
  3030         # For compatibility reasons, we can't handle bundle paths as
  3023         # normal URLS
  3031         # normal URLS
  3024         if path.startswith('bundle:'):
  3032         if path.startswith(b'bundle:'):
  3025             self.scheme = 'bundle'
  3033             self.scheme = b'bundle'
  3026             path = path[7:]
  3034             path = path[7:]
  3027             if path.startswith('//'):
  3035             if path.startswith(b'//'):
  3028                 path = path[2:]
  3036                 path = path[2:]
  3029             self.path = path
  3037             self.path = path
  3030             return
  3038             return
  3031 
  3039 
  3032         if self._matchscheme(path):
  3040         if self._matchscheme(path):
  3033             parts = path.split(':', 1)
  3041             parts = path.split(b':', 1)
  3034             if parts[0]:
  3042             if parts[0]:
  3035                 self.scheme, path = parts
  3043                 self.scheme, path = parts
  3036                 self._localpath = False
  3044                 self._localpath = False
  3037 
  3045 
  3038         if not path:
  3046         if not path:
  3039             path = None
  3047             path = None
  3040             if self._localpath:
  3048             if self._localpath:
  3041                 self.path = ''
  3049                 self.path = b''
  3042                 return
  3050                 return
  3043         else:
  3051         else:
  3044             if self._localpath:
  3052             if self._localpath:
  3045                 self.path = path
  3053                 self.path = path
  3046                 return
  3054                 return
  3047 
  3055 
  3048             if parsequery and '?' in path:
  3056             if parsequery and b'?' in path:
  3049                 path, self.query = path.split('?', 1)
  3057                 path, self.query = path.split(b'?', 1)
  3050                 if not path:
  3058                 if not path:
  3051                     path = None
  3059                     path = None
  3052                 if not self.query:
  3060                 if not self.query:
  3053                     self.query = None
  3061                     self.query = None
  3054 
  3062 
  3055             # // is required to specify a host/authority
  3063             # // is required to specify a host/authority
  3056             if path and path.startswith('//'):
  3064             if path and path.startswith(b'//'):
  3057                 parts = path[2:].split('/', 1)
  3065                 parts = path[2:].split(b'/', 1)
  3058                 if len(parts) > 1:
  3066                 if len(parts) > 1:
  3059                     self.host, path = parts
  3067                     self.host, path = parts
  3060                 else:
  3068                 else:
  3061                     self.host = parts[0]
  3069                     self.host = parts[0]
  3062                     path = None
  3070                     path = None
  3063                 if not self.host:
  3071                 if not self.host:
  3064                     self.host = None
  3072                     self.host = None
  3065                     # path of file:///d is /d
  3073                     # path of file:///d is /d
  3066                     # path of file:///d:/ is d:/, not /d:/
  3074                     # path of file:///d:/ is d:/, not /d:/
  3067                     if path and not hasdriveletter(path):
  3075                     if path and not hasdriveletter(path):
  3068                         path = '/' + path
  3076                         path = b'/' + path
  3069 
  3077 
  3070             if self.host and '@' in self.host:
  3078             if self.host and b'@' in self.host:
  3071                 self.user, self.host = self.host.rsplit('@', 1)
  3079                 self.user, self.host = self.host.rsplit(b'@', 1)
  3072                 if ':' in self.user:
  3080                 if b':' in self.user:
  3073                     self.user, self.passwd = self.user.split(':', 1)
  3081                     self.user, self.passwd = self.user.split(b':', 1)
  3074                 if not self.host:
  3082                 if not self.host:
  3075                     self.host = None
  3083                     self.host = None
  3076 
  3084 
  3077             # Don't split on colons in IPv6 addresses without ports
  3085             # Don't split on colons in IPv6 addresses without ports
  3078             if (
  3086             if (
  3079                 self.host
  3087                 self.host
  3080                 and ':' in self.host
  3088                 and b':' in self.host
  3081                 and not (self.host.startswith('[') and self.host.endswith(']'))
  3089                 and not (
       
  3090                     self.host.startswith(b'[') and self.host.endswith(b']')
       
  3091                 )
  3082             ):
  3092             ):
  3083                 self._hostport = self.host
  3093                 self._hostport = self.host
  3084                 self.host, self.port = self.host.rsplit(':', 1)
  3094                 self.host, self.port = self.host.rsplit(b':', 1)
  3085                 if not self.host:
  3095                 if not self.host:
  3086                     self.host = None
  3096                     self.host = None
  3087 
  3097 
  3088             if (
  3098             if (
  3089                 self.host
  3099                 self.host
  3090                 and self.scheme == 'file'
  3100                 and self.scheme == b'file'
  3091                 and self.host not in ('localhost', '127.0.0.1', '[::1]')
  3101                 and self.host not in (b'localhost', b'127.0.0.1', b'[::1]')
  3092             ):
  3102             ):
  3093                 raise error.Abort(_('file:// URLs can only refer to localhost'))
  3103                 raise error.Abort(
       
  3104                     _(b'file:// URLs can only refer to localhost')
       
  3105                 )
  3094 
  3106 
  3095         self.path = path
  3107         self.path = path
  3096 
  3108 
  3097         # leave the query string escaped
  3109         # leave the query string escaped
  3098         for a in ('user', 'passwd', 'host', 'port', 'path', 'fragment'):
  3110         for a in (b'user', b'passwd', b'host', b'port', b'path', b'fragment'):
  3099             v = getattr(self, a)
  3111             v = getattr(self, a)
  3100             if v is not None:
  3112             if v is not None:
  3101                 setattr(self, a, urlreq.unquote(v))
  3113                 setattr(self, a, urlreq.unquote(v))
  3102 
  3114 
  3103     @encoding.strmethod
  3115     @encoding.strmethod
  3104     def __repr__(self):
  3116     def __repr__(self):
  3105         attrs = []
  3117         attrs = []
  3106         for a in (
  3118         for a in (
  3107             'scheme',
  3119             b'scheme',
  3108             'user',
  3120             b'user',
  3109             'passwd',
  3121             b'passwd',
  3110             'host',
  3122             b'host',
  3111             'port',
  3123             b'port',
  3112             'path',
  3124             b'path',
  3113             'query',
  3125             b'query',
  3114             'fragment',
  3126             b'fragment',
  3115         ):
  3127         ):
  3116             v = getattr(self, a)
  3128             v = getattr(self, a)
  3117             if v is not None:
  3129             if v is not None:
  3118                 attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
  3130                 attrs.append(b'%s: %r' % (a, pycompat.bytestr(v)))
  3119         return '<url %s>' % ', '.join(attrs)
  3131         return b'<url %s>' % b', '.join(attrs)
  3120 
  3132 
  3121     def __bytes__(self):
  3133     def __bytes__(self):
  3122         r"""Join the URL's components back into a URL string.
  3134         r"""Join the URL's components back into a URL string.
  3123 
  3135 
  3124         Examples:
  3136         Examples:
  3152         >>> print(url(br'file:///D:\data\hg'))
  3164         >>> print(url(br'file:///D:\data\hg'))
  3153         file:///D:\data\hg
  3165         file:///D:\data\hg
  3154         """
  3166         """
  3155         if self._localpath:
  3167         if self._localpath:
  3156             s = self.path
  3168             s = self.path
  3157             if self.scheme == 'bundle':
  3169             if self.scheme == b'bundle':
  3158                 s = 'bundle:' + s
  3170                 s = b'bundle:' + s
  3159             if self.fragment:
  3171             if self.fragment:
  3160                 s += '#' + self.fragment
  3172                 s += b'#' + self.fragment
  3161             return s
  3173             return s
  3162 
  3174 
  3163         s = self.scheme + ':'
  3175         s = self.scheme + b':'
  3164         if self.user or self.passwd or self.host:
  3176         if self.user or self.passwd or self.host:
  3165             s += '//'
  3177             s += b'//'
  3166         elif self.scheme and (
  3178         elif self.scheme and (
  3167             not self.path
  3179             not self.path
  3168             or self.path.startswith('/')
  3180             or self.path.startswith(b'/')
  3169             or hasdriveletter(self.path)
  3181             or hasdriveletter(self.path)
  3170         ):
  3182         ):
  3171             s += '//'
  3183             s += b'//'
  3172             if hasdriveletter(self.path):
  3184             if hasdriveletter(self.path):
  3173                 s += '/'
  3185                 s += b'/'
  3174         if self.user:
  3186         if self.user:
  3175             s += urlreq.quote(self.user, safe=self._safechars)
  3187             s += urlreq.quote(self.user, safe=self._safechars)
  3176         if self.passwd:
  3188         if self.passwd:
  3177             s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
  3189             s += b':' + urlreq.quote(self.passwd, safe=self._safechars)
  3178         if self.user or self.passwd:
  3190         if self.user or self.passwd:
  3179             s += '@'
  3191             s += b'@'
  3180         if self.host:
  3192         if self.host:
  3181             if not (self.host.startswith('[') and self.host.endswith(']')):
  3193             if not (self.host.startswith(b'[') and self.host.endswith(b']')):
  3182                 s += urlreq.quote(self.host)
  3194                 s += urlreq.quote(self.host)
  3183             else:
  3195             else:
  3184                 s += self.host
  3196                 s += self.host
  3185         if self.port:
  3197         if self.port:
  3186             s += ':' + urlreq.quote(self.port)
  3198             s += b':' + urlreq.quote(self.port)
  3187         if self.host:
  3199         if self.host:
  3188             s += '/'
  3200             s += b'/'
  3189         if self.path:
  3201         if self.path:
  3190             # TODO: similar to the query string, we should not unescape the
  3202             # TODO: similar to the query string, we should not unescape the
  3191             # path when we store it, the path might contain '%2f' = '/',
  3203             # path when we store it, the path might contain '%2f' = '/',
  3192             # which we should *not* escape.
  3204             # which we should *not* escape.
  3193             s += urlreq.quote(self.path, safe=self._safepchars)
  3205             s += urlreq.quote(self.path, safe=self._safepchars)
  3194         if self.query:
  3206         if self.query:
  3195             # we store the query in escaped form.
  3207             # we store the query in escaped form.
  3196             s += '?' + self.query
  3208             s += b'?' + self.query
  3197         if self.fragment is not None:
  3209         if self.fragment is not None:
  3198             s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
  3210             s += b'#' + urlreq.quote(self.fragment, safe=self._safepchars)
  3199         return s
  3211         return s
  3200 
  3212 
  3201     __str__ = encoding.strmethod(__bytes__)
  3213     __str__ = encoding.strmethod(__bytes__)
  3202 
  3214 
  3203     def authinfo(self):
  3215     def authinfo(self):
  3211             return (s, None)
  3223             return (s, None)
  3212         # authinfo[1] is passed to urllib2 password manager, and its
  3224         # authinfo[1] is passed to urllib2 password manager, and its
  3213         # URIs must not contain credentials. The host is passed in the
  3225         # URIs must not contain credentials. The host is passed in the
  3214         # URIs list because Python < 2.4.3 uses only that to search for
  3226         # URIs list because Python < 2.4.3 uses only that to search for
  3215         # a password.
  3227         # a password.
  3216         return (s, (None, (s, self.host), self.user, self.passwd or ''))
  3228         return (s, (None, (s, self.host), self.user, self.passwd or b''))
  3217 
  3229 
  3218     def isabs(self):
  3230     def isabs(self):
  3219         if self.scheme and self.scheme != 'file':
  3231         if self.scheme and self.scheme != b'file':
  3220             return True  # remote URL
  3232             return True  # remote URL
  3221         if hasdriveletter(self.path):
  3233         if hasdriveletter(self.path):
  3222             return True  # absolute for our purposes - can't be joined()
  3234             return True  # absolute for our purposes - can't be joined()
  3223         if self.path.startswith(br'\\'):
  3235         if self.path.startswith(br'\\'):
  3224             return True  # Windows UNC path
  3236             return True  # Windows UNC path
  3225         if self.path.startswith('/'):
  3237         if self.path.startswith(b'/'):
  3226             return True  # POSIX-style
  3238             return True  # POSIX-style
  3227         return False
  3239         return False
  3228 
  3240 
  3229     def localpath(self):
  3241     def localpath(self):
  3230         if self.scheme == 'file' or self.scheme == 'bundle':
  3242         if self.scheme == b'file' or self.scheme == b'bundle':
  3231             path = self.path or '/'
  3243             path = self.path or b'/'
  3232             # For Windows, we need to promote hosts containing drive
  3244             # For Windows, we need to promote hosts containing drive
  3233             # letters to paths with drive letters.
  3245             # letters to paths with drive letters.
  3234             if hasdriveletter(self._hostport):
  3246             if hasdriveletter(self._hostport):
  3235                 path = self._hostport + '/' + self.path
  3247                 path = self._hostport + b'/' + self.path
  3236             elif (
  3248             elif (
  3237                 self.host is not None and self.path and not hasdriveletter(path)
  3249                 self.host is not None and self.path and not hasdriveletter(path)
  3238             ):
  3250             ):
  3239                 path = '/' + path
  3251                 path = b'/' + path
  3240             return path
  3252             return path
  3241         return self._origpath
  3253         return self._origpath
  3242 
  3254 
  3243     def islocal(self):
  3255     def islocal(self):
  3244         '''whether localpath will return something that posixfile can open'''
  3256         '''whether localpath will return something that posixfile can open'''
  3245         return (
  3257         return (
  3246             not self.scheme or self.scheme == 'file' or self.scheme == 'bundle'
  3258             not self.scheme
       
  3259             or self.scheme == b'file'
       
  3260             or self.scheme == b'bundle'
  3247         )
  3261         )
  3248 
  3262 
  3249 
  3263 
  3250 def hasscheme(path):
  3264 def hasscheme(path):
  3251     return bool(url(path).scheme)
  3265     return bool(url(path).scheme)
  3252 
  3266 
  3253 
  3267 
  3254 def hasdriveletter(path):
  3268 def hasdriveletter(path):
  3255     return path and path[1:2] == ':' and path[0:1].isalpha()
  3269     return path and path[1:2] == b':' and path[0:1].isalpha()
  3256 
  3270 
  3257 
  3271 
  3258 def urllocalpath(path):
  3272 def urllocalpath(path):
  3259     return url(path, parsequery=False, parsefragment=False).localpath()
  3273     return url(path, parsequery=False, parsefragment=False).localpath()
  3260 
  3274 
  3268     user.
  3282     user.
  3269 
  3283 
  3270     Raises an error.Abort when the url is unsafe.
  3284     Raises an error.Abort when the url is unsafe.
  3271     """
  3285     """
  3272     path = urlreq.unquote(path)
  3286     path = urlreq.unquote(path)
  3273     if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
  3287     if path.startswith(b'ssh://-') or path.startswith(b'svn+ssh://-'):
  3274         raise error.Abort(
  3288         raise error.Abort(
  3275             _('potentially unsafe url: %r') % (pycompat.bytestr(path),)
  3289             _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
  3276         )
  3290         )
  3277 
  3291 
  3278 
  3292 
  3279 def hidepassword(u):
  3293 def hidepassword(u):
  3280     '''hide user credential in a url string'''
  3294     '''hide user credential in a url string'''
  3281     u = url(u)
  3295     u = url(u)
  3282     if u.passwd:
  3296     if u.passwd:
  3283         u.passwd = '***'
  3297         u.passwd = b'***'
  3284     return bytes(u)
  3298     return bytes(u)
  3285 
  3299 
  3286 
  3300 
  3287 def removeauth(u):
  3301 def removeauth(u):
  3288     '''remove all authentication information from a url string'''
  3302     '''remove all authentication information from a url string'''
  3290     u.user = u.passwd = None
  3304     u.user = u.passwd = None
  3291     return bytes(u)
  3305     return bytes(u)
  3292 
  3306 
  3293 
  3307 
  3294 timecount = unitcountfn(
  3308 timecount = unitcountfn(
  3295     (1, 1e3, _('%.0f s')),
  3309     (1, 1e3, _(b'%.0f s')),
  3296     (100, 1, _('%.1f s')),
  3310     (100, 1, _(b'%.1f s')),
  3297     (10, 1, _('%.2f s')),
  3311     (10, 1, _(b'%.2f s')),
  3298     (1, 1, _('%.3f s')),
  3312     (1, 1, _(b'%.3f s')),
  3299     (100, 0.001, _('%.1f ms')),
  3313     (100, 0.001, _(b'%.1f ms')),
  3300     (10, 0.001, _('%.2f ms')),
  3314     (10, 0.001, _(b'%.2f ms')),
  3301     (1, 0.001, _('%.3f ms')),
  3315     (1, 0.001, _(b'%.3f ms')),
  3302     (100, 0.000001, _('%.1f us')),
  3316     (100, 0.000001, _(b'%.1f us')),
  3303     (10, 0.000001, _('%.2f us')),
  3317     (10, 0.000001, _(b'%.2f us')),
  3304     (1, 0.000001, _('%.3f us')),
  3318     (1, 0.000001, _(b'%.3f us')),
  3305     (100, 0.000000001, _('%.1f ns')),
  3319     (100, 0.000000001, _(b'%.1f ns')),
  3306     (10, 0.000000001, _('%.2f ns')),
  3320     (10, 0.000000001, _(b'%.2f ns')),
  3307     (1, 0.000000001, _('%.3f ns')),
  3321     (1, 0.000000001, _(b'%.3f ns')),
  3308 )
  3322 )
  3309 
  3323 
  3310 
  3324 
  3311 @attr.s
  3325 @attr.s
  3312 class timedcmstats(object):
  3326 class timedcmstats(object):
  3320     elapsed = attr.ib(default=0)
  3334     elapsed = attr.ib(default=0)
  3321     # the number of nested timedcm context managers.
  3335     # the number of nested timedcm context managers.
  3322     level = attr.ib(default=1)
  3336     level = attr.ib(default=1)
  3323 
  3337 
  3324     def __bytes__(self):
  3338     def __bytes__(self):
  3325         return timecount(self.elapsed) if self.elapsed else '<unknown>'
  3339         return timecount(self.elapsed) if self.elapsed else b'<unknown>'
  3326 
  3340 
  3327     __str__ = encoding.strmethod(__bytes__)
  3341     __str__ = encoding.strmethod(__bytes__)
  3328 
  3342 
  3329 
  3343 
  3330 @contextlib.contextmanager
  3344 @contextlib.contextmanager
  3364     def wrapper(*args, **kwargs):
  3378     def wrapper(*args, **kwargs):
  3365         with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
  3379         with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
  3366             result = func(*args, **kwargs)
  3380             result = func(*args, **kwargs)
  3367         stderr = procutil.stderr
  3381         stderr = procutil.stderr
  3368         stderr.write(
  3382         stderr.write(
  3369             '%s%s: %s\n'
  3383             b'%s%s: %s\n'
  3370             % (
  3384             % (
  3371                 ' ' * time_stats.level * 2,
  3385                 b' ' * time_stats.level * 2,
  3372                 pycompat.bytestr(func.__name__),
  3386                 pycompat.bytestr(func.__name__),
  3373                 time_stats,
  3387                 time_stats,
  3374             )
  3388             )
  3375         )
  3389         )
  3376         return result
  3390         return result
  3377 
  3391 
  3378     return wrapper
  3392     return wrapper
  3379 
  3393 
  3380 
  3394 
  3381 _sizeunits = (
  3395 _sizeunits = (
  3382     ('m', 2 ** 20),
  3396     (b'm', 2 ** 20),
  3383     ('k', 2 ** 10),
  3397     (b'k', 2 ** 10),
  3384     ('g', 2 ** 30),
  3398     (b'g', 2 ** 30),
  3385     ('kb', 2 ** 10),
  3399     (b'kb', 2 ** 10),
  3386     ('mb', 2 ** 20),
  3400     (b'mb', 2 ** 20),
  3387     ('gb', 2 ** 30),
  3401     (b'gb', 2 ** 30),
  3388     ('b', 1),
  3402     (b'b', 1),
  3389 )
  3403 )
  3390 
  3404 
  3391 
  3405 
  3392 def sizetoint(s):
  3406 def sizetoint(s):
  3393     '''Convert a space specifier to a byte count.
  3407     '''Convert a space specifier to a byte count.
  3404         for k, u in _sizeunits:
  3418         for k, u in _sizeunits:
  3405             if t.endswith(k):
  3419             if t.endswith(k):
  3406                 return int(float(t[: -len(k)]) * u)
  3420                 return int(float(t[: -len(k)]) * u)
  3407         return int(t)
  3421         return int(t)
  3408     except ValueError:
  3422     except ValueError:
  3409         raise error.ParseError(_("couldn't parse size: %s") % s)
  3423         raise error.ParseError(_(b"couldn't parse size: %s") % s)
  3410 
  3424 
  3411 
  3425 
  3412 class hooks(object):
  3426 class hooks(object):
  3413     '''A collection of hook functions that can be used to extend a
  3427     '''A collection of hook functions that can be used to extend a
  3414     function's behavior. Hooks are called in lexicographic order,
  3428     function's behavior. Hooks are called in lexicographic order,
  3426         for source, hook in self._hooks:
  3440         for source, hook in self._hooks:
  3427             results.append(hook(*args))
  3441             results.append(hook(*args))
  3428         return results
  3442         return results
  3429 
  3443 
  3430 
  3444 
  3431 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
  3445 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
  3432     '''Yields lines for a nicely formatted stacktrace.
  3446     '''Yields lines for a nicely formatted stacktrace.
  3433     Skips the 'skip' last entries, then return the last 'depth' entries.
  3447     Skips the 'skip' last entries, then return the last 'depth' entries.
  3434     Each file+linenumber is formatted according to fileline.
  3448     Each file+linenumber is formatted according to fileline.
  3435     Each line is formatted according to line.
  3449     Each line is formatted according to line.
  3436     If line is None, it yields:
  3450     If line is None, it yields:
  3452             else:
  3466             else:
  3453                 yield line % (fnmax, fnln, func)
  3467                 yield line % (fnmax, fnln, func)
  3454 
  3468 
  3455 
  3469 
  3456 def debugstacktrace(
  3470 def debugstacktrace(
  3457     msg='stacktrace', skip=0, f=procutil.stderr, otherf=procutil.stdout, depth=0
  3471     msg=b'stacktrace',
       
  3472     skip=0,
       
  3473     f=procutil.stderr,
       
  3474     otherf=procutil.stdout,
       
  3475     depth=0,
  3458 ):
  3476 ):
  3459     '''Writes a message to f (stderr) with a nicely formatted stacktrace.
  3477     '''Writes a message to f (stderr) with a nicely formatted stacktrace.
  3460     Skips the 'skip' entries closest to the call, then show 'depth' entries.
  3478     Skips the 'skip' entries closest to the call, then show 'depth' entries.
  3461     By default it will flush stdout first.
  3479     By default it will flush stdout first.
  3462     It can be used everywhere and intentionally does not require an ui object.
  3480     It can be used everywhere and intentionally does not require an ui object.
  3463     Not be used in production code but very convenient while developing.
  3481     Not be used in production code but very convenient while developing.
  3464     '''
  3482     '''
  3465     if otherf:
  3483     if otherf:
  3466         otherf.flush()
  3484         otherf.flush()
  3467     f.write('%s at:\n' % msg.rstrip())
  3485     f.write(b'%s at:\n' % msg.rstrip())
  3468     for line in getstackframes(skip + 1, depth=depth):
  3486     for line in getstackframes(skip + 1, depth=depth):
  3469         f.write(line)
  3487         f.write(line)
  3470     f.flush()
  3488     f.flush()
  3471 
  3489 
  3472 
  3490 
  3480             for f, s in map.iteritems():
  3498             for f, s in map.iteritems():
  3481                 if s[0] != skip:
  3499                 if s[0] != skip:
  3482                     addpath(f)
  3500                     addpath(f)
  3483         elif skip is not None:
  3501         elif skip is not None:
  3484             raise error.ProgrammingError(
  3502             raise error.ProgrammingError(
  3485                 "skip character is only supported " "with a dict source"
  3503                 b"skip character is only supported " b"with a dict source"
  3486             )
  3504             )
  3487         else:
  3505         else:
  3488             for f in map:
  3506             for f in map:
  3489                 addpath(f)
  3507                 addpath(f)
  3490 
  3508 
  3517 if rustdirs is not None:
  3535 if rustdirs is not None:
  3518     dirs = rustdirs
  3536     dirs = rustdirs
  3519 
  3537 
  3520 
  3538 
  3521 def finddirs(path):
  3539 def finddirs(path):
  3522     pos = path.rfind('/')
  3540     pos = path.rfind(b'/')
  3523     while pos != -1:
  3541     while pos != -1:
  3524         yield path[:pos]
  3542         yield path[:pos]
  3525         pos = path.rfind('/', 0, pos)
  3543         pos = path.rfind(b'/', 0, pos)
  3526     yield ''
  3544     yield b''
  3527 
  3545 
  3528 
  3546 
  3529 # convenient shortcut
  3547 # convenient shortcut
  3530 dst = debugstacktrace
  3548 dst = debugstacktrace
  3531 
  3549 
  3543     in the provided context and is not in the set of other names.
  3561     in the provided context and is not in the set of other names.
  3544     """
  3562     """
  3545     if others is None:
  3563     if others is None:
  3546         others = set()
  3564         others = set()
  3547 
  3565 
  3548     fn = '%s~%s' % (f, tag)
  3566     fn = b'%s~%s' % (f, tag)
  3549     if fn not in ctx and fn not in others:
  3567     if fn not in ctx and fn not in others:
  3550         return fn
  3568         return fn
  3551     for n in itertools.count(1):
  3569     for n in itertools.count(1):
  3552         fn = '%s~%s~%s' % (f, tag, n)
  3570         fn = b'%s~%s~%s' % (f, tag, n)
  3553         if fn not in ctx and fn not in others:
  3571         if fn not in ctx and fn not in others:
  3554             return fn
  3572             return fn
  3555 
  3573 
  3556 
  3574 
  3557 def readexactly(stream, n):
  3575 def readexactly(stream, n):
  3558     '''read n bytes from stream.read and abort if less was available'''
  3576     '''read n bytes from stream.read and abort if less was available'''
  3559     s = stream.read(n)
  3577     s = stream.read(n)
  3560     if len(s) < n:
  3578     if len(s) < n:
  3561         raise error.Abort(
  3579         raise error.Abort(
  3562             _("stream ended unexpectedly" " (got %d bytes, expected %d)")
  3580             _(b"stream ended unexpectedly" b" (got %d bytes, expected %d)")
  3563             % (len(s), n)
  3581             % (len(s), n)
  3564         )
  3582         )
  3565     return s
  3583     return s
  3566 
  3584 
  3567 
  3585 
  3587     Traceback (most recent call last):
  3605     Traceback (most recent call last):
  3588         ...
  3606         ...
  3589     ProgrammingError: negative value for uvarint: -1
  3607     ProgrammingError: negative value for uvarint: -1
  3590     """
  3608     """
  3591     if value < 0:
  3609     if value < 0:
  3592         raise error.ProgrammingError('negative value for uvarint: %d' % value)
  3610         raise error.ProgrammingError(b'negative value for uvarint: %d' % value)
  3593     bits = value & 0x7F
  3611     bits = value & 0x7F
  3594     value >>= 7
  3612     value >>= 7
  3595     bytes = []
  3613     bytes = []
  3596     while value:
  3614     while value:
  3597         bytes.append(pycompat.bytechr(0x80 | bits))
  3615         bytes.append(pycompat.bytechr(0x80 | bits))
  3598         bits = value & 0x7F
  3616         bits = value & 0x7F
  3599         value >>= 7
  3617         value >>= 7
  3600     bytes.append(pycompat.bytechr(bits))
  3618     bytes.append(pycompat.bytechr(bits))
  3601 
  3619 
  3602     return ''.join(bytes)
  3620     return b''.join(bytes)
  3603 
  3621 
  3604 
  3622 
  3605 def uvarintdecodestream(fh):
  3623 def uvarintdecodestream(fh):
  3606     """Decode an unsigned variable length integer from a stream.
  3624     """Decode an unsigned variable length integer from a stream.
  3607 
  3625