diff mercurial/transaction.py @ 28830:a5009789960c

transaction: allow running file generators after finalizers Previously, transaction.close would run the file generators before running the finalizers (see the list below for what is in each). Since file generators contain the bookmarks and the dirstate, this meant we made the dirstate and bookmarks visible to external readers before we actually wrote the commits into the changelog, which could result in missing bookmarks and missing working copy parents (especially on servers with high commit throughput, since pulls might fail to see certain bookmarks in this situation). By moving the changelog writing to be before the bookmark/dirstate writing, we ensure the commits are present before they are referenced. This implementation allows certain file generators to be after the finalizers. We didn't want to move all of the generators, since it's important that things like phases actually run before the finalizers (otherwise you could expose commits as public when they really shouldn't be). For reference, file generators currently consist of: bookmarks, dirstate, and phases. Finalizers currently consist of: changelog, revbranchcache, and fncache.
author Durham Goode <durham@fb.com>
date Thu, 07 Apr 2016 14:10:49 -0700
parents 24361fb68cba
children 14e683d6b273
line wrap: on
line diff
--- a/mercurial/transaction.py	Thu Apr 07 11:11:55 2016 +0000
+++ b/mercurial/transaction.py	Thu Apr 07 14:10:49 2016 -0700
@@ -23,6 +23,19 @@
 
 version = 2
 
+# These are the file generators that should only be executed after the
+# finalizers are done, since they rely on the output of the finalizers (like
+# the changelog having been written).
+postfinalizegenerators = set([
+    'bookmarks',
+    'dirstate'
+])
+
+class GenerationGroup(object):
+    ALL='all'
+    PREFINALIZE='prefinalize'
+    POSTFINALIZE='postfinalize'
+
 def active(func):
     def _active(self, *args, **kwds):
         if self.count == 0:
@@ -276,12 +289,19 @@
         # but for bookmarks that are handled outside this mechanism.
         self._filegenerators[genid] = (order, filenames, genfunc, location)
 
-    def _generatefiles(self, suffix=''):
+    def _generatefiles(self, suffix='', group=GenerationGroup.ALL):
         # write files registered for generation
         any = False
-        for entry in sorted(self._filegenerators.values()):
+        for id, entry in sorted(self._filegenerators.iteritems()):
             any = True
             order, filenames, genfunc, location = entry
+
+            # for generation at closing, check if it's before or after finalize
+            postfinalize = group == GenerationGroup.POSTFINALIZE
+            if (group != GenerationGroup.ALL and
+                (id in postfinalizegenerators) != (postfinalize)):
+                continue
+
             vfs = self._vfsmap[location]
             files = []
             try:
@@ -407,10 +427,11 @@
         '''commit the transaction'''
         if self.count == 1:
             self.validator(self)  # will raise exception if needed
-            self._generatefiles()
+            self._generatefiles(group=GenerationGroup.PREFINALIZE)
             categories = sorted(self._finalizecallback)
             for cat in categories:
                 self._finalizecallback[cat](self)
+            self._generatefiles(group=GenerationGroup.POSTFINALIZE)
 
         self.count -= 1
         if self.count != 0: