dirstate: add a concept of "fallback" flags to dirstate item
authorPierre-Yves David <pierre-yves.david@octobus.net>
Mon, 18 Oct 2021 20:02:15 +0200
changeset 48252 602c8e8411f5
parent 48251 dfc5a505ddc5
child 48253 948570aa7630
dirstate: add a concept of "fallback" flags to dirstate item The concept is defined and "used" by the flag code, but it is neither persisted nor set anywhere yet. We currently focus on defining the semantic of the attribute. More to come in the next changesets Check the inline documentation for details. Differential Revision: https://phab.mercurial-scm.org/D11686
mercurial/cext/parsers.c
mercurial/cext/util.h
mercurial/dirstate.py
mercurial/pure/parsers.py
rust/hg-core/src/dirstate/entry.rs
rust/hg-cpython/src/dirstate/item.rs
--- a/mercurial/cext/parsers.c	Fri Oct 15 16:33:19 2021 +0200
+++ b/mercurial/cext/parsers.c	Mon Oct 18 20:02:15 2021 +0200
@@ -188,6 +188,17 @@
 	}
 }
 
+static inline bool dirstate_item_c_has_fallback_exec(dirstateItemObject *self)
+{
+	return (bool)self->flags & dirstate_flag_has_fallback_exec;
+}
+
+static inline bool
+dirstate_item_c_has_fallback_symlink(dirstateItemObject *self)
+{
+	return (bool)self->flags & dirstate_flag_has_fallback_symlink;
+}
+
 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
 {
 	if (self->flags & dirstate_flag_has_meaningful_data) {
@@ -498,6 +509,83 @@
 	return PyBytes_FromStringAndSize(&state, 1);
 };
 
+static PyObject *dirstate_item_get_has_fallback_exec(dirstateItemObject *self)
+{
+	if (dirstate_item_c_has_fallback_exec(self)) {
+		Py_RETURN_TRUE;
+	} else {
+		Py_RETURN_FALSE;
+	}
+};
+
+static PyObject *dirstate_item_get_fallback_exec(dirstateItemObject *self)
+{
+	if (dirstate_item_c_has_fallback_exec(self)) {
+		if (self->flags & dirstate_flag_fallback_exec) {
+			Py_RETURN_TRUE;
+		} else {
+			Py_RETURN_FALSE;
+		}
+	} else {
+		Py_RETURN_NONE;
+	}
+};
+
+static int dirstate_item_set_fallback_exec(dirstateItemObject *self,
+                                           PyObject *value)
+{
+	if ((value == Py_None) || (value == NULL)) {
+		self->flags &= ~dirstate_flag_has_fallback_exec;
+	} else {
+		self->flags |= dirstate_flag_has_fallback_exec;
+		if (PyObject_IsTrue(value)) {
+			self->flags |= dirstate_flag_fallback_exec;
+		} else {
+			self->flags &= ~dirstate_flag_fallback_exec;
+		}
+	}
+	return 0;
+};
+
+static PyObject *
+dirstate_item_get_has_fallback_symlink(dirstateItemObject *self)
+{
+	if (dirstate_item_c_has_fallback_symlink(self)) {
+		Py_RETURN_TRUE;
+	} else {
+		Py_RETURN_FALSE;
+	}
+};
+
+static PyObject *dirstate_item_get_fallback_symlink(dirstateItemObject *self)
+{
+	if (dirstate_item_c_has_fallback_symlink(self)) {
+		if (self->flags & dirstate_flag_fallback_symlink) {
+			Py_RETURN_TRUE;
+		} else {
+			Py_RETURN_FALSE;
+		}
+	} else {
+		Py_RETURN_NONE;
+	}
+};
+
+static int dirstate_item_set_fallback_symlink(dirstateItemObject *self,
+                                              PyObject *value)
+{
+	if ((value == Py_None) || (value == NULL)) {
+		self->flags &= ~dirstate_flag_has_fallback_symlink;
+	} else {
+		self->flags |= dirstate_flag_has_fallback_symlink;
+		if (PyObject_IsTrue(value)) {
+			self->flags |= dirstate_flag_fallback_symlink;
+		} else {
+			self->flags &= ~dirstate_flag_fallback_symlink;
+		}
+	}
+	return 0;
+};
+
 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
 {
 	if (dirstate_item_c_tracked(self)) {
@@ -588,6 +676,14 @@
     {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
     {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
     {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
+    {"has_fallback_exec", (getter)dirstate_item_get_has_fallback_exec, NULL,
+     "has_fallback_exec", NULL},
+    {"fallback_exec", (getter)dirstate_item_get_fallback_exec,
+     (setter)dirstate_item_set_fallback_exec, "fallback_exec", NULL},
+    {"has_fallback_symlink", (getter)dirstate_item_get_has_fallback_symlink,
+     NULL, "has_fallback_symlink", NULL},
+    {"fallback_symlink", (getter)dirstate_item_get_fallback_symlink,
+     (setter)dirstate_item_set_fallback_symlink, "fallback_symlink", NULL},
     {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
     {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
      NULL},
--- a/mercurial/cext/util.h	Fri Oct 15 16:33:19 2021 +0200
+++ b/mercurial/cext/util.h	Mon Oct 18 20:02:15 2021 +0200
@@ -42,6 +42,10 @@
 static const int dirstate_flag_expected_state_is_modified = 1 << 8;
 static const int dirstate_flag_all_unknown_recorded = 1 << 9;
 static const int dirstate_flag_all_ignored_recorded = 1 << 10;
+static const int dirstate_flag_fallback_exec = 1 << 11;
+static const int dirstate_flag_has_fallback_exec = 1 << 12;
+static const int dirstate_flag_fallback_symlink = 1 << 13;
+static const int dirstate_flag_has_fallback_symlink = 1 << 14;
 
 extern PyTypeObject dirstateItemType;
 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateItemType)
--- a/mercurial/dirstate.py	Fri Oct 15 16:33:19 2021 +0200
+++ b/mercurial/dirstate.py	Mon Oct 18 20:02:15 2021 +0200
@@ -259,7 +259,11 @@
             def f(x):
                 if os.path.islink(self._join(x)):
                     return b'l'
-                if b'x' in fallback(x):
+                entry = self.get_entry(x)
+                if entry.has_fallback_exec:
+                    if entry.fallback_exec:
+                        return b'x'
+                elif b'x' in fallback(x):
                     return b'x'
                 return b''
 
@@ -269,13 +273,28 @@
             def f(x):
                 if b'l' in fallback(x):
                     return b'l'
+                entry = self.get_entry(x)
+                if entry.has_fallback_symlink:
+                    if entry.fallback_symlink:
+                        return b'l'
                 if util.isexec(self._join(x)):
                     return b'x'
                 return b''
 
             return f
         else:
-            return fallback
+
+            def f(x):
+                entry = self.get_entry(x)
+                if entry.has_fallback_symlink:
+                    if entry.fallback_symlink:
+                        return b'l'
+                if entry.has_fallback_exec:
+                    if entry.fallback_exec:
+                        return b'x'
+                    elif entry.has_fallback_symlink:
+                        return b''
+                return fallback(x)
 
     @propertycache
     def _cwd(self):
--- a/mercurial/pure/parsers.py	Fri Oct 15 16:33:19 2021 +0200
+++ b/mercurial/pure/parsers.py	Mon Oct 18 20:02:15 2021 +0200
@@ -96,6 +96,8 @@
     _mode = attr.ib()
     _size = attr.ib()
     _mtime = attr.ib()
+    _fallback_exec = attr.ib()
+    _fallback_symlink = attr.ib()
 
     def __init__(
         self,
@@ -110,6 +112,9 @@
         self._p1_tracked = p1_tracked
         self._p2_info = p2_info
 
+        self._fallback_exec = None
+        self._fallback_symlink = None
+
         self._mode = None
         self._size = None
         self._mtime = None
@@ -282,6 +287,85 @@
         return self.v1_state()
 
     @property
+    def has_fallback_exec(self):
+        """True if "fallback" information are available for the "exec" bit
+
+        Fallback information can be stored in the dirstate to keep track of
+        filesystem attribute tracked by Mercurial when the underlying file
+        system or operating system does not support that property, (e.g.
+        Windows).
+
+        Not all version of the dirstate on-disk storage support preserving this
+        information.
+        """
+        return self._fallback_exec is not None
+
+    @property
+    def fallback_exec(self):
+        """ "fallback" information for the executable bit
+
+        True if the file should be considered executable when we cannot get
+        this information from the files system. False if it should be
+        considered non-executable.
+
+        See has_fallback_exec for details."""
+        return self._fallback_exec
+
+    @fallback_exec.setter
+    def set_fallback_exec(self, value):
+        """control "fallback" executable bit
+
+        Set to:
+        - True if the file should be considered executable,
+        - False if the file should be considered non-executable,
+        - None if we do not have valid fallback data.
+
+        See has_fallback_exec for details."""
+        if value is None:
+            self._fallback_exec = None
+        else:
+            self._fallback_exec = bool(value)
+
+    @property
+    def has_fallback_symlink(self):
+        """True if "fallback" information are available for symlink status
+
+        Fallback information can be stored in the dirstate to keep track of
+        filesystem attribute tracked by Mercurial when the underlying file
+        system or operating system does not support that property, (e.g.
+        Windows).
+
+        Not all version of the dirstate on-disk storage support preserving this
+        information."""
+        return self._fallback_symlink is not None
+
+    @property
+    def fallback_symlink(self):
+        """ "fallback" information for symlink status
+
+        True if the file should be considered executable when we cannot get
+        this information from the files system. False if it should be
+        considered non-executable.
+
+        See has_fallback_exec for details."""
+        return self._fallback_symlink
+
+    @fallback_symlink.setter
+    def set_fallback_symlink(self, value):
+        """control "fallback" symlink status
+
+        Set to:
+        - True if the file should be considered a symlink,
+        - False if the file should be considered not a symlink,
+        - None if we do not have valid fallback data.
+
+        See has_fallback_symlink for details."""
+        if value is None:
+            self._fallback_symlink = None
+        else:
+            self._fallback_symlink = bool(value)
+
+    @property
     def tracked(self):
         """True is the file is tracked in the working copy"""
         return self._wc_tracked
--- a/rust/hg-core/src/dirstate/entry.rs	Fri Oct 15 16:33:19 2021 +0200
+++ b/rust/hg-core/src/dirstate/entry.rs	Mon Oct 18 20:02:15 2021 +0200
@@ -29,6 +29,10 @@
         const WDIR_TRACKED = 1 << 0;
         const P1_TRACKED = 1 << 1;
         const P2_INFO = 1 << 2;
+        const HAS_FALLBACK_EXEC = 1 << 3;
+        const FALLBACK_EXEC = 1 << 4;
+        const HAS_FALLBACK_SYMLINK = 1 << 5;
+        const FALLBACK_SYMLINK = 1 << 6;
     }
 }
 
@@ -421,6 +425,52 @@
         self.v1_mtime()
     }
 
+    pub fn get_fallback_exec(&self) -> Option<bool> {
+        if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
+            Some(self.flags.contains(Flags::FALLBACK_EXEC))
+        } else {
+            None
+        }
+    }
+
+    pub fn set_fallback_exec(&mut self, value: Option<bool>) {
+        match value {
+            None => {
+                self.flags.remove(Flags::HAS_FALLBACK_EXEC);
+                self.flags.remove(Flags::FALLBACK_EXEC);
+            }
+            Some(exec) => {
+                self.flags.insert(Flags::HAS_FALLBACK_EXEC);
+                if exec {
+                    self.flags.insert(Flags::FALLBACK_EXEC);
+                }
+            }
+        }
+    }
+
+    pub fn get_fallback_symlink(&self) -> Option<bool> {
+        if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
+            Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
+        } else {
+            None
+        }
+    }
+
+    pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
+        match value {
+            None => {
+                self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
+                self.flags.remove(Flags::FALLBACK_SYMLINK);
+            }
+            Some(symlink) => {
+                self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
+                if symlink {
+                    self.flags.insert(Flags::FALLBACK_SYMLINK);
+                }
+            }
+        }
+    }
+
     pub fn drop_merge_data(&mut self) {
         if self.flags.contains(Flags::P2_INFO) {
             self.flags.remove(Flags::P2_INFO);
--- a/rust/hg-cpython/src/dirstate/item.rs	Fri Oct 15 16:33:19 2021 +0200
+++ b/rust/hg-cpython/src/dirstate/item.rs	Mon Oct 18 20:02:15 2021 +0200
@@ -1,4 +1,5 @@
 use cpython::exc;
+use cpython::ObjectProtocol;
 use cpython::PyBytes;
 use cpython::PyErr;
 use cpython::PyNone;
@@ -62,6 +63,70 @@
     }
 
     @property
+    def has_fallback_exec(&self) -> PyResult<bool> {
+        match self.entry(py).get().get_fallback_exec() {
+            Some(_) => Ok(true),
+            None => Ok(false),
+        }
+    }
+
+    @property
+    def fallback_exec(&self) -> PyResult<Option<bool>> {
+        match self.entry(py).get().get_fallback_exec() {
+            Some(exec) => Ok(Some(exec)),
+            None => Ok(None),
+        }
+    }
+
+    @fallback_exec.setter
+    def set_fallback_exec(&self, value: Option<PyObject>) -> PyResult<()> {
+        match value {
+            None => {self.entry(py).get().set_fallback_exec(None);},
+            Some(value) => {
+            if value.is_none(py) {
+                self.entry(py).get().set_fallback_exec(None);
+            } else {
+                self.entry(py).get().set_fallback_exec(
+                    Some(value.is_true(py)?)
+                );
+            }},
+        }
+        Ok(())
+    }
+
+    @property
+    def has_fallback_symlink(&self) -> PyResult<bool> {
+        match self.entry(py).get().get_fallback_symlink() {
+            Some(_) => Ok(true),
+            None => Ok(false),
+        }
+    }
+
+    @property
+    def fallback_symlink(&self) -> PyResult<Option<bool>> {
+        match self.entry(py).get().get_fallback_symlink() {
+            Some(symlink) => Ok(Some(symlink)),
+            None => Ok(None),
+        }
+    }
+
+    @fallback_symlink.setter
+    def set_fallback_symlink(&self, value: Option<PyObject>) -> PyResult<()> {
+        match value {
+            None => {self.entry(py).get().set_fallback_symlink(None);},
+            Some(value) => {
+            if value.is_none(py) {
+                self.entry(py).get().set_fallback_symlink(None);
+            } else {
+                self.entry(py).get().set_fallback_symlink(
+                    Some(value.is_true(py)?)
+                );
+            }},
+        }
+        Ok(())
+    }
+
+    @property
     def tracked(&self) -> PyResult<bool> {
         Ok(self.entry(py).get().tracked())
     }