diff mercurial/exchange.py @ 37498:aacfca6f9767

wireproto: support for pullbundles Pullbundles are similar to clonebundles, but served as normal inline bundle streams. They are almost transparent to the client -- the only visible effect is that the client might get less changes than what it asked for, i.e. not all requested head revisions are provided. The client announces support for the necessary retries with the partial-pull capability. After receiving a partial bundle, it updates the set of revisions shared with the server and drops all now-known heads from the request list. It will then rerun getbundle until no changes are received or all remote heads are present. Extend badserverext to support per-socket limit, i.e. don't assume that the same limits should be applied to all sockets. Differential Revision: https://phab.mercurial-scm.org/D1856
author Joerg Sonnenberger <joerg@bec.de>
date Thu, 18 Jan 2018 12:54:01 +0100
parents f7d3915d5b3a
children cc8c06835097
line wrap: on
line diff
--- a/mercurial/exchange.py	Fri Apr 06 22:39:58 2018 -0700
+++ b/mercurial/exchange.py	Thu Jan 18 12:54:01 2018 +0100
@@ -1375,6 +1375,44 @@
         if self._tr is not None:
             self._tr.release()
 
+def _fullpullbundle2(repo, pullop):
+    # The server may send a partial reply, i.e. when inlining
+    # pre-computed bundles. In that case, update the common
+    # set based on the results and pull another bundle.
+    #
+    # There are two indicators that the process is finished:
+    # - no changeset has been added, or
+    # - all remote heads are known locally.
+    # The head check must use the unfiltered view as obsoletion
+    # markers can hide heads.
+    unfi = repo.unfiltered()
+    unficl = unfi.changelog
+    def headsofdiff(h1, h2):
+        """Returns heads(h1 % h2)"""
+        res = unfi.set('heads(%ln %% %ln)', h1, h2)
+        return set(ctx.node() for ctx in res)
+    def headsofunion(h1, h2):
+        """Returns heads((h1 + h2) - null)"""
+        res = unfi.set('heads((%ln + %ln - null))', h1, h2)
+        return set(ctx.node() for ctx in res)
+    while True:
+        old_heads = unficl.heads()
+        clstart = len(unficl)
+        _pullbundle2(pullop)
+        if changegroup.NARROW_REQUIREMENT in repo.requirements:
+            # XXX narrow clones filter the heads on the server side during
+            # XXX getbundle and result in partial replies as well.
+            # XXX Disable pull bundles in this case as band aid to avoid
+            # XXX extra round trips.
+            break
+        if clstart == len(unficl):
+            break
+        if all(unficl.hasnode(n) for n in pullop.rheads):
+            break
+        new_heads = headsofdiff(unficl.heads(), old_heads)
+        pullop.common = headsofunion(new_heads, pullop.common)
+        pullop.rheads = set(pullop.rheads) - pullop.common
+
 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
          streamclonerequested=None):
     """Fetch repository data from a remote.
@@ -1420,7 +1458,7 @@
         streamclone.maybeperformlegacystreamclone(pullop)
         _pulldiscovery(pullop)
         if pullop.canusebundle2:
-            _pullbundle2(pullop)
+            _fullpullbundle2(repo, pullop)
         _pullchangeset(pullop)
         _pullphase(pullop)
         _pullbookmarks(pullop)