[PATCH] enable long/reserved paths on Windows (EXPERIMENTAL)
Adrian Buehlmann
adrian at cadifra.com
Tue Jun 24 17:38:59 UTC 2008
# HG changeset patch
# User Adrian Buehlmann <adrian at cadifra.com>
# Date 1214326991 -7200
# Node ID bee7843ebe514828cdd0e0041d51526ce236127f
# Parent af7b26b0f884d749ae8115256c009eea4c023378
enable long/reserved paths on Windows (EXPERIMENTAL)
THIS PATCH IS EXPERIMENTAL ONLY, NOT INTENDED FOR INCLUSION INTO
OFFICIAL MERCURIAL REPOS
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -129,7 +129,7 @@
dest = localpath(dest)
source = localpath(source)
- if os.path.exists(dest):
+ if os.path.exists(util.longpath(dest)):
raise util.Abort(_("destination '%s' already exists") % dest)
class DirCleanup(object):
@@ -165,17 +165,17 @@
if copy:
def force_copy(src, dst):
- if not os.path.exists(src):
+ if not os.path.exists(util.longpath(src)):
# Tolerate empty source repository and optional files
return
util.copyfiles(src, dst)
src_store = os.path.realpath(src_repo.spath)
- if not os.path.exists(dest):
- os.mkdir(dest)
+ if not os.path.exists(util.longpath(dest)):
+ os.mkdir(util.longpath(dest))
try:
dest_path = os.path.realpath(os.path.join(dest, ".hg"))
- os.mkdir(dest_path)
+ os.mkdir(util.longpath(dest_path))
except OSError, inst:
if inst.errno == errno.EEXIST:
dir_cleanup.close()
@@ -188,7 +188,7 @@
# copy the dummy changelog
force_copy(src_repo.join("00changelog.i"), dummy_changelog)
dest_store = os.path.join(dest_path, "store")
- os.mkdir(dest_store)
+ os.mkdir(util.longpath(dest_store))
else:
dest_store = dest_path
# copy the requires file
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -25,14 +25,15 @@
self.opener = util.opener(self.path)
self.wopener = util.opener(self.root)
- if not os.path.isdir(self.path):
+ lp = util.longpath
+ if not os.path.isdir(lp(self.path)):
if create:
- if not os.path.exists(path):
- os.mkdir(path)
- os.mkdir(self.path)
+ if not os.path.exists(lp(path)):
+ os.mkdir(lp(path))
+ os.mkdir(lp(self.path))
requirements = ["revlogv1"]
if parentui.configbool('format', 'usestore', True):
- os.mkdir(os.path.join(self.path, "store"))
+ os.mkdir(lp(os.path.join(self.path, "store")))
requirements.append("store")
# create an invalid changelog
self.opener("00changelog.i", "a").write(
diff --git a/mercurial/lock.py b/mercurial/lock.py
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -104,7 +104,7 @@
try:
l = lock(self.f + '.break')
l.trylock()
- os.unlink(self.f)
+ os.unlink(util.longpath(self.f))
l.release()
except (LockHeld, LockUnavailable):
return locker
diff --git a/mercurial/osutil.py b/mercurial/osutil.py
--- a/mercurial/osutil.py
+++ b/mercurial/osutil.py
@@ -1,4 +1,4 @@
-import os, stat
+import os, stat, util
def _mode_to_kind(mode):
if stat.S_ISREG(mode): return stat.S_IFREG
@@ -26,10 +26,10 @@
'''
result = []
prefix = path + os.sep
- names = os.listdir(path)
+ names = os.listdir(util.longpath(path))
names.sort()
for fn in names:
- st = os.lstat(prefix + fn)
+ st = os.lstat(util.longpath(prefix + fn))
if stat:
result.append((fn, _mode_to_kind(st.st_mode), st))
else:
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -356,7 +356,11 @@
def parseindex(self, fp, inline):
try:
- size = util.fstat(fp).st_size
+ if callable(getattr(fp, "size", None)):
+ # assuming util_win32.posixfile_nt
+ size = fp.size()
+ else:
+ size = util.fstat(fp).st_size
except AttributeError:
size = 0
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -12,7 +12,7 @@
# of the GNU General Public License, incorporated herein by reference.
from i18n import _
-import os
+import os, util
class transaction(object):
def __init__(self, report, opener, journal, after=None, createmode=None):
@@ -26,15 +26,15 @@
self.map = {}
self.journal = journal
- self.file = open(self.journal, "w")
+ self.file = open(util.longpath(self.journal), "w")
if createmode is not None:
- os.chmod(self.journal, createmode & 0666)
+ os.chmod(util.longpath(self.journal), createmode & 0666)
def __del__(self):
if self.journal:
if self.entries: self.abort()
self.file.close()
- try: os.unlink(self.journal)
+ try: os.unlink(util.longpath(self.journal))
except: pass
def add(self, file, offset, data=None):
@@ -74,7 +74,7 @@
if self.after:
self.after()
else:
- os.unlink(self.journal)
+ os.unlink(util.longpath(self.journal))
self.journal = None
def abort(self):
@@ -103,6 +103,6 @@
opener(f, "a").truncate(int(o))
else:
fn = opener(f).name
- os.unlink(fn)
- os.unlink(file)
+ os.unlink(util.longpath(fn))
+ os.unlink(util.longpath(file))
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -644,8 +644,9 @@
def rename(src, dst):
"""forcibly rename a file"""
+ lsrc, ldst = longpath(src), longpath(dst)
try:
- os.rename(src, dst)
+ os.rename(lsrc, ldst)
except OSError, err: # FIXME: check err (EEXIST ?)
# on windows, rename to existing file is not allowed, so we
# must delete destination first. but if file is open, unlink
@@ -653,19 +654,19 @@
# 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 '.')
+ fd, temp = tempfile.mkstemp()
os.close(fd)
- os.unlink(temp)
- os.rename(dst, temp)
- os.unlink(temp)
- os.rename(src, dst)
+ os.unlink(longpath(temp))
+ os.rename(longpath(dst), longpath(temp))
+ os.unlink(longpath(temp))
+ os.rename(longpath(src), longpath(dst))
def unlink(f):
"""unlink and remove the directory if it is empty"""
- os.unlink(f)
+ os.unlink(longpath(f))
# try removing directories that might now be empty
try:
- os.removedirs(os.path.dirname(f))
+ os.removedirs(longpath(os.path.dirname(f)))
except OSError:
pass
@@ -688,11 +689,11 @@
"""Copy a directory tree using hardlinks if possible"""
if hardlink is None:
- hardlink = (os.stat(src).st_dev ==
- os.stat(os.path.dirname(dst)).st_dev)
+ hardlink = (os.stat(longpath(src)).st_dev ==
+ os.stat(longpath(os.path.dirname(dst))).st_dev)
- if os.path.isdir(src):
- os.mkdir(dst)
+ if os.path.isdir(longpath(src)):
+ os.mkdir(longpath(dst))
for name, kind in osutil.listdir(src):
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
@@ -700,12 +701,12 @@
else:
if hardlink:
try:
- os_link(src, dst)
+ os_link(longpath(src), longpath(dst))
except (IOError, OSError):
hardlink = False
- shutil.copy(src, dst)
+ shutil.copy(longpath(src), longpath(dst))
else:
- shutil.copy(src, dst)
+ shutil.copy(longpath(src), longpath(dst))
class path_auditor(object):
'''ensure that a filesystem path contains no banned components.
@@ -763,7 +764,7 @@
self.auditeddir.update(prefixes)
def _makelock_file(info, pathname):
- ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
+ ld = os.open(longpath(pathname), os.O_CREAT | os.O_WRONLY | os.O_EXCL)
os.write(ld, info)
os.close(ld)
@@ -983,6 +984,23 @@
def lookup_reg(key, name=None, scope=None):
return None
+def makedirs(name, mode=None):
+ """recursive directory creation with parent mode inheritance"""
+ print "makedirs %s" % name
+ try:
+ os.mkdir(name)
+ if mode is not None:
+ os.chmod(name, mode)
+ return
+ except OSError, err:
+ if err.errno == errno.EEXIST:
+ return
+ if err.errno != errno.ENOENT:
+ raise
+ parent = os.path.abspath(os.path.dirname(name))
+ makedirs(parent, mode)
+ makedirs(name, mode)
+
# Platform specific variants
if os.name == 'nt':
import msvcrt
@@ -1089,6 +1107,19 @@
def localpath(path):
return path.replace('/', '\\')
+
+ _longpathprefix = "\\\\?\\"
+ def longpath(path):
+ '''convert path to a Windows long path
+ needed to call Windows api with paths longer than 260'''
+ if path.startswith(_longpathprefix):
+ res = path
+ else:
+ if (len(path) < 256): # don't try expanding relative paths if
+ # they are too long
+ path = os.path.abspath(path)
+ res = unicode(_longpathprefix + path)
+ return res
def normpath(path):
return pconvert(os.path.normpath(path))
@@ -1259,6 +1290,9 @@
def localpath(path):
return path
+ def longpath(path):
+ return path
+
normpath = os.path.normpath
samestat = os.path.samestat
@@ -1395,13 +1429,13 @@
Returns the name of the temporary file.
"""
d, fn = os.path.split(name)
- fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
+ fd, temp = tempfile.mkstemp(prefix='.%s-' % fn)
os.close(fd)
# Temporary files are created with mode 0600, which is usually not
# what we want. If the original file already exists, just copy
# its mode. Otherwise, manually obey umask.
try:
- st_mode = os.lstat(name).st_mode & 0777
+ st_mode = os.lstat(longpath(name)).st_mode & 0777
except OSError, inst:
if inst.errno != errno.ENOENT:
raise
@@ -1409,7 +1443,7 @@
if st_mode is None:
st_mode = ~_umask
st_mode &= 0666
- os.chmod(temp, st_mode)
+ os.chmod(longpath(temp), st_mode)
if emptyok:
return temp
try:
@@ -1427,7 +1461,7 @@
ifp.close()
ofp.close()
except:
- try: os.unlink(temp)
+ try: os.unlink(longpath(temp))
except: pass
raise
return temp
@@ -1453,25 +1487,9 @@
def __del__(self):
if not self.closed:
try:
- os.unlink(self.temp)
+ os.unlink(longpath(self.temp))
except: pass
posixfile.close(self)
-
-def makedirs(name, mode=None):
- """recursive directory creation with parent mode inheritance"""
- try:
- os.mkdir(name)
- if mode is not None:
- os.chmod(name, mode)
- return
- except OSError, err:
- if err.errno == errno.EEXIST:
- return
- if err.errno != errno.ENOENT:
- raise
- parent = os.path.abspath(os.path.dirname(name))
- makedirs(parent, mode)
- makedirs(name, mode)
class opener(object):
"""Open files relative to a base directory
diff --git a/mercurial/util_win32.py b/mercurial/util_win32.py
--- a/mercurial/util_win32.py
+++ b/mercurial/util_win32.py
@@ -15,7 +15,7 @@
import errno, os, sys, pywintypes, win32con, win32file, win32process
import cStringIO, winerror
-import osutil
+import osutil, util
from win32com.shell import shell,shellcon
class WinError:
@@ -146,13 +146,14 @@
self.win_strerror)
def os_link(src, dst):
+ lp = util.longpath
try:
- win32file.CreateHardLink(dst, src)
+ win32file.CreateHardLink(lp(dst), lp(src))
# CreateHardLink sometimes succeeds on mapped drives but
# following nlinks() returns 1. Check it now and bail out.
if nlinks(src) < 2:
try:
- win32file.DeleteFile(dst)
+ win32file.DeleteFileW(lp(dst))
except:
pass
# Fake hardlinking error
@@ -164,7 +165,7 @@
def nlinks(pathname):
"""Return number of hardlinks for the given file."""
try:
- fh = win32file.CreateFile(pathname,
+ fh = win32file.CreateFileW(util.longpath(pathname),
win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
None, win32file.OPEN_EXISTING, 0, None)
res = win32file.GetFileInformationByHandle(fh)
@@ -268,7 +269,8 @@
def __init__(self, name, mode='rb'):
self.closed = False
- self.name = name
+ # use long file names. Must be absolute and use backslash path sep only
+ self.name = '\\\\?\\' + name.replace('/','\\')
self.mode = mode
access = 0
if 'r' in mode or '+' in mode:
@@ -282,7 +284,8 @@
else:
creation = win32file.CREATE_ALWAYS
try:
- self.handle = win32file.CreateFile(name,
+ # use ..W function in order to get \\?\.. long paths through
+ self.handle = win32file.CreateFileW(self.name,
access,
win32file.FILE_SHARE_READ |
win32file.FILE_SHARE_WRITE |
@@ -292,7 +295,7 @@
win32file.FILE_ATTRIBUTE_NORMAL,
0)
except pywintypes.error, err:
- raise WinIOError(err, name)
+ raise WinIOError(err, self.name)
def __iter__(self):
for line in self.read().splitlines(True):
@@ -359,6 +362,9 @@
except pywintypes.error, err:
raise WinIOError(err)
+ def size(self):
+ return win32file.GetFileSize(self.handle)
+
getuser_fallback = win32api.GetUserName
def set_signal_handler_win32():
@@ -369,3 +375,20 @@
def handler(event):
win32process.ExitProcess(1)
win32api.SetConsoleCtrlHandler(handler)
+
+def makedirs(name, mode=None):
+ """recursive directory creation for Windows long paths. Parameter mode is unused"""
+ if not name.startswith('\\\\?\\'):
+ name = '\\\\?\\' + name.replace('/', '\\')
+ try:
+ win32file.CreateDirectoryW(name, None)
+ return
+ except pywintypes.error, details:
+ err = WinOSError(details)
+ if err.errno == errno.EEXIST:
+ return
+ if err.errno != errno.ENOENT:
+ raise
+ parent = os.path.dirname(name)
+ makedirs(parent, mode)
+ makedirs(name, mode)
More information about the Mercurial-devel
mailing list