dirstate-item: keep the full information in memory (for pure form)
authorPierre-Yves David <pierre-yves.david@octobus.net>
Fri, 27 Aug 2021 15:10:21 +0200
changeset 47927 b81f52ca8075
parent 47926 05f2be3affe3
child 47928 f1033c2d9b66
dirstate-item: keep the full information in memory (for pure form) This changeset is finally reaching the area where we have been headed this whole time. Since all implementation details are contained inside de DirstateItem logic, we can change the way it is implemented. So we store the information that are passed to the object and use them to dynamically compute the "legacy" value. For now we only do this for the Pure implementation, as this is a good demonstration for the logic is fully insulated. The next step will be the update the C code too, to implement the various method with the new attribute (instead of the legacy one) and then start seriously thinking about the parameters we feed into DirstateItem. Differential Revision: https://phab.mercurial-scm.org/D11364
mercurial/pure/parsers.py
--- a/mercurial/pure/parsers.py	Thu Aug 26 16:50:14 2021 +0200
+++ b/mercurial/pure/parsers.py	Fri Aug 27 15:10:21 2021 +0200
@@ -56,7 +56,17 @@
     - mtime,
     """
 
-    _state = attr.ib()
+    _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()
     _mode = attr.ib()
     _size = attr.ib()
     _mtime = attr.ib()
@@ -76,47 +86,21 @@
             msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
             raise error.ProgrammingError(msg)
 
-        self._state = None
-        self._mode = 0
-        self._size = NONNORMAL
-        self._mtime = AMBIGUOUS_TIME
-        if not (p1_tracked or p2_tracked or wc_tracked):
-            pass  # the object has no state to record
-        elif merged:
-            self._state = b'm'
-            self._size = FROM_P2
-            self._mtime = AMBIGUOUS_TIME
-        elif not (p1_tracked or p2_tracked) and wc_tracked:
-            self._state = b'a'
-            self._size = NONNORMAL
-            self._mtime = AMBIGUOUS_TIME
-        elif (p1_tracked or p2_tracked) and not wc_tracked:
-            self._state = b'r'
-            self._size = 0
-            self._mtime = 0
-        elif clean_p2 and wc_tracked:
-            self._state = b'n'
-            self._size = FROM_P2
-            self._mtime = AMBIGUOUS_TIME
-        elif not p1_tracked and p2_tracked and wc_tracked:
-            self._state = b'n'
-            self._size = FROM_P2
-            self._mtime = AMBIGUOUS_TIME
-        elif possibly_dirty:
-            self._state = b'n'
-            self._size = NONNORMAL
-            self._mtime = AMBIGUOUS_TIME
-        elif wc_tracked:
-            # this is a "normal" file
-            if parentfiledata is None:
-                msg = b'failed to pass parentfiledata for a normal file'
-                raise error.ProgrammingError(msg)
-            self._state = b'n'
+        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
+        if parentfiledata is None:
+            self._mode = None
+            self._size = None
+            self._mtime = None
+        else:
             self._mode = parentfiledata[0]
             self._size = parentfiledata[1]
             self._mtime = parentfiledata[2]
-        else:
-            assert False, 'unreachable'
 
     @classmethod
     def new_added(cls):
@@ -125,10 +109,9 @@
         Should eventually be removed
         """
         instance = cls()
-        instance._state = b'a'
-        instance._mode = 0
-        instance._size = NONNORMAL
-        instance._mtime = AMBIGUOUS_TIME
+        instance._wc_tracked = True
+        instance._p1_tracked = False
+        instance._p2_tracked = False
         return instance
 
     @classmethod
@@ -138,10 +121,10 @@
         Should eventually be removed
         """
         instance = cls()
-        instance._state = b'm'
-        instance._mode = 0
-        instance._size = FROM_P2
-        instance._mtime = AMBIGUOUS_TIME
+        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
 
     @classmethod
@@ -151,10 +134,10 @@
         Should eventually be removed
         """
         instance = cls()
-        instance._state = b'n'
-        instance._mode = 0
-        instance._size = FROM_P2
-        instance._mtime = AMBIGUOUS_TIME
+        instance._wc_tracked = True
+        instance._p1_tracked = False  # might actually be True
+        instance._p2_tracked = True
+        instance._clean_p2 = True
         return instance
 
     @classmethod
@@ -164,10 +147,9 @@
         Should eventually be removed
         """
         instance = cls()
-        instance._state = b'n'
-        instance._mode = 0
-        instance._size = NONNORMAL
-        instance._mtime = AMBIGUOUS_TIME
+        instance._wc_tracked = True
+        instance._p1_tracked = True
+        instance._possibly_dirty = True
         return instance
 
     @classmethod
@@ -179,7 +161,8 @@
         assert size != FROM_P2
         assert size != NONNORMAL
         instance = cls()
-        instance._state = b'n'
+        instance._wc_tracked = True
+        instance._p1_tracked = True
         instance._mode = mode
         instance._size = size
         instance._mtime = mtime
@@ -192,12 +175,44 @@
         Since the dirstate-v1 format is frozen, the signature of this function
         is not expected to change, unlike the __init__ one.
         """
-        instance = cls()
-        instance._state = state
-        instance._mode = mode
-        instance._size = size
-        instance._mtime = mtime
-        return instance
+        if state == b'm':
+            return cls.new_merged()
+        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 ?
+                )
+            elif size == FROM_P2:
+                instance._clean_p2 = True
+                instance._p1_tracked = (
+                    False  # We actually don't know (file history)
+                )
+                instance._p2_tracked = True
+            else:
+                instance._p1_tracked = True
+            return instance
+        elif state == b'n':
+            if size == FROM_P2:
+                return cls.new_from_p2()
+            elif size == NONNORMAL:
+                return cls.new_possibly_dirty()
+            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)
+        else:
+            raise RuntimeError(b'unknown state: %s' % state)
 
     def set_possibly_dirty(self):
         """Mark a file as "possibly dirty"
@@ -205,7 +220,7 @@
         This means the next status call will have to actually check its content
         to make sure it is correct.
         """
-        self._mtime = AMBIGUOUS_TIME
+        self._possibly_dirty = True
 
     def set_untracked(self):
         """mark a file as untracked in the working copy
@@ -213,15 +228,10 @@
         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
+        self._wc_tracked = False
+        self._mode = None
+        self._size = None
+        self._mtime = None
 
     @property
     def mode(self):
@@ -319,19 +329,89 @@
 
     def v1_state(self):
         """return a "state" suitable for v1 serialization"""
-        return self._state
+        if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
+            # the object has no state to record, this is -currently-
+            # unsupported
+            raise RuntimeError('untracked item')
+        elif not self._wc_tracked:
+            return b'r'
+        elif self._merged:
+            return b'm'
+        elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
+            return b'a'
+        elif self._clean_p2 and self._wc_tracked:
+            return b'n'
+        elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
+            return b'n'
+        elif self._possibly_dirty:
+            return b'n'
+        elif self._wc_tracked:
+            return b'n'
+        else:
+            raise RuntimeError('unreachable')
 
     def v1_mode(self):
         """return a "mode" suitable for v1 serialization"""
-        return self._mode
+        return self._mode if self._mode is not None else 0
 
     def v1_size(self):
         """return a "size" suitable for v1 serialization"""
-        return self._size
+        if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
+            # the object has no state to record, this is -currently-
+            # unsupported
+            raise RuntimeError('untracked item')
+        elif not self._wc_tracked:
+            # File was deleted
+            if self._merged:
+                return NONNORMAL
+            elif self._clean_p2:
+                return FROM_P2
+            else:
+                return 0
+        elif self._merged:
+            return FROM_P2
+        elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
+            # Added
+            return NONNORMAL
+        elif self._clean_p2 and self._wc_tracked:
+            return FROM_P2
+        elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
+            return FROM_P2
+        elif self._possibly_dirty:
+            if self._size is None:
+                return NONNORMAL
+            else:
+                return self._size
+        elif self._wc_tracked:
+            return self._size
+        else:
+            raise RuntimeError('unreachable')
 
     def v1_mtime(self):
         """return a "mtime" suitable for v1 serialization"""
-        return self._mtime
+        if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
+            # the object has no state to record, this is -currently-
+            # unsupported
+            raise RuntimeError('untracked item')
+        elif not self._wc_tracked:
+            return 0
+        elif self._possibly_dirty:
+            return AMBIGUOUS_TIME
+        elif self._merged:
+            return AMBIGUOUS_TIME
+        elif not (self._p1_tracked or self._p2_tracked) and self._wc_tracked:
+            return AMBIGUOUS_TIME
+        elif self._clean_p2 and self._wc_tracked:
+            return AMBIGUOUS_TIME
+        elif not self._p1_tracked and self._p2_tracked and self._wc_tracked:
+            return AMBIGUOUS_TIME
+        elif self._wc_tracked:
+            if self._mtime is None:
+                return 0
+            else:
+                return self._mtime
+        else:
+            raise RuntimeError('unreachable')
 
     def need_delay(self, now):
         """True if the stored mtime would be ambiguous with the current time"""