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.
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 (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) |
/* 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 | ||||