comparison mercurial/wireprotov2server.py @ 39813:c30faea8d02d

wireprotov2: advertise set of valid values for requestable fields changesetdata, manifestdata, and filedata all allow the caller to specify what data fields to request. Data fields are extensible and may evolve over time. In order to prevent clients from making requests for fields that are not available, the client needs to know what fields are available. This commit teaches the server to declare a set of "valid values" for wire protocol command arguments. That set of values is exposed in the command's capabilities descriptor. The changesetdata, manifestdata, and filedata commands all declare their set of available "fields." Differential Revision: https://phab.mercurial-scm.org/D4619
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 17 Sep 2018 11:54:00 -0700
parents 8e7e822e85ec
children d059cb669632
comparison
equal deleted inserted replaced
39812:8e7e822e85ec 39813:c30faea8d02d
431 'compression': compression, 431 'compression': compression,
432 'framingmediatypes': [FRAMINGTYPE], 432 'framingmediatypes': [FRAMINGTYPE],
433 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES), 433 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
434 } 434 }
435 435
436 # TODO expose available changesetdata fields.
437
438 for command, entry in COMMANDS.items(): 436 for command, entry in COMMANDS.items():
439 args = {} 437 args = {}
440 438
441 for arg, meta in entry.args.items(): 439 for arg, meta in entry.args.items():
442 args[arg] = { 440 args[arg] = {
446 b'required': meta['required'], 444 b'required': meta['required'],
447 } 445 }
448 446
449 if not meta['required']: 447 if not meta['required']:
450 args[arg][b'default'] = meta['default']() 448 args[arg][b'default'] = meta['default']()
449
450 if meta['validvalues']:
451 args[arg][b'validvalues'] = meta['validvalues']
451 452
452 caps['commands'][command] = { 453 caps['commands'][command] = {
453 'args': args, 454 'args': args,
454 'permissions': [entry.permission], 455 'permissions': [entry.permission],
455 } 456 }
561 Bool indicating whether the argument is required. 562 Bool indicating whether the argument is required.
562 563
563 ``example`` 564 ``example``
564 An example value for this argument. 565 An example value for this argument.
565 566
567 ``validvalues``
568 Set of recognized values for this argument.
569
566 ``permission`` defines the permission type needed to run this command. 570 ``permission`` defines the permission type needed to run this command.
567 Can be ``push`` or ``pull``. These roughly map to read-write and read-only, 571 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
568 respectively. Default is to assume command requires ``push`` permissions 572 respectively. Default is to assume command requires ``push`` permissions
569 because otherwise commands not declaring their permissions could modify 573 because otherwise commands not declaring their permissions could modify
570 a repository that is supposed to be read-only. 574 a repository that is supposed to be read-only.
617 'as required but has a default value' % 621 'as required but has a default value' %
618 (arg, name)) 622 (arg, name))
619 623
620 meta.setdefault('default', lambda: None) 624 meta.setdefault('default', lambda: None)
621 meta.setdefault('required', False) 625 meta.setdefault('required', False)
626 meta.setdefault('validvalues', None)
622 627
623 def register(func): 628 def register(func):
624 if name in COMMANDS: 629 if name in COMMANDS:
625 raise error.ProgrammingError('%s command already registered ' 630 raise error.ProgrammingError('%s command already registered '
626 'for version 2' % name) 631 'for version 2' % name)
654 }, 659 },
655 'fields': { 660 'fields': {
656 'type': 'set', 661 'type': 'set',
657 'default': set, 662 'default': set,
658 'example': {b'parents', b'revision'}, 663 'example': {b'parents', b'revision'},
664 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
659 }, 665 },
660 }, 666 },
661 permission='pull') 667 permission='pull')
662 def changesetdata(repo, proto, noderange, nodes, fields): 668 def changesetdata(repo, proto, noderange, nodes, fields):
663 # TODO look for unknown fields and abort when they can't be serviced. 669 # TODO look for unknown fields and abort when they can't be serviced.
670 # This could probably be validated by dispatcher using validvalues.
664 671
665 if noderange is None and nodes is None: 672 if noderange is None and nodes is None:
666 raise error.WireprotoCommandError( 673 raise error.WireprotoCommandError(
667 'noderange or nodes must be defined') 674 'noderange or nodes must be defined')
668 675
806 }, 813 },
807 'fields': { 814 'fields': {
808 'type': 'set', 815 'type': 'set',
809 'default': set, 816 'default': set,
810 'example': {b'parents', b'revision'}, 817 'example': {b'parents', b'revision'},
818 'validvalues': {b'parents', b'revision'},
811 }, 819 },
812 'path': { 820 'path': {
813 'type': 'bytes', 821 'type': 'bytes',
814 'required': True, 822 'required': True,
815 'example': b'foo.txt', 823 'example': b'foo.txt',
964 }, 972 },
965 'fields': { 973 'fields': {
966 'type': 'set', 974 'type': 'set',
967 'default': set, 975 'default': set,
968 'example': {b'parents', b'revision'}, 976 'example': {b'parents', b'revision'},
977 'validvalues': {b'parents', b'revision'},
969 }, 978 },
970 'tree': { 979 'tree': {
971 'type': 'bytes', 980 'type': 'bytes',
972 'required': True, 981 'required': True,
973 'example': b'', 982 'example': b'',