Patchwork D11686: dirstate: add a concept of "fallback" flags to dirstate item

login
register
mail settings
Submitter phabricator
Date Oct. 19, 2021, 1:21 a.m.
Message ID <differential-rev-PHID-DREV-eha53auzd7kirzsmjjeb-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/50012/
State Superseded
Headers show

Comments

phabricator - Oct. 19, 2021, 1:21 a.m.
marmoute created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  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.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D11686

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

CHANGE DETAILS




To: marmoute, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs
--- a/rust/hg-cpython/src/dirstate/item.rs
+++ b/rust/hg-cpython/src/dirstate/item.rs
@@ -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())
     }
diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs
--- a/rust/hg-core/src/dirstate/entry.rs
+++ b/rust/hg-core/src/dirstate/entry.rs
@@ -22,6 +22,8 @@ 
     pub(crate) flags: Flags,
     mode_size: Option<(u32, u32)>,
     mtime: Option<u32>,
+    fallback_exec: Option<bool>,
+    fallback_symlink: Option<bool>,
 }
 
 bitflags! {
@@ -196,6 +198,8 @@ 
             flags,
             mode_size,
             mtime,
+            fallback_exec: None,
+            fallback_symlink: None,
         }
     }
 
@@ -213,12 +217,16 @@ 
                         flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
                         mode_size: None,
                         mtime: None,
+                        fallback_exec: None,
+                        fallback_symlink: None,
                     }
                 } else if size == SIZE_NON_NORMAL {
                     Self {
                         flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
                         mode_size: None,
                         mtime: None,
+                        fallback_exec: None,
+                        fallback_symlink: None,
                     }
                 } else if mtime == MTIME_UNSET {
                     // TODO: return an error for negative values?
@@ -228,6 +236,8 @@ 
                         flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
                         mode_size: Some((mode, size)),
                         mtime: None,
+                        fallback_exec: None,
+                        fallback_symlink: None,
                     }
                 } else {
                     // TODO: return an error for negative values?
@@ -238,6 +248,8 @@ 
                         flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
                         mode_size: Some((mode, size)),
                         mtime: Some(mtime),
+                        fallback_exec: None,
+                        fallback_symlink: None,
                     }
                 }
             }
@@ -245,6 +257,8 @@ 
                 flags: Flags::WDIR_TRACKED,
                 mode_size: None,
                 mtime: None,
+                fallback_exec: None,
+                fallback_symlink: None,
             },
             EntryState::Removed => Self {
                 flags: if size == SIZE_NON_NORMAL {
@@ -257,6 +271,8 @@ 
                 },
                 mode_size: None,
                 mtime: None,
+                fallback_exec: None,
+                fallback_symlink: None,
             },
             EntryState::Merged => Self {
                 flags: Flags::WDIR_TRACKED
@@ -264,6 +280,8 @@ 
                     | Flags::P2_INFO, // might not be true because of rename ?
                 mode_size: None,
                 mtime: None,
+                fallback_exec: None,
+                fallback_symlink: None,
             },
         }
     }
@@ -421,6 +439,22 @@ 
         self.v1_mtime()
     }
 
+    pub fn get_fallback_exec(&self) -> Option<bool> {
+        self.fallback_exec
+    }
+
+    pub fn set_fallback_exec(&mut self, value: Option<bool>) {
+        self.fallback_exec = value;
+    }
+
+    pub fn get_fallback_symlink(&self) -> Option<bool> {
+        self.fallback_exec
+    }
+
+    pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
+        self.fallback_exec = value;
+    }
+
     pub fn drop_merge_data(&mut self) {
         if self.flags.contains(Flags::P2_INFO) {
             self.flags.remove(Flags::P2_INFO);
diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py
--- a/mercurial/pure/parsers.py
+++ b/mercurial/pure/parsers.py
@@ -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,79 @@ 
         return self.v1_state()
 
     @property
+    def has_fallback_exec(self):
+        """True is "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."""
+        self._fallback_exec = bool(value)
+
+    @property
+    def has_fallback_symlink(self):
+        """True is "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."""
+        self._fallback_symlink = bool(value)
+
+    @property
     def tracked(self):
         """True is the file is tracked in the working copy"""
         return self._wc_tracked
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -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):
diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/util.h
@@ -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_unrecorded_ignored = 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)
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -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},