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}, 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_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 --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/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,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 --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 @@ -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 --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 { + 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()) }