mercurial/util.py
changeset 45942 89a2afe31e82
parent 45146 a0791bfd9cfa
child 46113 59fa3890d40a
equal deleted inserted replaced
45941:346af7687c6f 45942:89a2afe31e82
  1262         self._copied = getattr(self, '_copied', 0) + 1
  1262         self._copied = getattr(self, '_copied', 0) + 1
  1263         return self
  1263         return self
  1264 
  1264 
  1265 
  1265 
  1266 class sortdict(collections.OrderedDict):
  1266 class sortdict(collections.OrderedDict):
  1267     '''a simple sorted dictionary
  1267     """a simple sorted dictionary
  1268 
  1268 
  1269     >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
  1269     >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
  1270     >>> d2 = d1.copy()
  1270     >>> d2 = d1.copy()
  1271     >>> d2
  1271     >>> d2
  1272     sortdict([('a', 0), ('b', 1)])
  1272     sortdict([('a', 0), ('b', 1)])
  1274     >>> list(d2.keys()) # should still be in last-set order
  1274     >>> list(d2.keys()) # should still be in last-set order
  1275     ['b', 'a']
  1275     ['b', 'a']
  1276     >>> d1.insert(1, b'a.5', 0.5)
  1276     >>> d1.insert(1, b'a.5', 0.5)
  1277     >>> d1
  1277     >>> d1
  1278     sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
  1278     sortdict([('a', 0), ('a.5', 0.5), ('b', 1)])
  1279     '''
  1279     """
  1280 
  1280 
  1281     def __setitem__(self, key, value):
  1281     def __setitem__(self, key, value):
  1282         if key in self:
  1282         if key in self:
  1283             del self[key]
  1283             del self[key]
  1284         super(sortdict, self).__setitem__(key, value)
  1284         super(sortdict, self).__setitem__(key, value)
  1759     if prop in obj.__dict__:
  1759     if prop in obj.__dict__:
  1760         del obj.__dict__[prop]
  1760         del obj.__dict__[prop]
  1761 
  1761 
  1762 
  1762 
  1763 def increasingchunks(source, min=1024, max=65536):
  1763 def increasingchunks(source, min=1024, max=65536):
  1764     '''return no less than min bytes per chunk while data remains,
  1764     """return no less than min bytes per chunk while data remains,
  1765     doubling min after each chunk until it reaches max'''
  1765     doubling min after each chunk until it reaches max"""
  1766 
  1766 
  1767     def log2(x):
  1767     def log2(x):
  1768         if not x:
  1768         if not x:
  1769             return 0
  1769             return 0
  1770         i = 0
  1770         i = 0
  1831     # PyPy runs slower with gc disabled
  1831     # PyPy runs slower with gc disabled
  1832     nogc = lambda x: x
  1832     nogc = lambda x: x
  1833 
  1833 
  1834 
  1834 
  1835 def pathto(root, n1, n2):
  1835 def pathto(root, n1, n2):
  1836     '''return the relative path from one place to another.
  1836     """return the relative path from one place to another.
  1837     root should use os.sep to separate directories
  1837     root should use os.sep to separate directories
  1838     n1 should use os.sep to separate directories
  1838     n1 should use os.sep to separate directories
  1839     n2 should use "/" to separate directories
  1839     n2 should use "/" to separate directories
  1840     returns an os.sep-separated path.
  1840     returns an os.sep-separated path.
  1841 
  1841 
  1842     If n1 is a relative path, it's assumed it's
  1842     If n1 is a relative path, it's assumed it's
  1843     relative to root.
  1843     relative to root.
  1844     n2 should always be relative to root.
  1844     n2 should always be relative to root.
  1845     '''
  1845     """
  1846     if not n1:
  1846     if not n1:
  1847         return localpath(n2)
  1847         return localpath(n2)
  1848     if os.path.isabs(n1):
  1848     if os.path.isabs(n1):
  1849         if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
  1849         if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
  1850             return os.path.join(root, localpath(n2))
  1850             return os.path.join(root, localpath(n2))
  1890     b'zfs',
  1890     b'zfs',
  1891 }
  1891 }
  1892 
  1892 
  1893 
  1893 
  1894 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
  1894 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
  1895     '''copy a file, preserving mode and optionally other stat info like
  1895     """copy a file, preserving mode and optionally other stat info like
  1896     atime/mtime
  1896     atime/mtime
  1897 
  1897 
  1898     checkambig argument is used with filestat, and is useful only if
  1898     checkambig argument is used with filestat, and is useful only if
  1899     destination file is guarded by any lock (e.g. repo.lock or
  1899     destination file is guarded by any lock (e.g. repo.lock or
  1900     repo.wlock).
  1900     repo.wlock).
  1901 
  1901 
  1902     copystat and checkambig should be exclusive.
  1902     copystat and checkambig should be exclusive.
  1903     '''
  1903     """
  1904     assert not (copystat and checkambig)
  1904     assert not (copystat and checkambig)
  1905     oldstat = None
  1905     oldstat = None
  1906     if os.path.lexists(dest):
  1906     if os.path.lexists(dest):
  1907         if checkambig:
  1907         if checkambig:
  1908             oldstat = checkambig and filestat.frompath(dest)
  1908             oldstat = checkambig and filestat.frompath(dest)
  2015 }
  2015 }
  2016 _winreservedchars = b':*?"<>|'
  2016 _winreservedchars = b':*?"<>|'
  2017 
  2017 
  2018 
  2018 
  2019 def checkwinfilename(path):
  2019 def checkwinfilename(path):
  2020     r'''Check that the base-relative path is a valid filename on Windows.
  2020     r"""Check that the base-relative path is a valid filename on Windows.
  2021     Returns None if the path is ok, or a UI string describing the problem.
  2021     Returns None if the path is ok, or a UI string describing the problem.
  2022 
  2022 
  2023     >>> checkwinfilename(b"just/a/normal/path")
  2023     >>> checkwinfilename(b"just/a/normal/path")
  2024     >>> checkwinfilename(b"foo/bar/con.xml")
  2024     >>> checkwinfilename(b"foo/bar/con.xml")
  2025     "filename contains 'con', which is reserved on Windows"
  2025     "filename contains 'con', which is reserved on Windows"
  2037     >>> checkwinfilename(b"../bar")
  2037     >>> checkwinfilename(b"../bar")
  2038     >>> checkwinfilename(b"foo\\")
  2038     >>> checkwinfilename(b"foo\\")
  2039     "filename ends with '\\', which is invalid on Windows"
  2039     "filename ends with '\\', which is invalid on Windows"
  2040     >>> checkwinfilename(b"foo\\/bar")
  2040     >>> checkwinfilename(b"foo\\/bar")
  2041     "directory name ends with '\\', which is invalid on Windows"
  2041     "directory name ends with '\\', which is invalid on Windows"
  2042     '''
  2042     """
  2043     if path.endswith(b'\\'):
  2043     if path.endswith(b'\\'):
  2044         return _(b"filename ends with '\\', which is invalid on Windows")
  2044         return _(b"filename ends with '\\', which is invalid on Windows")
  2045     if b'\\/' in path:
  2045     if b'\\/' in path:
  2046         return _(b"directory name ends with '\\', which is invalid on Windows")
  2046         return _(b"directory name ends with '\\', which is invalid on Windows")
  2047     for n in path.replace(b'\\', b'/').split(b'/'):
  2047     for n in path.replace(b'\\', b'/').split(b'/'):
  2173             _re2 = bool(re2.match(r'\[([^\[]+)\]', b'[ui]'))
  2173             _re2 = bool(re2.match(r'\[([^\[]+)\]', b'[ui]'))
  2174         except ImportError:
  2174         except ImportError:
  2175             _re2 = False
  2175             _re2 = False
  2176 
  2176 
  2177     def compile(self, pat, flags=0):
  2177     def compile(self, pat, flags=0):
  2178         '''Compile a regular expression, using re2 if possible
  2178         """Compile a regular expression, using re2 if possible
  2179 
  2179 
  2180         For best performance, use only re2-compatible regexp features. The
  2180         For best performance, use only re2-compatible regexp features. The
  2181         only flags from the re module that are re2-compatible are
  2181         only flags from the re module that are re2-compatible are
  2182         IGNORECASE and MULTILINE.'''
  2182         IGNORECASE and MULTILINE."""
  2183         if _re2 is None:
  2183         if _re2 is None:
  2184             self._checkre2()
  2184             self._checkre2()
  2185         if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
  2185         if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
  2186             if flags & remod.IGNORECASE:
  2186             if flags & remod.IGNORECASE:
  2187                 pat = b'(?i)' + pat
  2187                 pat = b'(?i)' + pat
  2193                 pass
  2193                 pass
  2194         return remod.compile(pat, flags)
  2194         return remod.compile(pat, flags)
  2195 
  2195 
  2196     @propertycache
  2196     @propertycache
  2197     def escape(self):
  2197     def escape(self):
  2198         '''Return the version of escape corresponding to self.compile.
  2198         """Return the version of escape corresponding to self.compile.
  2199 
  2199 
  2200         This is imperfect because whether re2 or re is used for a particular
  2200         This is imperfect because whether re2 or re is used for a particular
  2201         function depends on the flags, etc, but it's the best we can do.
  2201         function depends on the flags, etc, but it's the best we can do.
  2202         '''
  2202         """
  2203         global _re2
  2203         global _re2
  2204         if _re2 is None:
  2204         if _re2 is None:
  2205             self._checkre2()
  2205             self._checkre2()
  2206         if _re2:
  2206         if _re2:
  2207             return re2.escape
  2207             return re2.escape
  2213 
  2213 
  2214 _fspathcache = {}
  2214 _fspathcache = {}
  2215 
  2215 
  2216 
  2216 
  2217 def fspath(name, root):
  2217 def fspath(name, root):
  2218     '''Get name in the case stored in the filesystem
  2218     """Get name in the case stored in the filesystem
  2219 
  2219 
  2220     The name should be relative to root, and be normcase-ed for efficiency.
  2220     The name should be relative to root, and be normcase-ed for efficiency.
  2221 
  2221 
  2222     Note that this function is unnecessary, and should not be
  2222     Note that this function is unnecessary, and should not be
  2223     called, for case-sensitive filesystems (simply because it's expensive).
  2223     called, for case-sensitive filesystems (simply because it's expensive).
  2224 
  2224 
  2225     The root should be normcase-ed, too.
  2225     The root should be normcase-ed, too.
  2226     '''
  2226     """
  2227 
  2227 
  2228     def _makefspathcacheentry(dir):
  2228     def _makefspathcacheentry(dir):
  2229         return {normcase(n): n for n in os.listdir(dir)}
  2229         return {normcase(n): n for n in os.listdir(dir)}
  2230 
  2230 
  2231     seps = pycompat.ossep
  2231     seps = pycompat.ossep
  2299         and path.endswith(pycompat.osaltsep)
  2299         and path.endswith(pycompat.osaltsep)
  2300     )
  2300     )
  2301 
  2301 
  2302 
  2302 
  2303 def splitpath(path):
  2303 def splitpath(path):
  2304     '''Split path by os.sep.
  2304     """Split path by os.sep.
  2305     Note that this function does not use os.altsep because this is
  2305     Note that this function does not use os.altsep because this is
  2306     an alternative of simple "xxx.split(os.sep)".
  2306     an alternative of simple "xxx.split(os.sep)".
  2307     It is recommended to use os.path.normpath() before using this
  2307     It is recommended to use os.path.normpath() before using this
  2308     function if need.'''
  2308     function if need."""
  2309     return path.split(pycompat.ossep)
  2309     return path.split(pycompat.ossep)
  2310 
  2310 
  2311 
  2311 
  2312 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
  2312 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
  2313     """Create a temporary file with the same contents from name
  2313     """Create a temporary file with the same contents from name
  2457     def __ne__(self, other):
  2457     def __ne__(self, other):
  2458         return not self == other
  2458         return not self == other
  2459 
  2459 
  2460 
  2460 
  2461 class atomictempfile(object):
  2461 class atomictempfile(object):
  2462     '''writable file object that atomically updates a file
  2462     """writable file object that atomically updates a file
  2463 
  2463 
  2464     All writes will go to a temporary copy of the original file. Call
  2464     All writes will go to a temporary copy of the original file. Call
  2465     close() when you are done writing, and atomictempfile will rename
  2465     close() when you are done writing, and atomictempfile will rename
  2466     the temporary copy to the original name, making the changes
  2466     the temporary copy to the original name, making the changes
  2467     visible. If the object is destroyed without being closed, all your
  2467     visible. If the object is destroyed without being closed, all your
  2468     writes are discarded.
  2468     writes are discarded.
  2469 
  2469 
  2470     checkambig argument of constructor is used with filestat, and is
  2470     checkambig argument of constructor is used with filestat, and is
  2471     useful only if target file is guarded by any lock (e.g. repo.lock
  2471     useful only if target file is guarded by any lock (e.g. repo.lock
  2472     or repo.wlock).
  2472     or repo.wlock).
  2473     '''
  2473     """
  2474 
  2474 
  2475     def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
  2475     def __init__(self, name, mode=b'w+b', createmode=None, checkambig=False):
  2476         self.__name = name  # permanent name
  2476         self.__name = name  # permanent name
  2477         self._tempname = mktempcopy(
  2477         self._tempname = mktempcopy(
  2478             name,
  2478             name,
  3363 
  3363 
  3364 timedcm._nested = 0
  3364 timedcm._nested = 0
  3365 
  3365 
  3366 
  3366 
  3367 def timed(func):
  3367 def timed(func):
  3368     '''Report the execution time of a function call to stderr.
  3368     """Report the execution time of a function call to stderr.
  3369 
  3369 
  3370     During development, use as a decorator when you need to measure
  3370     During development, use as a decorator when you need to measure
  3371     the cost of a function, e.g. as follows:
  3371     the cost of a function, e.g. as follows:
  3372 
  3372 
  3373     @util.timed
  3373     @util.timed
  3374     def foo(a, b, c):
  3374     def foo(a, b, c):
  3375         pass
  3375         pass
  3376     '''
  3376     """
  3377 
  3377 
  3378     def wrapper(*args, **kwargs):
  3378     def wrapper(*args, **kwargs):
  3379         with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
  3379         with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
  3380             result = func(*args, **kwargs)
  3380             result = func(*args, **kwargs)
  3381         stderr = procutil.stderr
  3381         stderr = procutil.stderr
  3402     (b'b', 1),
  3402     (b'b', 1),
  3403 )
  3403 )
  3404 
  3404 
  3405 
  3405 
  3406 def sizetoint(s):
  3406 def sizetoint(s):
  3407     '''Convert a space specifier to a byte count.
  3407     """Convert a space specifier to a byte count.
  3408 
  3408 
  3409     >>> sizetoint(b'30')
  3409     >>> sizetoint(b'30')
  3410     30
  3410     30
  3411     >>> sizetoint(b'2.2kb')
  3411     >>> sizetoint(b'2.2kb')
  3412     2252
  3412     2252
  3413     >>> sizetoint(b'6M')
  3413     >>> sizetoint(b'6M')
  3414     6291456
  3414     6291456
  3415     '''
  3415     """
  3416     t = s.strip().lower()
  3416     t = s.strip().lower()
  3417     try:
  3417     try:
  3418         for k, u in _sizeunits:
  3418         for k, u in _sizeunits:
  3419             if t.endswith(k):
  3419             if t.endswith(k):
  3420                 return int(float(t[: -len(k)]) * u)
  3420                 return int(float(t[: -len(k)]) * u)
  3422     except ValueError:
  3422     except ValueError:
  3423         raise error.ParseError(_(b"couldn't parse size: %s") % s)
  3423         raise error.ParseError(_(b"couldn't parse size: %s") % s)
  3424 
  3424 
  3425 
  3425 
  3426 class hooks(object):
  3426 class hooks(object):
  3427     '''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
  3428     function's behavior. Hooks are called in lexicographic order,
  3428     function's behavior. Hooks are called in lexicographic order,
  3429     based on the names of their sources.'''
  3429     based on the names of their sources."""
  3430 
  3430 
  3431     def __init__(self):
  3431     def __init__(self):
  3432         self._hooks = []
  3432         self._hooks = []
  3433 
  3433 
  3434     def add(self, source, hook):
  3434     def add(self, source, hook):
  3441             results.append(hook(*args))
  3441             results.append(hook(*args))
  3442         return results
  3442         return results
  3443 
  3443 
  3444 
  3444 
  3445 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
  3445 def getstackframes(skip=0, line=b' %-*s in %s\n', fileline=b'%s:%d', depth=0):
  3446     '''Yields lines for a nicely formatted stacktrace.
  3446     """Yields lines for a nicely formatted stacktrace.
  3447     Skips the 'skip' last entries, then return the last 'depth' entries.
  3447     Skips the 'skip' last entries, then return the last 'depth' entries.
  3448     Each file+linenumber is formatted according to fileline.
  3448     Each file+linenumber is formatted according to fileline.
  3449     Each line is formatted according to line.
  3449     Each line is formatted according to line.
  3450     If line is None, it yields:
  3450     If line is None, it yields:
  3451       length of longest filepath+line number,
  3451       length of longest filepath+line number,
  3452       filepath+linenumber,
  3452       filepath+linenumber,
  3453       function
  3453       function
  3454 
  3454 
  3455     Not be used in production code but very convenient while developing.
  3455     Not be used in production code but very convenient while developing.
  3456     '''
  3456     """
  3457     entries = [
  3457     entries = [
  3458         (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
  3458         (fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
  3459         for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
  3459         for fn, ln, func, _text in traceback.extract_stack()[: -skip - 1]
  3460     ][-depth:]
  3460     ][-depth:]
  3461     if entries:
  3461     if entries:
  3473     f=procutil.stderr,
  3473     f=procutil.stderr,
  3474     otherf=procutil.stdout,
  3474     otherf=procutil.stdout,
  3475     depth=0,
  3475     depth=0,
  3476     prefix=b'',
  3476     prefix=b'',
  3477 ):
  3477 ):
  3478     '''Writes a message to f (stderr) with a nicely formatted stacktrace.
  3478     """Writes a message to f (stderr) with a nicely formatted stacktrace.
  3479     Skips the 'skip' entries closest to the call, then show 'depth' entries.
  3479     Skips the 'skip' entries closest to the call, then show 'depth' entries.
  3480     By default it will flush stdout first.
  3480     By default it will flush stdout first.
  3481     It can be used everywhere and intentionally does not require an ui object.
  3481     It can be used everywhere and intentionally does not require an ui object.
  3482     Not be used in production code but very convenient while developing.
  3482     Not be used in production code but very convenient while developing.
  3483     '''
  3483     """
  3484     if otherf:
  3484     if otherf:
  3485         otherf.flush()
  3485         otherf.flush()
  3486     f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
  3486     f.write(b'%s%s at:\n' % (prefix, msg.rstrip()))
  3487     for line in getstackframes(skip + 1, depth=depth):
  3487     for line in getstackframes(skip + 1, depth=depth):
  3488         f.write(prefix + line)
  3488         f.write(prefix + line)