[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