diff 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
line wrap: on
line diff
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -406,8 +406,18 @@ def rename(src, dst):
     """forcibly rename a file"""
     try:
         os.rename(src, dst)
-    except:
-        os.unlink(dst)
+    except OSError, err:
+        # on windows, rename to existing file is not allowed, so we
+        # must delete destination first. but if file is open, unlink
+        # schedules it for delete but does not delete it. rename
+        # happens immediately even for open files, so we create
+        # temporary file, delete it, rename destination to that name,
+        # then delete that. then rename is safe to do.
+        fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
+        os.close(fd)
+        os.unlink(temp)
+        os.rename(dst, temp)
+        os.unlink(temp)
         os.rename(src, dst)
 
 def unlink(f):
@@ -449,90 +459,13 @@ def audit_path(path):
         or os.pardir in parts):
         raise Abort(_("path contains illegal component: %s\n") % path)
 
-def opener(base, audit=True):
-    """
-    return a function that opens files relative to base
-
-    this function is used to hide the details of COW semantics and
-    remote file access from higher level code.
-    """
-    p = base
-    audit_p = audit
-
-    def mktempcopy(name):
-        d, fn = os.path.split(name)
-        fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
-        fp = os.fdopen(fd, "wb")
-        try:
-            fp.write(file(name, "rb").read())
-        except:
-            try: os.unlink(temp)
-            except: pass
-            raise
-        fp.close()
-        st = os.lstat(name)
-        os.chmod(temp, st.st_mode)
-        return temp
-
-    class atomictempfile(file):
-        """the file will only be copied when rename is called"""
-        def __init__(self, name, mode):
-            self.__name = name
-            self.temp = mktempcopy(name)
-            file.__init__(self, self.temp, mode)
-        def rename(self):
-            if not self.closed:
-                file.close(self)
-                rename(self.temp, self.__name)
-        def __del__(self):
-            if not self.closed:
-                try:
-                    os.unlink(self.temp)
-                except: pass
-                file.close(self)
-
-    class atomicfile(atomictempfile):
-        """the file will only be copied on close"""
-        def __init__(self, name, mode):
-            atomictempfile.__init__(self, name, mode)
-        def close(self):
-            self.rename()
-        def __del__(self):
-            self.rename()
-
-    def o(path, mode="r", text=False, atomic=False, atomictemp=False):
-        if audit_p:
-            audit_path(path)
-        f = os.path.join(p, path)
-
-        if not text:
-            mode += "b" # for that other OS
-
-        if mode[0] != "r":
-            try:
-                nlink = nlinks(f)
-            except OSError:
-                d = os.path.dirname(f)
-                if not os.path.isdir(d):
-                    os.makedirs(d)
-            else:
-                if atomic:
-                    return atomicfile(f, mode)
-                elif atomictemp:
-                    return atomictempfile(f, mode)
-                if nlink > 1:
-                    rename(mktempcopy(f), f)
-        return file(f, mode)
-
-    return o
-
 def _makelock_file(info, pathname):
     ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
     os.write(ld, info)
     os.close(ld)
 
 def _readlock_file(pathname):
-    return file(pathname).read()
+    return posixfile(pathname).read()
 
 def nlinks(pathname):
     """Return number of hardlinks for the given file."""
@@ -544,6 +477,15 @@ else:
     def os_link(src, dst):
         raise OSError(0, _("Hardlinks not supported"))
 
+def fstat(fp):
+    '''stat file object that may not have fileno method.'''
+    try:
+        return os.fstat(fp.fileno())
+    except AttributeError:
+        return os.stat(fp.name)
+
+posixfile = file
+
 # Platform specific variants
 if os.name == 'nt':
     demandload(globals(), "msvcrt")
@@ -722,6 +664,84 @@ else:
             return _("stopped by signal %d") % val, val
         raise ValueError(_("invalid exit code"))
 
+def opener(base, audit=True):
+    """
+    return a function that opens files relative to base
+
+    this function is used to hide the details of COW semantics and
+    remote file access from higher level code.
+    """
+    p = base
+    audit_p = audit
+
+    def mktempcopy(name):
+        d, fn = os.path.split(name)
+        fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
+        os.close(fd)
+        fp = posixfile(temp, "wb")
+        try:
+            fp.write(posixfile(name, "rb").read())
+        except:
+            try: os.unlink(temp)
+            except: pass
+            raise
+        fp.close()
+        st = os.lstat(name)
+        os.chmod(temp, st.st_mode)
+        return temp
+
+    class atomictempfile(posixfile):
+        """the file will only be copied when rename is called"""
+        def __init__(self, name, mode):
+            self.__name = name
+            self.temp = mktempcopy(name)
+            posixfile.__init__(self, self.temp, mode)
+        def rename(self):
+            if not self.closed:
+                posixfile.close(self)
+                rename(self.temp, self.__name)
+        def __del__(self):
+            if not self.closed:
+                try:
+                    os.unlink(self.temp)
+                except: pass
+                posixfile.close(self)
+
+    class atomicfile(atomictempfile):
+        """the file will only be copied on close"""
+        def __init__(self, name, mode):
+            atomictempfile.__init__(self, name, mode)
+        def close(self):
+            self.rename()
+        def __del__(self):
+            self.rename()
+
+    def o(path, mode="r", text=False, atomic=False, atomictemp=False):
+        if audit_p:
+            audit_path(path)
+        f = os.path.join(p, path)
+
+        if not text:
+            mode += "b" # for that other OS
+
+        if mode[0] != "r":
+            try:
+                nlink = nlinks(f)
+            except OSError:
+                d = os.path.dirname(f)
+                if not os.path.isdir(d):
+                    os.makedirs(d)
+            else:
+                if atomic:
+                    return atomicfile(f, mode)
+                elif atomictemp:
+                    return atomictempfile(f, mode)
+                if nlink > 1:
+                    rename(mktempcopy(f), f)
+        return posixfile(f, mode)
+
+    return o
+
 class chunkbuffer(object):
     """Allow arbitrary sized chunks of data to be efficiently read from an
     iterator over chunks of arbitrary size."""