Mercurial > hg
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'', |