89 if nh: |
93 if nh: |
90 return nh, ht[1] |
94 return nh, ht[1] |
91 return ht[0] + b'/', ht[1] |
95 return ht[0] + b'/', ht[1] |
92 |
96 |
93 |
97 |
94 def openhardlinks(): |
98 def openhardlinks() -> bool: |
95 '''return true if it is safe to hold open file handles to hardlinks''' |
99 '''return true if it is safe to hold open file handles to hardlinks''' |
96 return True |
100 return True |
97 |
101 |
98 |
102 |
99 def nlinks(name: bytes) -> int: |
103 def nlinks(name: bytes) -> int: |
100 '''return number of hardlinks for the given file''' |
104 '''return number of hardlinks for the given file''' |
101 return os.lstat(name).st_nlink |
105 return os.lstat(name).st_nlink |
102 |
106 |
103 |
107 |
104 def parsepatchoutput(output_line): |
108 def parsepatchoutput(output_line: bytes) -> bytes: |
105 """parses the output produced by patch and returns the filename""" |
109 """parses the output produced by patch and returns the filename""" |
106 pf = output_line[14:] |
110 pf = output_line[14:] |
107 if pycompat.sysplatform == b'OpenVMS': |
111 if pycompat.sysplatform == b'OpenVMS': |
108 if pf[0] == b'`': |
112 if pf[0] == b'`': |
109 pf = pf[1:-1] # Remove the quotes |
113 pf = pf[1:-1] # Remove the quotes |
111 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf: |
115 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf: |
112 pf = pf[1:-1] # Remove the quotes |
116 pf = pf[1:-1] # Remove the quotes |
113 return pf |
117 return pf |
114 |
118 |
115 |
119 |
116 def sshargs(sshcmd, host, user, port): |
120 def sshargs( |
|
121 sshcmd: bytes, host: bytes, user: Optional[bytes], port: Optional[bytes] |
|
122 ) -> bytes: |
117 '''Build argument list for ssh''' |
123 '''Build argument list for ssh''' |
118 args = user and (b"%s@%s" % (user, host)) or host |
124 args = user and (b"%s@%s" % (user, host)) or host |
119 if b'-' in args[:1]: |
125 if b'-' in args[:1]: |
120 raise error.Abort( |
126 raise error.Abort( |
121 _(b'illegal ssh hostname or username starting with -: %s') % args |
127 _(b'illegal ssh hostname or username starting with -: %s') % args |
124 if port: |
130 if port: |
125 args = b'-p %s %s' % (shellquote(port), args) |
131 args = b'-p %s %s' % (shellquote(port), args) |
126 return args |
132 return args |
127 |
133 |
128 |
134 |
129 def isexec(f): |
135 def isexec(f: bytes) -> bool: |
130 """check whether a file is executable""" |
136 """check whether a file is executable""" |
131 return os.lstat(f).st_mode & 0o100 != 0 |
137 return os.lstat(f).st_mode & 0o100 != 0 |
132 |
138 |
133 |
139 |
134 def setflags(f, l, x): |
140 def setflags(f: bytes, l: bool, x: bool) -> None: |
135 st = os.lstat(f) |
141 st = os.lstat(f) |
136 s = st.st_mode |
142 s = st.st_mode |
137 if l: |
143 if l: |
138 if not stat.S_ISLNK(s): |
144 if not stat.S_ISLNK(s): |
139 # switch file to link |
145 # switch file to link |
173 elif not x and sx: |
179 elif not x and sx: |
174 # Turn off all +x bits |
180 # Turn off all +x bits |
175 os.chmod(f, s & 0o666) |
181 os.chmod(f, s & 0o666) |
176 |
182 |
177 |
183 |
178 def copymode(src, dst, mode=None, enforcewritable=False): |
184 def copymode( |
|
185 src: bytes, |
|
186 dst: bytes, |
|
187 mode: Optional[bytes] = None, |
|
188 enforcewritable: bool = False, |
|
189 ) -> None: |
179 """Copy the file mode from the file at path src to dst. |
190 """Copy the file mode from the file at path src to dst. |
180 If src doesn't exist, we're using mode instead. If mode is None, we're |
191 If src doesn't exist, we're using mode instead. If mode is None, we're |
181 using umask.""" |
192 using umask.""" |
182 try: |
193 try: |
183 st_mode = os.lstat(src).st_mode & 0o777 |
194 st_mode = os.lstat(src).st_mode & 0o777 |
193 new_mode |= stat.S_IWUSR |
204 new_mode |= stat.S_IWUSR |
194 |
205 |
195 os.chmod(dst, new_mode) |
206 os.chmod(dst, new_mode) |
196 |
207 |
197 |
208 |
198 def checkexec(path): |
209 def checkexec(path: bytes) -> bool: |
199 """ |
210 """ |
200 Check whether the given path is on a filesystem with UNIX-like exec flags |
211 Check whether the given path is on a filesystem with UNIX-like exec flags |
201 |
212 |
202 Requires a directory (like /foo/.hg) |
213 Requires a directory (like /foo/.hg) |
203 """ |
214 """ |
273 except (IOError, OSError): |
284 except (IOError, OSError): |
274 # we don't care, the user probably won't be able to commit anyway |
285 # we don't care, the user probably won't be able to commit anyway |
275 return False |
286 return False |
276 |
287 |
277 |
288 |
278 def checklink(path): |
289 def checklink(path: bytes) -> bool: |
279 """check whether the given path is on a symlink-capable filesystem""" |
290 """check whether the given path is on a symlink-capable filesystem""" |
280 # mktemp is not racy because symlink creation will fail if the |
291 # mktemp is not racy because symlink creation will fail if the |
281 # file already exists |
292 # file already exists |
282 while True: |
293 while True: |
283 cachedir = os.path.join(path, b'.hg', b'wcache') |
294 cachedir = os.path.join(path, b'.hg', b'wcache') |
360 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc. |
371 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc. |
361 """ |
372 """ |
362 return getattr(osutil, 'getfstype', lambda x: None)(dirpath) |
373 return getattr(osutil, 'getfstype', lambda x: None)(dirpath) |
363 |
374 |
364 |
375 |
365 def get_password(): |
376 def get_password() -> bytes: |
366 return encoding.strtolocal(getpass.getpass('')) |
377 return encoding.strtolocal(getpass.getpass('')) |
367 |
378 |
368 |
379 |
369 def setbinary(fd): |
380 def setbinary(fd) -> None: |
370 pass |
381 pass |
371 |
382 |
372 |
383 |
373 def pconvert(path): |
384 def pconvert(path: bytes) -> bytes: |
374 return path |
385 return path |
375 |
386 |
376 |
387 |
377 def localpath(path): |
388 def localpath(path: bytes) -> bytes: |
378 return path |
389 return path |
379 |
390 |
380 |
391 |
381 def samefile(fpath1: bytes, fpath2: bytes) -> bool: |
392 def samefile(fpath1: bytes, fpath2: bytes) -> bool: |
382 """Returns whether path1 and path2 refer to the same file. This is only |
393 """Returns whether path1 and path2 refer to the same file. This is only |
391 st2 = os.lstat(fpath2) |
402 st2 = os.lstat(fpath2) |
392 return st1.st_dev == st2.st_dev |
403 return st1.st_dev == st2.st_dev |
393 |
404 |
394 |
405 |
395 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems |
406 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems |
396 def normcase(path): |
407 def normcase(path: bytes) -> bytes: |
397 return path.lower() |
408 return path.lower() |
398 |
409 |
399 |
410 |
400 # what normcase does to ASCII strings |
411 # what normcase does to ASCII strings |
401 normcasespec = encoding.normcasespecs.lower |
412 normcasespec = encoding.normcasespecs.lower |
402 # fallback normcase function for non-ASCII strings |
413 # fallback normcase function for non-ASCII strings |
403 normcasefallback = normcase |
414 normcasefallback = normcase |
404 |
415 |
405 if pycompat.isdarwin: |
416 if pycompat.isdarwin: |
406 |
417 |
407 def normcase(path): |
418 def normcase(path: bytes) -> bytes: |
408 """ |
419 """ |
409 Normalize a filename for OS X-compatible comparison: |
420 Normalize a filename for OS X-compatible comparison: |
410 - escape-encode invalid characters |
421 - escape-encode invalid characters |
411 - decompose to NFD |
422 - decompose to NFD |
412 - lowercase |
423 - lowercase |
427 except UnicodeDecodeError: |
438 except UnicodeDecodeError: |
428 return normcasefallback(path) |
439 return normcasefallback(path) |
429 |
440 |
430 normcasespec = encoding.normcasespecs.lower |
441 normcasespec = encoding.normcasespecs.lower |
431 |
442 |
432 def normcasefallback(path): |
443 def normcasefallback(path: bytes) -> bytes: |
433 try: |
444 try: |
434 u = path.decode('utf-8') |
445 u = path.decode('utf-8') |
435 except UnicodeDecodeError: |
446 except UnicodeDecodeError: |
436 # OS X percent-encodes any bytes that aren't valid utf-8 |
447 # OS X percent-encodes any bytes that aren't valid utf-8 |
437 s = b'' |
448 s = b'' |
494 |
505 |
495 # Cygwin translates native ACLs to POSIX permissions, |
506 # Cygwin translates native ACLs to POSIX permissions, |
496 # but these translations are not supported by native |
507 # but these translations are not supported by native |
497 # tools, so the exec bit tends to be set erroneously. |
508 # tools, so the exec bit tends to be set erroneously. |
498 # Therefore, disable executable bit access on Cygwin. |
509 # Therefore, disable executable bit access on Cygwin. |
499 def checkexec(path): |
510 def checkexec(path: bytes) -> bool: |
500 return False |
511 return False |
501 |
512 |
502 # Similarly, Cygwin's symlink emulation is likely to create |
513 # Similarly, Cygwin's symlink emulation is likely to create |
503 # problems when Mercurial is used from both Cygwin and native |
514 # problems when Mercurial is used from both Cygwin and native |
504 # Windows, with other native tools, or on shared volumes |
515 # Windows, with other native tools, or on shared volumes |
505 def checklink(path): |
516 def checklink(path: bytes) -> bool: |
506 return False |
517 return False |
507 |
518 |
508 |
519 |
509 _needsshellquote = None |
520 _needsshellquote = None |
510 |
521 |
511 |
522 |
512 def shellquote(s): |
523 def shellquote(s: bytes) -> bytes: |
513 if pycompat.sysplatform == b'OpenVMS': |
524 if pycompat.sysplatform == b'OpenVMS': |
514 return b'"%s"' % s |
525 return b'"%s"' % s |
515 global _needsshellquote |
526 global _needsshellquote |
516 if _needsshellquote is None: |
527 if _needsshellquote is None: |
517 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search |
528 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search |
520 return s |
531 return s |
521 else: |
532 else: |
522 return b"'%s'" % s.replace(b"'", b"'\\''") |
533 return b"'%s'" % s.replace(b"'", b"'\\''") |
523 |
534 |
524 |
535 |
525 def shellsplit(s): |
536 def shellsplit(s: bytes) -> List[bytes]: |
526 """Parse a command string in POSIX shell way (best-effort)""" |
537 """Parse a command string in POSIX shell way (best-effort)""" |
527 return pycompat.shlexsplit(s, posix=True) |
538 return pycompat.shlexsplit(s, posix=True) |
528 |
539 |
529 |
540 |
530 def testpid(pid: int) -> bool: |
541 def testpid(pid: int) -> bool: |
536 return True |
547 return True |
537 except OSError as inst: |
548 except OSError as inst: |
538 return inst.errno != errno.ESRCH |
549 return inst.errno != errno.ESRCH |
539 |
550 |
540 |
551 |
541 def isowner(st): |
552 def isowner(st: os.stat_result) -> bool: |
542 """Return True if the stat object st is from the current user.""" |
553 """Return True if the stat object st is from the current user.""" |
543 return st.st_uid == os.getuid() |
554 return st.st_uid == os.getuid() |
544 |
555 |
545 |
556 |
546 def findexe(command): |
557 def findexe(command: bytes) -> Optional[bytes]: |
547 """Find executable for command searching like which does. |
558 """Find executable for command searching like which does. |
548 If command is a basename then PATH is searched for command. |
559 If command is a basename then PATH is searched for command. |
549 PATH isn't searched if command is an absolute or relative path. |
560 PATH isn't searched if command is an absolute or relative path. |
550 If command isn't found None is returned.""" |
561 If command isn't found None is returned.""" |
551 if pycompat.sysplatform == b'OpenVMS': |
562 if pycompat.sysplatform == b'OpenVMS': |
552 return command |
563 return command |
553 |
564 |
554 def findexisting(executable): |
565 def findexisting(executable: bytes) -> Optional[bytes]: |
555 b'Will return executable if existing file' |
566 b'Will return executable if existing file' |
556 if os.path.isfile(executable) and os.access(executable, os.X_OK): |
567 if os.path.isfile(executable) and os.access(executable, os.X_OK): |
557 return executable |
568 return executable |
558 return None |
569 return None |
559 |
570 |
575 |
586 |
576 |
587 |
577 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK} |
588 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK} |
578 |
589 |
579 |
590 |
580 def statfiles(files): |
591 def statfiles(files: Sequence[bytes]) -> Iterator[Optional[os.stat_result]]: |
581 """Stat each file in files. Yield each stat, or None if a file does not |
592 """Stat each file in files. Yield each stat, or None if a file does not |
582 exist or has a type we don't care about.""" |
593 exist or has a type we don't care about.""" |
583 lstat = os.lstat |
594 lstat = os.lstat |
584 getkind = stat.S_IFMT |
595 getkind = stat.S_IFMT |
585 for nf in files: |
596 for nf in files: |
595 def getuser() -> bytes: |
606 def getuser() -> bytes: |
596 '''return name of current user''' |
607 '''return name of current user''' |
597 return pycompat.fsencode(getpass.getuser()) |
608 return pycompat.fsencode(getpass.getuser()) |
598 |
609 |
599 |
610 |
600 def username(uid=None): |
611 def username(uid: Optional[int] = None) -> Optional[bytes]: |
601 """Return the name of the user with the given uid. |
612 """Return the name of the user with the given uid. |
602 |
613 |
603 If uid is None, return the name of the current user.""" |
614 If uid is None, return the name of the current user.""" |
604 |
615 |
605 if uid is None: |
616 if uid is None: |
621 return pycompat.fsencode(grp.getgrgid(gid)[0]) |
632 return pycompat.fsencode(grp.getgrgid(gid)[0]) |
622 except KeyError: |
633 except KeyError: |
623 return pycompat.bytestr(gid) |
634 return pycompat.bytestr(gid) |
624 |
635 |
625 |
636 |
626 def groupmembers(name): |
637 def groupmembers(name: bytes) -> List[bytes]: |
627 """Return the list of members of the group with the given |
638 """Return the list of members of the group with the given |
628 name, KeyError if the group does not exist. |
639 name, KeyError if the group does not exist. |
629 """ |
640 """ |
630 name = pycompat.fsdecode(name) |
641 name = pycompat.fsdecode(name) |
631 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem)) |
642 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem)) |
641 |
652 |
642 def makedir(path: bytes, notindexed: bool) -> None: |
653 def makedir(path: bytes, notindexed: bool) -> None: |
643 os.mkdir(path) |
654 os.mkdir(path) |
644 |
655 |
645 |
656 |
646 def lookupreg(key, name=None, scope=None): |
657 def lookupreg( |
|
658 key: bytes, |
|
659 name: Optional[bytes] = None, |
|
660 scope: Optional[Union[int, Iterable[int]]] = None, |
|
661 ) -> Optional[bytes]: |
647 return None |
662 return None |
648 |
663 |
649 |
664 |
650 def hidewindow() -> None: |
665 def hidewindow() -> None: |
651 """Hide current shell window. |
666 """Hide current shell window. |
688 |
703 |
689 def __ne__(self, other): |
704 def __ne__(self, other): |
690 return not self == other |
705 return not self == other |
691 |
706 |
692 |
707 |
693 def statislink(st): |
708 def statislink(st: Optional[os.stat_result]) -> bool: |
694 '''check whether a stat result is a symlink''' |
709 '''check whether a stat result is a symlink''' |
695 return st and stat.S_ISLNK(st.st_mode) |
710 return stat.S_ISLNK(st.st_mode) if st else False |
696 |
711 |
697 |
712 |
698 def statisexec(st): |
713 def statisexec(st: Optional[os.stat_result]) -> bool: |
699 '''check whether a stat result is an executable file''' |
714 '''check whether a stat result is an executable file''' |
700 return st and (st.st_mode & 0o100 != 0) |
715 return (st.st_mode & 0o100 != 0) if st else False |
701 |
716 |
702 |
717 |
703 def poll(fds): |
718 def poll(fds): |
704 """block until something happens on any file descriptor |
719 """block until something happens on any file descriptor |
705 |
720 |
712 except ValueError: # out of range file descriptor |
727 except ValueError: # out of range file descriptor |
713 raise NotImplementedError() |
728 raise NotImplementedError() |
714 return sorted(list(set(sum(res, [])))) |
729 return sorted(list(set(sum(res, [])))) |
715 |
730 |
716 |
731 |
717 def readpipe(pipe): |
732 def readpipe(pipe) -> bytes: |
718 """Read all available data from a pipe.""" |
733 """Read all available data from a pipe.""" |
719 # We can't fstat() a pipe because Linux will always report 0. |
734 # We can't fstat() a pipe because Linux will always report 0. |
720 # So, we set the pipe to non-blocking mode and read everything |
735 # So, we set the pipe to non-blocking mode and read everything |
721 # that's available. |
736 # that's available. |
722 flags = fcntl.fcntl(pipe, fcntl.F_GETFL) |
737 flags = fcntl.fcntl(pipe, fcntl.F_GETFL) |
737 return b''.join(chunks) |
752 return b''.join(chunks) |
738 finally: |
753 finally: |
739 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags) |
754 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags) |
740 |
755 |
741 |
756 |
742 def bindunixsocket(sock, path): |
757 def bindunixsocket(sock, path: bytes) -> None: |
743 """Bind the UNIX domain socket to the specified path""" |
758 """Bind the UNIX domain socket to the specified path""" |
744 # use relative path instead of full path at bind() if possible, since |
759 # use relative path instead of full path at bind() if possible, since |
745 # AF_UNIX path has very small length limit (107 chars) on common |
760 # AF_UNIX path has very small length limit (107 chars) on common |
746 # platforms (see sys/un.h) |
761 # platforms (see sys/un.h) |
747 dirname, basename = os.path.split(path) |
762 dirname, basename = os.path.split(path) |