Comments
Patch
@@ -1431,3 +1431,34 @@ class backgroundfilecloser(object):
return
self._queue.put(fh, block=True, timeout=None)
+
+class checkambigatclosing(closewrapbase):
+ """Proxy for a file object, to avoid ambiguity of file stat
+
+ See also util.filestat for detail about "ambiguity of file stat".
+
+ This proxy is useful only if the target file is guarded by any
+ lock (e.g. repo.lock or repo.wlock)
+
+ Do not instantiate outside of the vfs layer.
+ """
+ def __init__(self, fh):
+ super(checkambigatclosing, self).__init__(fh)
+ object.__setattr__(self, '_oldstat', util.filestat(fh.name))
+
+ def _checkambig(self):
+ oldstat = self._oldstat
+ if oldstat.stat:
+ newstat = util.filestat(self._origfh.name)
+ if newstat.isambig(oldstat):
+ # stat of changed file is ambiguous to original one
+ advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
+ os.utime(self._origfh.name, (advanced, advanced))
+
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ self._origfh.__exit__(exc_type, exc_value, exc_tb)
+ self._checkambig()
+
+ def close(self):
+ self._origfh.close()
+ self._checkambig()
@@ -179,6 +179,56 @@ def setbeforeget(repo):
print("* file y created")
print(repo.cached)
+def antiambiguity():
+ filename = 'ambigcheck'
+
+ # try some times, because reproduction of ambiguity depends on
+ # "filesystem time"
+ for i in xrange(5):
+ fp = open(filename, 'w')
+ fp.write('FOO')
+ fp.close()
+
+ oldstat = os.stat(filename)
+ if oldstat.st_ctime != oldstat.st_mtime:
+ # subsequent changing never causes ambiguity
+ continue
+
+ repetition = 3
+
+ # repeat changing via checkambigatclosing, to examine whether
+ # st_mtime is advanced multiple times as expecetd
+ for i in xrange(repetition):
+ # explicit closing
+ fp = scmutil.checkambigatclosing(open(filename, 'a'))
+ fp.write('FOO')
+ fp.close()
+
+ # implicit closing by "with" statement
+ with scmutil.checkambigatclosing(open(filename, 'a')) as fp:
+ fp.write('BAR')
+
+ newstat = os.stat(filename)
+ if oldstat.st_ctime != newstat.st_ctime:
+ # timestamp ambiguity was naturally avoided while repetition
+ continue
+
+ # st_mtime should be advanced "repetition * 2" times, because
+ # all changes occured at same time (in sec)
+ expected = (oldstat.st_mtime + repetition * 2) & 0x7fffffff
+ if newstat.st_mtime != expected:
+ print("'newstat.st_mtime %s is not %s (as %s + %s * 2)" %
+ (newstat.st_mtime, expected, oldstat.st_mtime, repetition))
+
+ # no more examination is needed regardless of result
+ break
+ else:
+ # This platform seems too slow to examine anti-ambiguity
+ # of file timestamp (or test happened to be executed at
+ # bad timing). Exit silently in this case, because running
+ # on other faster platforms can detect problems
+ pass
+
print('basic:')
print()
basic(fakerepo())
@@ -191,3 +241,7 @@ print()
print('setbeforeget:')
print()
setbeforeget(fakerepo())
+print()
+print('antiambiguity:')
+print()
+antiambiguity()
@@ -58,3 +58,6 @@ string 2 set externally
* file y created
creating
string from function
+
+antiambiguity:
+