workingctx: add a way for extensions to run code at status fixup time
authorSiddharth Agarwal <sid0@fb.com>
Mon, 12 Jun 2017 13:56:50 -0700
changeset 32814 2083d1643d69
parent 32813 6d73b7ff8f92
child 32815 15e85dded933
workingctx: add a way for extensions to run code at status fixup time Some extensions like fsmonitor need to run code after dirstate.status is called, but while the wlock is held. The extensions could grab the wlock again, but that has its own peculiar race issues. For example, fsmonitor would not like its state to be written out if the dirstate has changed underneath (see issue5581 for what can go wrong in that sort of case). To protect against these sorts of issues, allow extensions to declare that they would like to run some code to run at fixup time. fsmonitor will switch to using this in the next patch in the series.
mercurial/context.py
mercurial/localrepo.py
--- a/mercurial/context.py	Mon Jun 12 13:56:43 2017 -0700
+++ b/mercurial/context.py	Mon Jun 12 13:56:50 2017 -0700
@@ -1742,7 +1742,8 @@
 
     def _poststatusfixup(self, status, fixup):
         """update dirstate for files that are actually clean"""
-        if fixup:
+        poststatus = self._repo.postdsstatus()
+        if fixup or poststatus:
             try:
                 oldid = self._repo.dirstate.identity()
 
@@ -1752,15 +1753,20 @@
                 # taking the lock
                 with self._repo.wlock(False):
                     if self._repo.dirstate.identity() == oldid:
-                        normal = self._repo.dirstate.normal
-                        for f in fixup:
-                            normal(f)
-                        # write changes out explicitly, because nesting
-                        # wlock at runtime may prevent 'wlock.release()'
-                        # after this block from doing so for subsequent
-                        # changing files
-                        tr = self._repo.currenttransaction()
-                        self._repo.dirstate.write(tr)
+                        if fixup:
+                            normal = self._repo.dirstate.normal
+                            for f in fixup:
+                                normal(f)
+                            # write changes out explicitly, because nesting
+                            # wlock at runtime may prevent 'wlock.release()'
+                            # after this block from doing so for subsequent
+                            # changing files
+                            tr = self._repo.currenttransaction()
+                            self._repo.dirstate.write(tr)
+
+                        if poststatus:
+                            for ps in poststatus:
+                                ps(self, status)
                     else:
                         # in this case, writing changes out breaks
                         # consistency, because .hg/dirstate was
@@ -1770,6 +1776,9 @@
                                             'identity mismatch\n')
             except error.LockError:
                 pass
+            finally:
+                # Even if the wlock couldn't be grabbed, clear out the list.
+                self._repo.clearpostdsstatus()
 
     def _dirstatestatus(self, match=None, ignored=False, clean=False,
                         unknown=False):
--- a/mercurial/localrepo.py	Mon Jun 12 13:56:43 2017 -0700
+++ b/mercurial/localrepo.py	Mon Jun 12 13:56:50 2017 -0700
@@ -400,6 +400,9 @@
         # - bookmark changes
         self.filteredrevcache = {}
 
+        # post-dirstate-status hooks
+        self._postdsstatus = []
+
         # generic mapping between names and nodes
         self.names = namespaces.namespaces()
 
@@ -1884,6 +1887,36 @@
         return self[node1].status(node2, match, ignored, clean, unknown,
                                   listsubrepos)
 
+    def addpostdsstatus(self, ps):
+        """Add a callback to run within the wlock, at the point at which status
+        fixups happen.
+
+        On status completion, callback(wctx, status) will be called with the
+        wlock held, unless the dirstate has changed from underneath or the wlock
+        couldn't be grabbed.
+
+        Callbacks should not capture and use a cached copy of the dirstate --
+        it might change in the meanwhile. Instead, they should access the
+        dirstate via wctx.repo().dirstate.
+
+        This list is emptied out after each status run -- extensions should
+        make sure it adds to this list each time dirstate.status is called.
+        Extensions should also make sure they don't call this for statuses
+        that don't involve the dirstate.
+        """
+
+        # The list is located here for uniqueness reasons -- it is actually
+        # managed by the workingctx, but that isn't unique per-repo.
+        self._postdsstatus.append(ps)
+
+    def postdsstatus(self):
+        """Used by workingctx to get the list of post-dirstate-status hooks."""
+        return self._postdsstatus
+
+    def clearpostdsstatus(self):
+        """Used by workingctx to clear post-dirstate-status hooks."""
+        del self._postdsstatus[:]
+
     def heads(self, start=None):
         if start is None:
             cl = self.changelog