mercurial/utils/storageutil.py
changeset 40008 842ffcf1d42f
parent 40007 1470183068b8
child 40009 631c6f5058b9
--- a/mercurial/utils/storageutil.py	Fri Sep 28 11:51:17 2018 -0700
+++ b/mercurial/utils/storageutil.py	Fri Sep 28 16:16:22 2018 -0700
@@ -262,3 +262,149 @@
                     futurelargelinkrevs.add(plinkrev)
 
     return strippoint, brokenrevs
+
+def emitrevisions(store, revs, resultcls, deltaparentfn, candeltafn,
+                  rawsizefn, revdifffn, flagsfn, sendfulltext=False,
+                  revisiondata=False, assumehaveparentrevisions=False,
+                  deltaprevious=False):
+    """Generic implementation of ifiledata.emitrevisions().
+
+    Emitting revision data is subtly complex. This function attempts to
+    encapsulate all the logic for doing so in a backend-agnostic way.
+
+    ``store``
+       Object conforming to ``ifilestorage`` interface.
+
+    ``revs``
+       List of integer revision numbers whose data to emit.
+
+    ``resultcls``
+       A type implementing the ``irevisiondelta`` interface that will be
+       constructed and returned.
+
+    ``deltaparentfn``
+       Callable receiving a revision number and returning the revision number
+       of a revision that the internal delta is stored against. This delta
+       will be preferred over computing a new arbitrary delta.
+
+    ``candeltafn``
+       Callable receiving a pair of revision numbers that returns a bool
+       indicating whether a delta between them can be produced.
+
+    ``rawsizefn``
+       Callable receiving a revision number and returning the length of the
+       ``store.revision(rev, raw=True)``.
+
+    ``revdifffn``
+       Callable receiving a pair of revision numbers that returns a delta
+       between them.
+
+    ``flagsfn``
+       Callable receiving a revision number and returns the integer flags
+       value for it.
+
+    ``sendfulltext``
+       Whether to send fulltext revisions instead of deltas, if allowed.
+
+    ``revisiondata``
+    ``assumehaveparentrevisions``
+    ``deltaprevious``
+       See ``ifiledata.emitrevisions()`` interface documentation.
+    """
+
+    fnode = store.node
+
+    prevrev = None
+
+    if deltaprevious or assumehaveparentrevisions:
+        prevrev = store.parentrevs(revs[0])[0]
+
+    # Set of revs available to delta against.
+    available = set()
+
+    for rev in revs:
+        if rev == nullrev:
+            continue
+
+        node = fnode(rev)
+        deltaparentrev = deltaparentfn(rev)
+        p1rev, p2rev = store.parentrevs(rev)
+
+        # Forced delta against previous mode.
+        if deltaprevious:
+            baserev = prevrev
+
+        # We're instructed to send fulltext. Honor that.
+        elif sendfulltext:
+            baserev = nullrev
+
+        # There is a delta in storage. We try to use that because it
+        # amounts to effectively copying data from storage and is
+        # therefore the fastest.
+        elif deltaparentrev != nullrev:
+            # Base revision was already emitted in this group. We can
+            # always safely use the delta.
+            if deltaparentrev in available:
+                baserev = deltaparentrev
+
+            # Base revision is a parent that hasn't been emitted already.
+            # Use it if we can assume the receiver has the parent revision.
+            elif (assumehaveparentrevisions
+                  and deltaparentrev in (p1rev, p2rev)):
+                baserev = deltaparentrev
+
+            # No guarantee the receiver has the delta parent. Send delta
+            # against last revision (if possible), which in the common case
+            # should be similar enough to this revision that the delta is
+            # reasonable.
+            elif prevrev is not None:
+                baserev = prevrev
+            else:
+                baserev = nullrev
+
+        # Storage has a fulltext revision.
+
+        # Let's use the previous revision, which is as good a guess as any.
+        # There is definitely room to improve this logic.
+        elif prevrev is not None:
+            baserev = prevrev
+        else:
+            baserev = nullrev
+
+        # But we can't actually use our chosen delta base for whatever
+        # reason. Reset to fulltext.
+        if baserev != nullrev and not candeltafn(baserev, rev):
+            baserev = nullrev
+
+        revision = None
+        delta = None
+        baserevisionsize = None
+
+        if revisiondata:
+            if store.iscensored(baserev) or store.iscensored(rev):
+                try:
+                    revision = store.revision(node, raw=True)
+                except error.CensoredNodeError as e:
+                    revision = e.tombstone
+
+                if baserev != nullrev:
+                    baserevisionsize = rawsizefn(baserev)
+
+            elif baserev == nullrev and not deltaprevious:
+                revision = store.revision(node, raw=True)
+                available.add(rev)
+            else:
+                delta = revdifffn(baserev, rev)
+                available.add(rev)
+
+        yield resultcls(
+            node=node,
+            p1node=fnode(p1rev),
+            p2node=fnode(p2rev),
+            basenode=fnode(baserev),
+            flags=flagsfn(rev),
+            baserevisionsize=baserevisionsize,
+            revision=revision,
+            delta=delta)
+
+        prevrev = rev