diff mercurial/changelog.py @ 51106:d83d788590a8

changelog-delay: move the delay/divert logic inside the (inner) revlog Instead of hacking throught the vfs/opener, we implement the delay/divert logic inside the `_InnerRevlog` and `randomaccessfile` object. This will allow to an alternative implementation of the `_InnerRevlog` that does not need to use Python details. As a result, the new implementation can use the transaction less agressively and avoid some extra output since no data had been written yet. That seems like a good side effect.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 24 Oct 2023 11:08:49 +0200
parents 1c0f3994d733
children dcaa2df1f688
line wrap: on
line diff
--- a/mercurial/changelog.py	Thu Oct 26 05:37:37 2023 +0200
+++ b/mercurial/changelog.py	Tue Oct 24 11:08:49 2023 +0200
@@ -27,7 +27,6 @@
 from .revlogutils import (
     constants as revlog_constants,
     flagutil,
-    randomaccessfile,
 )
 
 _defaultextra = {b'branch': b'default'}
@@ -92,38 +91,6 @@
     return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
 
 
-class _divertopener:
-    def __init__(self, opener, target):
-        self._opener = opener
-        self._target = target
-
-    def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
-        if name != self._target:
-            return self._opener(name, mode, **kwargs)
-        return self._opener(name + b".a", mode, **kwargs)
-
-    def __getattr__(self, attr):
-        return getattr(self._opener, attr)
-
-
-class _delayopener:
-    """build an opener that stores chunks in 'buf' instead of 'target'"""
-
-    def __init__(self, opener, target, buf):
-        self._opener = opener
-        self._target = target
-        self._buf = buf
-
-    def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
-        if name != self._target:
-            return self._opener(name, mode, **kwargs)
-        assert not kwargs
-        return randomaccessfile.appender(self._opener, name, mode, self._buf)
-
-    def __getattr__(self, attr):
-        return getattr(self._opener, attr)
-
-
 @attr.s
 class _changelogrevision:
     # Extensions might modify _defaultextra, so let the constructor below pass
@@ -354,10 +321,7 @@
         # chains.
         self._storedeltachains = False
 
-        self._realopener = opener
-        self._delayed = False
-        self._delaybuf = None
-        self._divert = False
+        self._v2_delayed = False
         self._filteredrevs = frozenset()
         self._filteredrevs_hashcache = {}
         self._copiesstorage = opener.options.get(b'copies-storage')
@@ -374,90 +338,47 @@
         self._filteredrevs_hashcache = {}
 
     def _write_docket(self, tr):
-        if not self.is_delaying:
+        if not self._v2_delayed:
             super(changelog, self)._write_docket(tr)
 
-    @property
-    def is_delaying(self):
-        return self._delayed
-
     def delayupdate(self, tr):
         """delay visibility of index updates to other readers"""
         assert not self._inner.is_open
-        if self._docket is None and not self.is_delaying:
-            if len(self) == 0:
-                self._divert = True
-                if self._realopener.exists(self._indexfile + b'.a'):
-                    self._realopener.unlink(self._indexfile + b'.a')
-                self.opener = _divertopener(self._realopener, self._indexfile)
-            else:
-                self._delaybuf = []
-                self.opener = _delayopener(
-                    self._realopener, self._indexfile, self._delaybuf
-                )
-            self._inner.opener = self.opener
-            self._inner._segmentfile.opener = self.opener
-            self._inner._segmentfile_sidedata.opener = self.opener
-        self._delayed = True
+        if self._docket is not None:
+            self._v2_delayed = True
+        else:
+            new_index = self._inner.delay()
+            if new_index is not None:
+                self._indexfile = new_index
+                tr.registertmp(new_index)
         tr.addpending(b'cl-%i' % id(self), self._writepending)
         tr.addfinalize(b'cl-%i' % id(self), self._finalize)
 
     def _finalize(self, tr):
         """finalize index updates"""
         assert not self._inner.is_open
-        self._delayed = False
-        self.opener = self._realopener
-        self._inner.opener = self.opener
-        self._inner._segmentfile.opener = self.opener
-        self._inner._segmentfile_sidedata.opener = self.opener
-        # move redirected index data back into place
         if self._docket is not None:
-            self._write_docket(tr)
-        elif self._divert:
-            assert not self._delaybuf
-            tmpname = self._indexfile + b".a"
-            nfile = self.opener.open(tmpname)
-            nfile.close()
-            self.opener.rename(tmpname, self._indexfile, checkambig=True)
-        elif self._delaybuf:
-            fp = self.opener(self._indexfile, b'a', checkambig=True)
-            fp.write(b"".join(self._delaybuf))
-            fp.close()
-            self._delaybuf = None
-        self._divert = False
-        # split when we're done
-        self._enforceinlinesize(tr, side_write=False)
+            self._docket.write(tr)
+            self._v2_delayed = False
+        else:
+            new_index_file = self._inner.finalize_pending()
+            self._indexfile = new_index_file
+            # split when we're done
+            self._enforceinlinesize(tr, side_write=False)
 
     def _writepending(self, tr):
         """create a file containing the unfinalized state for
         pretxnchangegroup"""
         assert not self._inner.is_open
         if self._docket:
-            return self._docket.write(tr, pending=True)
-        if self._delaybuf:
-            # make a temporary copy of the index
-            fp1 = self._realopener(self._indexfile)
-            pendingfilename = self._indexfile + b".a"
-            # register as a temp file to ensure cleanup on failure
-            tr.registertmp(pendingfilename)
-            # write existing data
-            fp2 = self._realopener(pendingfilename, b"w")
-            fp2.write(fp1.read())
-            # add pending data
-            fp2.write(b"".join(self._delaybuf))
-            fp2.close()
-            # switch modes so finalize can simply rename
-            self._delaybuf = None
-            self._divert = True
-            self.opener = _divertopener(self._realopener, self._indexfile)
-            self._inner.opener = self.opener
-            self._inner._segmentfile.opener = self.opener
-            self._inner._segmentfile_sidedata.opener = self.opener
-
-        if self._divert:
-            return True
-
-        return False
+            any_pending = self._docket.write(tr, pending=True)
+            self._v2_delayed = False
+        else:
+            new_index, any_pending = self._inner.write_pending()
+            if new_index is not None:
+                self._indexfile = new_index
+                tr.registertmp(new_index)
+        return any_pending
 
     def _enforceinlinesize(self, tr, side_write=True):
         if not self.is_delaying: