diff mercurial/exchange.py @ 34329:10e162bb9bf5

pull: use 'phase-heads' to retrieve phase information A new bundle2 capability 'phases' has been added. If 'heads' is part of the supported value for 'phases', the server supports reading and sending 'phase- heads' bundle2 part. Server is now able to process a 'phases' boolean parameter to 'getbundle'. If 'True', a 'phase-heads' bundle2 part will be included in the bundle with phase information relevant to the whole pulled set. If this method is available the phases listkey namespace will no longer be listed. Beside the more efficient encoding of the data, this new method will greatly improve the phase exchange efficiency for repositories with non-served changesets (obsolete, secret) since we'll no longer send data about the filtered heads. Add a new 'devel.legacy.exchange' config item to allow fallback to the old 'listkey in bundle2' method. Reminder: the pulled set is not just the changesets bundled by the pull. It also contains changeset selected by the "pull specification" on the client side (eg: everything for bare pull). One of the reason why the 'pulled set' is important is to make sure we can move -common- nodes to public.
author Boris Feld <boris.feld@octobus.net>
date Sun, 24 Sep 2017 21:27:18 +0200
parents e45ec589f962
children 6c7aaf59b21e
line wrap: on
line diff
--- a/mercurial/exchange.py	Wed Sep 20 18:29:10 2017 +0200
+++ b/mercurial/exchange.py	Sun Sep 24 21:27:18 2017 +0200
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import collections
 import errno
 import hashlib
 
@@ -1349,12 +1350,20 @@
     kwargs['common'] = pullop.common
     kwargs['heads'] = pullop.heads or pullop.rheads
     kwargs['cg'] = pullop.fetch
+
+    ui = pullop.repo.ui
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    if (not legacyphase and 'heads' in pullop.remotebundle2caps.get('phases')):
+        kwargs['phases'] = True
+        pullop.stepsdone.add('phases')
+
     if 'listkeys' in pullop.remotebundle2caps:
-        kwargs['listkeys'] = ['phases']
+        if 'phases' not in pullop.stepsdone:
+            kwargs['listkeys'] = ['phases']
         if pullop.remotebookmarks is None:
             # make sure to always includes bookmark data when migrating
             # `hg incoming --bundle` to using this function.
-            kwargs['listkeys'].append('bookmarks')
+            kwargs.setdefault('listkeys', []).append('bookmarks')
 
     # If this is a full pull / clone and the server supports the clone bundles
     # feature, tell the server whether we attempted a clone bundle. The
@@ -1663,6 +1672,53 @@
         markers = sorted(markers)
         bundle2.buildobsmarkerspart(bundler, markers)
 
+@getbundle2partsgenerator('phases')
+def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, heads=None, **kwargs):
+    """add phase heads part to the requested bundle"""
+    if kwargs.get('phases', False):
+        if not 'heads' in b2caps.get('phases'):
+            raise ValueError(_('no common phases exchange method'))
+        if heads is None:
+            heads = repo.heads()
+
+        headsbyphase = collections.defaultdict(set)
+        if repo.publishing():
+            headsbyphase[phases.public] = heads
+        else:
+            # find the appropriate heads to move
+
+            phase = repo._phasecache.phase
+            node = repo.changelog.node
+            rev = repo.changelog.rev
+            for h in heads:
+                headsbyphase[phase(repo, rev(h))].add(h)
+            seenphases = list(headsbyphase.keys())
+
+            # We do not handle anything but public and draft phase for now)
+            if seenphases:
+                assert max(seenphases) <= phases.draft
+
+            # if client is pulling non-public changesets, we need to find
+            # intermediate public heads.
+            draftheads = headsbyphase.get(phases.draft, set())
+            if draftheads:
+                publicheads = headsbyphase.get(phases.public, set())
+
+                revset = 'heads(only(%ln, %ln) and public())'
+                extraheads = repo.revs(revset, draftheads, publicheads)
+                for r in extraheads:
+                    headsbyphase[phases.public].add(node(r))
+
+        # transform data in a format used by the encoding function
+        phasemapping = []
+        for phase in phases.allphases:
+            phasemapping.append(sorted(headsbyphase[phase]))
+
+        # generate the actual part
+        phasedata = phases.binaryencode(phasemapping)
+        bundler.newpart('phase-heads', data=phasedata)
+
 @getbundle2partsgenerator('hgtagsfnodes')
 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
                          b2caps=None, heads=None, common=None,