[PATCH 1 of 4 v5] revlog: add ability to apply transforms on flags

Remi Chaintron remi at fb.com
Wed Dec 14 06:58:07 EST 2016


# HG changeset patch
# User Remi Chaintron <remi at fb.com>
# Date 1481716603 0
#      Wed Dec 14 11:56:43 2016 +0000
# Branch stable
# Node ID 7599256f2800bbacc3b478ac8d2c4a5746eb4cd1
# Parent  d34c04da52a8f2eb61b51bb0aa9cedf1e4b43474
revlog: add ability to apply transforms on flags

Add a mechanism for extensions/subclasses of revlog to register transforms on
read/write operations on revlog, based on revision flags.

Due to some operations being non-commutative, transforms applied in a specific
order when writing the contents of the revlog need to be applied on the reverse
order when reading.
In order to allow the composition of such operation, the 'processflags' method
applies transforms on flags in the order defined by REVIDX_FLAGS_ORDER.

Extensions can use the `localrepository.registerflagtransform()` method to
register default transforms on flags in `reposetup()` in order to ensure their
transform will not override/be overridden by other extensions' transforms.

diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -147,7 +147,9 @@
             delta = self._chunk(chain.pop())
             text = mdiff.patches(text, [delta])
 
-        self.checkhash(text, node, rev=rev)
+        text, validatehash = self.processflags(text, self.flags(rev))
+        if validatehash is True:
+            self.checkhash(text, node, rev=rev)
         self._cache = (node, rev, text)
         return text
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -48,6 +48,7 @@
     phases,
     pushkey,
     repoview,
+    revlog,
     revset,
     scmutil,
     store,
@@ -1959,6 +1960,21 @@
             fp.close()
         return self.pathto(fp.name[len(self.root) + 1:])
 
+    def registerflagtransform(self, flag, transform):
+        """Register transform on revlog flags.
+        
+        Extensions can rely on this method to set default transforms on revlog
+        flags, and ensure they are not overwritten by/overwriting other 
+        extensions'.
+        """
+        if not flag & revlog.REVIDX_KNOWN_FLAGS:
+            raise error.Abort(_("Cannot register transform on unknown flag."))
+        transform = revlog.REVIDX_FLAGS_TRANSFORMS.get(flag, None)
+        if transform is not None:
+            raise error.Abort(
+                _("Cannot register multiple transforms on flags."))
+        revlog.REVIDX_FLAGS_TRANSFORMS[flag] = transform
+
 # used to avoid circular references so destructors work
 def aftertrans(files):
     renamefiles = [tuple(t) for t in files]
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -56,6 +56,12 @@
 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
 REVIDX_DEFAULT_FLAGS = 0
 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
+# stable order in which flags need to be processed and their transforms applied
+REVIDX_FLAGS_ORDER = [
+    REVIDX_ISCENSORED,
+]
+REVIDX_FLAGS_TRANSFORMS = { }
+
 
 # max size of revlog with inline data
 _maxinline = 131072
@@ -1203,11 +1209,6 @@
         if rev is None:
             rev = self.rev(node)
 
-        # check rev flags
-        if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
-            raise RevlogError(_('incompatible revision flag %x') %
-                              (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
-
         chain, stopped = self._deltachain(rev, stoprev=cachedrev)
         if stopped:
             text = self._cache[2]
@@ -1221,7 +1222,11 @@
             bins = bins[1:]
 
         text = mdiff.patches(text, bins)
-        self.checkhash(text, node, rev=rev)
+
+        text, validatehash = self.processflags(text, self.flags(rev))
+        if validatehash:
+            self.checkhash(text, node, rev=rev)
+
         self._cache = (node, rev, text)
         return text
 
@@ -1233,6 +1238,45 @@
         """
         return hash(text, p1, p2)
 
+    def processflags(self, text, flags, reverse=False):
+        """Process revision flags and apply registered transforms.
+
+        ``text`` - the revision data to process
+        ``flags`` - the revision flags
+        ``reverse`` - an option argument describing whether the flags should be
+        processed in order or reverse order.
+
+        This method processes the flags in the order (or reverse order if
+        ``reverse`` is False) defined by REVIDX_FLAGS_TRANSFORMS, applying the
+        transforms registered for present flags. The order of flags defined in
+        REVIDX_FLAGS_TRANSFORMS needs to be stable for non-commutative
+        operations.
+
+        Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
+        processed text and ``validatehash`` is a bool indicating whether the
+        returned text should be checked for hash integrity.
+        """
+        # Check all flags are known.
+        if flags & ~REVIDX_KNOWN_FLAGS:
+            raise RevlogError(_('incompatible revision flag %x') %
+                              (flags & ~REVIDX_KNOWN_FLAGS))
+
+        validatehash = True
+        # Depending on the operation (read or write), the order might be
+        # reversed due to non-commutative transforms.
+        orderedflags = REVIDX_FLAGS_ORDER
+        if reverse:
+            orderedflags = reversed(orderedflags)
+
+        for flag in orderedflags:
+            # If a transform has been registered for a known flag, apply and
+            # update result tuple.
+            if flag & flags:
+                f = REVIDX_FLAGS_TRANSFORMS.get(flag, None)
+                if f is not None:
+                    text, validatehash = f(self, text)
+        return text, validatehash
+
     def checkhash(self, text, node, p1=None, p2=None, rev=None):
         """Check node hash integrity.
 
@@ -1415,7 +1459,10 @@
                 basetext = self.revision(self.node(baserev), _df=fh)
                 btext[0] = mdiff.patch(basetext, delta)
             try:
-                self.checkhash(btext[0], node, p1=p1, p2=p2)
+                btext[0], validatehash = self.processflags(btext[0], flags,
+                                                           reverse=True)
+                if validatehash is True:
+                    self.checkhash(btext[0], node, p1=p1, p2=p2)
                 if flags & REVIDX_ISCENSORED:
                     raise RevlogError(_('node %s is not censored') % node)
             except CensoredNodeError:


More information about the Mercurial-devel mailing list