--- a/mercurial/lock.py Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/lock.py Thu Jan 20 20:35:54 2011 +0100
@@ -113,7 +113,7 @@
# held, or can race and break valid lock.
try:
l = lock(self.f + '.break', timeout=0)
- os.unlink(self.f)
+ util.unlink(self.f)
l.release()
except error.LockError:
return locker
@@ -126,7 +126,7 @@
if self.releasefn:
self.releasefn()
try:
- os.unlink(self.f)
+ util.unlink(self.f)
except OSError:
pass
--- a/mercurial/posix.py Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/posix.py Thu Jan 20 20:35:54 2011 +0100
@@ -13,6 +13,7 @@
nulldev = '/dev/null'
normpath = os.path.normpath
samestat = os.path.samestat
+unlink = os.unlink
rename = os.rename
expandglobs = False
--- a/mercurial/util.py Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/util.py Thu Jan 20 20:35:54 2011 +0100
@@ -911,7 +911,7 @@
return atomictempfile(f, mode, self.createmode)
try:
if 'w' in mode:
- os.unlink(f)
+ unlink(f)
nlink = 0
else:
# nlinks() may behave differently for files on Windows
@@ -919,7 +919,9 @@
fd = open(f)
nlink = nlinks(f)
fd.close()
- except (OSError, IOError):
+ except (OSError, IOError), e:
+ if e.errno != errno.ENOENT:
+ raise
nlink = 0
if not os.path.isdir(dirname):
makedirs(dirname, self.createmode)
--- a/mercurial/windows.py Tue Jan 18 15:55:49 2011 -0600
+++ b/mercurial/windows.py Thu Jan 20 20:35:54 2011 +0100
@@ -285,40 +285,54 @@
except OSError:
pass
+def unlink(f):
+ '''try to implement POSIX' unlink semantics on Windows'''
+
+ # POSIX allows to unlink and rename open files. Windows has serious
+ # problems with doing that:
+ # - Calling os.unlink (or os.rename) on a file f fails if f or any
+ # hardlinked copy of f has been opened with Python's open(). There is no
+ # way such a file can be deleted or renamed on Windows (other than
+ # scheduling the delete or rename for the next reboot).
+ # - Calling os.unlink on a file that has been opened with Mercurial's
+ # posixfile (or comparable methods) will delay the actual deletion of
+ # the file for as long as the file is held open. The filename is blocked
+ # during that time and cannot be used for recreating a new file under
+ # that same name ("zombie file"). Directories containing such zombie files
+ # cannot be removed or moved.
+ # A file that has been opened with posixfile can be renamed, so we rename
+ # f to a random temporary name before calling os.unlink on it. This allows
+ # callers to recreate f immediately while having other readers do their
+ # implicit zombie filename blocking on a temporary name.
+
+ for tries in xrange(10):
+ temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
+ try:
+ os.rename(f, temp) # raises OSError EEXIST if temp exists
+ break
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+ else:
+ raise IOError, (errno.EEXIST, "No usable temporary filename found")
+
+ try:
+ os.unlink(temp)
+ except:
+ # Some very rude AV-scanners on Windows may cause this 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 in this case 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.
-
- for tries in xrange(10):
- temp = '%s-%08x' % (dst, random.randint(0, 0xffffffff))
- try:
- os.rename(dst, temp) # raises OSError EEXIST if temp exists
- break
- except OSError, e:
- if e.errno != errno.EEXIST:
- raise
- else:
- raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
- 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
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+ unlink(dst)
os.rename(src, dst)
def spawndetached(args):