diff mercurial/wireprotov2server.py @ 39646:9c2c77c73f23

wireprotov2: define and implement "changesetdata" command This commit introduces the "changesetdata" wire protocol command. The role of the command is to expose data associated with changelog revisions, including the raw revision data itself. This command is the first piece of a new clone/pull strategy that is built on top of domain-specific commands for data retrieval. Instead of a monolithic "getbundle" command that transfers all of the things, we'll be introducing commands for fetching specific pieces of data. Since the changeset is the fundamental unit from which we derive pointers to other data (manifests, file nodes, etc), it makes sense to start reimplementing pull with this data. The command accepts as arguments a set of root and head revisions defining the changesets that should be fetched as well as an explicit list of nodes. By default, the command returns only the node values: the client must explicitly request additional fields be added to the response. Current supported fields are the list of parent nodes and the revision fulltext. My plan is to eventually add support for transferring other data associated with changesets, including phases, bookmarks, obsolescence markers, etc. Since the response format is CBOR, we'll be able to add this data into the response object relatively easily (it should be as simple as adding a key in a map). The documentation captures a number of TODO items. Some of these may require BC breaking changes. That's fine: wire protocol v2 is still highly experimental. Differential Revision: https://phab.mercurial-scm.org/D4481
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 12 Sep 2018 10:01:16 -0700
parents 07b58266bce3
children c1aacb0d76ff
line wrap: on
line diff
--- a/mercurial/wireprotov2server.py	Wed Sep 12 09:58:23 2018 -0700
+++ b/mercurial/wireprotov2server.py	Wed Sep 12 10:01:16 2018 -0700
@@ -9,7 +9,11 @@
 import contextlib
 
 from .i18n import _
+from .node import (
+    nullid,
+)
 from . import (
+    discovery,
     encoding,
     error,
     pycompat,
@@ -460,6 +464,81 @@
 def capabilitiesv2(repo, proto):
     yield _capabilitiesv2(repo, proto)
 
+@wireprotocommand('changesetdata',
+                  args={
+                      'noderange': [[b'0123456...'], [b'abcdef...']],
+                      'nodes': [b'0123456...'],
+                      'fields': {b'parents', b'revision'},
+                  },
+                  permission='pull')
+def changesetdata(repo, proto, noderange=None, nodes=None, fields=None):
+    fields = fields or set()
+
+    if noderange is None and nodes is None:
+        raise error.WireprotoCommandError(
+            'noderange or nodes must be defined')
+
+    if noderange is not None:
+        if len(noderange) != 2:
+            raise error.WireprotoCommandError(
+                'noderange must consist of 2 elements')
+
+        if not noderange[1]:
+            raise error.WireprotoCommandError(
+                'heads in noderange request cannot be empty')
+
+    cl = repo.changelog
+    hasnode = cl.hasnode
+
+    seen = set()
+    outgoing = []
+
+    if nodes is not None:
+        outgoing.extend(n for n in nodes if hasnode(n))
+        seen |= set(outgoing)
+
+    if noderange is not None:
+        if noderange[0]:
+            common = [n for n in noderange[0] if hasnode(n)]
+        else:
+            common = [nullid]
+
+        for n in discovery.outgoing(repo, common, noderange[1]).missing:
+            if n not in seen:
+                outgoing.append(n)
+            # Don't need to add to seen here because this is the final
+            # source of nodes and there should be no duplicates in this
+            # list.
+
+    seen.clear()
+
+    if outgoing:
+        repo.hook('preoutgoing', throw=True, source='serve')
+
+    yield {
+        b'totalitems': len(outgoing),
+    }
+
+    # It is already topologically sorted by revision number.
+    for node in outgoing:
+        d = {
+            b'node': node,
+        }
+
+        if b'parents' in fields:
+            d[b'parents'] = cl.parents(node)
+
+        revisiondata = None
+
+        if b'revision' in fields:
+            revisiondata = cl.revision(node, raw=True)
+            d[b'revisionsize'] = len(revisiondata)
+
+        yield d
+
+        if revisiondata is not None:
+            yield revisiondata
+
 @wireprotocommand('heads',
                   args={
                       'publiconly': False,