--- a/mercurial/changegroup.py Wed Aug 08 20:17:48 2018 -0700
+++ b/mercurial/changegroup.py Thu Aug 09 09:28:26 2018 -0700
@@ -502,6 +502,32 @@
return readexactly(self._fh, n)
@attr.s(slots=True, frozen=True)
+class revisiondeltarequest(object):
+ """Describes a request to construct a revision delta.
+
+ Instances are converted into ``revisiondelta`` later.
+ """
+ # Revision whose delta will be generated.
+ node = attr.ib()
+
+ # Linknode value.
+ linknode = attr.ib()
+
+ # Parent revisions to record in ``revisiondelta`` instance.
+ p1node = attr.ib()
+ p2node = attr.ib()
+
+ # Base revision that delta should be generated against. If nullrev,
+ # the full revision data should be populated. If None, the delta
+ # may be generated against any base revision that is an ancestor of
+ # this revision. If any other numeric value, the delta should be
+ # produced against that revision.
+ baserev = attr.ib()
+
+ # Whether this should be marked as an ellipsis revision.
+ ellipsis = attr.ib(default=False)
+
+@attr.s(slots=True, frozen=True)
class revisiondelta(object):
"""Describes a delta entry in a changegroup.
@@ -587,14 +613,21 @@
key = lambda n: cl.rev(lookup(n))
return [store.rev(n) for n in sorted(nodes, key=key)]
-def _revisiondeltanormal(store, rev, prev, linknode, forcedeltaparentprev):
- """Construct a revision delta for non-ellipses changegroup generation."""
- node = store.node(rev)
- p1, p2 = store.parentrevs(rev)
+def _handlerevisiondeltarequest(store, request, prev):
+ """Obtain a revisiondelta from a revisiondeltarequest"""
+
+ node = request.node
+ rev = store.rev(node)
- if forcedeltaparentprev:
- base = prev
+ # Requesting a full revision.
+ if request.baserev == nullrev:
+ base = nullrev
+ # Requesting an explicit revision.
+ elif request.baserev is not None:
+ base = request.baserev
+ # Allowing us to choose.
else:
+ p1, p2 = store.parentrevs(rev)
dp = store.deltaparent(rev)
if dp == nullrev and store.storedeltachains:
@@ -637,23 +670,23 @@
else:
delta = store.revdiff(base, rev)
- p1n, p2n = store.parents(node)
+ extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
return revisiondelta(
node=node,
- p1node=p1n,
- p2node=p2n,
+ p1node=request.p1node,
+ p2node=request.p2node,
+ linknode=request.linknode,
basenode=store.node(base),
- linknode=linknode,
- flags=store.flags(rev),
+ flags=store.flags(rev) | extraflags,
baserevisionsize=baserevisionsize,
revision=revision,
delta=delta,
)
-def _revisiondeltanarrow(cl, store, ischangelog, rev, linkrev,
- linknode, clrevtolocalrev, fullclnodes,
- precomputedellipsis):
+def _makenarrowdeltarequest(cl, store, ischangelog, rev, node, linkrev,
+ linknode, clrevtolocalrev, fullclnodes,
+ precomputedellipsis):
linkparents = precomputedellipsis[linkrev]
def local(clrev):
"""Turn a changelog revnum into a local revnum.
@@ -726,23 +759,16 @@
else:
p1, p2 = sorted(local(p) for p in linkparents)
- n = store.node(rev)
- p1n, p2n = store.node(p1), store.node(p2)
- flags = store.flags(rev)
- flags |= revlog.REVIDX_ELLIPSIS
+ p1node, p2node = store.node(p1), store.node(p2)
# TODO: try and actually send deltas for ellipsis data blocks
-
- return revisiondelta(
- node=n,
- p1node=p1n,
- p2node=p2n,
- basenode=nullid,
+ return revisiondeltarequest(
+ node=node,
+ p1node=p1node,
+ p2node=p2node,
linknode=linknode,
- flags=flags,
- baserevisionsize=None,
- revision=store.revision(n),
- delta=None,
+ baserev=nullrev,
+ ellipsis=True,
)
def deltagroup(repo, revs, store, ischangelog, lookup, forcedeltaparentprev,
@@ -759,25 +785,32 @@
if not revs:
return
+ # We perform two passes over the revisions whose data we will emit.
+ #
+ # In the first pass, we obtain information about the deltas that will
+ # be generated. This involves computing linknodes and adjusting the
+ # request to take shallow fetching into account. The end result of
+ # this pass is a list of "request" objects stating which deltas
+ # to obtain.
+ #
+ # The second pass is simply resolving the requested deltas.
+
cl = repo.changelog
+ # In the first pass, collect info about the deltas we'll be
+ # generating.
+ requests = []
+
# Add the parent of the first rev.
revs.insert(0, store.parentrevs(revs[0])[0])
- # build deltas
- progress = None
- if units is not None:
- progress = repo.ui.makeprogress(_('bundling'), unit=units,
- total=(len(revs) - 1))
-
for i in pycompat.xrange(len(revs) - 1):
- if progress:
- progress.update(i + 1)
-
prev = revs[i]
curr = revs[i + 1]
- linknode = lookup(store.node(curr))
+ node = store.node(curr)
+ linknode = lookup(node)
+ p1node, p2node = store.parents(node)
if ellipses:
linkrev = cl.rev(linknode)
@@ -786,21 +819,47 @@
# This is a node to send in full, because the changeset it
# corresponds to was a full changeset.
if linknode in fullclnodes:
- delta = _revisiondeltanormal(store, curr, prev, linknode,
- forcedeltaparentprev)
+ requests.append(revisiondeltarequest(
+ node=node,
+ p1node=p1node,
+ p2node=p2node,
+ linknode=linknode,
+ baserev=None,
+ ))
+
elif linkrev not in precomputedellipsis:
- delta = None
+ pass
else:
- delta = _revisiondeltanarrow(
- cl, store, ischangelog, curr, linkrev, linknode,
+ requests.append(_makenarrowdeltarequest(
+ cl, store, ischangelog, curr, node, linkrev, linknode,
clrevtolocalrev, fullclnodes,
- precomputedellipsis)
+ precomputedellipsis))
else:
- delta = _revisiondeltanormal(store, curr, prev, linknode,
- forcedeltaparentprev)
+ requests.append(revisiondeltarequest(
+ node=node,
+ p1node=p1node,
+ p2node=p2node,
+ linknode=linknode,
+ baserev=prev if forcedeltaparentprev else None,
+ ))
- if delta:
- yield delta
+ # We expect the first pass to be fast, so we only engage the progress
+ # meter for constructing the revision deltas.
+ progress = None
+ if units is not None:
+ progress = repo.ui.makeprogress(_('bundling'), unit=units,
+ total=len(requests))
+
+ prevrev = revs[0]
+ for i, request in enumerate(requests):
+ if progress:
+ progress.update(i + 1)
+
+ delta = _handlerevisiondeltarequest(store, request, prevrev)
+
+ yield delta
+
+ prevrev = store.rev(request.node)
if progress:
progress.complete()