# HG changeset patch # User Pierre-Yves David # Date 2021-10-01 18:35:30 # Node ID 38488d488ec13bbc2b2a26e8e9f677f6dcdcad32 # Parent 25836b0029f54432980c85b90cfeb6a3cc5360a5 dirstate-item: change the internal storage and constructor value This should be closer to what we do need and what we can actually reliably record. In practice it means that we abandon the prospect of storing much more refined data for now. We don't have the necessary information nor code using it right now. So it seems safer to just use a clearer version of what we had so far. See the documentation changes for details. Differential Revision: https://phab.mercurial-scm.org/D11557 diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c --- a/mercurial/cext/parsers.c +++ b/mercurial/cext/parsers.c @@ -52,37 +52,32 @@ static PyObject *dirstate_item_new(PyTyp dirstateItemObject *t; int wc_tracked; int p1_tracked; - int p2_tracked; - int merged; - int clean_p1; - int clean_p2; - int possibly_dirty; + int p2_info; + int has_meaningful_data; + int has_meaningful_mtime; + int mode; + int size; + int mtime; PyObject *parentfiledata; static char *keywords_name[] = { - "wc_tracked", "p1_tracked", "p2_tracked", - "merged", "clean_p1", "clean_p2", - "possibly_dirty", "parentfiledata", NULL, + "wc_tracked", + "p1_tracked", + "p2_info", + "has_meaningful_data", + "has_meaningful_mtime", + "parentfiledata", + NULL, }; wc_tracked = 0; p1_tracked = 0; - p2_tracked = 0; - merged = 0; - clean_p1 = 0; - clean_p2 = 0; - possibly_dirty = 0; + p2_info = 0; + has_meaningful_mtime = 1; + has_meaningful_data = 1; parentfiledata = Py_None; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiiiO", keywords_name, - &wc_tracked, &p1_tracked, &p2_tracked, - &merged, &clean_p1, &clean_p2, - &possibly_dirty, &parentfiledata - - )) { - return NULL; - } - if (merged && (clean_p1 || clean_p2)) { - PyErr_SetString(PyExc_RuntimeError, - "`merged` argument incompatible with " - "`clean_p1`/`clean_p2`"); + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked, + &p2_info, &has_meaningful_data, &has_meaningful_mtime, + &parentfiledata)) { return NULL; } t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1); @@ -97,24 +92,10 @@ static PyObject *dirstate_item_new(PyTyp if (p1_tracked) { t->flags |= dirstate_flag_p1_tracked; } - if (p2_tracked) { - t->flags |= dirstate_flag_p2_tracked; - } - if (possibly_dirty) { - t->flags |= dirstate_flag_possibly_dirty; - } - if (merged) { - t->flags |= dirstate_flag_merged; + if (p2_info) { + t->flags |= dirstate_flag_p2_info; } - if (clean_p1) { - t->flags |= dirstate_flag_clean_p1; - } - if (clean_p2) { - t->flags |= dirstate_flag_clean_p2; - } - t->mode = 0; - t->size = dirstate_v1_nonnormal; - t->mtime = ambiguous_time; + if (parentfiledata != Py_None) { if (!PyTuple_CheckExact(parentfiledata)) { PyErr_SetString( @@ -122,12 +103,26 @@ static PyObject *dirstate_item_new(PyTyp "parentfiledata should be a Tuple or None"); return NULL; } - t->mode = - (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0)); - t->size = - (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1)); - t->mtime = - (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2)); + mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0)); + size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1)); + mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2)); + } else { + has_meaningful_data = 0; + has_meaningful_mtime = 0; + } + if (has_meaningful_data) { + t->flags |= dirstate_flag_has_meaningful_data; + t->mode = mode; + t->size = size; + } else { + t->mode = 0; + t->size = 0; + } + if (has_meaningful_mtime) { + t->flags |= dirstate_flag_has_meaningful_mtime; + t->mtime = mtime; + } else { + t->mtime = 0; } return (PyObject *)t; } @@ -142,12 +137,20 @@ static inline bool dirstate_item_c_track return (self->flags & dirstate_flag_wc_tracked); } +static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self) +{ + const unsigned char mask = dirstate_flag_wc_tracked | + dirstate_flag_p1_tracked | + dirstate_flag_p2_info; + return (self->flags & mask); +} + static inline bool dirstate_item_c_added(dirstateItemObject *self) { - unsigned char mask = + const unsigned char mask = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | - dirstate_flag_p2_tracked); - unsigned char target = dirstate_flag_wc_tracked; + dirstate_flag_p2_info); + const unsigned char target = dirstate_flag_wc_tracked; return (self->flags & mask) == target; } @@ -157,21 +160,21 @@ static inline bool dirstate_item_c_remov return false; } return (self->flags & - (dirstate_flag_p1_tracked | dirstate_flag_p2_tracked)); + (dirstate_flag_p1_tracked | dirstate_flag_p2_info)); } static inline bool dirstate_item_c_merged(dirstateItemObject *self) { return ((self->flags & dirstate_flag_wc_tracked) && - (self->flags & dirstate_flag_merged)); + (self->flags & dirstate_flag_p1_tracked) && + (self->flags & dirstate_flag_p2_info)); } static inline bool dirstate_item_c_from_p2(dirstateItemObject *self) { - if (!dirstate_item_c_tracked(self)) { - return false; - } - return (self->flags & dirstate_flag_clean_p2); + return ((self->flags & dirstate_flag_wc_tracked) && + !(self->flags & dirstate_flag_p1_tracked) && + (self->flags & dirstate_flag_p2_info)); } static inline char dirstate_item_c_v1_state(dirstateItemObject *self) @@ -189,29 +192,32 @@ static inline char dirstate_item_c_v1_st static inline int dirstate_item_c_v1_mode(dirstateItemObject *self) { - return self->mode; + if (self->flags & dirstate_flag_has_meaningful_data) { + return self->mode; + } else { + return 0; + } } static inline int dirstate_item_c_v1_size(dirstateItemObject *self) { - if (dirstate_item_c_removed(self) && - (self->flags & dirstate_flag_merged)) { - return dirstate_v1_nonnormal; - } else if (dirstate_item_c_removed(self) && - (self->flags & dirstate_flag_clean_p2)) { - return dirstate_v1_from_p2; + if (!(self->flags & dirstate_flag_wc_tracked) && + (self->flags & dirstate_flag_p2_info)) { + if (self->flags & dirstate_flag_p1_tracked) { + return dirstate_v1_nonnormal; + } else { + return dirstate_v1_from_p2; + } } else if (dirstate_item_c_removed(self)) { return 0; - } else if (dirstate_item_c_merged(self)) { + } else if (self->flags & dirstate_flag_p2_info) { return dirstate_v1_from_p2; } else if (dirstate_item_c_added(self)) { return dirstate_v1_nonnormal; - } else if (dirstate_item_c_from_p2(self)) { - return dirstate_v1_from_p2; - } else if (self->flags & dirstate_flag_possibly_dirty) { - return self->size; /* NON NORMAL ? */ + } else if (self->flags & dirstate_flag_has_meaningful_data) { + return self->size; } else { - return self->size; + return dirstate_v1_nonnormal; } } @@ -219,13 +225,10 @@ static inline int dirstate_item_c_v1_mti { if (dirstate_item_c_removed(self)) { return 0; - } else if (self->flags & dirstate_flag_possibly_dirty) { - return ambiguous_time; - } else if (dirstate_item_c_merged(self)) { - return ambiguous_time; - } else if (dirstate_item_c_added(self)) { - return ambiguous_time; - } else if (dirstate_item_c_from_p2(self)) { + } else if (!(self->flags & dirstate_flag_has_meaningful_mtime) || + !(self->flags & dirstate_flag_p1_tracked) || + !(self->flags & dirstate_flag_wc_tracked) || + (self->flags & dirstate_flag_p2_info)) { return ambiguous_time; } else { return self->mtime; @@ -278,58 +281,43 @@ dirstate_item_from_v1_data(char state, i if (!t) { return NULL; } + t->flags = 0; + t->mode = 0; + t->size = 0; + t->mtime = 0; if (state == 'm') { - t->flags = - (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | - dirstate_flag_p2_tracked | dirstate_flag_merged); - t->mode = 0; - t->size = dirstate_v1_from_p2; - t->mtime = ambiguous_time; + t->flags = (dirstate_flag_wc_tracked | + dirstate_flag_p1_tracked | dirstate_flag_p2_info); } else if (state == 'a') { t->flags = dirstate_flag_wc_tracked; - t->mode = 0; - t->size = dirstate_v1_nonnormal; - t->mtime = ambiguous_time; } else if (state == 'r') { - t->mode = 0; - t->size = 0; - t->mtime = 0; if (size == dirstate_v1_nonnormal) { t->flags = - (dirstate_flag_p1_tracked | - dirstate_flag_p2_tracked | dirstate_flag_merged); + dirstate_flag_p1_tracked | dirstate_flag_p2_info; } else if (size == dirstate_v1_from_p2) { - t->flags = - (dirstate_flag_p2_tracked | dirstate_flag_clean_p2); + t->flags = dirstate_flag_p2_info; } else { t->flags = dirstate_flag_p1_tracked; } } else if (state == 'n') { if (size == dirstate_v1_from_p2) { t->flags = - (dirstate_flag_wc_tracked | - dirstate_flag_p2_tracked | dirstate_flag_clean_p2); - t->mode = 0; - t->size = dirstate_v1_from_p2; - t->mtime = ambiguous_time; + dirstate_flag_wc_tracked | dirstate_flag_p2_info; } else if (size == dirstate_v1_nonnormal) { - t->flags = (dirstate_flag_wc_tracked | - dirstate_flag_p1_tracked | - dirstate_flag_possibly_dirty); - t->mode = 0; - t->size = dirstate_v1_nonnormal; - t->mtime = ambiguous_time; + t->flags = + dirstate_flag_wc_tracked | dirstate_flag_p1_tracked; } else if (mtime == ambiguous_time) { t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | - dirstate_flag_possibly_dirty); + dirstate_flag_has_meaningful_data); t->mode = mode; t->size = size; - t->mtime = 0; } else { t->flags = (dirstate_flag_wc_tracked | - dirstate_flag_p1_tracked); + dirstate_flag_p1_tracked | + dirstate_flag_has_meaningful_data | + dirstate_flag_has_meaningful_mtime); t->mode = mode; t->size = size; t->mtime = mtime; @@ -371,8 +359,8 @@ static PyObject *dirstate_item_new_added } t->flags = dirstate_flag_wc_tracked; t->mode = 0; - t->size = dirstate_v1_nonnormal; - t->mtime = ambiguous_time; + t->size = 0; + t->mtime = 0; return (PyObject *)t; }; @@ -387,10 +375,10 @@ static PyObject *dirstate_item_new_merge return NULL; } t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | - dirstate_flag_p2_tracked | dirstate_flag_merged); + dirstate_flag_p2_info); t->mode = 0; - t->size = dirstate_v1_from_p2; - t->mtime = ambiguous_time; + t->size = 0; + t->mtime = 0; return (PyObject *)t; }; @@ -406,11 +394,10 @@ static PyObject *dirstate_item_new_from_ if (!t) { return NULL; } - t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p2_tracked | - dirstate_flag_clean_p2); + t->flags = dirstate_flag_wc_tracked | dirstate_flag_p2_info; t->mode = 0; - t->size = dirstate_v1_from_p2; - t->mtime = ambiguous_time; + t->size = 0; + t->mtime = 0; return (PyObject *)t; }; @@ -426,11 +413,10 @@ static PyObject *dirstate_item_new_possi if (!t) { return NULL; } - t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | - dirstate_flag_possibly_dirty); + t->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked; t->mode = 0; - t->size = dirstate_v1_nonnormal; - t->mtime = ambiguous_time; + t->size = 0; + t->mtime = 0; return (PyObject *)t; }; @@ -462,7 +448,7 @@ static PyObject *dirstate_item_new_norma to make sure it is correct. */ static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self) { - self->flags |= dirstate_flag_possibly_dirty; + self->flags &= ~dirstate_flag_has_meaningful_mtime; Py_RETURN_NONE; } @@ -474,7 +460,9 @@ static PyObject *dirstate_item_set_clean if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) { return NULL; } - self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked; + self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | + dirstate_flag_has_meaningful_data | + dirstate_flag_has_meaningful_mtime; self->mode = mode; self->size = size; self->mtime = mtime; @@ -484,11 +472,7 @@ static PyObject *dirstate_item_set_clean static PyObject *dirstate_item_set_tracked(dirstateItemObject *self) { self->flags |= dirstate_flag_wc_tracked; - self->flags |= dirstate_flag_possibly_dirty; - /* size = None on the python size turn into size = NON_NORMAL when - * accessed. So the next line is currently required, but a some future - * clean up would be welcome. */ - self->size = dirstate_v1_nonnormal; + self->flags &= ~dirstate_flag_has_meaningful_mtime; Py_RETURN_NONE; } @@ -503,22 +487,13 @@ static PyObject *dirstate_item_set_untra static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self) { - if (dirstate_item_c_merged(self) || dirstate_item_c_from_p2(self)) { - if (dirstate_item_c_merged(self)) { - self->flags |= dirstate_flag_p1_tracked; - } else { - self->flags &= ~dirstate_flag_p1_tracked; - } - self->flags &= - ~(dirstate_flag_merged | dirstate_flag_clean_p1 | - dirstate_flag_clean_p2 | dirstate_flag_p2_tracked); - self->flags |= dirstate_flag_possibly_dirty; + if (self->flags & dirstate_flag_p2_info) { + self->flags &= ~(dirstate_flag_p2_info | + dirstate_flag_has_meaningful_data | + dirstate_flag_has_meaningful_mtime); self->mode = 0; self->mtime = 0; - /* size = None on the python size turn into size = NON_NORMAL - * when accessed. So the next line is currently required, but a - * some future clean up would be welcome. */ - self->size = dirstate_v1_nonnormal; + self->size = 0; } Py_RETURN_NONE; } @@ -624,11 +599,9 @@ static PyObject *dirstate_item_get_maybe { if (!(self->flags & dirstate_flag_wc_tracked)) { Py_RETURN_FALSE; - } else if (dirstate_item_c_added(self)) { + } else if (!(self->flags & dirstate_flag_p1_tracked)) { Py_RETURN_FALSE; - } else if (self->flags & dirstate_flag_merged) { - Py_RETURN_FALSE; - } else if (self->flags & dirstate_flag_clean_p2) { + } else if (self->flags & dirstate_flag_p2_info) { Py_RETURN_FALSE; } else { Py_RETURN_TRUE; @@ -637,10 +610,7 @@ static PyObject *dirstate_item_get_maybe static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self) { - unsigned char mask = dirstate_flag_wc_tracked | - dirstate_flag_p1_tracked | - dirstate_flag_p2_tracked; - if ((self->flags & mask) != 0) { + if (dirstate_item_c_any_tracked(self)) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h --- a/mercurial/cext/util.h +++ b/mercurial/cext/util.h @@ -33,11 +33,9 @@ typedef struct { static const unsigned char dirstate_flag_wc_tracked = 1; static const unsigned char dirstate_flag_p1_tracked = 1 << 1; -static const unsigned char dirstate_flag_p2_tracked = 1 << 2; -static const unsigned char dirstate_flag_possibly_dirty = 1 << 3; -static const unsigned char dirstate_flag_merged = 1 << 4; -static const unsigned char dirstate_flag_clean_p1 = 1 << 5; -static const unsigned char dirstate_flag_clean_p2 = 1 << 6; +static const unsigned char dirstate_flag_p2_info = 1 << 2; +static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3; +static const unsigned char dirstate_flag_has_meaningful_mtime = 1 << 4; extern PyTypeObject dirstateItemType; #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateItemType) diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -137,14 +137,7 @@ class _dirstatemapcommon(object): if entry is None: self._dirs_incr(filename) entry = DirstateItem( - p1_tracked=False, - p2_tracked=False, wc_tracked=True, - merged=False, - clean_p1=False, - clean_p2=False, - possibly_dirty=False, - parentfiledata=None, ) self._insert_entry(filename, entry) @@ -208,37 +201,20 @@ class _dirstatemapcommon(object): self._drop_entry(filename) self._dirs_decr(filename, old_entry=old_entry) return - elif merged: - pass - elif not (p1_tracked or p2_tracked) and wc_tracked: - pass # file is added, nothing special to adjust - elif (p1_tracked or p2_tracked) and not wc_tracked: - pass - elif clean_p2 and wc_tracked: - pass - elif not p1_tracked and p2_tracked and wc_tracked: - clean_p2 = True - elif possibly_dirty: - pass - elif wc_tracked: - # this is a "normal" file - if parentfiledata is None: - msg = b'failed to pass parentfiledata for a normal file: %s' - msg %= filename - raise error.ProgrammingError(msg) - else: - assert False, 'unreachable' + + p2_info = merged or clean_p2 + if merged: + assert p1_tracked + + has_meaningful_mtime = not possibly_dirty old_entry = self._map.get(filename) self._dirs_incr(filename, old_entry) entry = DirstateItem( wc_tracked=wc_tracked, p1_tracked=p1_tracked, - p2_tracked=p2_tracked, - merged=merged, - clean_p1=clean_p1, - clean_p2=clean_p2, - possibly_dirty=possibly_dirty, + p2_info=p2_info, + has_meaningful_mtime=has_meaningful_mtime, parentfiledata=parentfiledata, ) self._insert_entry(filename, entry) diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py +++ b/mercurial/pure/parsers.py @@ -53,36 +53,32 @@ class DirstateItem(object): # about file tracking - wc_tracked: is the file tracked by the working copy - p1_tracked: is the file tracked in working copy first parent - - p2_tracked: is the file tracked in working copy second parent - - # about what possible merge action related to this file - - clean_p1: merge picked the file content from p1 - - clean_p2: merge picked the file content from p2 - - merged: file gather changes from both side. + - p2_info: the file has been involved in some merge operation. Either + because it was actually merged, or because the p2 version was + ahead, or because some renamed moved it there. In either case + `hg status` will want it displayed as modified. # about the file state expected from p1 manifest: - mode: the file mode in p1 - size: the file size in p1 + These value can be set to None, which mean we don't have a meaningful value + to compare with. Either because we don't really care about them as there + `status` is known without having to look at the disk or because we don't + know these right now and a full comparison will be needed to find out if + the file is clean. + # about the file state on disk last time we saw it: - mtime: the last known clean mtime for the file. - The last three item (mode, size and mtime) can be None if no meaningful (or - trusted) value exists. - + This value can be set to None if no cachable state exist. Either because we + do not care (see previous section) or because we could not cache something + yet. """ _wc_tracked = attr.ib() _p1_tracked = attr.ib() - _p2_tracked = attr.ib() - # the three item above should probably be combined - # - # However it is unclear if they properly cover some of the most advanced - # merge case. So we should probably wait on this to be settled. - _merged = attr.ib() - _clean_p1 = attr.ib() - _clean_p2 = attr.ib() - _possibly_dirty = attr.ib() + _p2_info = attr.ib() _mode = attr.ib() _size = attr.ib() _mtime = attr.ib() @@ -91,32 +87,25 @@ class DirstateItem(object): self, wc_tracked=False, p1_tracked=False, - p2_tracked=False, - merged=False, - clean_p1=False, - clean_p2=False, - possibly_dirty=False, + p2_info=False, + has_meaningful_data=True, + has_meaningful_mtime=True, parentfiledata=None, ): - if merged and (clean_p1 or clean_p2): - msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`' - raise error.ProgrammingError(msg) - - assert not (merged and not p1_tracked) self._wc_tracked = wc_tracked self._p1_tracked = p1_tracked - self._p2_tracked = p2_tracked - self._merged = merged - self._clean_p1 = clean_p1 - self._clean_p2 = clean_p2 - self._possibly_dirty = possibly_dirty + self._p2_info = p2_info + + self._mode = None + self._size = None + self._mtime = None if parentfiledata is None: - self._mode = None - self._size = None - self._mtime = None - else: + has_meaningful_mtime = False + has_meaningful_data = False + if has_meaningful_data: self._mode = parentfiledata[0] self._size = parentfiledata[1] + if has_meaningful_mtime: self._mtime = parentfiledata[2] @classmethod @@ -125,11 +114,7 @@ class DirstateItem(object): Should eventually be removed """ - instance = cls() - instance._wc_tracked = True - instance._p1_tracked = False - instance._p2_tracked = False - return instance + return cls(wc_tracked=True) @classmethod def new_merged(cls): @@ -137,12 +122,7 @@ class DirstateItem(object): Should eventually be removed """ - instance = cls() - instance._wc_tracked = True - instance._p1_tracked = True # might not be True because of rename ? - instance._p2_tracked = True # might not be True because of rename ? - instance._merged = True - return instance + return cls(wc_tracked=True, p1_tracked=True, p2_info=True) @classmethod def new_from_p2(cls): @@ -150,12 +130,7 @@ class DirstateItem(object): Should eventually be removed """ - instance = cls() - instance._wc_tracked = True - instance._p1_tracked = False # might actually be True - instance._p2_tracked = True - instance._clean_p2 = True - return instance + return cls(wc_tracked=True, p2_info=True) @classmethod def new_possibly_dirty(cls): @@ -163,11 +138,7 @@ class DirstateItem(object): Should eventually be removed """ - instance = cls() - instance._wc_tracked = True - instance._p1_tracked = True - instance._possibly_dirty = True - return instance + return cls(wc_tracked=True, p1_tracked=True) @classmethod def new_normal(cls, mode, size, mtime): @@ -177,13 +148,11 @@ class DirstateItem(object): """ assert size != FROM_P2 assert size != NONNORMAL - instance = cls() - instance._wc_tracked = True - instance._p1_tracked = True - instance._mode = mode - instance._size = size - instance._mtime = mtime - return instance + return cls( + wc_tracked=True, + p1_tracked=True, + parentfiledata=(mode, size, mtime), + ) @classmethod def from_v1_data(cls, state, mode, size, mtime): @@ -197,25 +166,16 @@ class DirstateItem(object): elif state == b'a': return cls.new_added() elif state == b'r': - instance = cls() - instance._wc_tracked = False if size == NONNORMAL: - instance._merged = True - instance._p1_tracked = ( - True # might not be True because of rename ? - ) - instance._p2_tracked = ( - True # might not be True because of rename ? - ) + p1_tracked = True + p2_info = True elif size == FROM_P2: - instance._clean_p2 = True - instance._p1_tracked = ( - False # We actually don't know (file history) - ) - instance._p2_tracked = True + p1_tracked = False + p2_info = True else: - instance._p1_tracked = True - return instance + p1_tracked = True + p2_info = False + return cls(p1_tracked=p1_tracked, p2_info=p2_info) elif state == b'n': if size == FROM_P2: return cls.new_from_p2() @@ -224,7 +184,6 @@ class DirstateItem(object): elif mtime == AMBIGUOUS_TIME: instance = cls.new_normal(mode, size, 42) instance._mtime = None - instance._possibly_dirty = True return instance else: return cls.new_normal(mode, size, mtime) @@ -237,7 +196,7 @@ class DirstateItem(object): This means the next status call will have to actually check its content to make sure it is correct. """ - self._possibly_dirty = True + self._mtime = None def set_clean(self, mode, size, mtime): """mark a file as "clean" cancelling potential "possibly dirty call" @@ -249,10 +208,6 @@ class DirstateItem(object): """ self._wc_tracked = True self._p1_tracked = True - self._p2_tracked = False # this might be wrong - self._merged = False - self._clean_p2 = False - self._possibly_dirty = False self._mode = mode self._size = size self._mtime = mtime @@ -263,11 +218,11 @@ class DirstateItem(object): This will ultimately be called by command like `hg add`. """ self._wc_tracked = True - # `set_tracked` is replacing various `normallookup` call. So we set - # "possibly dirty" to stay on the safe side. + # `set_tracked` is replacing various `normallookup` call. So we mark + # the files as needing lookup # # Consider dropping this in the future in favor of something less broad. - self._possibly_dirty = True + self._mtime = None def set_untracked(self): """mark a file as untracked in the working copy @@ -284,18 +239,11 @@ class DirstateItem(object): This is to be call by the dirstatemap code when the second parent is dropped """ - if not (self.merged or self.from_p2): - return - self._p1_tracked = self.merged # why is this not already properly set ? - - self._merged = False - self._clean_p1 = False - self._clean_p2 = False - self._p2_tracked = False - self._possibly_dirty = True - self._mode = None - self._size = None - self._mtime = None + if self._p2_info: + self._p2_info = False + self._mode = None + self._size = None + self._mtime = None @property def mode(self): @@ -334,23 +282,21 @@ class DirstateItem(object): @property def any_tracked(self): """True is the file is tracked anywhere (wc or parents)""" - return self._wc_tracked or self._p1_tracked or self._p2_tracked + return self._wc_tracked or self._p1_tracked or self._p2_info @property def added(self): """True if the file has been added""" - return self._wc_tracked and not (self._p1_tracked or self._p2_tracked) + return self._wc_tracked and not (self._p1_tracked or self._p2_info) @property def maybe_clean(self): """True if the file has a chance to be in the "clean" state""" if not self._wc_tracked: return False - elif self.added: + elif not self._p1_tracked: return False - elif self._merged: - return False - elif self._clean_p2: + elif self._p2_info: return False return True @@ -360,7 +306,7 @@ class DirstateItem(object): Should only be set if a merge is in progress in the dirstate """ - return self._wc_tracked and self._merged + return self._wc_tracked and self._p1_tracked and self._p2_info @property def from_p2(self): @@ -370,18 +316,16 @@ class DirstateItem(object): Should only be set if a merge is in progress in the dirstate """ - if not self._wc_tracked: - return False - return self._clean_p2 + return self._wc_tracked and (not self._p1_tracked) and self._p2_info @property def removed(self): """True if the file has been removed""" - return not self._wc_tracked and (self._p1_tracked or self._p2_tracked) + return not self._wc_tracked and (self._p1_tracked or self._p2_info) def v1_state(self): """return a "state" suitable for v1 serialization""" - if not (self._p1_tracked or self._p2_tracked or self._wc_tracked): + if not self.any_tracked: # the object has no state to record, this is -currently- # unsupported raise RuntimeError('untracked item') @@ -404,9 +348,9 @@ class DirstateItem(object): # the object has no state to record, this is -currently- # unsupported raise RuntimeError('untracked item') - elif self.removed and self._merged: + elif self.removed and self._p1_tracked and self._p2_info: return NONNORMAL - elif self.removed and self._clean_p2: + elif self.removed and self._p2_info: return FROM_P2 elif self.removed: return 0 @@ -416,8 +360,8 @@ class DirstateItem(object): return NONNORMAL elif self.from_p2: return FROM_P2 - elif self._possibly_dirty: - return self._size if self._size is not None else NONNORMAL + elif self._size is None: + return NONNORMAL else: return self._size @@ -429,16 +373,14 @@ class DirstateItem(object): raise RuntimeError('untracked item') elif self.removed: return 0 - elif self._possibly_dirty: - return AMBIGUOUS_TIME - elif self.merged: + elif self._mtime is None: return AMBIGUOUS_TIME - elif self.added: + elif self._p2_info: return AMBIGUOUS_TIME - elif self.from_p2: + elif not self._p1_tracked: return AMBIGUOUS_TIME else: - return self._mtime if self._mtime is not None else 0 + return self._mtime def need_delay(self, now): """True if the stored mtime would be ambiguous with the current time""" 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 @@ -16,21 +16,15 @@ pub enum EntryState { #[derive(Debug, PartialEq, Copy, Clone)] pub struct DirstateEntry { flags: Flags, - mode: i32, - size: i32, - mtime: i32, + mode_size: Option<(i32, i32)>, + mtime: Option, } bitflags! { - pub struct Flags: u8 { + struct Flags: u8 { const WDIR_TRACKED = 1 << 0; const P1_TRACKED = 1 << 1; - const P2_TRACKED = 1 << 2; - const POSSIBLY_DIRTY = 1 << 3; - const MERGED = 1 << 4; - const CLEAN_P1 = 1 << 5; - const CLEAN_P2 = 1 << 6; - const ENTRYLESS_TREE_NODE = 1 << 7; + const P2_INFO = 1 << 2; } } @@ -48,15 +42,19 @@ pub const SIZE_NON_NORMAL: i32 = -1; impl DirstateEntry { pub fn new( - flags: Flags, - mode_size_mtime: Option<(i32, i32, i32)>, + wdir_tracked: bool, + p1_tracked: bool, + p2_info: bool, + mode_size: Option<(i32, i32)>, + mtime: Option, ) -> Self { - let (mode, size, mtime) = - mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET)); + let mut flags = Flags::empty(); + flags.set(Flags::WDIR_TRACKED, wdir_tracked); + flags.set(Flags::P1_TRACKED, p1_tracked); + flags.set(Flags::P2_INFO, p2_info); Self { flags, - mode, - size, + mode_size, mtime, } } @@ -75,12 +73,9 @@ impl DirstateEntry { Self::new_possibly_dirty() } else if mtime == MTIME_UNSET { Self { - flags: Flags::WDIR_TRACKED - | Flags::P1_TRACKED - | Flags::POSSIBLY_DIRTY, - mode, - size, - mtime: 0, + flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, + mode_size: Some((mode, size)), + mtime: None, } } else { Self::new_normal(mode, size, mtime) @@ -89,18 +84,15 @@ impl DirstateEntry { EntryState::Added => Self::new_added(), EntryState::Removed => Self { flags: if size == SIZE_NON_NORMAL { - Flags::P1_TRACKED // might not be true because of rename ? - | Flags::P2_TRACKED // might not be true because of rename ? - | Flags::MERGED + Flags::P1_TRACKED | Flags::P2_INFO } else if size == SIZE_FROM_OTHER_PARENT { // We don’t know if P1_TRACKED should be set (file history) - Flags::P2_TRACKED | Flags::CLEAN_P2 + Flags::P2_INFO } else { Flags::P1_TRACKED }, - mode: 0, - size: 0, - mtime: 0, + mode_size: None, + mtime: None, }, EntryState::Merged => Self::new_merged(), } @@ -109,30 +101,25 @@ impl DirstateEntry { pub fn new_from_p2() -> Self { Self { // might be missing P1_TRACKED - flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2, - mode: 0, - size: SIZE_FROM_OTHER_PARENT, - mtime: MTIME_UNSET, + flags: Flags::WDIR_TRACKED | Flags::P2_INFO, + mode_size: None, + mtime: None, } } pub fn new_possibly_dirty() -> Self { Self { - flags: Flags::WDIR_TRACKED - | Flags::P1_TRACKED - | Flags::POSSIBLY_DIRTY, - mode: 0, - size: SIZE_NON_NORMAL, - mtime: MTIME_UNSET, + flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, + mode_size: None, + mtime: None, } } pub fn new_added() -> Self { Self { flags: Flags::WDIR_TRACKED, - mode: 0, - size: SIZE_NON_NORMAL, - mtime: MTIME_UNSET, + mode_size: None, + mtime: None, } } @@ -140,20 +127,17 @@ impl DirstateEntry { Self { flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED // might not be true because of rename ? - | Flags::P2_TRACKED // might not be true because of rename ? - | Flags::MERGED, - mode: 0, - size: SIZE_NON_NORMAL, - mtime: MTIME_UNSET, + | Flags::P2_INFO, // might not be true because of rename ? + mode_size: None, + mtime: None, } } pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self { Self { flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, - mode, - size, - mtime, + mode_size: Some((mode, size)), + mtime: Some(mtime), } } @@ -169,36 +153,34 @@ impl DirstateEntry { self.flags.contains(Flags::WDIR_TRACKED) } - fn tracked_in_any_parent(&self) -> bool { - self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED) + fn in_either_parent(&self) -> bool { + self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO) } pub fn removed(&self) -> bool { - self.tracked_in_any_parent() - && !self.flags.contains(Flags::WDIR_TRACKED) + self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED) } pub fn merged(&self) -> bool { - self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED) + self.flags + .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO) } pub fn added(&self) -> bool { - self.flags.contains(Flags::WDIR_TRACKED) - && !self.tracked_in_any_parent() + self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent() } pub fn from_p2(&self) -> bool { - self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2) + self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO) + && !self.flags.contains(Flags::P1_TRACKED) } pub fn maybe_clean(&self) -> bool { if !self.flags.contains(Flags::WDIR_TRACKED) { false - } else if self.added() { + } else if !self.flags.contains(Flags::P1_TRACKED) { false - } else if self.flags.contains(Flags::MERGED) { - false - } else if self.flags.contains(Flags::CLEAN_P2) { + } else if self.flags.contains(Flags::P2_INFO) { false } else { true @@ -207,11 +189,15 @@ impl DirstateEntry { pub fn any_tracked(&self) -> bool { self.flags.intersects( - Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_TRACKED, + Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, ) } - pub fn state(&self) -> EntryState { + fn v1_state(&self) -> EntryState { + if !self.any_tracked() { + // TODO: return an Option instead? + panic!("Accessing v1_state of an untracked DirstateEntry") + } if self.removed() { EntryState::Removed } else if self.merged() { @@ -223,14 +209,24 @@ impl DirstateEntry { } } - pub fn mode(&self) -> i32 { - self.mode + fn v1_mode(&self) -> i32 { + if let Some((mode, _size)) = self.mode_size { + mode + } else { + 0 + } } - pub fn size(&self) -> i32 { - if self.removed() && self.flags.contains(Flags::MERGED) { + fn v1_size(&self) -> i32 { + if !self.any_tracked() { + // TODO: return an Option instead? + panic!("Accessing v1_size of an untracked DirstateEntry") + } + if self.removed() + && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO) + { SIZE_NON_NORMAL - } else if self.removed() && self.flags.contains(Flags::CLEAN_P2) { + } else if self.removed() && self.flags.contains(Flags::P2_INFO) { SIZE_FROM_OTHER_PARENT } else if self.removed() { 0 @@ -240,87 +236,81 @@ impl DirstateEntry { SIZE_NON_NORMAL } else if self.from_p2() { SIZE_FROM_OTHER_PARENT - } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { - self.size // TODO: SIZE_NON_NORMAL ? + } else if let Some((_mode, size)) = self.mode_size { + size } else { - self.size + SIZE_NON_NORMAL } } - pub fn mtime(&self) -> i32 { + fn v1_mtime(&self) -> i32 { + if !self.any_tracked() { + // TODO: return an Option instead? + panic!("Accessing v1_mtime of an untracked DirstateEntry") + } if self.removed() { 0 - } else if self.flags.contains(Flags::POSSIBLY_DIRTY) { - MTIME_UNSET - } else if self.merged() { + } else if self.flags.contains(Flags::P2_INFO) { MTIME_UNSET - } else if self.added() { - MTIME_UNSET - } else if self.from_p2() { + } else if !self.flags.contains(Flags::P1_TRACKED) { MTIME_UNSET } else { - self.mtime + self.mtime.unwrap_or(MTIME_UNSET) } } + // TODO: return `Option`? None when `!self.any_tracked` + pub fn state(&self) -> EntryState { + self.v1_state() + } + + // TODO: return Option? + pub fn mode(&self) -> i32 { + self.v1_mode() + } + + // TODO: return Option? + pub fn size(&self) -> i32 { + self.v1_size() + } + + // TODO: return Option? + pub fn mtime(&self) -> i32 { + self.v1_mtime() + } + pub fn drop_merge_data(&mut self) { - if self.flags.contains(Flags::CLEAN_P1) - || self.flags.contains(Flags::CLEAN_P2) - || self.flags.contains(Flags::MERGED) - || self.flags.contains(Flags::P2_TRACKED) - { - if self.flags.contains(Flags::MERGED) { - self.flags.insert(Flags::P1_TRACKED); - } else { - self.flags.remove(Flags::P1_TRACKED); - } - self.flags.remove( - Flags::MERGED - | Flags::CLEAN_P1 - | Flags::CLEAN_P2 - | Flags::P2_TRACKED, - ); - self.flags.insert(Flags::POSSIBLY_DIRTY); - self.mode = 0; - self.mtime = 0; - // size = None on the python size turn into size = NON_NORMAL when - // accessed. So the next line is currently required, but a some - // future clean up would be welcome. - self.size = SIZE_NON_NORMAL; + if self.flags.contains(Flags::P2_INFO) { + self.flags.remove(Flags::P2_INFO); + self.mode_size = None; + self.mtime = None; } } pub fn set_possibly_dirty(&mut self) { - self.flags.insert(Flags::POSSIBLY_DIRTY) + self.mtime = None } pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) { self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); - self.flags.remove( - Flags::P2_TRACKED // This might be wrong - | Flags::MERGED - | Flags::CLEAN_P2 - | Flags::POSSIBLY_DIRTY, - ); - self.mode = mode; - self.size = size; - self.mtime = mtime; + self.mode_size = Some((mode, size)); + self.mtime = Some(mtime); } pub fn set_tracked(&mut self) { - self.flags - .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY); - // size = None on the python size turn into size = NON_NORMAL when - // accessed. So the next line is currently required, but a some future - // clean up would be welcome. - self.size = SIZE_NON_NORMAL; + self.flags.insert(Flags::WDIR_TRACKED); + // `set_tracked` is replacing various `normallookup` call. So we mark + // the files as needing lookup + // + // Consider dropping this in the future in favor of something less + // broad. + self.mtime = None; } pub fn set_untracked(&mut self) { self.flags.remove(Flags::WDIR_TRACKED); - self.mode = 0; - self.size = 0; - self.mtime = 0; + self.mode_size = None; + self.mtime = None; } /// Returns `(state, mode, size, mtime)` for the puprose of serialization @@ -330,7 +320,12 @@ impl DirstateEntry { /// want to not represent these cases that way in memory, but serialization /// will need to keep the same format. pub fn v1_data(&self) -> (u8, i32, i32, i32) { - (self.state().into(), self.mode(), self.size(), self.mtime()) + ( + self.v1_state().into(), + self.v1_mode(), + self.v1_size(), + self.v1_mtime(), + ) } pub(crate) fn is_from_other_parent(&self) -> bool { @@ -354,12 +349,7 @@ impl DirstateEntry { /// Returns a `(state, mode, size, mtime)` tuple as for /// `DirstateMapMethods::debug_iter`. pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { - let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) { - b' ' - } else { - self.state().into() - }; - (state, self.mode(), self.size(), self.mtime()) + (self.state().into(), self.mode(), self.size(), self.mtime()) } pub fn mtime_is_ambiguous(&self, now: i32) -> bool { @@ -378,14 +368,10 @@ impl DirstateEntry { // dirstate, forcing future 'status' calls to compare the // contents of the file if the size is the same. This prevents // mistakenly treating such files as clean. - self.clear_mtime() + self.set_possibly_dirty() } ambiguous } - - pub fn clear_mtime(&mut self) { - self.mtime = -1; - } } impl EntryState { diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -695,7 +695,7 @@ impl<'on_disk> DirstateMap<'on_disk> { path.as_ref(), )? { if let NodeData::Entry(entry) = &mut node.data { - entry.clear_mtime(); + entry.set_possibly_dirty(); } } } 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 @@ -6,7 +6,6 @@ use cpython::PyObject; use cpython::PyResult; use cpython::Python; use cpython::PythonObject; -use hg::dirstate::entry::Flags; use hg::dirstate::DirstateEntry; use hg::dirstate::EntryState; use std::cell::Cell; @@ -19,23 +18,25 @@ py_class!(pub class DirstateItem |py| { _cls, wc_tracked: bool = false, p1_tracked: bool = false, - p2_tracked: bool = false, - merged: bool = false, - clean_p1: bool = false, - clean_p2: bool = false, - possibly_dirty: bool = false, + p2_info: bool = false, + has_meaningful_data: bool = true, + has_meaningful_mtime: bool = true, parentfiledata: Option<(i32, i32, i32)> = None, ) -> PyResult { - let mut flags = Flags::empty(); - flags.set(Flags::WDIR_TRACKED, wc_tracked); - flags.set(Flags::P1_TRACKED, p1_tracked); - flags.set(Flags::P2_TRACKED, p2_tracked); - flags.set(Flags::MERGED, merged); - flags.set(Flags::CLEAN_P1, clean_p1); - flags.set(Flags::CLEAN_P2, clean_p2); - flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty); - let entry = DirstateEntry::new(flags, parentfiledata); + let mut mode_size_opt = None; + let mut mtime_opt = None; + if let Some((mode, size, mtime)) = parentfiledata { + if has_meaningful_data { + mode_size_opt = Some((mode, size)) + } + if has_meaningful_mtime { + mtime_opt = Some(mtime) + } + } + let entry = DirstateEntry::new( + wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt, + ); DirstateItem::create_instance(py, Cell::new(entry)) }