localrepo: "blindly" do a dirstate backup at the end of the transaction
authorPierre-Yves David <pierre-yves.david@octobus.net>
Thu, 16 Feb 2023 11:42:43 +0100
changeset 50084 5b9c3ae807c8
parent 50083 a28cedb26139
child 50085 ff12f42415f5
localrepo: "blindly" do a dirstate backup at the end of the transaction Having the file backup mechanism dealing with file backup as benefit. So lets move closer to that. The fact `hg rollback` even needs this is sad. I hope to have the time to implement one of the alternative soon.
mercurial/dirstate.py
mercurial/localrepo.py
--- a/mercurial/dirstate.py	Thu Feb 16 17:12:21 2023 +0100
+++ b/mercurial/dirstate.py	Thu Feb 16 11:42:43 2023 +0100
@@ -1615,6 +1615,22 @@
         else:
             return self._filename
 
+    def all_file_names(self):
+        """list all filename currently used by this dirstate
+
+        This is only used to do `hg rollback` related backup in the transaction
+        """
+        if not self._opener.exists(self._filename):
+            # no data every written to disk yet
+            return ()
+        elif self._use_dirstate_v2:
+            return (
+                self._filename,
+                self._map.docket.data_filename(),
+            )
+        else:
+            return (self._filename,)
+
     def data_backup_filename(self, backupname):
         if not self._use_dirstate_v2:
             return None
--- a/mercurial/localrepo.py	Thu Feb 16 17:12:21 2023 +0100
+++ b/mercurial/localrepo.py	Thu Feb 16 11:42:43 2023 +0100
@@ -2647,6 +2647,32 @@
         tr.addpostclose(b'refresh-filecachestats', self._refreshfilecachestats)
         self._transref = weakref.ref(tr)
         scmutil.registersummarycallback(self, tr, desc)
+        # This only exist to deal with the need of rollback to have viable
+        # parents at the end of the operation. So backup viable parents at the
+        # time of this operation.
+        #
+        # We only do it when the `wlock` is taken, otherwise other might be
+        # altering the dirstate under us.
+        #
+        # This is really not a great way to do this (first, because we cannot
+        # always do it). There are more viable alternative that exists
+        #
+        # - backing only the working copy parent in a dedicated files and doing
+        #   a clean "keep-update" to them on `hg rollback`.
+        #
+        # - slightly changing the behavior an applying a logic similar to "hg
+        # strip" to pick a working copy destination on `hg rollback`
+        if self.currentwlock() is not None:
+            ds = self.dirstate
+
+            def backup_dirstate(tr):
+                for f in ds.all_file_names():
+                    # hardlink backup is okay because `dirstate` is always
+                    # atomically written and possible data file are append only
+                    # and resistant to trailing data.
+                    tr.addbackup(f, hardlink=True, location=b'plain')
+
+            tr.addvalidator(b'dirstate-backup', backup_dirstate)
         return tr
 
     def _journalfiles(self):