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.
--- a/mercurial/appendfile.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/appendfile.py Tue May 02 14:30:00 2006 -0700
@@ -6,7 +6,7 @@
# of the GNU General Public License, incorporated herein by reference.
from demandload import *
-demandload(globals(), "cStringIO changelog errno manifest os tempfile")
+demandload(globals(), "cStringIO changelog errno manifest os tempfile util")
# writes to metadata files are ordered. reads: changelog, manifest,
# normal files. writes: normal files, manifest, changelog.
@@ -36,19 +36,21 @@
def __init__(self, fp, tmpname):
if tmpname:
self.tmpname = tmpname
- self.tmpfp = open(self.tmpname, 'ab+')
+ self.tmpfp = util.posixfile(self.tmpname, 'ab+')
else:
fd, self.tmpname = tempfile.mkstemp()
- self.tmpfp = os.fdopen(fd, 'ab+')
+ os.close(fd)
+ self.tmpfp = util.posixfile(self.tmpname, 'ab+')
self.realfp = fp
self.offset = fp.tell()
# real file is not written by anyone else. cache its size so
# seek and read can be fast.
- self.realsize = os.fstat(fp.fileno()).st_size
+ self.realsize = util.fstat(fp).st_size
+ self.name = fp.name
def end(self):
self.tmpfp.flush() # make sure the stat is correct
- return self.realsize + os.fstat(self.tmpfp.fileno()).st_size
+ return self.realsize + util.fstat(self.tmpfp).st_size
def tell(self):
return self.offset
--- a/mercurial/bundlerepo.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/bundlerepo.py Tue May 02 14:30:00 2006 -0700
@@ -160,7 +160,7 @@
def __init__(self, ui, path, bundlename):
localrepo.localrepository.__init__(self, ui, path)
f = open(bundlename, "rb")
- s = os.fstat(f.fileno())
+ s = util.fstat(f)
self.bundlefile = f
header = self.bundlefile.read(6)
if not header.startswith("HG"):
--- a/mercurial/revlog.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/revlog.py Tue May 02 14:30:00 2006 -0700
@@ -14,7 +14,7 @@
from i18n import gettext as _
from demandload import demandload
demandload(globals(), "binascii changegroup errno heapq mdiff os")
-demandload(globals(), "sha struct zlib")
+demandload(globals(), "sha struct util zlib")
# revlog version strings
REVLOGV0 = 0
@@ -322,7 +322,7 @@
i = ""
else:
try:
- st = os.fstat(f.fileno())
+ st = util.fstat(f)
except AttributeError, inst:
st = None
else:
--- a/mercurial/sshrepo.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/sshrepo.py Tue May 02 14:30:00 2006 -0700
@@ -57,7 +57,7 @@
def readerr(self):
while 1:
- size = os.fstat(self.pipee.fileno())[stat.ST_SIZE]
+ size = util.fstat(self.pipee).st_size
if size == 0: break
l = self.pipee.readline()
if not l: break
--- a/mercurial/util.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/util.py Tue May 02 14:30:00 2006 -0700
@@ -406,8 +406,18 @@
"""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 @@
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 @@
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 @@
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."""
--- a/mercurial/util_win32.py Tue Apr 25 23:28:40 2006 +0200
+++ b/mercurial/util_win32.py Tue May 02 14:30:00 2006 -0700
@@ -16,9 +16,9 @@
from demandload import *
from i18n import gettext as _
demandload(globals(), 'errno os pywintypes win32con win32file win32process')
-demandload(globals(), 'winerror')
+demandload(globals(), 'cStringIO winerror')
-class WinError(OSError):
+class WinError:
winerror_map = {
winerror.ERROR_ACCESS_DENIED: errno.EACCES,
winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
@@ -105,7 +105,7 @@
winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
winerror.ERROR_PATH_BUSY: errno.EBUSY,
- winerror.ERROR_PATH_NOT_FOUND: errno.ENOTDIR,
+ winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
winerror.ERROR_PIPE_BUSY: errno.EBUSY,
winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
@@ -129,6 +129,19 @@
def __init__(self, err):
self.win_errno, self.win_function, self.win_strerror = err
+ if self.win_strerror.endswith('.'):
+ self.win_strerror = self.win_strerror[:-1]
+
+class WinIOError(WinError, IOError):
+ def __init__(self, err, filename=None):
+ WinError.__init__(self, err)
+ IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
+ self.win_strerror)
+ self.filename = filename
+
+class WinOSError(WinError, OSError):
+ def __init__(self, err):
+ WinError.__init__(self, err)
OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
self.win_strerror)
@@ -137,7 +150,7 @@
try:
win32file.CreateHardLink(dst, src)
except pywintypes.error, details:
- raise WinError(details)
+ raise WinOSError(details)
def nlinks(pathname):
"""Return number of hardlinks for the given file."""
@@ -169,3 +182,99 @@
proc = win32api.GetCurrentProcess()
filename = win32process.GetModuleFileNameEx(proc, 0)
return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
+
+class posixfile(object):
+ '''file object with posix-like semantics. on windows, normal
+ files can not be deleted or renamed if they are open. must open
+ with win32file.FILE_SHARE_DELETE. this flag does not exist on
+ windows <= nt.'''
+
+ # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
+ # but does not work at all. wrap win32 file api instead.
+
+ def __init__(self, name, mode='rb'):
+ access = 0
+ if 'r' in mode or '+' in mode:
+ access |= win32file.GENERIC_READ
+ if 'w' in mode or 'a' in mode:
+ access |= win32file.GENERIC_WRITE
+ if 'r' in mode:
+ creation = win32file.OPEN_EXISTING
+ elif 'a' in mode:
+ creation = win32file.OPEN_ALWAYS
+ else:
+ creation = win32file.CREATE_ALWAYS
+ try:
+ self.handle = win32file.CreateFile(name,
+ access,
+ win32file.FILE_SHARE_READ |
+ win32file.FILE_SHARE_WRITE |
+ win32file.FILE_SHARE_DELETE,
+ None,
+ creation,
+ win32file.FILE_ATTRIBUTE_NORMAL,
+ 0)
+ except pywintypes.error, err:
+ raise WinIOError(err, name)
+ self.closed = False
+ self.name = name
+ self.mode = mode
+
+ def read(self, count=-1):
+ try:
+ cs = cStringIO.StringIO()
+ while count:
+ wincount = int(count)
+ if wincount == -1:
+ wincount = 1048576
+ val, data = win32file.ReadFile(self.handle, wincount)
+ if not data: break
+ cs.write(data)
+ if count != -1:
+ count -= len(data)
+ return cs.getvalue()
+ except pywintypes.error, err:
+ raise WinIOError(err)
+
+ def write(self, data):
+ try:
+ if 'a' in self.mode:
+ win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
+ nwrit = 0
+ while nwrit < len(data):
+ val, nwrit = win32file.WriteFile(self.handle, data)
+ data = data[nwrit:]
+ except pywintypes.error, err:
+ raise WinIOError(err)
+
+ def seek(self, pos, whence=0):
+ try:
+ win32file.SetFilePointer(self.handle, int(pos), whence)
+ except pywintypes.error, err:
+ raise WinIOError(err)
+
+ def tell(self):
+ try:
+ return win32file.SetFilePointer(self.handle, 0,
+ win32file.FILE_CURRENT)
+ except pywintypes.error, err:
+ raise WinIOError(err)
+
+ def close(self):
+ if not self.closed:
+ self.handle = None
+ self.closed = True
+
+ def flush(self):
+ try:
+ win32file.FlushFileBuffers(self.handle)
+ except pywintypes.error, err:
+ raise WinIOError(err)
+
+ def truncate(self, pos=0):
+ try:
+ win32file.SetFilePointer(self.handle, int(pos),
+ win32file.FILE_BEGIN)
+ win32file.SetEndOfFile(self.handle)
+ except pywintypes.error, err:
+ raise WinIOError(err)