diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -1453,11 +1453,12 @@ class atomictempfile(object): visible. If the object is destroyed without being closed, all your writes are discarded. ''' - def __init__(self, name, mode='w+b', createmode=None): + def __init__(self, name, mode='w+b', createmode=None, checkambig=False): self.__name = name # permanent name self._tempname = mktempcopy(name, emptyok=('w' in mode), createmode=createmode) self._fp = posixfile(self._tempname, mode) + self._checkambig = checkambig # delegated methods self.write = self._fp.write @@ -1468,7 +1469,17 @@ class atomictempfile(object): def close(self): if not self._fp.closed: self._fp.close() - rename(self._tempname, localpath(self.__name)) + filename = localpath(self.__name) + oldstat = self._checkambig and filestat(filename) + if oldstat and oldstat.stat: + rename(self._tempname, filename) + newstat = filestat(filename) + if newstat.isambig(oldstat): + # stat of changed file is ambiguous to original one + advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff + os.utime(filename, (advanced, advanced)) + else: + rename(self._tempname, filename) def discard(self): if not self._fp.closed: diff --git a/tests/test-atomictempfile.py b/tests/test-atomictempfile.py --- a/tests/test-atomictempfile.py +++ b/tests/test-atomictempfile.py @@ -42,6 +42,46 @@ class testatomictempfile(unittest.TestCa def test3_oops(self): self.assertRaises(TypeError, atomictempfile) + # checkambig=True avoids ambiguity of timestamp + def test4_checkambig(self): + def atomicwrite(checkambig): + f = atomictempfile('foo', checkambig=checkambig) + f.write('FOO') + f.close() + + # try some times, because reproduction of ambiguity depends on + # "filesystem time" + for i in xrange(5): + atomicwrite(False) + oldstat = os.stat('foo') + if oldstat.st_ctime != oldstat.st_mtime: + # subsequent changing never causes ambiguity + continue + + repetition = 3 + + # repeat atomic write with checkambig=True, to examine + # whether st_mtime is advanced multiple times as expecetd + for j in xrange(repetition): + atomicwrite(True) + newstat = os.stat('foo') + if oldstat.st_ctime != newstat.st_ctime: + # timestamp ambiguity was naturally avoided while repetition + continue + + # st_mtime should be advanced "repetition" times, because + # all atomicwrite() occured at same time (in sec) + self.assertTrue(newstat.st_mtime == + ((oldstat.st_mtime + repetition) & 0x7fffffff)) + # no more examination is needed, if assumption above is true + 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 + if __name__ == '__main__': import silenttestrunner silenttestrunner.main(__name__)