|
@@
-592,10
+592,12
b' def supportedcompengines(ui, role):'
|
|
592
|
|
|
592
|
|
|
593
|
class commandentry(object):
|
|
593
|
class commandentry(object):
|
|
594
|
"""Represents a declared wire protocol command."""
|
|
594
|
"""Represents a declared wire protocol command."""
|
|
595
|
def __init__(self, func, args='', transports=None):
|
|
595
|
def __init__(self, func, args='', transports=None,
|
|
|
|
|
596
|
permission='push'):
|
|
596
|
self.func = func
|
|
597
|
self.func = func
|
|
597
|
self.args = args
|
|
598
|
self.args = args
|
|
598
|
self.transports = transports or set()
|
|
599
|
self.transports = transports or set()
|
|
|
|
|
600
|
self.permission = permission
|
|
599
|
|
|
601
|
|
|
600
|
def _merge(self, func, args):
|
|
602
|
def _merge(self, func, args):
|
|
601
|
"""Merge this instance with an incoming 2-tuple.
|
|
603
|
"""Merge this instance with an incoming 2-tuple.
|
|
@@
-605,7
+607,8
b' class commandentry(object):'
|
|
605
|
data not captured by the 2-tuple and a new instance containing
|
|
607
|
data not captured by the 2-tuple and a new instance containing
|
|
606
|
the union of the two objects is returned.
|
|
608
|
the union of the two objects is returned.
|
|
607
|
"""
|
|
609
|
"""
|
|
608
|
return commandentry(func, args=args, transports=set(self.transports))
|
|
610
|
return commandentry(func, args=args, transports=set(self.transports),
|
|
|
|
|
611
|
permission=self.permission)
|
|
609
|
|
|
612
|
|
|
610
|
# Old code treats instances as 2-tuples. So expose that interface.
|
|
613
|
# Old code treats instances as 2-tuples. So expose that interface.
|
|
611
|
def __iter__(self):
|
|
614
|
def __iter__(self):
|
|
@@
-643,7
+646,8
b' class commanddict(dict):'
|
|
643
|
else:
|
|
646
|
else:
|
|
644
|
# Use default values from @wireprotocommand.
|
|
647
|
# Use default values from @wireprotocommand.
|
|
645
|
v = commandentry(v[0], args=v[1],
|
|
648
|
v = commandentry(v[0], args=v[1],
|
|
646
|
transports=set(wireprototypes.TRANSPORTS))
|
|
649
|
transports=set(wireprototypes.TRANSPORTS),
|
|
|
|
|
650
|
permission='push')
|
|
647
|
else:
|
|
651
|
else:
|
|
648
|
raise ValueError('command entries must be commandentry instances '
|
|
652
|
raise ValueError('command entries must be commandentry instances '
|
|
649
|
'or 2-tuples')
|
|
653
|
'or 2-tuples')
|
|
@@
-672,12
+676,8
b" POLICY_V2_ONLY = 'v2-only'"
|
|
672
|
|
|
676
|
|
|
673
|
commands = commanddict()
|
|
677
|
commands = commanddict()
|
|
674
|
|
|
678
|
|
|
675
|
# Maps wire protocol name to operation type. This is used for permissions
|
|
679
|
def wireprotocommand(name, args='', transportpolicy=POLICY_ALL,
|
|
676
|
# checking. All defined @wireiprotocommand should have an entry in this
|
|
680
|
permission='push'):
|
|
677
|
# dict.
|
|
|
|
|
678
|
permissions = {}
|
|
|
|
|
679
|
|
|
|
|
|
680
|
def wireprotocommand(name, args='', transportpolicy=POLICY_ALL):
|
|
|
|
|
681
|
"""Decorator to declare a wire protocol command.
|
|
681
|
"""Decorator to declare a wire protocol command.
|
|
682
|
|
|
682
|
|
|
683
|
``name`` is the name of the wire protocol command being provided.
|
|
683
|
``name`` is the name of the wire protocol command being provided.
|
|
@@
-688,6
+688,12
b" def wireprotocommand(name, args='', tran"
|
|
688
|
``transportpolicy`` is a POLICY_* constant denoting which transports
|
|
688
|
``transportpolicy`` is a POLICY_* constant denoting which transports
|
|
689
|
this wire protocol command should be exposed to. By default, commands
|
|
689
|
this wire protocol command should be exposed to. By default, commands
|
|
690
|
are exposed to all wire protocol transports.
|
|
690
|
are exposed to all wire protocol transports.
|
|
|
|
|
691
|
|
|
|
|
|
692
|
``permission`` defines the permission type needed to run this command.
|
|
|
|
|
693
|
Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
|
|
|
|
|
694
|
respectively. Default is to assume command requires ``push`` permissions
|
|
|
|
|
695
|
because otherwise commands not declaring their permissions could modify
|
|
|
|
|
696
|
a repository that is supposed to be read-only.
|
|
691
|
"""
|
|
697
|
"""
|
|
692
|
if transportpolicy == POLICY_ALL:
|
|
698
|
if transportpolicy == POLICY_ALL:
|
|
693
|
transports = set(wireprototypes.TRANSPORTS)
|
|
699
|
transports = set(wireprototypes.TRANSPORTS)
|
|
@@
-701,14
+707,18
b" def wireprotocommand(name, args='', tran"
|
|
701
|
raise error.Abort(_('invalid transport policy value: %s') %
|
|
707
|
raise error.Abort(_('invalid transport policy value: %s') %
|
|
702
|
transportpolicy)
|
|
708
|
transportpolicy)
|
|
703
|
|
|
709
|
|
|
|
|
|
710
|
if permission not in ('push', 'pull'):
|
|
|
|
|
711
|
raise error.Abort(_('invalid wire protocol permission; got %s; '
|
|
|
|
|
712
|
'expected "push" or "pull"') % permission)
|
|
|
|
|
713
|
|
|
704
|
def register(func):
|
|
714
|
def register(func):
|
|
705
|
commands[name] = commandentry(func, args=args, transports=transports)
|
|
715
|
commands[name] = commandentry(func, args=args, transports=transports,
|
|
|
|
|
716
|
permission=permission)
|
|
706
|
return func
|
|
717
|
return func
|
|
707
|
return register
|
|
718
|
return register
|
|
708
|
|
|
719
|
|
|
709
|
# TODO define a more appropriate permissions type to use for this.
|
|
720
|
# TODO define a more appropriate permissions type to use for this.
|
|
710
|
permissions['batch'] = 'pull'
|
|
721
|
@wireprotocommand('batch', 'cmds *', permission='pull')
|
|
711
|
@wireprotocommand('batch', 'cmds *')
|
|
|
|
|
712
|
def batch(repo, proto, cmds, others):
|
|
722
|
def batch(repo, proto, cmds, others):
|
|
713
|
repo = repo.filtered("served")
|
|
723
|
repo = repo.filtered("served")
|
|
714
|
res = []
|
|
724
|
res = []
|
|
@@
-725,11
+735,9
b' def batch(repo, proto, cmds, others):'
|
|
725
|
# checking on each batched command.
|
|
735
|
# checking on each batched command.
|
|
726
|
# TODO formalize permission checking as part of protocol interface.
|
|
736
|
# TODO formalize permission checking as part of protocol interface.
|
|
727
|
if util.safehasattr(proto, 'checkperm'):
|
|
737
|
if util.safehasattr(proto, 'checkperm'):
|
|
728
|
# Assume commands with no defined permissions are writes / for
|
|
738
|
perm = commands[op].permission
|
|
729
|
# pushes. This is the safest from a security perspective because
|
|
739
|
assert perm in ('push', 'pull')
|
|
730
|
# it doesn't allow commands with undefined semantics from
|
|
740
|
proto.checkperm(perm)
|
|
731
|
# bypassing permissions checks.
|
|
|
|
|
732
|
proto.checkperm(permissions.get(op, 'push'))
|
|
|
|
|
733
|
|
|
741
|
|
|
734
|
if spec:
|
|
742
|
if spec:
|
|
735
|
keys = spec.split()
|
|
743
|
keys = spec.split()
|
|
@@
-758,8
+766,8
b' def batch(repo, proto, cmds, others):'
|
|
758
|
|
|
766
|
|
|
759
|
return bytesresponse(';'.join(res))
|
|
767
|
return bytesresponse(';'.join(res))
|
|
760
|
|
|
768
|
|
|
761
|
permissions['between'] = 'pull'
|
|
769
|
@wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY,
|
|
762
|
@wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY)
|
|
770
|
permission='pull')
|
|
763
|
def between(repo, proto, pairs):
|
|
771
|
def between(repo, proto, pairs):
|
|
764
|
pairs = [decodelist(p, '-') for p in pairs.split(" ")]
|
|
772
|
pairs = [decodelist(p, '-') for p in pairs.split(" ")]
|
|
765
|
r = []
|
|
773
|
r = []
|
|
@@
-768,8
+776,7
b' def between(repo, proto, pairs):'
|
|
768
|
|
|
776
|
|
|
769
|
return bytesresponse(''.join(r))
|
|
777
|
return bytesresponse(''.join(r))
|
|
770
|
|
|
778
|
|
|
771
|
permissions['branchmap'] = 'pull'
|
|
779
|
@wireprotocommand('branchmap', permission='pull')
|
|
772
|
@wireprotocommand('branchmap')
|
|
|
|
|
773
|
def branchmap(repo, proto):
|
|
780
|
def branchmap(repo, proto):
|
|
774
|
branchmap = repo.branchmap()
|
|
781
|
branchmap = repo.branchmap()
|
|
775
|
heads = []
|
|
782
|
heads = []
|
|
@@
-780,8
+787,8
b' def branchmap(repo, proto):'
|
|
780
|
|
|
787
|
|
|
781
|
return bytesresponse('\n'.join(heads))
|
|
788
|
return bytesresponse('\n'.join(heads))
|
|
782
|
|
|
789
|
|
|
783
|
permissions['branches'] = 'pull'
|
|
790
|
@wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY,
|
|
784
|
@wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY)
|
|
791
|
permission='pull')
|
|
785
|
def branches(repo, proto, nodes):
|
|
792
|
def branches(repo, proto, nodes):
|
|
786
|
nodes = decodelist(nodes)
|
|
793
|
nodes = decodelist(nodes)
|
|
787
|
r = []
|
|
794
|
r = []
|
|
@@
-790,8
+797,7
b' def branches(repo, proto, nodes):'
|
|
790
|
|
|
797
|
|
|
791
|
return bytesresponse(''.join(r))
|
|
798
|
return bytesresponse(''.join(r))
|
|
792
|
|
|
799
|
|
|
793
|
permissions['clonebundles'] = 'pull'
|
|
800
|
@wireprotocommand('clonebundles', '', permission='pull')
|
|
794
|
@wireprotocommand('clonebundles', '')
|
|
|
|
|
795
|
def clonebundles(repo, proto):
|
|
801
|
def clonebundles(repo, proto):
|
|
796
|
"""Server command for returning info for available bundles to seed clones.
|
|
802
|
"""Server command for returning info for available bundles to seed clones.
|
|
797
|
|
|
803
|
|
|
@@
-843,13
+849,12
b' def _capabilities(repo, proto):'
|
|
843
|
|
|
849
|
|
|
844
|
# If you are writing an extension and consider wrapping this function. Wrap
|
|
850
|
# If you are writing an extension and consider wrapping this function. Wrap
|
|
845
|
# `_capabilities` instead.
|
|
851
|
# `_capabilities` instead.
|
|
846
|
permissions['capabilities'] = 'pull'
|
|
852
|
@wireprotocommand('capabilities', permission='pull')
|
|
847
|
@wireprotocommand('capabilities')
|
|
|
|
|
848
|
def capabilities(repo, proto):
|
|
853
|
def capabilities(repo, proto):
|
|
849
|
return bytesresponse(' '.join(_capabilities(repo, proto)))
|
|
854
|
return bytesresponse(' '.join(_capabilities(repo, proto)))
|
|
850
|
|
|
855
|
|
|
851
|
permissions['changegroup'] = 'pull'
|
|
856
|
@wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY,
|
|
852
|
@wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY)
|
|
857
|
permission='pull')
|
|
853
|
def changegroup(repo, proto, roots):
|
|
858
|
def changegroup(repo, proto, roots):
|
|
854
|
nodes = decodelist(roots)
|
|
859
|
nodes = decodelist(roots)
|
|
855
|
outgoing = discovery.outgoing(repo, missingroots=nodes,
|
|
860
|
outgoing = discovery.outgoing(repo, missingroots=nodes,
|
|
@@
-858,9
+863,9
b' def changegroup(repo, proto, roots):'
|
|
858
|
gen = iter(lambda: cg.read(32768), '')
|
|
863
|
gen = iter(lambda: cg.read(32768), '')
|
|
859
|
return streamres(gen=gen)
|
|
864
|
return streamres(gen=gen)
|
|
860
|
|
|
865
|
|
|
861
|
permissions['changegroupsubset'] = 'pull'
|
|
|
|
|
862
|
@wireprotocommand('changegroupsubset', 'bases heads',
|
|
866
|
@wireprotocommand('changegroupsubset', 'bases heads',
|
|
863
|
transportpolicy=POLICY_V1_ONLY)
|
|
867
|
transportpolicy=POLICY_V1_ONLY,
|
|
|
|
|
868
|
permission='pull')
|
|
864
|
def changegroupsubset(repo, proto, bases, heads):
|
|
869
|
def changegroupsubset(repo, proto, bases, heads):
|
|
865
|
bases = decodelist(bases)
|
|
870
|
bases = decodelist(bases)
|
|
866
|
heads = decodelist(heads)
|
|
871
|
heads = decodelist(heads)
|
|
@@
-870,16
+875,15
b' def changegroupsubset(repo, proto, bases'
|
|
870
|
gen = iter(lambda: cg.read(32768), '')
|
|
875
|
gen = iter(lambda: cg.read(32768), '')
|
|
871
|
return streamres(gen=gen)
|
|
876
|
return streamres(gen=gen)
|
|
872
|
|
|
877
|
|
|
873
|
permissions['debugwireargs'] = 'pull'
|
|
878
|
@wireprotocommand('debugwireargs', 'one two *',
|
|
874
|
@wireprotocommand('debugwireargs', 'one two *')
|
|
879
|
permission='pull')
|
|
875
|
def debugwireargs(repo, proto, one, two, others):
|
|
880
|
def debugwireargs(repo, proto, one, two, others):
|
|
876
|
# only accept optional args from the known set
|
|
881
|
# only accept optional args from the known set
|
|
877
|
opts = options('debugwireargs', ['three', 'four'], others)
|
|
882
|
opts = options('debugwireargs', ['three', 'four'], others)
|
|
878
|
return bytesresponse(repo.debugwireargs(one, two,
|
|
883
|
return bytesresponse(repo.debugwireargs(one, two,
|
|
879
|
**pycompat.strkwargs(opts)))
|
|
884
|
**pycompat.strkwargs(opts)))
|
|
880
|
|
|
885
|
|
|
881
|
permissions['getbundle'] = 'pull'
|
|
886
|
@wireprotocommand('getbundle', '*', permission='pull')
|
|
882
|
@wireprotocommand('getbundle', '*')
|
|
|
|
|
883
|
def getbundle(repo, proto, others):
|
|
887
|
def getbundle(repo, proto, others):
|
|
884
|
opts = options('getbundle', gboptsmap.keys(), others)
|
|
888
|
opts = options('getbundle', gboptsmap.keys(), others)
|
|
885
|
for k, v in opts.iteritems():
|
|
889
|
for k, v in opts.iteritems():
|
|
@@
-945,14
+949,12
b' def getbundle(repo, proto, others):'
|
|
945
|
|
|
949
|
|
|
946
|
return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
|
|
950
|
return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
|
|
947
|
|
|
951
|
|
|
948
|
permissions['heads'] = 'pull'
|
|
952
|
@wireprotocommand('heads', permission='pull')
|
|
949
|
@wireprotocommand('heads')
|
|
|
|
|
950
|
def heads(repo, proto):
|
|
953
|
def heads(repo, proto):
|
|
951
|
h = repo.heads()
|
|
954
|
h = repo.heads()
|
|
952
|
return bytesresponse(encodelist(h) + '\n')
|
|
955
|
return bytesresponse(encodelist(h) + '\n')
|
|
953
|
|
|
956
|
|
|
954
|
permissions['hello'] = 'pull'
|
|
957
|
@wireprotocommand('hello', permission='pull')
|
|
955
|
@wireprotocommand('hello')
|
|
|
|
|
956
|
def hello(repo, proto):
|
|
958
|
def hello(repo, proto):
|
|
957
|
"""Called as part of SSH handshake to obtain server info.
|
|
959
|
"""Called as part of SSH handshake to obtain server info.
|
|
958
|
|
|
960
|
|
|
@@
-967,14
+969,12
b' def hello(repo, proto):'
|
|
967
|
caps = capabilities(repo, proto).data
|
|
969
|
caps = capabilities(repo, proto).data
|
|
968
|
return bytesresponse('capabilities: %s\n' % caps)
|
|
970
|
return bytesresponse('capabilities: %s\n' % caps)
|
|
969
|
|
|
971
|
|
|
970
|
permissions['listkeys'] = 'pull'
|
|
972
|
@wireprotocommand('listkeys', 'namespace', permission='pull')
|
|
971
|
@wireprotocommand('listkeys', 'namespace')
|
|
|
|
|
972
|
def listkeys(repo, proto, namespace):
|
|
973
|
def listkeys(repo, proto, namespace):
|
|
973
|
d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
|
|
974
|
d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
|
|
974
|
return bytesresponse(pushkeymod.encodekeys(d))
|
|
975
|
return bytesresponse(pushkeymod.encodekeys(d))
|
|
975
|
|
|
976
|
|
|
976
|
permissions['lookup'] = 'pull'
|
|
977
|
@wireprotocommand('lookup', 'key', permission='pull')
|
|
977
|
@wireprotocommand('lookup', 'key')
|
|
|
|
|
978
|
def lookup(repo, proto, key):
|
|
978
|
def lookup(repo, proto, key):
|
|
979
|
try:
|
|
979
|
try:
|
|
980
|
k = encoding.tolocal(key)
|
|
980
|
k = encoding.tolocal(key)
|
|
@@
-986,14
+986,12
b' def lookup(repo, proto, key):'
|
|
986
|
success = 0
|
|
986
|
success = 0
|
|
987
|
return bytesresponse('%d %s\n' % (success, r))
|
|
987
|
return bytesresponse('%d %s\n' % (success, r))
|
|
988
|
|
|
988
|
|
|
989
|
permissions['known'] = 'pull'
|
|
989
|
@wireprotocommand('known', 'nodes *', permission='pull')
|
|
990
|
@wireprotocommand('known', 'nodes *')
|
|
|
|
|
991
|
def known(repo, proto, nodes, others):
|
|
990
|
def known(repo, proto, nodes, others):
|
|
992
|
v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
|
|
991
|
v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
|
|
993
|
return bytesresponse(v)
|
|
992
|
return bytesresponse(v)
|
|
994
|
|
|
993
|
|
|
995
|
permissions['pushkey'] = 'push'
|
|
994
|
@wireprotocommand('pushkey', 'namespace key old new', permission='push')
|
|
996
|
@wireprotocommand('pushkey', 'namespace key old new')
|
|
|
|
|
997
|
def pushkey(repo, proto, namespace, key, old, new):
|
|
995
|
def pushkey(repo, proto, namespace, key, old, new):
|
|
998
|
# compatibility with pre-1.8 clients which were accidentally
|
|
996
|
# compatibility with pre-1.8 clients which were accidentally
|
|
999
|
# sending raw binary nodes rather than utf-8-encoded hex
|
|
997
|
# sending raw binary nodes rather than utf-8-encoded hex
|
|
@@
-1014,8
+1012,7
b' def pushkey(repo, proto, namespace, key,'
|
|
1014
|
output = output.getvalue() if output else ''
|
|
1012
|
output = output.getvalue() if output else ''
|
|
1015
|
return bytesresponse('%d\n%s' % (int(r), output))
|
|
1013
|
return bytesresponse('%d\n%s' % (int(r), output))
|
|
1016
|
|
|
1014
|
|
|
1017
|
permissions['stream_out'] = 'pull'
|
|
1015
|
@wireprotocommand('stream_out', permission='pull')
|
|
1018
|
@wireprotocommand('stream_out')
|
|
|
|
|
1019
|
def stream(repo, proto):
|
|
1016
|
def stream(repo, proto):
|
|
1020
|
'''If the server supports streaming clone, it advertises the "stream"
|
|
1017
|
'''If the server supports streaming clone, it advertises the "stream"
|
|
1021
|
capability with a value representing the version and flags of the repo
|
|
1018
|
capability with a value representing the version and flags of the repo
|
|
@@
-1023,8
+1020,7
b' def stream(repo, proto):'
|
|
1023
|
'''
|
|
1020
|
'''
|
|
1024
|
return streamres_legacy(streamclone.generatev1wireproto(repo))
|
|
1021
|
return streamres_legacy(streamclone.generatev1wireproto(repo))
|
|
1025
|
|
|
1022
|
|
|
1026
|
permissions['unbundle'] = 'push'
|
|
1023
|
@wireprotocommand('unbundle', 'heads', permission='push')
|
|
1027
|
@wireprotocommand('unbundle', 'heads')
|
|
|
|
|
1028
|
def unbundle(repo, proto, heads):
|
|
1024
|
def unbundle(repo, proto, heads):
|
|
1029
|
their_heads = decodelist(heads)
|
|
1025
|
their_heads = decodelist(heads)
|
|
1030
|
|
|
1026
|
|