[PATCH 1 of 3] extract new function 'unlinkopened' and use it in rename

Adrian Buehlmann adrian at cadifra.com
Thu Dec 2 15:38:36 CST 2010


# HG changeset patch
# User Adrian Buehlmann <adrian at cadifra.com>
# Date 1291196682 -3600
# Node ID 58ffd94ff4ead5ec6da62581db662ac63cd714ef
# Parent  8c6b7a5f38c4585b1b2c2ceaffa8023f36a18479
extract new function 'unlinkopened' and use it in rename

unlinkopened is just os.unlink for non-Windows platforms

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -15,6 +15,7 @@ normpath = os.path.normpath
 samestat = os.path.samestat
 rename = os.rename
 expandglobs = False
+unlinkopened = os.unlink
 
 umask = os.umask(0)
 os.umask(umask)
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -284,42 +284,45 @@ def unlink(f):
     except OSError:
         pass
 
+def unlinkopened(f):
+    '''unlink a possibly opened file
+
+    Windows can't delete open files'''
+
+    # If a file is open on Windows, os.unlink may fail with an OSError. Rename
+    # works and happens immediately even for open files, so we rename f to a
+    # temporary name, then delete that. Windows will delay the deletion of the
+    # temp file, until the last reading process closes the file.
+
+    # The temporary name is chosen at random to avoid the situation where a
+    # file is left lying around from a previous aborted run. The usual race
+    # condition this introduces can't be avoided as we need the name to rename
+    # into, and not the file itself. Due to the nature of the operation
+    # however, any races will at worst lead to the rename failing and the
+    # current operation aborting.
+
+    def tempname(prefix):
+        for tries in xrange(10):
+            temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
+            if not os.path.exists(temp):
+                return temp
+        raise IOError, (errno.EEXIST, "No usable temporary filename found")
+
+    temp = tempname(f)
+    os.rename(f, temp)
+    try:
+        os.unlink(temp)
+    except:
+        # Some rude AV-scanners on Windows may quickly open the just renamed-to
+        # temp file again. Leaking that tempfile is superior than aborting here.
+        pass
+
 def rename(src, dst):
     '''atomically rename file src to dst, replacing dst if it exists'''
     try:
         os.rename(src, dst)
     except OSError: # FIXME: check err (EEXIST ?)
-
-        # On windows, rename to existing file is not allowed, so we
-        # must delete destination first. But if a file is open, unlink
-        # schedules it for delete but does not delete it. Rename
-        # happens immediately even for open files, so we rename
-        # destination to a temporary name, then delete that. Then
-        # rename is safe to do.
-        # The temporary name is chosen at random to avoid the situation
-        # where a file is left lying around from a previous aborted run.
-        # The usual race condition this introduces can't be avoided as
-        # we need the name to rename into, and not the file itself. Due
-        # to the nature of the operation however, any races will at worst
-        # lead to the rename failing and the current operation aborting.
-
-        def tempname(prefix):
-            for tries in xrange(10):
-                temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
-                if not os.path.exists(temp):
-                    return temp
-            raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
-        temp = tempname(dst)
-        os.rename(dst, temp)
-        try:
-            os.unlink(temp)
-        except:
-            # Some rude AV-scanners on Windows may cause the unlink to
-            # fail. Not aborting here just leaks the temp file, whereas
-            # aborting at this point may leave serious inconsistencies.
-            # Ideally, we would notify the user here.
-            pass
+        unlinkopened(dst)
         os.rename(src, dst)
 
 def spawndetached(args):


More information about the Mercurial-devel mailing list