comparison 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
comparison
equal deleted inserted replaced
39645:a86d21e70b2b 39646:9c2c77c73f23
7 from __future__ import absolute_import 7 from __future__ import absolute_import
8 8
9 import contextlib 9 import contextlib
10 10
11 from .i18n import _ 11 from .i18n import _
12 from .node import (
13 nullid,
14 )
12 from . import ( 15 from . import (
16 discovery,
13 encoding, 17 encoding,
14 error, 18 error,
15 pycompat, 19 pycompat,
16 streamclone, 20 streamclone,
17 util, 21 util,
458 462
459 @wireprotocommand('capabilities', permission='pull') 463 @wireprotocommand('capabilities', permission='pull')
460 def capabilitiesv2(repo, proto): 464 def capabilitiesv2(repo, proto):
461 yield _capabilitiesv2(repo, proto) 465 yield _capabilitiesv2(repo, proto)
462 466
467 @wireprotocommand('changesetdata',
468 args={
469 'noderange': [[b'0123456...'], [b'abcdef...']],
470 'nodes': [b'0123456...'],
471 'fields': {b'parents', b'revision'},
472 },
473 permission='pull')
474 def changesetdata(repo, proto, noderange=None, nodes=None, fields=None):
475 fields = fields or set()
476
477 if noderange is None and nodes is None:
478 raise error.WireprotoCommandError(
479 'noderange or nodes must be defined')
480
481 if noderange is not None:
482 if len(noderange) != 2:
483 raise error.WireprotoCommandError(
484 'noderange must consist of 2 elements')
485
486 if not noderange[1]:
487 raise error.WireprotoCommandError(
488 'heads in noderange request cannot be empty')
489
490 cl = repo.changelog
491 hasnode = cl.hasnode
492
493 seen = set()
494 outgoing = []
495
496 if nodes is not None:
497 outgoing.extend(n for n in nodes if hasnode(n))
498 seen |= set(outgoing)
499
500 if noderange is not None:
501 if noderange[0]:
502 common = [n for n in noderange[0] if hasnode(n)]
503 else:
504 common = [nullid]
505
506 for n in discovery.outgoing(repo, common, noderange[1]).missing:
507 if n not in seen:
508 outgoing.append(n)
509 # Don't need to add to seen here because this is the final
510 # source of nodes and there should be no duplicates in this
511 # list.
512
513 seen.clear()
514
515 if outgoing:
516 repo.hook('preoutgoing', throw=True, source='serve')
517
518 yield {
519 b'totalitems': len(outgoing),
520 }
521
522 # It is already topologically sorted by revision number.
523 for node in outgoing:
524 d = {
525 b'node': node,
526 }
527
528 if b'parents' in fields:
529 d[b'parents'] = cl.parents(node)
530
531 revisiondata = None
532
533 if b'revision' in fields:
534 revisiondata = cl.revision(node, raw=True)
535 d[b'revisionsize'] = len(revisiondata)
536
537 yield d
538
539 if revisiondata is not None:
540 yield revisiondata
541
463 @wireprotocommand('heads', 542 @wireprotocommand('heads',
464 args={ 543 args={
465 'publiconly': False, 544 'publiconly': False,
466 }, 545 },
467 permission='pull') 546 permission='pull')