Mercurial > public > mercurial-scm > hg
comparison mercurial/wireprotov2server.py @ 40176:41263df08109
wireprotov2: change how revisions are specified to changesetdata
Right now, we have a handful of arguments for specifying the revisions
whose data should be returned. Defining how all these arguments
interact when various combinations are present is difficult.
This commit establishes a new, generic mechanism for specifying
revisions. Instead of a hodgepodge of arguments defining things, we
have a list of dicts that specify revision selectors. The final set
of revisions is a union of all these selectors.
We implement support for specifying revisions based on:
* An explicit list of changeset revisions
* An explicit list of changeset revisions plus ancestry depth
* A DAG range between changeset roots and heads
If you squint hard enough, this problem has already been solved by
revsets. But I'm reluctant to expose revsets to the wire protocol
because that would require servers to implement a revset parser.
Plus there are security and performance implications: the set
of revision selectors needs to be narrowly and specifically tailored
for what is appropriate to be executing on a server. Perhaps there
would be a way for us to express the "parse tree" of a revset
query, for example. I'm not sure. We can explore this space another
time. For now, the new mechanism should bring sufficient flexibility
while remaining relatively simple.
The selector "types" are prefixed with "changeset" because I plan
to add manifest and file-flavored selectors as well. This will enable
us to e.g. select file revisions based on a range of changeset
revisions.
Differential Revision: https://phab.mercurial-scm.org/D4979
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 08 Oct 2018 18:17:12 -0700 |
parents | 6c42409691ec |
children | 41e2633bcd00 |
comparison
equal
deleted
inserted
replaced
40175:6c42409691ec | 40176:41263df08109 |
---|---|
775 Extensions can monkeypatch this function to provide custom caching | 775 Extensions can monkeypatch this function to provide custom caching |
776 backends. | 776 backends. |
777 """ | 777 """ |
778 return None | 778 return None |
779 | 779 |
780 def resolvenodes(repo, revisions): | |
781 """Resolve nodes from a revisions specifier data structure.""" | |
782 cl = repo.changelog | |
783 clhasnode = cl.hasnode | |
784 | |
785 seen = set() | |
786 nodes = [] | |
787 | |
788 if not isinstance(revisions, list): | |
789 raise error.WireprotoCommandError('revisions must be defined as an ' | |
790 'array') | |
791 | |
792 for spec in revisions: | |
793 if b'type' not in spec: | |
794 raise error.WireprotoCommandError( | |
795 'type key not present in revision specifier') | |
796 | |
797 typ = spec[b'type'] | |
798 | |
799 if typ == b'changesetexplicit': | |
800 if b'nodes' not in spec: | |
801 raise error.WireprotoCommandError( | |
802 'nodes key not present in changesetexplicit revision ' | |
803 'specifier') | |
804 | |
805 for node in spec[b'nodes']: | |
806 if node not in seen: | |
807 nodes.append(node) | |
808 seen.add(node) | |
809 | |
810 elif typ == b'changesetexplicitdepth': | |
811 for key in (b'nodes', b'depth'): | |
812 if key not in spec: | |
813 raise error.WireprotoCommandError( | |
814 '%s key not present in changesetexplicitdepth revision ' | |
815 'specifier', (key,)) | |
816 | |
817 for rev in repo.revs(b'ancestors(%ln, %d)', spec[b'nodes'], | |
818 spec[b'depth'] - 1): | |
819 node = cl.node(rev) | |
820 | |
821 if node not in seen: | |
822 nodes.append(node) | |
823 seen.add(node) | |
824 | |
825 elif typ == b'changesetdagrange': | |
826 for key in (b'roots', b'heads'): | |
827 if key not in spec: | |
828 raise error.WireprotoCommandError( | |
829 '%s key not present in changesetdagrange revision ' | |
830 'specifier', (key,)) | |
831 | |
832 if not spec[b'heads']: | |
833 raise error.WireprotoCommandError( | |
834 'heads key in changesetdagrange cannot be empty') | |
835 | |
836 if spec[b'roots']: | |
837 common = [n for n in spec[b'roots'] if clhasnode(n)] | |
838 else: | |
839 common = [nullid] | |
840 | |
841 for n in discovery.outgoing(repo, common, spec[b'heads']).missing: | |
842 if n not in seen: | |
843 nodes.append(n) | |
844 seen.add(n) | |
845 | |
846 else: | |
847 raise error.WireprotoCommandError( | |
848 'unknown revision specifier type: %s', (typ,)) | |
849 | |
850 return nodes | |
851 | |
780 @wireprotocommand('branchmap', permission='pull') | 852 @wireprotocommand('branchmap', permission='pull') |
781 def branchmapv2(repo, proto): | 853 def branchmapv2(repo, proto): |
782 yield {encoding.fromlocal(k): v | 854 yield {encoding.fromlocal(k): v |
783 for k, v in repo.branchmap().iteritems()} | 855 for k, v in repo.branchmap().iteritems()} |
784 | 856 |
787 yield _capabilitiesv2(repo, proto) | 859 yield _capabilitiesv2(repo, proto) |
788 | 860 |
789 @wireprotocommand( | 861 @wireprotocommand( |
790 'changesetdata', | 862 'changesetdata', |
791 args={ | 863 args={ |
792 'noderange': { | 864 'revisions': { |
793 'type': 'list', | 865 'type': 'list', |
794 'default': lambda: None, | 866 'example': [{ |
795 'example': [[b'0123456...'], [b'abcdef...']], | 867 b'type': b'changesetexplicit', |
796 }, | 868 b'nodes': [b'abcdef...'], |
797 'nodes': { | 869 }], |
798 'type': 'list', | |
799 'default': lambda: None, | |
800 'example': [b'0123456...'], | |
801 }, | |
802 'nodesdepth': { | |
803 'type': 'int', | |
804 'default': lambda: None, | |
805 'example': 10, | |
806 }, | 870 }, |
807 'fields': { | 871 'fields': { |
808 'type': 'set', | 872 'type': 'set', |
809 'default': set, | 873 'default': set, |
810 'example': {b'parents', b'revision'}, | 874 'example': {b'parents', b'revision'}, |
811 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'}, | 875 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'}, |
812 }, | 876 }, |
813 }, | 877 }, |
814 permission='pull') | 878 permission='pull') |
815 def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields): | 879 def changesetdata(repo, proto, revisions, fields): |
816 # TODO look for unknown fields and abort when they can't be serviced. | 880 # TODO look for unknown fields and abort when they can't be serviced. |
817 # This could probably be validated by dispatcher using validvalues. | 881 # This could probably be validated by dispatcher using validvalues. |
818 | 882 |
819 if noderange is None and nodes is None: | |
820 raise error.WireprotoCommandError( | |
821 'noderange or nodes must be defined') | |
822 | |
823 if nodesdepth is not None and nodes is None: | |
824 raise error.WireprotoCommandError( | |
825 'nodesdepth requires the nodes argument') | |
826 | |
827 if noderange is not None: | |
828 if len(noderange) != 2: | |
829 raise error.WireprotoCommandError( | |
830 'noderange must consist of 2 elements') | |
831 | |
832 if not noderange[1]: | |
833 raise error.WireprotoCommandError( | |
834 'heads in noderange request cannot be empty') | |
835 | |
836 cl = repo.changelog | 883 cl = repo.changelog |
837 hasnode = cl.hasnode | 884 outgoing = resolvenodes(repo, revisions) |
838 | |
839 seen = set() | |
840 outgoing = [] | |
841 | |
842 if nodes is not None: | |
843 outgoing = [n for n in nodes if hasnode(n)] | |
844 | |
845 if nodesdepth: | |
846 outgoing = [cl.node(r) for r in | |
847 repo.revs(b'ancestors(%ln, %d)', outgoing, | |
848 nodesdepth - 1)] | |
849 | |
850 seen |= set(outgoing) | |
851 | |
852 if noderange is not None: | |
853 if noderange[0]: | |
854 common = [n for n in noderange[0] if hasnode(n)] | |
855 else: | |
856 common = [nullid] | |
857 | |
858 for n in discovery.outgoing(repo, common, noderange[1]).missing: | |
859 if n not in seen: | |
860 outgoing.append(n) | |
861 # Don't need to add to seen here because this is the final | |
862 # source of nodes and there should be no duplicates in this | |
863 # list. | |
864 | |
865 seen.clear() | |
866 publishing = repo.publishing() | 885 publishing = repo.publishing() |
867 | 886 |
868 if outgoing: | 887 if outgoing: |
869 repo.hook('preoutgoing', throw=True, source='serve') | 888 repo.hook('preoutgoing', throw=True, source='serve') |
870 | 889 |