win32: correctly break hardlinks on network drives (issue761) stable
authorPatrick Mezard <pmezard@gmail.com>
Thu, 19 Aug 2010 22:51:09 +0200
branchstable
changeset 11991 50523b4407f6
parent 11987 3145951e50fe
child 11992 ccd8e592c3c5
win32: correctly break hardlinks on network drives (issue761) win32.nlinks() was often returning 1 instead of the correct hardlinks count when reading from network drives. This made commit or push to a repository on a network share to fail breaking the hardlinks in the datastore, possibly causing integrity errors in repositories linked locally on the remote side. Here is what the MSDN says about GetFileInformationByHandle(): Depending on the underlying network features of the operating system and the type of server connected to, the GetFileInformationByHandle function may fail, return partial information, or full information for the given file. In practice, we never got the correct hardlinks count when reading from and to many combinations of Window XP, 2003, Vista and 7, via network drives or RDP shares. It always returned 1 instead. The only setup returning an accurate links count was a samba on Debian. To avoid this, Mercurial now breaks the hardlinks unconditionally when writing to a network drive.
mercurial/win32.py
--- a/mercurial/win32.py	Wed Aug 18 10:53:52 2010 -0400
+++ b/mercurial/win32.py	Thu Aug 19 22:51:09 2010 +0200
@@ -23,15 +23,6 @@
 def os_link(src, dst):
     try:
         win32file.CreateHardLink(dst, 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)
-            except:
-                pass
-            # Fake hardlinking error
-            raise OSError(errno.EINVAL, 'Hardlinking not supported')
     except pywintypes.error:
         raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
     except NotImplementedError: # Another fake error win Win98
@@ -54,9 +45,19 @@
     """Return number of hardlinks for the given file."""
     res = _getfileinfo(pathname)
     if res is not None:
-        return res[7]
+        links = res[7]
     else:
-        return os.lstat(pathname).st_nlink
+        links = os.lstat(pathname).st_nlink
+    if links < 2:
+        # Known to be wrong for most network drives
+        dirname = os.path.dirname(pathname)
+        if not dirname:
+            dirname = '.'
+        dt = win32file.GetDriveType(dirname + '\\')
+        if dt == 4 or dt == 1:
+            # Fake hardlink to force COW for network drives
+            links = 2
+    return links
 
 def samefile(fpath1, fpath2):
     """Returns whether fpath1 and fpath2 refer to the same file. This is only