# HG changeset patch # User Matt Mackall # Date 1319405547 18000 # Node ID c681e478c42953a15614fa1b23208c833fabd5ab # Parent 799e56609ef6485e12a5c9deafeef657185397d2 windows: sanity-check symlink placeholders On Windows, we store symlinks as plain files with the link contents. Via user error or NFS/Samba assistance, these files often end up with 'normal' file contents. Committing these changes thus gives an invalid symlink that can't be checked out on Unix. Here we filter out any modified symlink placeholders that look suspicious when computing status: - more than 1K (looks more like a normal file) - contain NULs (not allowed on Unix, probably a binary) - contains \n (filenames can't contain \n, very unusual for symlinks, very common for files) diff -r 799e56609ef6 -r c681e478c429 mercurial/localrepo.py --- a/mercurial/localrepo.py Sun Oct 23 21:59:15 2011 +0200 +++ b/mercurial/localrepo.py Sun Oct 23 16:32:27 2011 -0500 @@ -1354,6 +1354,22 @@ added.append(fn) removed = mf1.keys() + if working and modified and not self.dirstate._checklink: + # Symlink placeholders may get non-symlink-like contents + # via user error or dereferencing by NFS or Samba servers, + # so we filter out any placeholders that don't look like a + # symlink + sane = [] + for f in modified: + if ctx2.flags(f) == 'l': + d = ctx2[f].data() + if len(d) >= 1024 or '\n' in d or util.binary(d): + self.ui.debug('ignoring suspect symlink placeholder' + ' "%s"\n' % f) + continue + sane.append(f) + modified = sane + r = modified, added, removed, deleted, unknown, ignored, clean if listsubrepos: diff -r 799e56609ef6 -r c681e478c429 tests/test-symlink-placeholder.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-symlink-placeholder.t Sun Oct 23 16:32:27 2011 -0500 @@ -0,0 +1,68 @@ +Create extension that can disable symlink support: + + $ cat > nolink.py < from mercurial import extensions, util + > def setflags(orig, f, l, x): + > pass + > def checklink(orig, path): + > return False + > def extsetup(ui): + > extensions.wrapfunction(util, 'setflags', setflags) + > extensions.wrapfunction(util, 'checklink', checklink) + > EOF + + $ hg init unix-repo + $ cd unix-repo + $ echo foo > a + $ ln -s a b + $ hg ci -Am0 + adding a + adding b + $ cd .. + +Simulate a checkout shared on NFS/Samba: + + $ hg clone -q unix-repo shared + $ cd shared + $ rm b + $ echo foo > b + $ hg --config extensions.n=$TESTTMP/nolink.py status --debug + ignoring suspect symlink placeholder "b" + +Make a clone using placeholders: + + $ hg --config extensions.n=$TESTTMP/nolink.py clone . ../win-repo + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd ../win-repo + $ cat b + a (no-eol) + $ hg --config extensions.n=$TESTTMP/nolink.py st --debug + +Write binary data to the placeholder: + + >>> open('b', 'w').write('this is a binary\0') + $ hg --config extensions.n=$TESTTMP/nolink.py st --debug + ignoring suspect symlink placeholder "b" + +Write a long string to the placeholder: + + >>> open('b', 'w').write('this' * 1000) + $ hg --config extensions.n=$TESTTMP/nolink.py st --debug + ignoring suspect symlink placeholder "b" + +Commit shouldn't succeed: + + $ hg --config extensions.n=$TESTTMP/nolink.py ci -m1 + nothing changed + [1] + +Write a valid string to the placeholder: + + >>> open('b', 'w').write('this') + $ hg --config extensions.n=$TESTTMP/nolink.py st --debug + M b + $ hg --config extensions.n=$TESTTMP/nolink.py ci -m1 + $ hg manifest tip --verbose + 644 a + 644 @ b