This will be used to support the mtime-second-ambiguous flag from dirstate
v2. See format documentation for details.
For now, we only make it possible to store the information, no other logic have
been added.
Alphare |
hg-reviewers |
This will be used to support the mtime-second-ambiguous flag from dirstate
v2. See format documentation for details.
For now, we only make it possible to store the information, no other logic have
been added.
No Linters Available |
No Unit Test Coverage |
Path | Packages | |||
---|---|---|---|---|
M | mercurial/cext/parsers.c (22 lines) | |||
M | mercurial/dirstateutils/timestamp.py (6 lines) | |||
M | mercurial/pure/parsers.py (18 lines) | |||
M | rust/hg-core/src/dirstate/entry.rs (21 lines) | |||
M | rust/hg-core/src/dirstate_tree/on_disk.rs (1 line) | |||
M | rust/hg-cpython/src/dirstate/item.rs (22 lines) | |||
M | tests/fakedirstatewritetime.py (2 lines) |
Commit | Parents | Author | Summary | Date |
---|---|---|---|---|
c8061dce088b | 79db2867bc9f | Pierre-Yves David | Nov 23 2021, 10:40 PM |
/* We do all the initialization here and not a tp_init function because | /* We do all the initialization here and not a tp_init function because | ||||
* dirstate_item is immutable. */ | * dirstate_item is immutable. */ | ||||
dirstateItemObject *t; | dirstateItemObject *t; | ||||
int wc_tracked; | int wc_tracked; | ||||
int p1_tracked; | int p1_tracked; | ||||
int p2_info; | int p2_info; | ||||
int has_meaningful_data; | int has_meaningful_data; | ||||
int has_meaningful_mtime; | int has_meaningful_mtime; | ||||
int mtime_second_ambiguous; | |||||
int mode; | int mode; | ||||
int size; | int size; | ||||
int mtime_s; | int mtime_s; | ||||
int mtime_ns; | int mtime_ns; | ||||
PyObject *parentfiledata; | PyObject *parentfiledata; | ||||
PyObject *mtime; | PyObject *mtime; | ||||
PyObject *fallback_exec; | PyObject *fallback_exec; | ||||
PyObject *fallback_symlink; | PyObject *fallback_symlink; | ||||
static char *keywords_name[] = { | static char *keywords_name[] = { | ||||
"wc_tracked", "p1_tracked", "p2_info", | "wc_tracked", "p1_tracked", "p2_info", | ||||
"has_meaningful_data", "has_meaningful_mtime", "parentfiledata", | "has_meaningful_data", "has_meaningful_mtime", "parentfiledata", | ||||
"fallback_exec", "fallback_symlink", NULL, | "fallback_exec", "fallback_symlink", NULL, | ||||
}; | }; | ||||
wc_tracked = 0; | wc_tracked = 0; | ||||
p1_tracked = 0; | p1_tracked = 0; | ||||
p2_info = 0; | p2_info = 0; | ||||
has_meaningful_mtime = 1; | has_meaningful_mtime = 1; | ||||
has_meaningful_data = 1; | has_meaningful_data = 1; | ||||
mtime_second_ambiguous = 0; | |||||
parentfiledata = Py_None; | parentfiledata = Py_None; | ||||
fallback_exec = Py_None; | fallback_exec = Py_None; | ||||
fallback_symlink = Py_None; | fallback_symlink = Py_None; | ||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiOOO", keywords_name, | if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiOOO", keywords_name, | ||||
&wc_tracked, &p1_tracked, &p2_info, | &wc_tracked, &p1_tracked, &p2_info, | ||||
&has_meaningful_data, | &has_meaningful_data, | ||||
&has_meaningful_mtime, &parentfiledata, | &has_meaningful_mtime, &parentfiledata, | ||||
&fallback_exec, &fallback_symlink)) { | &fallback_exec, &fallback_symlink)) { | ||||
} | } | ||||
if (parentfiledata != Py_None) { | if (parentfiledata != Py_None) { | ||||
if (!PyArg_ParseTuple(parentfiledata, "iiO", &mode, &size, | if (!PyArg_ParseTuple(parentfiledata, "iiO", &mode, &size, | ||||
&mtime)) { | &mtime)) { | ||||
return NULL; | return NULL; | ||||
} | } | ||||
if (mtime != Py_None) { | if (mtime != Py_None) { | ||||
if (!PyArg_ParseTuple(mtime, "ii", &mtime_s, | if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, | ||||
&mtime_ns)) { | &mtime_second_ambiguous)) { | ||||
return NULL; | return NULL; | ||||
} | } | ||||
} else { | } else { | ||||
has_meaningful_mtime = 0; | has_meaningful_mtime = 0; | ||||
} | } | ||||
} else { | } else { | ||||
has_meaningful_data = 0; | has_meaningful_data = 0; | ||||
has_meaningful_mtime = 0; | has_meaningful_mtime = 0; | ||||
} | } | ||||
if (has_meaningful_data) { | if (has_meaningful_data) { | ||||
t->flags |= dirstate_flag_has_meaningful_data; | t->flags |= dirstate_flag_has_meaningful_data; | ||||
t->mode = mode; | t->mode = mode; | ||||
t->size = size; | t->size = size; | ||||
if (mtime_second_ambiguous) { | |||||
t->flags |= dirstate_flag_mtime_second_ambiguous; | |||||
} | |||||
} else { | } else { | ||||
t->mode = 0; | t->mode = 0; | ||||
t->size = 0; | t->size = 0; | ||||
} | } | ||||
if (has_meaningful_mtime) { | if (has_meaningful_mtime) { | ||||
t->flags |= dirstate_flag_has_mtime; | t->flags |= dirstate_flag_has_mtime; | ||||
t->mtime_s = mtime_s; | t->mtime_s = mtime_s; | ||||
t->mtime_ns = mtime_ns; | t->mtime_ns = mtime_ns; | ||||
return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); | return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); | ||||
}; | }; | ||||
static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, | static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, | ||||
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)) { | int other_second_ambiguous; | ||||
if (!PyArg_ParseTuple(other, "iii", &other_s, &other_ns, | |||||
&other_second_ambiguous)) { | |||||
return NULL; | return NULL; | ||||
} | } | ||||
if ((self->flags & dirstate_flag_has_mtime) && | if ((self->flags & dirstate_flag_has_mtime) && | ||||
self->mtime_s == other_s && | self->mtime_s == other_s && | ||||
(self->mtime_ns == other_ns || self->mtime_ns == 0 || | (self->mtime_ns == other_ns || self->mtime_ns == 0 || | ||||
other_ns == 0)) { | other_ns == 0)) { | ||||
Py_RETURN_TRUE; | Py_RETURN_TRUE; | ||||
} else { | } else { | ||||
self->flags &= ~dirstate_flag_has_mtime; | self->flags &= ~dirstate_flag_has_mtime; | ||||
Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
} | } | ||||
/* See docstring of the python implementation for details */ | /* See docstring of the python implementation for details */ | ||||
static PyObject *dirstate_item_set_clean(dirstateItemObject *self, | static PyObject *dirstate_item_set_clean(dirstateItemObject *self, | ||||
PyObject *args) | PyObject *args) | ||||
{ | { | ||||
int size, mode, mtime_s, mtime_ns; | int size, mode, mtime_s, mtime_ns, mtime_second_ambiguous; | ||||
PyObject *mtime; | PyObject *mtime; | ||||
mtime_s = 0; | mtime_s = 0; | ||||
mtime_ns = 0; | mtime_ns = 0; | ||||
mtime_second_ambiguous = 0; | |||||
if (!PyArg_ParseTuple(args, "iiO", &mode, &size, &mtime)) { | if (!PyArg_ParseTuple(args, "iiO", &mode, &size, &mtime)) { | ||||
return NULL; | return NULL; | ||||
} | } | ||||
if (mtime != Py_None) { | if (mtime != Py_None) { | ||||
if (!PyArg_ParseTuple(mtime, "ii", &mtime_s, &mtime_ns)) { | if (!PyArg_ParseTuple(mtime, "iii", &mtime_s, &mtime_ns, | ||||
&mtime_second_ambiguous)) { | |||||
return NULL; | return NULL; | ||||
} | } | ||||
} else { | } else { | ||||
self->flags &= ~dirstate_flag_has_mtime; | self->flags &= ~dirstate_flag_has_mtime; | ||||
} | } | ||||
self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | | self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | | ||||
dirstate_flag_has_meaningful_data | | dirstate_flag_has_meaningful_data | | ||||
dirstate_flag_has_mtime; | dirstate_flag_has_mtime; | ||||
if (mtime_second_ambiguous) { | |||||
self->flags |= dirstate_flag_mtime_second_ambiguous; | |||||
} | |||||
self->mode = mode; | self->mode = mode; | ||||
self->size = size; | self->size = size; | ||||
self->mtime_s = mtime_s; | self->mtime_s = mtime_s; | ||||
self->mtime_ns = mtime_ns; | self->mtime_ns = mtime_ns; | ||||
Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
} | } | ||||
static PyObject *dirstate_item_set_tracked(dirstateItemObject *self) | static PyObject *dirstate_item_set_tracked(dirstateItemObject *self) |
`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. | 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, second_ambiguous = value | ||||
value = (truncated_seconds & rangemask, subsec_nanos) | value = (truncated_seconds & rangemask, subsec_nanos, second_ambiguous) | ||||
return super(timestamp, cls).__new__(cls, value) | return super(timestamp, cls).__new__(cls, value) | ||||
def __eq__(self, other): | def __eq__(self, other): | ||||
raise error.ProgrammingError( | raise error.ProgrammingError( | ||||
'timestamp should never be compared directly' | 'timestamp should never be compared directly' | ||||
) | ) | ||||
def __gt__(self, other): | def __gt__(self, other): | ||||
secs = stat_result[stat.ST_MTIME] | secs = stat_result[stat.ST_MTIME] | ||||
subsec_nanos = 0 | subsec_nanos = 0 | ||||
else: | else: | ||||
billion = int(1e9) | billion = int(1e9) | ||||
secs = nanos // billion | secs = nanos // billion | ||||
subsec_nanos = nanos % billion | subsec_nanos = nanos % billion | ||||
return timestamp((secs, subsec_nanos)) | return timestamp((secs, subsec_nanos, False)) | ||||
def reliable_mtime_of(stat_result, present_mtime): | def reliable_mtime_of(stat_result, present_mtime): | ||||
"""same as `mtime_of`, but return None if the date might be ambiguous | """same as `mtime_of`, but return None if the date might be ambiguous | ||||
A modification time is reliable if it is older than "present_time" (or | A modification time is reliable if it is older than "present_time" (or | ||||
sufficiently in the futur). | sufficiently in the futur). | ||||
_p1_tracked = attr.ib() | _p1_tracked = attr.ib() | ||||
_p2_info = attr.ib() | _p2_info = attr.ib() | ||||
_mode = attr.ib() | _mode = attr.ib() | ||||
_size = attr.ib() | _size = attr.ib() | ||||
_mtime_s = attr.ib() | _mtime_s = attr.ib() | ||||
_mtime_ns = attr.ib() | _mtime_ns = attr.ib() | ||||
_fallback_exec = attr.ib() | _fallback_exec = attr.ib() | ||||
_fallback_symlink = attr.ib() | _fallback_symlink = attr.ib() | ||||
_mtime_second_ambiguous = attr.ib() | |||||
def __init__( | def __init__( | ||||
self, | self, | ||||
wc_tracked=False, | wc_tracked=False, | ||||
p1_tracked=False, | p1_tracked=False, | ||||
p2_info=False, | p2_info=False, | ||||
has_meaningful_data=True, | has_meaningful_data=True, | ||||
has_meaningful_mtime=True, | has_meaningful_mtime=True, | ||||
parentfiledata=None, | parentfiledata=None, | ||||
fallback_exec=None, | fallback_exec=None, | ||||
fallback_symlink=None, | fallback_symlink=None, | ||||
): | ): | ||||
self._wc_tracked = wc_tracked | self._wc_tracked = wc_tracked | ||||
self._p1_tracked = p1_tracked | self._p1_tracked = p1_tracked | ||||
self._p2_info = p2_info | self._p2_info = p2_info | ||||
self._fallback_exec = fallback_exec | self._fallback_exec = fallback_exec | ||||
self._fallback_symlink = fallback_symlink | self._fallback_symlink = fallback_symlink | ||||
self._mode = None | self._mode = None | ||||
self._size = None | self._size = None | ||||
self._mtime_s = None | self._mtime_s = None | ||||
self._mtime_ns = None | self._mtime_ns = None | ||||
self._mtime_second_ambiguous = False | |||||
if parentfiledata is None: | if parentfiledata is None: | ||||
has_meaningful_mtime = False | has_meaningful_mtime = False | ||||
has_meaningful_data = False | has_meaningful_data = False | ||||
elif parentfiledata[2] is None: | elif parentfiledata[2] is None: | ||||
has_meaningful_mtime = False | has_meaningful_mtime = False | ||||
if has_meaningful_data: | if has_meaningful_data: | ||||
self._mode = parentfiledata[0] | self._mode = parentfiledata[0] | ||||
self._size = parentfiledata[1] | self._size = parentfiledata[1] | ||||
if has_meaningful_mtime: | if has_meaningful_mtime: | ||||
self._mtime_s, self._mtime_ns = parentfiledata[2] | ( | ||||
self._mtime_s, | |||||
self._mtime_ns, | |||||
self._mtime_second_ambiguous, | |||||
) = parentfiledata[2] | |||||
@classmethod | @classmethod | ||||
def from_v2_data(cls, flags, size, mtime_s, mtime_ns): | def from_v2_data(cls, flags, size, mtime_s, mtime_ns): | ||||
"""Build a new DirstateItem object from V2 data""" | """Build a new DirstateItem object from V2 data""" | ||||
has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) | has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) | ||||
has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME) | has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME) | ||||
if flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS: | if flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS: | ||||
# The current code is not able to do the more subtle comparison that the | # The current code is not able to do the more subtle comparison that the | ||||
else: | else: | ||||
mode |= stat.S_IFREG | mode |= stat.S_IFREG | ||||
return cls( | return cls( | ||||
wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), | wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), | ||||
p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), | p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), | ||||
p2_info=bool(flags & DIRSTATE_V2_P2_INFO), | p2_info=bool(flags & DIRSTATE_V2_P2_INFO), | ||||
has_meaningful_data=has_mode_size, | has_meaningful_data=has_mode_size, | ||||
has_meaningful_mtime=has_meaningful_mtime, | has_meaningful_mtime=has_meaningful_mtime, | ||||
parentfiledata=(mode, size, (mtime_s, mtime_ns)), | parentfiledata=(mode, size, (mtime_s, mtime_ns, False)), | ||||
fallback_exec=fallback_exec, | fallback_exec=fallback_exec, | ||||
fallback_symlink=fallback_symlink, | fallback_symlink=fallback_symlink, | ||||
) | ) | ||||
@classmethod | @classmethod | ||||
def from_v1_data(cls, state, mode, size, mtime): | def from_v1_data(cls, state, mode, size, mtime): | ||||
"""Build a new DirstateItem object from V1 data | """Build a new DirstateItem object from V1 data | ||||
return cls(wc_tracked=True, p2_info=True) | return cls(wc_tracked=True, p2_info=True) | ||||
elif size == NONNORMAL: | elif size == NONNORMAL: | ||||
return cls(wc_tracked=True, p1_tracked=True) | return cls(wc_tracked=True, p1_tracked=True) | ||||
elif mtime == AMBIGUOUS_TIME: | elif mtime == AMBIGUOUS_TIME: | ||||
return cls( | return cls( | ||||
wc_tracked=True, | wc_tracked=True, | ||||
p1_tracked=True, | p1_tracked=True, | ||||
has_meaningful_mtime=False, | has_meaningful_mtime=False, | ||||
parentfiledata=(mode, size, (42, 0)), | parentfiledata=(mode, size, (42, 0, False)), | ||||
) | ) | ||||
else: | else: | ||||
return cls( | return cls( | ||||
wc_tracked=True, | wc_tracked=True, | ||||
p1_tracked=True, | p1_tracked=True, | ||||
parentfiledata=(mode, size, (mtime, 0)), | parentfiledata=(mode, size, (mtime, 0, False)), | ||||
) | ) | ||||
else: | else: | ||||
raise RuntimeError(b'unknown state: %s' % state) | raise RuntimeError(b'unknown state: %s' % state) | ||||
def set_possibly_dirty(self): | def set_possibly_dirty(self): | ||||
"""Mark a file as "possibly dirty" | """Mark a file as "possibly dirty" | ||||
This means the next status call will have to actually check its content | This means the next status call will have to actually check its content | ||||
currently expected to be call on "normal" entry only. There are not | currently expected to be call on "normal" entry only. There are not | ||||
reason for this to not change in the future as long as the ccode is | reason for this to not change in the future as long as the ccode is | ||||
updated to preserve the proper state of the non-normal files. | updated to preserve the proper state of the non-normal files. | ||||
""" | """ | ||||
self._wc_tracked = True | self._wc_tracked = True | ||||
self._p1_tracked = True | self._p1_tracked = True | ||||
self._mode = mode | self._mode = mode | ||||
self._size = size | self._size = size | ||||
self._mtime_s, self._mtime_ns = mtime | self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime | ||||
def set_tracked(self): | def set_tracked(self): | ||||
"""mark a file as tracked in the working copy | """mark a file as tracked in the working copy | ||||
This will ultimately be called by command like `hg add`. | This will ultimately be called by command like `hg add`. | ||||
""" | """ | ||||
self._wc_tracked = True | self._wc_tracked = True | ||||
# `set_tracked` is replacing various `normallookup` call. So we mark | # `set_tracked` is replacing various `normallookup` call. So we mark | ||||
def mtime(self): | def mtime(self): | ||||
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, second_ambiguous = other_mtime | ||||
return self_sec == other_sec and ( | return self_sec == other_sec and ( | ||||
self_ns == other_ns or self_ns == 0 or other_ns == 0 | self_ns == other_ns or self_ns == 0 or other_ns == 0 | ||||
) | ) | ||||
@property | @property | ||||
def state(self): | def state(self): | ||||
""" | """ | ||||
States are: | States are: |
} | } | ||||
/// A Unix timestamp with nanoseconds precision | /// A Unix timestamp with nanoseconds precision | ||||
#[derive(Debug, Copy, Clone)] | #[derive(Debug, Copy, Clone)] | ||||
pub struct TruncatedTimestamp { | pub struct TruncatedTimestamp { | ||||
truncated_seconds: u32, | truncated_seconds: u32, | ||||
/// Always in the `0 .. 1_000_000_000` range. | /// Always in the `0 .. 1_000_000_000` range. | ||||
nanoseconds: u32, | nanoseconds: u32, | ||||
second_ambiguous: bool, | |||||
} | } | ||||
impl TruncatedTimestamp { | impl TruncatedTimestamp { | ||||
/// Constructs from a timestamp potentially outside of the supported range, | /// Constructs from a timestamp potentially outside of the supported range, | ||||
/// and truncate the seconds components to its lower 31 bits. | /// and truncate the seconds components to its lower 31 bits. | ||||
/// | /// | ||||
/// Panics if the nanoseconds components is not in the expected range. | /// Panics if the nanoseconds components is not in the expected range. | ||||
pub fn new_truncate(seconds: i64, nanoseconds: u32) -> Self { | pub fn new_truncate( | ||||
seconds: i64, | |||||
nanoseconds: u32, | |||||
second_ambiguous: bool, | |||||
) -> Self { | |||||
assert!(nanoseconds < NSEC_PER_SEC); | assert!(nanoseconds < NSEC_PER_SEC); | ||||
Self { | Self { | ||||
truncated_seconds: seconds as u32 & RANGE_MASK_31BIT, | truncated_seconds: seconds as u32 & RANGE_MASK_31BIT, | ||||
nanoseconds, | nanoseconds, | ||||
second_ambiguous, | |||||
} | } | ||||
} | } | ||||
/// Construct from components. Returns an error if they are not in the | /// Construct from components. Returns an error if they are not in the | ||||
/// expcted range. | /// expcted range. | ||||
pub fn from_already_truncated( | pub fn from_already_truncated( | ||||
truncated_seconds: u32, | truncated_seconds: u32, | ||||
nanoseconds: u32, | nanoseconds: u32, | ||||
second_ambiguous: bool, | |||||
) -> Result<Self, DirstateV2ParseError> { | ) -> Result<Self, DirstateV2ParseError> { | ||||
if truncated_seconds & !RANGE_MASK_31BIT == 0 | if truncated_seconds & !RANGE_MASK_31BIT == 0 | ||||
&& nanoseconds < NSEC_PER_SEC | && nanoseconds < NSEC_PER_SEC | ||||
{ | { | ||||
Ok(Self { | Ok(Self { | ||||
truncated_seconds, | truncated_seconds, | ||||
nanoseconds, | nanoseconds, | ||||
second_ambiguous, | |||||
}) | }) | ||||
} else { | } else { | ||||
Err(DirstateV2ParseError) | Err(DirstateV2ParseError) | ||||
} | } | ||||
} | } | ||||
pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> { | pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> { | ||||
#[cfg(unix)] | #[cfg(unix)] | ||||
{ | { | ||||
use std::os::unix::fs::MetadataExt; | use std::os::unix::fs::MetadataExt; | ||||
let seconds = metadata.mtime(); | let seconds = metadata.mtime(); | ||||
// i64!-> u32 with value always in the `0 .. NSEC_PER_SEC` range | // i64!-> u32 with value always in the `0 .. NSEC_PER_SEC` range | ||||
let nanoseconds = metadata.mtime_nsec().try_into().unwrap(); | let nanoseconds = metadata.mtime_nsec().try_into().unwrap(); | ||||
Ok(Self::new_truncate(seconds, nanoseconds)) | Ok(Self::new_truncate(seconds, nanoseconds, false)) | ||||
} | } | ||||
#[cfg(not(unix))] | #[cfg(not(unix))] | ||||
{ | { | ||||
metadata.modified().map(Self::from) | metadata.modified().map(Self::from) | ||||
} | } | ||||
} | } | ||||
/// The lower 31 bits of the number of seconds since the epoch. | /// The lower 31 bits of the number of seconds since the epoch. | ||||
// For example if `system_time` was 4.3!seconds before | // For example if `system_time` was 4.3!seconds before | ||||
// the Unix epoch we get a Duration that represents | // the Unix epoch we get a Duration that represents | ||||
// `(-4, -0.3)` but we want `(-5, +0.7)`: | // `(-4, -0.3)` but we want `(-5, +0.7)`: | ||||
seconds = -1 - negative_secs; | seconds = -1 - negative_secs; | ||||
nanoseconds = NSEC_PER_SEC - negative_nanos; | nanoseconds = NSEC_PER_SEC - negative_nanos; | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
Self::new_truncate(seconds, nanoseconds) | Self::new_truncate(seconds, nanoseconds, false) | ||||
} | } | ||||
} | } | ||||
const NSEC_PER_SEC: u32 = 1_000_000_000; | const NSEC_PER_SEC: u32 = 1_000_000_000; | ||||
const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; | const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; | ||||
pub const MTIME_UNSET: i32 = -1; | pub const MTIME_UNSET: i32 = -1; | ||||
mode_size: Some((mode, size)), | mode_size: Some((mode, size)), | ||||
mtime: None, | mtime: None, | ||||
} | } | ||||
} else { | } else { | ||||
// TODO:!return an error for negative values? | // TODO:!return an error for negative values? | ||||
let mode = u32::try_from(mode).unwrap(); | let mode = u32::try_from(mode).unwrap(); | ||||
let size = u32::try_from(size).unwrap(); | let size = u32::try_from(size).unwrap(); | ||||
let mtime = u32::try_from(mtime).unwrap(); | let mtime = u32::try_from(mtime).unwrap(); | ||||
let mtime = | let mtime = TruncatedTimestamp::from_already_truncated( | ||||
TruncatedTimestamp::from_already_truncated(mtime, 0) | mtime, 0, false, | ||||
) | |||||
.unwrap(); | .unwrap(); | ||||
Self { | Self { | ||||
flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
mode_size: Some((mode, size)), | mode_size: Some((mode, size)), | ||||
mtime: Some(mtime), | mtime: Some(mtime), | ||||
} | } | ||||
} | } | ||||
} | } | ||||
EntryState::Added => Self { | EntryState::Added => Self { |
type Error = DirstateV2ParseError; | type Error = DirstateV2ParseError; | ||||
fn try_from( | fn try_from( | ||||
timestamp: PackedTruncatedTimestamp, | timestamp: PackedTruncatedTimestamp, | ||||
) -> Result<Self, Self::Error> { | ) -> Result<Self, Self::Error> { | ||||
Self::from_already_truncated( | Self::from_already_truncated( | ||||
timestamp.truncated_seconds.get(), | timestamp.truncated_seconds.get(), | ||||
timestamp.nanoseconds.get(), | timestamp.nanoseconds.get(), | ||||
false, | |||||
) | ) | ||||
} | } | ||||
} | } | ||||
impl PackedTruncatedTimestamp { | impl PackedTruncatedTimestamp { | ||||
fn null() -> Self { | fn null() -> Self { | ||||
Self { | Self { | ||||
truncated_seconds: 0.into(), | truncated_seconds: 0.into(), | ||||
nanoseconds: 0.into(), | nanoseconds: 0.into(), | ||||
} | } | ||||
} | } | ||||
} | } |
def __new__( | def __new__( | ||||
_cls, | _cls, | ||||
wc_tracked: bool = false, | wc_tracked: bool = false, | ||||
p1_tracked: bool = false, | p1_tracked: bool = false, | ||||
p2_info: bool = false, | p2_info: bool = false, | ||||
has_meaningful_data: bool = true, | has_meaningful_data: bool = true, | ||||
has_meaningful_mtime: bool = true, | has_meaningful_mtime: bool = true, | ||||
parentfiledata: Option<(u32, u32, Option<(u32, u32)>)> = None, | parentfiledata: Option<(u32, u32, Option<(u32, u32, bool)>)> = None, | ||||
fallback_exec: Option<bool> = None, | fallback_exec: Option<bool> = None, | ||||
fallback_symlink: Option<bool> = None, | fallback_symlink: Option<bool> = None, | ||||
) -> PyResult<DirstateItem> { | ) -> PyResult<DirstateItem> { | ||||
let mut mode_size_opt = None; | let mut mode_size_opt = None; | ||||
let mut mtime_opt = None; | let mut mtime_opt = None; | ||||
if let Some((mode, size, mtime)) = parentfiledata { | if let Some((mode, size, mtime)) = parentfiledata { | ||||
if has_meaningful_data { | if has_meaningful_data { | ||||
Ok(size) | Ok(size) | ||||
} | } | ||||
def v1_mtime(&self) -> PyResult<i32> { | def v1_mtime(&self) -> PyResult<i32> { | ||||
let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data(); | let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data(); | ||||
Ok(mtime) | Ok(mtime) | ||||
} | } | ||||
def mtime_likely_equal_to(&self, other: (u32, u32)) -> PyResult<bool> { | def mtime_likely_equal_to(&self, other: (u32, u32, bool)) | ||||
-> PyResult<bool> { | |||||
if let Some(mtime) = self.entry(py).get().truncated_mtime() { | if let Some(mtime) = self.entry(py).get().truncated_mtime() { | ||||
Ok(mtime.likely_equal(timestamp(py, other)?)) | Ok(mtime.likely_equal(timestamp(py, other)?)) | ||||
} else { | } else { | ||||
Ok(false) | Ok(false) | ||||
} | } | ||||
} | } | ||||
@classmethod | @classmethod | ||||
self.update(py, |entry| entry.drop_merge_data()); | self.update(py, |entry| entry.drop_merge_data()); | ||||
Ok(PyNone) | Ok(PyNone) | ||||
} | } | ||||
def set_clean( | def set_clean( | ||||
&self, | &self, | ||||
mode: u32, | mode: u32, | ||||
size: u32, | size: u32, | ||||
mtime: (u32, u32), | mtime: (u32, u32, bool), | ||||
) -> PyResult<PyNone> { | ) -> PyResult<PyNone> { | ||||
let mtime = timestamp(py, mtime)?; | let mtime = timestamp(py, mtime)?; | ||||
self.update(py, |entry| entry.set_clean(mode, size, mtime)); | self.update(py, |entry| entry.set_clean(mode, size, mtime)); | ||||
Ok(PyNone) | Ok(PyNone) | ||||
} | } | ||||
def set_possibly_dirty(&self) -> PyResult<PyNone> { | def set_possibly_dirty(&self) -> PyResult<PyNone> { | ||||
self.update(py, |entry| entry.set_possibly_dirty()); | self.update(py, |entry| entry.set_possibly_dirty()); | ||||
let mut entry = self.entry(py).get(); | let mut entry = self.entry(py).get(); | ||||
f(&mut entry); | f(&mut entry); | ||||
self.entry(py).set(entry) | self.entry(py).set(entry) | ||||
} | } | ||||
} | } | ||||
pub(crate) fn timestamp( | pub(crate) fn timestamp( | ||||
py: Python<'_>, | py: Python<'_>, | ||||
(s, ns): (u32, u32), | (s, ns, second_ambiguous): (u32, u32, bool), | ||||
) -> PyResult<TruncatedTimestamp> { | ) -> PyResult<TruncatedTimestamp> { | ||||
TruncatedTimestamp::from_already_truncated(s, ns).map_err(|_| { | TruncatedTimestamp::from_already_truncated(s, ns, second_ambiguous) | ||||
.map_err(|_| { | |||||
PyErr::new::<exc::ValueError, _>( | PyErr::new::<exc::ValueError, _>( | ||||
py, | py, | ||||
"expected mtime truncated to 31 bits", | "expected mtime truncated to 31 bits", | ||||
) | ) | ||||
}) | }) | ||||
} | } |
# useful to prevent subrepos from executing replaced one, | # useful to prevent subrepos from executing replaced one, | ||||
# because replacing 'parsers.pack_dirstate' is also effective | # because replacing 'parsers.pack_dirstate' is also effective | ||||
# in subrepos. | # in subrepos. | ||||
return func() | return func() | ||||
# parsing 'fakenow' in YYYYmmddHHMM format makes comparison between | # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between | ||||
# 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy | # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy | ||||
fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] | fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] | ||||
fakenow = timestamp.timestamp((fakenow, 0)) | fakenow = timestamp.timestamp((fakenow, 0, False)) | ||||
if has_rust_dirstate: | if has_rust_dirstate: | ||||
# The Rust implementation does not use public parse/pack dirstate | # The Rust implementation does not use public parse/pack dirstate | ||||
# to prevent conversion round-trips | # to prevent conversion round-trips | ||||
orig_dirstatemap_write = dirstatemapmod.dirstatemap.write | orig_dirstatemap_write = dirstatemapmod.dirstatemap.write | ||||
wrapper = lambda self, tr, st: orig_dirstatemap_write(self, tr, st) | wrapper = lambda self, tr, st: orig_dirstatemap_write(self, tr, st) | ||||
dirstatemapmod.dirstatemap.write = wrapper | dirstatemapmod.dirstatemap.write = wrapper | ||||