# HG changeset patch # User Mads Kiilerich <mad...@unity3d.com> # Date 1421194526 -3600 # Wed Jan 14 01:15:26 2015 +0100 # Node ID 23e2868f6dfd50d4d7ca3e0748cb62bcf4354a52 # Parent f383046bc26d39d307523521de2df5885ba111ff posix: give checkexec a fast path; keep the check files and test read only
Before, Mercurial would create a new temporary file every time, stat it, change its exec mode, stat it again, and delete it. Most of this dance was done to handle the rare and not-so-essential case of VFAT mounts on unix. The cost of that was paid by the much more common and important case of using normal file systems. Instead, try to create and preserve .hg/cache/checkisexec and .hg/cache/checknoexec with and without exec flag set. If the files exist and have correct exec flags set, we can conclude that that file system supports the exec flag. Best case, the whole exec check can thus be done with two stat calls. Worst case, we delete the wrong files and check as usual. That will be because temporary loss of exec bit or on file systems without support for the exec bit. In that case we check as we did before, with the additional overhead of one extra stat call. It is possible that this different test algorithm in some cases on odd file systems will give different behaviour. Again, I think it will be rare and special cases and I think it is worth the risk. test-clone.t happens to show the situation where checkisexec is left behind from the old style check, while checknoexec only will be created next time a exec check will be performed. diff --git a/mercurial/posix.py b/mercurial/posix.py --- a/mercurial/posix.py +++ b/mercurial/posix.py @@ -161,18 +161,55 @@ def checkexec(path): try: EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH cachedir = os.path.join(path, '.hg', 'cache') - if not os.path.isdir(cachedir): - cachedir = path - fh, fn = tempfile.mkstemp(dir=cachedir, prefix='hg-checkexec-') + if os.path.isdir(cachedir): + checkisexec = os.path.join(cachedir, 'checkisexec') + checknoexec = os.path.join(cachedir, 'checknoexec') + + try: + m = os.stat(checkisexec).st_mode + except OSError as e: + if e.errno != errno.ENOENT: + raise + # checkisexec does not exist - fall through ... + else: + # checkisexec exists, check if it actually is exec + if m & EXECFLAGS != 0: + # ensure checkisexec exists, check it isn't exec + try: + m = os.stat(checknoexec).st_mode + except OSError as e: + if e.errno != errno.ENOENT: + raise + file(checknoexec, 'w').close() # might fail + m = os.stat(checknoexec).st_mode + if m & EXECFLAGS == 0: + # check-exec is exec and check-no-exec is not exec + return True + # checknoexec exists but is exec - delete it + os.unlink(checknoexec) + # checkisexec exists but is not exec - delete it + os.unlink(checkisexec) + + # check using one file, leave it as checkisexec + checkdir = cachedir + else: + # check directly in path and don't leave checkisexec behind + checkdir = path + checkisexec = None + fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-') try: os.close(fh) m = os.stat(fn).st_mode - if m & EXECFLAGS: - return False - os.chmod(fn, m & 0o777 | EXECFLAGS) - return os.stat(fn).st_mode & EXECFLAGS + if m & EXECFLAGS == 0: + os.chmod(fn, m & 0o777 | EXECFLAGS) + if os.stat(fn).st_mode & EXECFLAGS != 0: + if checkisexec is not None: + os.rename(fn, checkisexec) + fn = None + return True finally: - os.unlink(fn) + if fn is not None: + os.unlink(fn) except (IOError, OSError): # we don't care, the user probably won't be able to commit anyway return False diff --git a/tests/test-clone.t b/tests/test-clone.t --- a/tests/test-clone.t +++ b/tests/test-clone.t @@ -31,6 +31,8 @@ Trigger branchcache creation: default 10:a7949464abda $ ls .hg/cache branch2-served + checkisexec + checknoexec rbc-names-v1 rbc-revs-v1 @@ -45,6 +47,7 @@ Ensure branchcache got copied over: $ ls .hg/cache branch2-served + checkisexec $ cat a a diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t --- a/tests/test-hardlinks.t +++ b/tests/test-hardlinks.t @@ -211,6 +211,8 @@ r4 has hardlinks in the working dir (not 2 r4/.hg/00changelog.i 2 r4/.hg/branch 2 r4/.hg/cache/branch2-served + 2 r4/.hg/cache/checkisexec + 2 r4/.hg/cache/checknoexec 2 r4/.hg/cache/rbc-names-v1 2 r4/.hg/cache/rbc-revs-v1 2 r4/.hg/dirstate @@ -247,6 +249,8 @@ Update back to revision 11 in r4 should 2 r4/.hg/00changelog.i 1 r4/.hg/branch 2 r4/.hg/cache/branch2-served + 2 r4/.hg/cache/checkisexec + 2 r4/.hg/cache/checknoexec 2 r4/.hg/cache/rbc-names-v1 2 r4/.hg/cache/rbc-revs-v1 1 r4/.hg/dirstate diff --git a/tests/test-tags.t b/tests/test-tags.t --- a/tests/test-tags.t +++ b/tests/test-tags.t @@ -672,6 +672,7 @@ Missing tags2* files means the cache was $ ls tagsclient/.hg/cache branch2-served + checkisexec hgtagsfnodes1 rbc-names-v1 rbc-revs-v1 @@ -696,6 +697,7 @@ Running hg tags should produce tags2* fi $ ls tagsclient/.hg/cache branch2-served + checkisexec hgtagsfnodes1 rbc-names-v1 rbc-revs-v1 _______________________________________________ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel