timestamp.py
128 lines
| 3.9 KiB
| text/x-python
|
PythonLexer
Simon Sapin
|
r49079 | # Copyright Mercurial Contributors | ||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
Simon Sapin
|
r49081 | import functools | ||
r49202 | import os | |||
Simon Sapin
|
r49079 | import stat | ||
r49226 | from .. import error | |||
Simon Sapin
|
r49079 | |||
rangemask = 0x7FFFFFFF | ||||
Simon Sapin
|
r49081 | @functools.total_ordering | ||
Simon Sapin
|
r49079 | class timestamp(tuple): | ||
""" | ||||
Simon Sapin
|
r49081 | A Unix timestamp with optional nanoseconds precision, | ||
Simon Sapin
|
r49079 | modulo 2**31 seconds. | ||
Simon Sapin
|
r49271 | A 3-tuple containing: | ||
Simon Sapin
|
r49079 | |||
`truncated_seconds`: seconds since the Unix epoch, | ||||
truncated to its lower 31 bits | ||||
`subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. | ||||
Simon Sapin
|
r49081 | When this is zero, the sub-second precision is considered unknown. | ||
Simon Sapin
|
r49271 | |||
`second_ambiguous`: whether this timestamp is still "reliable" | ||||
(see `reliable_mtime_of`) if we drop its sub-second component. | ||||
Simon Sapin
|
r49079 | """ | ||
def __new__(cls, value): | ||||
r49227 | truncated_seconds, subsec_nanos, second_ambiguous = value | |||
value = (truncated_seconds & rangemask, subsec_nanos, second_ambiguous) | ||||
Simon Sapin
|
r49079 | return super(timestamp, cls).__new__(cls, value) | ||
Simon Sapin
|
r49081 | def __eq__(self, other): | ||
r49226 | raise error.ProgrammingError( | |||
'timestamp should never be compared directly' | ||||
Simon Sapin
|
r49081 | ) | ||
def __gt__(self, other): | ||||
r49226 | raise error.ProgrammingError( | |||
'timestamp should never be compared directly' | ||||
) | ||||
Simon Sapin
|
r49081 | |||
Simon Sapin
|
r49079 | |||
r49202 | def get_fs_now(vfs): | |||
"""return a timestamp for "now" in the current vfs | ||||
This will raise an exception if no temporary files could be created. | ||||
""" | ||||
tmpfd, tmpname = vfs.mkstemp() | ||||
try: | ||||
return mtime_of(os.fstat(tmpfd)) | ||||
finally: | ||||
os.close(tmpfd) | ||||
vfs.unlink(tmpname) | ||||
Simon Sapin
|
r49079 | def zero(): | ||
""" | ||||
Returns the `timestamp` at the Unix epoch. | ||||
""" | ||||
return tuple.__new__(timestamp, (0, 0)) | ||||
def mtime_of(stat_result): | ||||
""" | ||||
Takes an `os.stat_result`-like object and returns a `timestamp` object | ||||
for its modification time. | ||||
""" | ||||
Simon Sapin
|
r49082 | try: | ||
# TODO: add this attribute to `osutil.stat` objects, | ||||
# see `mercurial/cext/osutil.c`. | ||||
# | ||||
# This attribute is also not available on Python 2. | ||||
nanos = stat_result.st_mtime_ns | ||||
except AttributeError: | ||||
# https://docs.python.org/2/library/os.html#os.stat_float_times | ||||
# "For compatibility with older Python versions, | ||||
# accessing stat_result as a tuple always returns integers." | ||||
secs = stat_result[stat.ST_MTIME] | ||||
Simon Sapin
|
r49079 | |||
Simon Sapin
|
r49082 | subsec_nanos = 0 | ||
else: | ||||
billion = int(1e9) | ||||
secs = nanos // billion | ||||
subsec_nanos = nanos % billion | ||||
Simon Sapin
|
r49079 | |||
r49227 | return timestamp((secs, subsec_nanos, False)) | |||
r49224 | ||||
def reliable_mtime_of(stat_result, present_mtime): | ||||
Simon Sapin
|
r49271 | """Same as `mtime_of`, but return `None` or a `Timestamp` with | ||
`second_ambiguous` set if the date might be ambiguous. | ||||
r49224 | ||||
A modification time is reliable if it is older than "present_time" (or | ||||
Simon Sapin
|
r49250 | sufficiently in the future). | ||
r49224 | ||||
Otherwise a concurrent modification might happens with the same mtime. | ||||
""" | ||||
file_mtime = mtime_of(stat_result) | ||||
file_second = file_mtime[0] | ||||
r49232 | file_ns = file_mtime[1] | |||
r49224 | boundary_second = present_mtime[0] | |||
r49232 | boundary_ns = present_mtime[1] | |||
r49224 | # If the mtime of the ambiguous file is younger (or equal) to the starting | |||
# point of the `status` walk, we cannot garantee that another, racy, write | ||||
# will not happen right after with the same mtime and we cannot cache the | ||||
# information. | ||||
# | ||||
r49232 | # However if the mtime is far away in the future, this is likely some | |||
r49224 | # mismatch between the current clock and previous file system operation. So | |||
# mtime more than one days in the future are considered fine. | ||||
r49232 | if boundary_second == file_second: | |||
if file_ns and boundary_ns: | ||||
if file_ns < boundary_ns: | ||||
return timestamp((file_second, file_ns, True)) | ||||
return None | ||||
elif boundary_second < file_second < (3600 * 24 + boundary_second): | ||||
r49224 | return None | |||
else: | ||||
return file_mtime | ||||