Mercurial > hg > mercurial-crew-with-dirclash
comparison mercurial/util.py @ 2176:9b42304d9896
fix file handling bugs on windows.
add util.posixfile class that has posix semantics on windows.
fix util.rename so it works with stupid windows delete semantics.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Tue, 02 May 2006 14:30:00 -0700 |
parents | 760339ccc799 |
children | 6886bc0b77af |
comparison
equal
deleted
inserted
replaced
2129:e5f5c21f4169 | 2176:9b42304d9896 |
---|---|
404 | 404 |
405 def rename(src, dst): | 405 def rename(src, dst): |
406 """forcibly rename a file""" | 406 """forcibly rename a file""" |
407 try: | 407 try: |
408 os.rename(src, dst) | 408 os.rename(src, dst) |
409 except: | 409 except OSError, err: |
410 os.unlink(dst) | 410 # on windows, rename to existing file is not allowed, so we |
411 # must delete destination first. but if file is open, unlink | |
412 # schedules it for delete but does not delete it. rename | |
413 # happens immediately even for open files, so we create | |
414 # temporary file, delete it, rename destination to that name, | |
415 # then delete that. then rename is safe to do. | |
416 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.') | |
417 os.close(fd) | |
418 os.unlink(temp) | |
419 os.rename(dst, temp) | |
420 os.unlink(temp) | |
411 os.rename(src, dst) | 421 os.rename(src, dst) |
412 | 422 |
413 def unlink(f): | 423 def unlink(f): |
414 """unlink and remove the directory if it is empty""" | 424 """unlink and remove the directory if it is empty""" |
415 os.unlink(f) | 425 os.unlink(f) |
447 parts = os.path.normcase(path).split(os.sep) | 457 parts = os.path.normcase(path).split(os.sep) |
448 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') | 458 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') |
449 or os.pardir in parts): | 459 or os.pardir in parts): |
450 raise Abort(_("path contains illegal component: %s\n") % path) | 460 raise Abort(_("path contains illegal component: %s\n") % path) |
451 | 461 |
462 def _makelock_file(info, pathname): | |
463 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) | |
464 os.write(ld, info) | |
465 os.close(ld) | |
466 | |
467 def _readlock_file(pathname): | |
468 return posixfile(pathname).read() | |
469 | |
470 def nlinks(pathname): | |
471 """Return number of hardlinks for the given file.""" | |
472 return os.stat(pathname).st_nlink | |
473 | |
474 if hasattr(os, 'link'): | |
475 os_link = os.link | |
476 else: | |
477 def os_link(src, dst): | |
478 raise OSError(0, _("Hardlinks not supported")) | |
479 | |
480 def fstat(fp): | |
481 '''stat file object that may not have fileno method.''' | |
482 try: | |
483 return os.fstat(fp.fileno()) | |
484 except AttributeError: | |
485 return os.stat(fp.name) | |
486 | |
487 posixfile = file | |
488 | |
489 # Platform specific variants | |
490 if os.name == 'nt': | |
491 demandload(globals(), "msvcrt") | |
492 nulldev = 'NUL:' | |
493 | |
494 class winstdout: | |
495 '''stdout on windows misbehaves if sent through a pipe''' | |
496 | |
497 def __init__(self, fp): | |
498 self.fp = fp | |
499 | |
500 def __getattr__(self, key): | |
501 return getattr(self.fp, key) | |
502 | |
503 def close(self): | |
504 try: | |
505 self.fp.close() | |
506 except: pass | |
507 | |
508 def write(self, s): | |
509 try: | |
510 return self.fp.write(s) | |
511 except IOError, inst: | |
512 if inst.errno != 0: raise | |
513 self.close() | |
514 raise IOError(errno.EPIPE, 'Broken pipe') | |
515 | |
516 sys.stdout = winstdout(sys.stdout) | |
517 | |
518 def system_rcpath(): | |
519 try: | |
520 return system_rcpath_win32() | |
521 except: | |
522 return [r'c:\mercurial\mercurial.ini'] | |
523 | |
524 def os_rcpath(): | |
525 '''return default os-specific hgrc search path''' | |
526 return system_rcpath() + [os.path.join(os.path.expanduser('~'), | |
527 'mercurial.ini')] | |
528 | |
529 def parse_patch_output(output_line): | |
530 """parses the output produced by patch and returns the file name""" | |
531 pf = output_line[14:] | |
532 if pf[0] == '`': | |
533 pf = pf[1:-1] # Remove the quotes | |
534 return pf | |
535 | |
536 def testpid(pid): | |
537 '''return False if pid dead, True if running or not known''' | |
538 return True | |
539 | |
540 def is_exec(f, last): | |
541 return last | |
542 | |
543 def set_exec(f, mode): | |
544 pass | |
545 | |
546 def set_binary(fd): | |
547 msvcrt.setmode(fd.fileno(), os.O_BINARY) | |
548 | |
549 def pconvert(path): | |
550 return path.replace("\\", "/") | |
551 | |
552 def localpath(path): | |
553 return path.replace('/', '\\') | |
554 | |
555 def normpath(path): | |
556 return pconvert(os.path.normpath(path)) | |
557 | |
558 makelock = _makelock_file | |
559 readlock = _readlock_file | |
560 | |
561 def explain_exit(code): | |
562 return _("exited with status %d") % code, code | |
563 | |
564 try: | |
565 # override functions with win32 versions if possible | |
566 from util_win32 import * | |
567 except ImportError: | |
568 pass | |
569 | |
570 else: | |
571 nulldev = '/dev/null' | |
572 | |
573 def rcfiles(path): | |
574 rcs = [os.path.join(path, 'hgrc')] | |
575 rcdir = os.path.join(path, 'hgrc.d') | |
576 try: | |
577 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir) | |
578 if f.endswith(".rc")]) | |
579 except OSError, inst: pass | |
580 return rcs | |
581 | |
582 def os_rcpath(): | |
583 '''return default os-specific hgrc search path''' | |
584 path = [] | |
585 if len(sys.argv) > 0: | |
586 path.extend(rcfiles(os.path.dirname(sys.argv[0]) + | |
587 '/../etc/mercurial')) | |
588 path.extend(rcfiles('/etc/mercurial')) | |
589 path.append(os.path.expanduser('~/.hgrc')) | |
590 path = [os.path.normpath(f) for f in path] | |
591 return path | |
592 | |
593 def parse_patch_output(output_line): | |
594 """parses the output produced by patch and returns the file name""" | |
595 pf = output_line[14:] | |
596 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0: | |
597 pf = pf[1:-1] # Remove the quotes | |
598 return pf | |
599 | |
600 def is_exec(f, last): | |
601 """check whether a file is executable""" | |
602 return (os.stat(f).st_mode & 0100 != 0) | |
603 | |
604 def set_exec(f, mode): | |
605 s = os.stat(f).st_mode | |
606 if (s & 0100 != 0) == mode: | |
607 return | |
608 if mode: | |
609 # Turn on +x for every +r bit when making a file executable | |
610 # and obey umask. | |
611 umask = os.umask(0) | |
612 os.umask(umask) | |
613 os.chmod(f, s | (s & 0444) >> 2 & ~umask) | |
614 else: | |
615 os.chmod(f, s & 0666) | |
616 | |
617 def set_binary(fd): | |
618 pass | |
619 | |
620 def pconvert(path): | |
621 return path | |
622 | |
623 def localpath(path): | |
624 return path | |
625 | |
626 normpath = os.path.normpath | |
627 | |
628 def makelock(info, pathname): | |
629 try: | |
630 os.symlink(info, pathname) | |
631 except OSError, why: | |
632 if why.errno == errno.EEXIST: | |
633 raise | |
634 else: | |
635 _makelock_file(info, pathname) | |
636 | |
637 def readlock(pathname): | |
638 try: | |
639 return os.readlink(pathname) | |
640 except OSError, why: | |
641 if why.errno == errno.EINVAL: | |
642 return _readlock_file(pathname) | |
643 else: | |
644 raise | |
645 | |
646 def testpid(pid): | |
647 '''return False if pid dead, True if running or not sure''' | |
648 try: | |
649 os.kill(pid, 0) | |
650 return True | |
651 except OSError, inst: | |
652 return inst.errno != errno.ESRCH | |
653 | |
654 def explain_exit(code): | |
655 """return a 2-tuple (desc, code) describing a process's status""" | |
656 if os.WIFEXITED(code): | |
657 val = os.WEXITSTATUS(code) | |
658 return _("exited with status %d") % val, val | |
659 elif os.WIFSIGNALED(code): | |
660 val = os.WTERMSIG(code) | |
661 return _("killed by signal %d") % val, val | |
662 elif os.WIFSTOPPED(code): | |
663 val = os.WSTOPSIG(code) | |
664 return _("stopped by signal %d") % val, val | |
665 raise ValueError(_("invalid exit code")) | |
666 | |
452 def opener(base, audit=True): | 667 def opener(base, audit=True): |
453 """ | 668 """ |
454 return a function that opens files relative to base | 669 return a function that opens files relative to base |
455 | 670 |
456 this function is used to hide the details of COW semantics and | 671 this function is used to hide the details of COW semantics and |
460 audit_p = audit | 675 audit_p = audit |
461 | 676 |
462 def mktempcopy(name): | 677 def mktempcopy(name): |
463 d, fn = os.path.split(name) | 678 d, fn = os.path.split(name) |
464 fd, temp = tempfile.mkstemp(prefix=fn, dir=d) | 679 fd, temp = tempfile.mkstemp(prefix=fn, dir=d) |
465 fp = os.fdopen(fd, "wb") | 680 os.close(fd) |
681 fp = posixfile(temp, "wb") | |
466 try: | 682 try: |
467 fp.write(file(name, "rb").read()) | 683 fp.write(posixfile(name, "rb").read()) |
468 except: | 684 except: |
469 try: os.unlink(temp) | 685 try: os.unlink(temp) |
470 except: pass | 686 except: pass |
471 raise | 687 raise |
472 fp.close() | 688 fp.close() |
473 st = os.lstat(name) | 689 st = os.lstat(name) |
474 os.chmod(temp, st.st_mode) | 690 os.chmod(temp, st.st_mode) |
475 return temp | 691 return temp |
476 | 692 |
477 class atomictempfile(file): | 693 class atomictempfile(posixfile): |
478 """the file will only be copied when rename is called""" | 694 """the file will only be copied when rename is called""" |
479 def __init__(self, name, mode): | 695 def __init__(self, name, mode): |
480 self.__name = name | 696 self.__name = name |
481 self.temp = mktempcopy(name) | 697 self.temp = mktempcopy(name) |
482 file.__init__(self, self.temp, mode) | 698 posixfile.__init__(self, self.temp, mode) |
483 def rename(self): | 699 def rename(self): |
484 if not self.closed: | 700 if not self.closed: |
485 file.close(self) | 701 posixfile.close(self) |
486 rename(self.temp, self.__name) | 702 rename(self.temp, self.__name) |
487 def __del__(self): | 703 def __del__(self): |
488 if not self.closed: | 704 if not self.closed: |
489 try: | 705 try: |
490 os.unlink(self.temp) | 706 os.unlink(self.temp) |
491 except: pass | 707 except: pass |
492 file.close(self) | 708 posixfile.close(self) |
493 | 709 |
494 class atomicfile(atomictempfile): | 710 class atomicfile(atomictempfile): |
495 """the file will only be copied on close""" | 711 """the file will only be copied on close""" |
496 def __init__(self, name, mode): | 712 def __init__(self, name, mode): |
497 atomictempfile.__init__(self, name, mode) | 713 atomictempfile.__init__(self, name, mode) |
520 return atomicfile(f, mode) | 736 return atomicfile(f, mode) |
521 elif atomictemp: | 737 elif atomictemp: |
522 return atomictempfile(f, mode) | 738 return atomictempfile(f, mode) |
523 if nlink > 1: | 739 if nlink > 1: |
524 rename(mktempcopy(f), f) | 740 rename(mktempcopy(f), f) |
525 return file(f, mode) | 741 return posixfile(f, mode) |
526 | 742 |
527 return o | 743 return o |
528 | |
529 def _makelock_file(info, pathname): | |
530 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) | |
531 os.write(ld, info) | |
532 os.close(ld) | |
533 | |
534 def _readlock_file(pathname): | |
535 return file(pathname).read() | |
536 | |
537 def nlinks(pathname): | |
538 """Return number of hardlinks for the given file.""" | |
539 return os.stat(pathname).st_nlink | |
540 | |
541 if hasattr(os, 'link'): | |
542 os_link = os.link | |
543 else: | |
544 def os_link(src, dst): | |
545 raise OSError(0, _("Hardlinks not supported")) | |
546 | |
547 # Platform specific variants | |
548 if os.name == 'nt': | |
549 demandload(globals(), "msvcrt") | |
550 nulldev = 'NUL:' | |
551 | |
552 class winstdout: | |
553 '''stdout on windows misbehaves if sent through a pipe''' | |
554 | |
555 def __init__(self, fp): | |
556 self.fp = fp | |
557 | |
558 def __getattr__(self, key): | |
559 return getattr(self.fp, key) | |
560 | |
561 def close(self): | |
562 try: | |
563 self.fp.close() | |
564 except: pass | |
565 | |
566 def write(self, s): | |
567 try: | |
568 return self.fp.write(s) | |
569 except IOError, inst: | |
570 if inst.errno != 0: raise | |
571 self.close() | |
572 raise IOError(errno.EPIPE, 'Broken pipe') | |
573 | |
574 sys.stdout = winstdout(sys.stdout) | |
575 | |
576 def system_rcpath(): | |
577 try: | |
578 return system_rcpath_win32() | |
579 except: | |
580 return [r'c:\mercurial\mercurial.ini'] | |
581 | |
582 def os_rcpath(): | |
583 '''return default os-specific hgrc search path''' | |
584 return system_rcpath() + [os.path.join(os.path.expanduser('~'), | |
585 'mercurial.ini')] | |
586 | |
587 def parse_patch_output(output_line): | |
588 """parses the output produced by patch and returns the file name""" | |
589 pf = output_line[14:] | |
590 if pf[0] == '`': | |
591 pf = pf[1:-1] # Remove the quotes | |
592 return pf | |
593 | |
594 def testpid(pid): | |
595 '''return False if pid dead, True if running or not known''' | |
596 return True | |
597 | |
598 def is_exec(f, last): | |
599 return last | |
600 | |
601 def set_exec(f, mode): | |
602 pass | |
603 | |
604 def set_binary(fd): | |
605 msvcrt.setmode(fd.fileno(), os.O_BINARY) | |
606 | |
607 def pconvert(path): | |
608 return path.replace("\\", "/") | |
609 | |
610 def localpath(path): | |
611 return path.replace('/', '\\') | |
612 | |
613 def normpath(path): | |
614 return pconvert(os.path.normpath(path)) | |
615 | |
616 makelock = _makelock_file | |
617 readlock = _readlock_file | |
618 | |
619 def explain_exit(code): | |
620 return _("exited with status %d") % code, code | |
621 | |
622 try: | |
623 # override functions with win32 versions if possible | |
624 from util_win32 import * | |
625 except ImportError: | |
626 pass | |
627 | |
628 else: | |
629 nulldev = '/dev/null' | |
630 | |
631 def rcfiles(path): | |
632 rcs = [os.path.join(path, 'hgrc')] | |
633 rcdir = os.path.join(path, 'hgrc.d') | |
634 try: | |
635 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir) | |
636 if f.endswith(".rc")]) | |
637 except OSError, inst: pass | |
638 return rcs | |
639 | |
640 def os_rcpath(): | |
641 '''return default os-specific hgrc search path''' | |
642 path = [] | |
643 if len(sys.argv) > 0: | |
644 path.extend(rcfiles(os.path.dirname(sys.argv[0]) + | |
645 '/../etc/mercurial')) | |
646 path.extend(rcfiles('/etc/mercurial')) | |
647 path.append(os.path.expanduser('~/.hgrc')) | |
648 path = [os.path.normpath(f) for f in path] | |
649 return path | |
650 | |
651 def parse_patch_output(output_line): | |
652 """parses the output produced by patch and returns the file name""" | |
653 pf = output_line[14:] | |
654 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0: | |
655 pf = pf[1:-1] # Remove the quotes | |
656 return pf | |
657 | |
658 def is_exec(f, last): | |
659 """check whether a file is executable""" | |
660 return (os.stat(f).st_mode & 0100 != 0) | |
661 | |
662 def set_exec(f, mode): | |
663 s = os.stat(f).st_mode | |
664 if (s & 0100 != 0) == mode: | |
665 return | |
666 if mode: | |
667 # Turn on +x for every +r bit when making a file executable | |
668 # and obey umask. | |
669 umask = os.umask(0) | |
670 os.umask(umask) | |
671 os.chmod(f, s | (s & 0444) >> 2 & ~umask) | |
672 else: | |
673 os.chmod(f, s & 0666) | |
674 | |
675 def set_binary(fd): | |
676 pass | |
677 | |
678 def pconvert(path): | |
679 return path | |
680 | |
681 def localpath(path): | |
682 return path | |
683 | |
684 normpath = os.path.normpath | |
685 | |
686 def makelock(info, pathname): | |
687 try: | |
688 os.symlink(info, pathname) | |
689 except OSError, why: | |
690 if why.errno == errno.EEXIST: | |
691 raise | |
692 else: | |
693 _makelock_file(info, pathname) | |
694 | |
695 def readlock(pathname): | |
696 try: | |
697 return os.readlink(pathname) | |
698 except OSError, why: | |
699 if why.errno == errno.EINVAL: | |
700 return _readlock_file(pathname) | |
701 else: | |
702 raise | |
703 | |
704 def testpid(pid): | |
705 '''return False if pid dead, True if running or not sure''' | |
706 try: | |
707 os.kill(pid, 0) | |
708 return True | |
709 except OSError, inst: | |
710 return inst.errno != errno.ESRCH | |
711 | |
712 def explain_exit(code): | |
713 """return a 2-tuple (desc, code) describing a process's status""" | |
714 if os.WIFEXITED(code): | |
715 val = os.WEXITSTATUS(code) | |
716 return _("exited with status %d") % val, val | |
717 elif os.WIFSIGNALED(code): | |
718 val = os.WTERMSIG(code) | |
719 return _("killed by signal %d") % val, val | |
720 elif os.WIFSTOPPED(code): | |
721 val = os.WSTOPSIG(code) | |
722 return _("stopped by signal %d") % val, val | |
723 raise ValueError(_("invalid exit code")) | |
724 | 744 |
725 class chunkbuffer(object): | 745 class chunkbuffer(object): |
726 """Allow arbitrary sized chunks of data to be efficiently read from an | 746 """Allow arbitrary sized chunks of data to be efficiently read from an |
727 iterator over chunks of arbitrary size.""" | 747 iterator over chunks of arbitrary size.""" |
728 | 748 |