dirstate-item: add a "second_ambiguous` flag in the mtime tuple
authorPierre-Yves David <pierre-yves.david@octobus.net>
Wed, 24 Nov 2021 04:40:00 +0100
changeset 48398 111098af6356
parent 48397 8d585aa9becf
child 48399 af303ae33cd7
dirstate-item: add a "second_ambiguous` flag in the mtime tuple 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. Differential Revision: https://phab.mercurial-scm.org/D11842
mercurial/cext/parsers.c
mercurial/dirstateutils/timestamp.py
mercurial/pure/parsers.py
rust/hg-core/src/dirstate/entry.rs
rust/hg-core/src/dirstate_tree/on_disk.rs
rust/hg-cpython/src/dirstate/item.rs
tests/fakedirstatewritetime.py
--- a/mercurial/cext/parsers.c	Tue Nov 23 19:27:17 2021 +0100
+++ b/mercurial/cext/parsers.c	Wed Nov 24 04:40:00 2021 +0100
@@ -61,6 +61,7 @@
 	int p2_info;
 	int has_meaningful_data;
 	int has_meaningful_mtime;
+	int mtime_second_ambiguous;
 	int mode;
 	int size;
 	int mtime_s;
@@ -79,6 +80,7 @@
 	p2_info = 0;
 	has_meaningful_mtime = 1;
 	has_meaningful_data = 1;
+	mtime_second_ambiguous = 0;
 	parentfiledata = Py_None;
 	fallback_exec = Py_None;
 	fallback_symlink = Py_None;
@@ -124,8 +126,8 @@
 			return NULL;
 		}
 		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;
 			}
 		} else {
@@ -139,6 +141,9 @@
 		t->flags |= dirstate_flag_has_meaningful_data;
 		t->mode = mode;
 		t->size = size;
+		if (mtime_second_ambiguous) {
+			t->flags |= dirstate_flag_mtime_second_ambiguous;
+		}
 	} else {
 		t->mode = 0;
 		t->size = 0;
@@ -325,7 +330,9 @@
 {
 	int other_s;
 	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;
 	}
 	if ((self->flags & dirstate_flag_has_mtime) &&
@@ -468,15 +475,17 @@
 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
                                          PyObject *args)
 {
-	int size, mode, mtime_s, mtime_ns;
+	int size, mode, mtime_s, mtime_ns, mtime_second_ambiguous;
 	PyObject *mtime;
 	mtime_s = 0;
 	mtime_ns = 0;
+	mtime_second_ambiguous = 0;
 	if (!PyArg_ParseTuple(args, "iiO", &mode, &size, &mtime)) {
 		return NULL;
 	}
 	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;
 		}
 	} else {
@@ -485,6 +494,9 @@
 	self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
 	              dirstate_flag_has_meaningful_data |
 	              dirstate_flag_has_mtime;
+	if (mtime_second_ambiguous) {
+		self->flags |= dirstate_flag_mtime_second_ambiguous;
+	}
 	self->mode = mode;
 	self->size = size;
 	self->mtime_s = mtime_s;
--- a/mercurial/dirstateutils/timestamp.py	Tue Nov 23 19:27:17 2021 +0100
+++ b/mercurial/dirstateutils/timestamp.py	Wed Nov 24 04:40:00 2021 +0100
@@ -31,8 +31,8 @@
     """
 
     def __new__(cls, value):
-        truncated_seconds, subsec_nanos = value
-        value = (truncated_seconds & rangemask, subsec_nanos)
+        truncated_seconds, subsec_nanos, second_ambiguous = value
+        value = (truncated_seconds & rangemask, subsec_nanos, second_ambiguous)
         return super(timestamp, cls).__new__(cls, value)
 
     def __eq__(self, other):
@@ -89,7 +89,7 @@
         secs = 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):
--- a/mercurial/pure/parsers.py	Tue Nov 23 19:27:17 2021 +0100
+++ b/mercurial/pure/parsers.py	Wed Nov 24 04:40:00 2021 +0100
@@ -104,6 +104,7 @@
     _mtime_ns = attr.ib()
     _fallback_exec = attr.ib()
     _fallback_symlink = attr.ib()
+    _mtime_second_ambiguous = attr.ib()
 
     def __init__(
         self,
@@ -127,6 +128,7 @@
         self._size = None
         self._mtime_s = None
         self._mtime_ns = None
+        self._mtime_second_ambiguous = False
         if parentfiledata is None:
             has_meaningful_mtime = False
             has_meaningful_data = False
@@ -136,7 +138,11 @@
             self._mode = parentfiledata[0]
             self._size = parentfiledata[1]
         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
     def from_v2_data(cls, flags, size, mtime_s, mtime_ns):
@@ -179,7 +185,7 @@
             p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
             has_meaningful_data=has_mode_size,
             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_symlink=fallback_symlink,
         )
@@ -216,13 +222,13 @@
                     wc_tracked=True,
                     p1_tracked=True,
                     has_meaningful_mtime=False,
-                    parentfiledata=(mode, size, (42, 0)),
+                    parentfiledata=(mode, size, (42, 0, False)),
                 )
             else:
                 return cls(
                     wc_tracked=True,
                     p1_tracked=True,
-                    parentfiledata=(mode, size, (mtime, 0)),
+                    parentfiledata=(mode, size, (mtime, 0, False)),
                 )
         else:
             raise RuntimeError(b'unknown state: %s' % state)
@@ -248,7 +254,7 @@
         self._p1_tracked = True
         self._mode = mode
         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):
         """mark a file as tracked in the working copy
@@ -303,7 +309,7 @@
         if self_sec is None:
             return False
         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 (
             self_ns == other_ns or self_ns == 0 or other_ns == 0
         )
--- a/rust/hg-core/src/dirstate/entry.rs	Tue Nov 23 19:27:17 2021 +0100
+++ b/rust/hg-core/src/dirstate/entry.rs	Wed Nov 24 04:40:00 2021 +0100
@@ -43,6 +43,7 @@
     truncated_seconds: u32,
     /// Always in the `0 .. 1_000_000_000` range.
     nanoseconds: u32,
+    second_ambiguous: bool,
 }
 
 impl TruncatedTimestamp {
@@ -50,11 +51,16 @@
     /// and truncate the seconds components to its lower 31 bits.
     ///
     /// 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);
         Self {
             truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
             nanoseconds,
+            second_ambiguous,
         }
     }
 
@@ -63,6 +69,7 @@
     pub fn from_already_truncated(
         truncated_seconds: u32,
         nanoseconds: u32,
+        second_ambiguous: bool,
     ) -> Result<Self, DirstateV2ParseError> {
         if truncated_seconds & !RANGE_MASK_31BIT == 0
             && nanoseconds < NSEC_PER_SEC
@@ -70,6 +77,7 @@
             Ok(Self {
                 truncated_seconds,
                 nanoseconds,
+                second_ambiguous,
             })
         } else {
             Err(DirstateV2ParseError)
@@ -83,7 +91,7 @@
             let seconds = metadata.mtime();
             // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
             let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
-            Ok(Self::new_truncate(seconds, nanoseconds))
+            Ok(Self::new_truncate(seconds, nanoseconds, false))
         }
         #[cfg(not(unix))]
         {
@@ -168,7 +176,7 @@
                 }
             }
         };
-        Self::new_truncate(seconds, nanoseconds)
+        Self::new_truncate(seconds, nanoseconds, false)
     }
 }
 
@@ -258,9 +266,10 @@
                     let mode = u32::try_from(mode).unwrap();
                     let size = u32::try_from(size).unwrap();
                     let mtime = u32::try_from(mtime).unwrap();
-                    let mtime =
-                        TruncatedTimestamp::from_already_truncated(mtime, 0)
-                            .unwrap();
+                    let mtime = TruncatedTimestamp::from_already_truncated(
+                        mtime, 0, false,
+                    )
+                    .unwrap();
                     Self {
                         flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
                         mode_size: Some((mode, size)),
--- a/rust/hg-core/src/dirstate_tree/on_disk.rs	Tue Nov 23 19:27:17 2021 +0100
+++ b/rust/hg-core/src/dirstate_tree/on_disk.rs	Wed Nov 24 04:40:00 2021 +0100
@@ -773,6 +773,7 @@
         Self::from_already_truncated(
             timestamp.truncated_seconds.get(),
             timestamp.nanoseconds.get(),
+            false,
         )
     }
 }
--- a/rust/hg-cpython/src/dirstate/item.rs	Tue Nov 23 19:27:17 2021 +0100
+++ b/rust/hg-cpython/src/dirstate/item.rs	Wed Nov 24 04:40:00 2021 +0100
@@ -23,7 +23,7 @@
         p2_info: bool = false,
         has_meaningful_data: 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_symlink: Option<bool> = None,
 
@@ -194,7 +194,8 @@
         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() {
             Ok(mtime.likely_equal(timestamp(py, other)?))
         } else {
@@ -227,7 +228,7 @@
         &self,
         mode: u32,
         size: u32,
-        mtime: (u32, u32),
+        mtime: (u32, u32, bool),
     ) -> PyResult<PyNone> {
         let mtime = timestamp(py, mtime)?;
         self.update(py, |entry| entry.set_clean(mode, size, mtime));
@@ -272,12 +273,13 @@
 
 pub(crate) fn timestamp(
     py: Python<'_>,
-    (s, ns): (u32, u32),
+    (s, ns, second_ambiguous): (u32, u32, bool),
 ) -> PyResult<TruncatedTimestamp> {
-    TruncatedTimestamp::from_already_truncated(s, ns).map_err(|_| {
-        PyErr::new::<exc::ValueError, _>(
-            py,
-            "expected mtime truncated to 31 bits",
-        )
-    })
+    TruncatedTimestamp::from_already_truncated(s, ns, second_ambiguous)
+        .map_err(|_| {
+            PyErr::new::<exc::ValueError, _>(
+                py,
+                "expected mtime truncated to 31 bits",
+            )
+        })
 }
--- a/tests/fakedirstatewritetime.py	Tue Nov 23 19:27:17 2021 +0100
+++ b/tests/fakedirstatewritetime.py	Wed Nov 24 04:40:00 2021 +0100
@@ -55,7 +55,7 @@
     # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between
     # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy
     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:
         # The Rust implementation does not use public parse/pack dirstate