[PATCH] enable long/reserved paths on Windows (EXPERIMENTAL)

Adrian Buehlmann adrian at cadifra.com
Wed Jun 25 07:49:26 CDT 2008


# HG changeset patch
# User Adrian Buehlmann <adrian at cadifra.com>
# Date 1214394398 -7200
# Node ID 21453658d42cfa4c1278c3abb2d19918221cfaca
# 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/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -220,9 +220,8 @@
         return pat
     if hasattr(pat, 'read') and 'r' in mode:
         return pat
-    return open(make_filename(repo, pat, node, total, seqno, revwidth,
-                              pathname),
-                mode)
+    n = make_filename(repo, pat, node, total, seqno, revwidth, pathname)
+    return open(util.longpath(n), mode)
 
 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
     if not globbed and default == 'relpath':
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -258,7 +258,7 @@
         'mark a file normal and clean'
         self._dirty = True
         self._changepath(f, 'n', True)
-        s = os.lstat(self._join(f))
+        s = os.lstat(util.longpath(self._join(f)))
         self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
         if f in self._copymap:
             del self._copymap[f]
@@ -510,6 +510,7 @@
         supported = self._supported
         _join = self._join
         known = {'.hg': 1}
+        lp = util.longpath
 
         # recursion free walker, faster than os.walk.
         def findfiles(s):
@@ -563,7 +564,7 @@
             nf = normpath(ff)
             f = _join(ff)
             try:
-                st = lstat(f)
+                st = lstat(lp(f))
             except OSError, inst:
                 found = False
                 for fn in dc:
@@ -617,6 +618,7 @@
         radd = removed.append
         dadd = deleted.append
         cadd = clean.append
+        lp = util.longpath
 
         for src, fn, st in self.statwalk(match, unknown=list_unknown,
                                          ignored=list_ignored):
@@ -634,7 +636,7 @@
                 nonexistent = True
                 if not st:
                     try:
-                        st = lstat(_join(fn))
+                        st = lstat(lp(_join(fn)))
                     except OSError, inst:
                         if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
                             raise
@@ -648,7 +650,7 @@
             # check the common case first
             if state == 'n':
                 if not st:
-                    st = lstat(_join(fn))
+                    st = lstat(lp(_join(fn)))
                 if (size >= 0 and
                     (size != st.st_size
                      or ((mode ^ st.st_mode) & 0100 and self._checkexec))
@@ -665,6 +667,6 @@
                 aadd(fn)
             elif state == 'r':
                 radd(fn)
-
+        
         return (lookup, modified, added, removed, deleted, unknown, ignored,
                 clean)
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,11 @@
     '''
     result = []
     prefix = path + os.sep
-    names = os.listdir(path)
+    names = os.listdir(util.longpath(path)) # returns unicode strings on Windows!
     names.sort()
     for fn in names:
-        st = os.lstat(prefix + fn)
+        fn = str(fn) # convert to non-unicode string (needed on Windows)
+        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,25 @@
 
     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'''
+        # print "longpath(%s)" % path
+        if path.startswith(_longpathprefix):
+            res = path
+        else:
+            path = path.replace('/', '\\').replace('\\.\\', '\\')
+            if path[-1] == '.':
+                path = path[:-1]
+            # print "path = %s" % path
+            if not os.path.isabs(path):
+                # print "not absolute"
+                path = os.path.abspath(path)
+                # print "path = %s" % path
+            res = unicode(_longpathprefix + path)
+        return res
 
     def normpath(path):
         return pconvert(os.path.normpath(path))
@@ -1259,6 +1296,9 @@
     def localpath(path):
         return path
 
+    def longpath(path):
+        return path
+
     normpath = os.path.normpath
     samestat = os.path.samestat
 
@@ -1395,13 +1435,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 +1449,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 +1467,7 @@
         ifp.close()
         ofp.close()
     except:
-        try: os.unlink(temp)
+        try: os.unlink(longpath(temp))
         except: pass
         raise
     return temp
@@ -1453,25 +1493,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