changeset 47890:3853e6ee160d

dirstatemap: replace `removefile` by an explicit `entry.set_untracked()` All the other caller goes through `reset_state`, so we can safely have an explicit method on `DirstateItem` object. This means that all the logic to preserve the previous state (from p2, merged, etc) is now properly encapsulated within the DirstateItem. This pave the way to using different storage for these information. Differential Revision: https://phab.mercurial-scm.org/D11315
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Fri, 20 Aug 2021 11:27:01 +0200
parents 6614ab9f061d
children 37bffc450897
files mercurial/cext/parsers.c mercurial/dirstate.py mercurial/dirstatemap.py mercurial/pure/parsers.py
diffstat 4 files changed, 50 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cext/parsers.c	Fri Aug 20 11:23:52 2021 +0200
+++ b/mercurial/cext/parsers.c	Fri Aug 20 11:27:01 2021 +0200
@@ -223,6 +223,21 @@
 	Py_RETURN_NONE;
 }
 
+static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
+{
+	if (self->state == 'm') {
+		self->size = dirstate_v1_nonnormal;
+	} else if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
+		self->size = dirstate_v1_from_p2;
+	} else {
+		self->size = 0;
+	}
+	self->state = 'r';
+	self->mode = 0;
+	self->mtime = 0;
+	Py_RETURN_NONE;
+}
+
 static PyMethodDef dirstate_item_methods[] = {
     {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
      "return a \"state\" suitable for v1 serialization"},
@@ -238,6 +253,8 @@
      "build a new DirstateItem object from V1 data"},
     {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
      METH_NOARGS, "mark a file as \"possibly dirty\""},
+    {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
+     "mark a file as \"untracked\""},
     {"dm_nonnormal", (PyCFunction)dm_nonnormal, METH_NOARGS,
      "True is the entry is non-normal in the dirstatemap sense"},
     {"dm_otherparent", (PyCFunction)dm_otherparent, METH_NOARGS,
--- a/mercurial/dirstate.py	Fri Aug 20 11:23:52 2021 +0200
+++ b/mercurial/dirstate.py	Fri Aug 20 11:27:01 2021 +0200
@@ -502,7 +502,7 @@
         else:
             self._dirty = True
             self._updatedfiles.add(filename)
-            self._map.removefile(filename, in_merge=self.in_merge)
+            self._map.set_untracked(filename)
             return True
 
     @requires_no_parents_change
--- a/mercurial/dirstatemap.py	Fri Aug 20 11:23:52 2021 +0200
+++ b/mercurial/dirstatemap.py	Fri Aug 20 11:27:01 2021 +0200
@@ -303,32 +303,15 @@
         else:
             assert False, 'unreachable'
 
-    def removefile(self, f, in_merge=False):
-        """
-        Mark a file as removed in the dirstate.
-
-        The `size` parameter is used to store sentinel values that indicate
-        the file's previous state.  In the future, we should refactor this
-        to be more explicit about what that state is.
-        """
-        entry = self.get(f)
-        size = 0
-        if in_merge:
-            # XXX we should not be able to have 'm' state and 'FROM_P2' if not
-            # during a merge. So I (marmoute) am not sure we need the
-            # conditionnal at all. Adding double checking this with assert
-            # would be nice.
-            if entry is not None:
-                # backup the previous state
-                if entry.merged:  # merge
-                    size = NONNORMAL
-                elif entry.from_p2:
-                    size = FROM_P2
-                    self.otherparentset.add(f)
-        if entry is not None and not (entry.merged or entry.from_p2):
+    def set_untracked(self, f):
+        """Mark a file as no longer tracked in the dirstate map"""
+        entry = self[f]
+        self._dirs_decr(f, old_entry=entry, remove_variant=True)
+        if entry.from_p2:
+            self.otherparentset.add(f)
+        elif not entry.merged:
             self.copymap.pop(f, None)
-        self._dirs_decr(f, old_entry=entry, remove_variant=True)
-        self._map[f] = DirstateItem(b'r', 0, size, 0)
+        entry.set_untracked()
         self.nonnormalset.add(f)
 
     def dropfile(self, f):
@@ -664,6 +647,14 @@
             else:
                 assert False, 'unreachable'
 
+        def set_untracked(self, f):
+            """Mark a file as no longer tracked in the dirstate map"""
+            # in merge is only trigger more logic, so it "fine" to pass it.
+            #
+            # the inner rust dirstate map code need to be adjusted once the API
+            # for dirstate/dirstatemap/DirstateItem is a bit more settled
+            self._rustmap.removefile(f, in_merge=True)
+
         def removefile(self, *args, **kwargs):
             return self._rustmap.removefile(*args, **kwargs)
 
--- a/mercurial/pure/parsers.py	Fri Aug 20 11:23:52 2021 +0200
+++ b/mercurial/pure/parsers.py	Fri Aug 20 11:27:01 2021 +0200
@@ -89,6 +89,22 @@
         """
         self._mtime = AMBIGUOUS_TIME
 
+    def set_untracked(self):
+        """mark a file as untracked in the working copy
+
+        This will ultimately be called by command like `hg remove`.
+        """
+        # backup the previous state (useful for merge)
+        size = 0
+        if self.merged:  # merge
+            size = NONNORMAL
+        elif self.from_p2:
+            size = FROM_P2
+        self._state = b'r'
+        self._mode = 0
+        self._size = size
+        self._mtime = 0
+
     def __getitem__(self, idx):
         if idx == 0 or idx == -4:
             msg = b"do not use item[x], use item.state"