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.
This commit is contained in:
Patrick Mezard 2010-08-19 22:51:09 +02:00
parent 5c97a02c1e
commit 5e126e9b42

View File

@ -23,15 +23,6 @@ from win32com.shell import shell, shellcon
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 @@ def nlinks(pathname):
"""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