diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -26,6 +26,7 @@ from . import ( changegroup, discovery, error, + exchangev2, lock as lockmod, logexchange, narrowspec, @@ -1506,17 +1507,21 @@ def pull(repo, remote, heads=None, force pullop.trmanager = transactionmanager(repo, 'pull', remote.url()) with repo.wlock(), repo.lock(), pullop.trmanager: - # This should ideally be in _pullbundle2(). However, it needs to run - # before discovery to avoid extra work. - _maybeapplyclonebundle(pullop) - streamclone.maybeperformlegacystreamclone(pullop) - _pulldiscovery(pullop) - if pullop.canusebundle2: - _fullpullbundle2(repo, pullop) - _pullchangeset(pullop) - _pullphase(pullop) - _pullbookmarks(pullop) - _pullobsolete(pullop) + # Use the modern wire protocol, if available. + if remote.capable('exchangev2'): + exchangev2.pull(pullop) + else: + # This should ideally be in _pullbundle2(). However, it needs to run + # before discovery to avoid extra work. + _maybeapplyclonebundle(pullop) + streamclone.maybeperformlegacystreamclone(pullop) + _pulldiscovery(pullop) + if pullop.canusebundle2: + _fullpullbundle2(repo, pullop) + _pullchangeset(pullop) + _pullphase(pullop) + _pullbookmarks(pullop) + _pullobsolete(pullop) # storing remotenames if repo.ui.configbool('experimental', 'remotenames'): diff --git a/mercurial/exchangev2.py b/mercurial/exchangev2.py new file mode 100644 --- /dev/null +++ b/mercurial/exchangev2.py @@ -0,0 +1,55 @@ +# exchangev2.py - repository exchange for wire protocol version 2 +# +# Copyright 2018 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +from .node import ( + nullid, +) +from . import ( + setdiscovery, +) + +def pull(pullop): + """Pull using wire protocol version 2.""" + repo = pullop.repo + remote = pullop.remote + + # Figure out what needs to be fetched. + common, fetch, remoteheads = _pullchangesetdiscovery( + repo, remote, pullop.heads, abortwhenunrelated=pullop.force) + +def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True): + """Determine which changesets need to be pulled.""" + + if heads: + knownnode = repo.changelog.hasnode + if all(knownnode(head) for head in heads): + return heads, False, heads + + # TODO wire protocol version 2 is capable of more efficient discovery + # than setdiscovery. Consider implementing something better. + common, fetch, remoteheads = setdiscovery.findcommonheads( + repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated) + + common = set(common) + remoteheads = set(remoteheads) + + # If a remote head is filtered locally, put it back in the common set. + # See the comment in exchange._pulldiscoverychangegroup() for more. + + if fetch and remoteheads: + nodemap = repo.unfiltered().changelog.nodemap + + common |= {head for head in remoteheads if head in nodemap} + + if set(remoteheads).issubset(common): + fetch = [] + + common.discard(nullid) + + return common, fetch, remoteheads diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py --- a/mercurial/httppeer.py +++ b/mercurial/httppeer.py @@ -802,7 +802,8 @@ class httpv2peer(object): return True # Other concepts. - if name in ('bundle2',): + # TODO remove exchangev2 once we have a command implemented. + if name in ('bundle2', 'exchangev2'): return True # Alias command-* to presence of command of that name. diff --git a/tests/test-wireproto-exchangev2.t b/tests/test-wireproto-exchangev2.t new file mode 100644 --- /dev/null +++ b/tests/test-wireproto-exchangev2.t @@ -0,0 +1,53 @@ +Tests for wire protocol version 2 exchange. +Tests in this file should be folded into existing tests once protocol +v2 has enough features that it can be enabled via #testcase in existing +tests. + + $ . $TESTDIR/wireprotohelpers.sh + $ enablehttpv2client + + $ hg init server-simple + $ enablehttpv2 server-simple + $ cd server-simple + $ cat >> .hg/hgrc << EOF + > [phases] + > publish = false + > EOF + $ echo a0 > a + $ echo b0 > b + $ hg -q commit -A -m 'commit 0' + + $ echo a1 > a + $ hg commit -m 'commit 1' + $ hg phase --public -r . + $ echo a2 > a + $ hg commit -m 'commit 2' + + $ hg -q up -r 0 + $ echo b1 > b + $ hg -q commit -m 'head 2 commit 1' + $ echo b2 > b + $ hg -q commit -m 'head 2 commit 2' + + $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log + $ cat hg.pid > $DAEMON_PIDS + + $ cd .. + +Test basic clone + + $ hg --debug clone -U http://localhost:$HGPORT client-simple + using http://localhost:$HGPORT/ + sending capabilities command + query 1; heads + sending 2 commands + sending command heads: {} + sending command known: { + 'nodes': [] + } + received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) + received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation) + received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) + received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation) + received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation) + received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos) diff --git a/tests/wireprotohelpers.sh b/tests/wireprotohelpers.sh --- a/tests/wireprotohelpers.sh +++ b/tests/wireprotohelpers.sh @@ -56,3 +56,10 @@ web.apiserver = true web.api.http-v2 = true EOF } + +enablehttpv2client() { + cat >> $HGRCPATH << EOF +[experimental] +httppeer.advertise-v2 = true +EOF +}