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 | ||||