comparison mercurial/posix.py @ 30446:b324b4e431e5

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.
author Mads Kiilerich <madski@unity3d.com>
date Wed, 14 Jan 2015 01:15:26 +0100
parents 1ce4c2062ab0
children 0d87b1caed92
comparison
equal deleted inserted replaced
30445:1ce4c2062ab0 30446:b324b4e431e5
159 # with exec bit on. 159 # with exec bit on.
160 160
161 try: 161 try:
162 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH 162 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
163 cachedir = os.path.join(path, '.hg', 'cache') 163 cachedir = os.path.join(path, '.hg', 'cache')
164 if not os.path.isdir(cachedir): 164 if os.path.isdir(cachedir):
165 cachedir = path 165 checkisexec = os.path.join(cachedir, 'checkisexec')
166 fh, fn = tempfile.mkstemp(dir=cachedir, prefix='hg-checkexec-') 166 checknoexec = os.path.join(cachedir, 'checknoexec')
167
168 try:
169 m = os.stat(checkisexec).st_mode
170 except OSError as e:
171 if e.errno != errno.ENOENT:
172 raise
173 # checkisexec does not exist - fall through ...
174 else:
175 # checkisexec exists, check if it actually is exec
176 if m & EXECFLAGS != 0:
177 # ensure checkisexec exists, check it isn't exec
178 try:
179 m = os.stat(checknoexec).st_mode
180 except OSError as e:
181 if e.errno != errno.ENOENT:
182 raise
183 file(checknoexec, 'w').close() # might fail
184 m = os.stat(checknoexec).st_mode
185 if m & EXECFLAGS == 0:
186 # check-exec is exec and check-no-exec is not exec
187 return True
188 # checknoexec exists but is exec - delete it
189 os.unlink(checknoexec)
190 # checkisexec exists but is not exec - delete it
191 os.unlink(checkisexec)
192
193 # check using one file, leave it as checkisexec
194 checkdir = cachedir
195 else:
196 # check directly in path and don't leave checkisexec behind
197 checkdir = path
198 checkisexec = None
199 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
167 try: 200 try:
168 os.close(fh) 201 os.close(fh)
169 m = os.stat(fn).st_mode 202 m = os.stat(fn).st_mode
170 if m & EXECFLAGS: 203 if m & EXECFLAGS == 0:
171 return False 204 os.chmod(fn, m & 0o777 | EXECFLAGS)
172 os.chmod(fn, m & 0o777 | EXECFLAGS) 205 if os.stat(fn).st_mode & EXECFLAGS != 0:
173 return os.stat(fn).st_mode & EXECFLAGS 206 if checkisexec is not None:
207 os.rename(fn, checkisexec)
208 fn = None
209 return True
174 finally: 210 finally:
175 os.unlink(fn) 211 if fn is not None:
212 os.unlink(fn)
176 except (IOError, OSError): 213 except (IOError, OSError):
177 # we don't care, the user probably won't be able to commit anyway 214 # we don't care, the user probably won't be able to commit anyway
178 return False 215 return False
179 216
180 def checklink(path): 217 def checklink(path):