When comparing mtimes for equality.
Some APIs simply return zero when more precision is not available.
When comparing values from different sources, if only one is truncated in
that way, doing a simple comparison would cause many false negatives.
( )
Alphare |
hg-reviewers |
When comparing mtimes for equality.
Some APIs simply return zero when more precision is not available.
When comparing values from different sources, if only one is truncated in
that way, doing a simple comparison would cause many false negatives.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
M | mercurial/cext/parsers.c (4 lines) | |||
M | mercurial/dirstateutils/timestamp.py (26 lines) | |||
M | mercurial/pure/parsers.py (4 lines) | |||
M | rust/hg-core/src/dirstate/entry.rs (10 lines) |
Status | Author | Revision | |
---|---|---|---|
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | marmoute | ||
Closed | SimonSapin | ||
Closed | SimonSapin | ||
Closed | marmoute | ||
Closed | SimonSapin |
PyObject *other) | PyObject *other) | ||||
{ | { | ||||
int other_s; | int other_s; | ||||
int other_ns; | int other_ns; | ||||
if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { | if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { | ||||
return NULL; | return NULL; | ||||
} | } | ||||
if ((self->flags & dirstate_flag_has_file_mtime) && | if ((self->flags & dirstate_flag_has_file_mtime) && | ||||
self->mtime_s == other_s && self->mtime_ns == other_ns) { | self->mtime_s == other_s && | ||||
(self->mtime_ns == other_ns || self->mtime_ns == 0 || | |||||
other_ns == 0)) { | |||||
Py_RETURN_TRUE; | Py_RETURN_TRUE; | ||||
} else { | } else { | ||||
Py_RETURN_FALSE; | Py_RETURN_FALSE; | ||||
} | } | ||||
}; | }; | ||||
/* This will never change since it's bound to V1 | /* This will never change since it's bound to V1 | ||||
*/ | */ |
# Copyright Mercurial Contributors | # Copyright Mercurial Contributors | ||||
# | # | ||||
# This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | from __future__ import absolute_import | ||||
import functools | |||||
import stat | import stat | ||||
rangemask = 0x7FFFFFFF | rangemask = 0x7FFFFFFF | ||||
@functools.total_ordering | |||||
class timestamp(tuple): | class timestamp(tuple): | ||||
""" | """ | ||||
A Unix timestamp with nanoseconds precision, | A Unix timestamp with optional nanoseconds precision, | ||||
modulo 2**31 seconds. | modulo 2**31 seconds. | ||||
A 2-tuple containing: | A 2-tuple containing: | ||||
`truncated_seconds`: seconds since the Unix epoch, | `truncated_seconds`: seconds since the Unix epoch, | ||||
truncated to its lower 31 bits | truncated to its lower 31 bits | ||||
`subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. | `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. | ||||
When this is zero, the sub-second precision is considered unknown. | |||||
""" | """ | ||||
def __new__(cls, value): | def __new__(cls, value): | ||||
truncated_seconds, subsec_nanos = value | truncated_seconds, subsec_nanos = value | ||||
value = (truncated_seconds & rangemask, subsec_nanos) | value = (truncated_seconds & rangemask, subsec_nanos) | ||||
return super(timestamp, cls).__new__(cls, value) | return super(timestamp, cls).__new__(cls, value) | ||||
def __eq__(self, other): | |||||
self_secs, self_subsec_nanos = self | |||||
other_secs, other_subsec_nanos = other | |||||
return self_secs == other_secs and ( | |||||
self_subsec_nanos == other_subsec_nanos | |||||
or self_subsec_nanos == 0 | |||||
or other_subsec_nanos == 0 | |||||
) | |||||
def __gt__(self, other): | |||||
self_secs, self_subsec_nanos = self | |||||
other_secs, other_subsec_nanos = other | |||||
if self_secs > other_secs: | |||||
return True | |||||
if self_secs < other_secs: | |||||
return False | |||||
if self_subsec_nanos == 0 or other_subsec_nanos == 0: | |||||
# they are considered equal, so not "greater than" | |||||
return False | |||||
return self_subsec_nanos > other_subsec_nanos | |||||
def zero(): | def zero(): | ||||
""" | """ | ||||
Returns the `timestamp` at the Unix epoch. | Returns the `timestamp` at the Unix epoch. | ||||
""" | """ | ||||
return tuple.__new__(timestamp, (0, 0)) | return tuple.__new__(timestamp, (0, 0)) | ||||
return self.v1_mtime() | return self.v1_mtime() | ||||
def mtime_likely_equal_to(self, other_mtime): | def mtime_likely_equal_to(self, other_mtime): | ||||
self_sec = self._mtime_s | self_sec = self._mtime_s | ||||
if self_sec is None: | if self_sec is None: | ||||
return False | return False | ||||
self_ns = self._mtime_ns | self_ns = self._mtime_ns | ||||
other_sec, other_ns = other_mtime | other_sec, other_ns = other_mtime | ||||
return self_sec == other_sec and self_ns == other_ns | return self_sec == other_sec and ( | ||||
self_ns == other_ns or self_ns == 0 or other_ns == 0 | |||||
) | |||||
@property | @property | ||||
def state(self): | def state(self): | ||||
""" | """ | ||||
States are: | States are: | ||||
n normal | n normal | ||||
m needs merging | m needs merging | ||||
r marked for removal | r marked for removal |
/// or given to `new_truncate` were very likely equal. A false positive is | /// or given to `new_truncate` were very likely equal. A false positive is | ||||
/// possible if they were exactly a multiple of 2**31 seconds apart (around | /// possible if they were exactly a multiple of 2**31 seconds apart (around | ||||
/// 68 years). This is deemed very unlikely to happen by chance, especially | /// 68 years). This is deemed very unlikely to happen by chance, especially | ||||
/// on filesystems that support sub-second precision. | /// on filesystems that support sub-second precision. | ||||
/// | /// | ||||
/// If someone is manipulating the modification times of some files to | /// If someone is manipulating the modification times of some files to | ||||
/// intentionally make `hg status` return incorrect results, not truncating | /// intentionally make `hg status` return incorrect results, not truncating | ||||
/// wouldn’t help much since they can set exactly the expected timestamp. | /// wouldn’t help much since they can set exactly the expected timestamp. | ||||
/// | |||||
/// Sub-second precision is ignored if it is zero in either value. | |||||
/// Some APIs simply return zero when more precision is not available. | |||||
/// When comparing values from different sources, if only one is truncated | |||||
/// in that way, doing a simple comparison would cause many false | |||||
/// negatives. | |||||
pub fn likely_equal(self, other: Self) -> bool { | pub fn likely_equal(self, other: Self) -> bool { | ||||
self.truncated_seconds == other.truncated_seconds | self.truncated_seconds == other.truncated_seconds | ||||
&& self.nanoseconds == other.nanoseconds | && (self.nanoseconds == other.nanoseconds | ||||
|| self.nanoseconds == 0 | |||||
|| other.nanoseconds == 0) | |||||
} | } | ||||
pub fn likely_equal_to_mtime_of( | pub fn likely_equal_to_mtime_of( | ||||
self, | self, | ||||
metadata: &fs::Metadata, | metadata: &fs::Metadata, | ||||
) -> io::Result<bool> { | ) -> io::Result<bool> { | ||||
Ok(self.likely_equal(Self::for_mtime_of(metadata)?)) | Ok(self.likely_equal(Self::for_mtime_of(metadata)?)) | ||||
} | } |