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