# HG changeset patch # User Pierre-Yves David # Date 1634580135 -7200 # Node ID 602c8e8411f55814a1d5b6f728c2f5ca792ab421 # Parent dfc5a505ddc5e9e8eaebdcca9b9808f0fd2e6c20 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 diff -r dfc5a505ddc5 -r 602c8e8411f5 mercurial/cext/parsers.c --- 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}, diff -r dfc5a505ddc5 -r 602c8e8411f5 mercurial/cext/util.h --- 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) diff -r dfc5a505ddc5 -r 602c8e8411f5 mercurial/dirstate.py --- 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): diff -r dfc5a505ddc5 -r 602c8e8411f5 mercurial/pure/parsers.py --- 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 diff -r dfc5a505ddc5 -r 602c8e8411f5 rust/hg-core/src/dirstate/entry.rs --- 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 { + 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) { + 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 { + 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) { + 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); diff -r dfc5a505ddc5 -r 602c8e8411f5 rust/hg-cpython/src/dirstate/item.rs --- 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 { + match self.entry(py).get().get_fallback_exec() { + Some(_) => Ok(true), + None => Ok(false), + } + } + + @property + def fallback_exec(&self) -> PyResult> { + 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) -> 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 { + match self.entry(py).get().get_fallback_symlink() { + Some(_) => Ok(true), + None => Ok(false), + } + } + + @property + def fallback_symlink(&self) -> PyResult> { + 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) -> 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 { Ok(self.entry(py).get().tracked()) }