diff mercurial/revlogutils/docket.py @ 47241:2219853a1503

revlogv2: track pending write in the docket and expose it to hooks The docket is now able to write pending data. We could have used a distinct intermediate files, however keeping everything in the same file will make it simpler to keep track of the various involved files if necessary. However it might prove more complicated for streaming clone. This will be dealt with later. Note that we lifted the stderr redirection in the test since we no longer suffer from "unkown working directory parent" message. Differential Revision: https://phab.mercurial-scm.org/D10631
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Mon, 03 May 2021 12:35:25 +0200
parents 6597255a4f94
children 4abd474a10af
line wrap: on
line diff
--- a/mercurial/revlogutils/docket.py	Mon May 03 12:35:14 2021 +0200
+++ b/mercurial/revlogutils/docket.py	Mon May 03 12:35:25 2021 +0200
@@ -19,6 +19,10 @@
 
 import struct
 
+from .. import (
+    error,
+)
+
 from . import (
     constants,
 )
@@ -29,19 +33,35 @@
 #          |   This is mandatory as docket must be compatible with the previous
 #          |   revlog index header.
 # * 8 bytes: size of index data
-S_HEADER = struct.Struct(constants.INDEX_HEADER.format + 'L')
+# * 8 bytes: pending size of index data
+S_HEADER = struct.Struct(constants.INDEX_HEADER.format + 'LL')
 
 
 class RevlogDocket(object):
     """metadata associated with revlog"""
 
-    def __init__(self, revlog, version_header=None, index_end=0):
+    def __init__(
+        self,
+        revlog,
+        use_pending=False,
+        version_header=None,
+        index_end=0,
+        pending_index_end=0,
+    ):
         self._version_header = version_header
+        self._read_only = bool(use_pending)
         self._dirty = False
         self._radix = revlog.radix
         self._path = revlog._docket_file
         self._opener = revlog.opener
-        self._index_end = index_end
+        # this assert should be True as long as we have a single index filename
+        assert index_end <= pending_index_end
+        self._initial_index_end = index_end
+        self._pending_index_end = pending_index_end
+        if use_pending:
+            self._index_end = self._pending_index_end
+        else:
+            self._index_end = self._initial_index_end
 
     def index_filepath(self):
         """file path to the current index file associated to this docket"""
@@ -58,22 +78,38 @@
             self._index_end = new_size
             self._dirty = True
 
-    def write(self, transaction, stripping=False):
+    def write(self, transaction, pending=False, stripping=False):
         """write the modification of disk if any
 
         This make the new content visible to all process"""
-        if self._dirty:
+        if not self._dirty:
+            return False
+        else:
+            if self._read_only:
+                msg = b'writing read-only docket: %s'
+                msg %= self._path
+                raise error.ProgrammingError(msg)
             if not stripping:
                 # XXX we could, leverage the docket while stripping. However it
                 # is not powerfull enough at the time of this comment
                 transaction.addbackup(self._path, location=b'store')
             with self._opener(self._path, mode=b'w', atomictemp=True) as f:
-                f.write(self._serialize())
-            self._dirty = False
+                f.write(self._serialize(pending=pending))
+            # if pending we still need to the write final data eventually
+            self._dirty = pending
+            return True
 
-    def _serialize(self):
+    def _serialize(self, pending=False):
+        if pending:
+            official_index_end = self._initial_index_end
+        else:
+            official_index_end = self._index_end
+
+        # this assert should be True as long as we have a single index filename
+        assert official_index_end <= self._index_end
         data = (
             self._version_header,
+            official_index_end,
             self._index_end,
         )
         return S_HEADER.pack(*data)
@@ -88,13 +124,15 @@
     return docket
 
 
-def parse_docket(revlog, data):
+def parse_docket(revlog, data, use_pending=False):
     """given some docket data return a docket object for the given revlog"""
     header = S_HEADER.unpack(data[: S_HEADER.size])
-    version_header, index_size = header
+    version_header, index_size, pending_index_size = header
     docket = RevlogDocket(
         revlog,
+        use_pending=use_pending,
         version_header=version_header,
         index_end=index_size,
+        pending_index_end=pending_index_size,
     )
     return docket