##// END OF EJS Templates
narrow: move adding of narrow server capabilities to core...
Pulkit Goyal -
r40111:ad8d8dc9 default
parent child Browse files
Show More
@@ -1,115 +1,106
1 # narrowwirepeer.py - passes narrow spec with unbundle command
1 # narrowwirepeer.py - passes narrow spec with unbundle command
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial import (
10 from mercurial import (
11 bundle2,
11 bundle2,
12 error,
12 error,
13 extensions,
13 extensions,
14 hg,
14 hg,
15 match as matchmod,
15 match as matchmod,
16 narrowspec,
16 narrowspec,
17 pycompat,
17 pycompat,
18 wireprototypes,
18 wireprototypes,
19 wireprotov1peer,
19 wireprotov1peer,
20 wireprotov1server,
20 wireprotov1server,
21 )
21 )
22
22
23 def uisetup():
23 def uisetup():
24 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
25 wireprotov1peer.wirepeer.narrow_widen = peernarrowwiden
24 wireprotov1peer.wirepeer.narrow_widen = peernarrowwiden
26
25
27 def addnarrowcap(orig, repo, proto):
28 """add the narrow capability to the server"""
29 caps = orig(repo, proto)
30 caps.append(wireprototypes.NARROWCAP)
31 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
32 caps.append(wireprototypes.ELLIPSESCAP)
33 return caps
34
35 def reposetup(repo):
26 def reposetup(repo):
36 def wirereposetup(ui, peer):
27 def wirereposetup(ui, peer):
37 def wrapped(orig, cmd, *args, **kwargs):
28 def wrapped(orig, cmd, *args, **kwargs):
38 if cmd == 'unbundle':
29 if cmd == 'unbundle':
39 # TODO: don't blindly add include/exclude wireproto
30 # TODO: don't blindly add include/exclude wireproto
40 # arguments to unbundle.
31 # arguments to unbundle.
41 include, exclude = repo.narrowpats
32 include, exclude = repo.narrowpats
42 kwargs[r"includepats"] = ','.join(include)
33 kwargs[r"includepats"] = ','.join(include)
43 kwargs[r"excludepats"] = ','.join(exclude)
34 kwargs[r"excludepats"] = ','.join(exclude)
44 return orig(cmd, *args, **kwargs)
35 return orig(cmd, *args, **kwargs)
45 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
36 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
46 hg.wirepeersetupfuncs.append(wirereposetup)
37 hg.wirepeersetupfuncs.append(wirereposetup)
47
38
48 @wireprotov1server.wireprotocommand('narrow_widen', 'oldincludes oldexcludes'
39 @wireprotov1server.wireprotocommand('narrow_widen', 'oldincludes oldexcludes'
49 ' newincludes newexcludes'
40 ' newincludes newexcludes'
50 ' commonheads cgversion'
41 ' commonheads cgversion'
51 ' known ellipses',
42 ' known ellipses',
52 permission='pull')
43 permission='pull')
53 def narrow_widen(repo, proto, oldincludes, oldexcludes, newincludes,
44 def narrow_widen(repo, proto, oldincludes, oldexcludes, newincludes,
54 newexcludes, commonheads, cgversion, known, ellipses):
45 newexcludes, commonheads, cgversion, known, ellipses):
55 """wireprotocol command to send data when a narrow clone is widen. We will
46 """wireprotocol command to send data when a narrow clone is widen. We will
56 be sending a changegroup here.
47 be sending a changegroup here.
57
48
58 The current set of arguments which are required:
49 The current set of arguments which are required:
59 oldincludes: the old includes of the narrow copy
50 oldincludes: the old includes of the narrow copy
60 oldexcludes: the old excludes of the narrow copy
51 oldexcludes: the old excludes of the narrow copy
61 newincludes: the new includes of the narrow copy
52 newincludes: the new includes of the narrow copy
62 newexcludes: the new excludes of the narrow copy
53 newexcludes: the new excludes of the narrow copy
63 commonheads: list of heads which are common between the server and client
54 commonheads: list of heads which are common between the server and client
64 cgversion(maybe): the changegroup version to produce
55 cgversion(maybe): the changegroup version to produce
65 known: list of nodes which are known on the client (used in ellipses cases)
56 known: list of nodes which are known on the client (used in ellipses cases)
66 ellipses: whether to send ellipses data or not
57 ellipses: whether to send ellipses data or not
67 """
58 """
68
59
69 try:
60 try:
70 oldincludes = wireprototypes.decodelist(oldincludes)
61 oldincludes = wireprototypes.decodelist(oldincludes)
71 newincludes = wireprototypes.decodelist(newincludes)
62 newincludes = wireprototypes.decodelist(newincludes)
72 oldexcludes = wireprototypes.decodelist(oldexcludes)
63 oldexcludes = wireprototypes.decodelist(oldexcludes)
73 newexcludes = wireprototypes.decodelist(newexcludes)
64 newexcludes = wireprototypes.decodelist(newexcludes)
74 # validate the patterns
65 # validate the patterns
75 narrowspec.validatepatterns(set(oldincludes))
66 narrowspec.validatepatterns(set(oldincludes))
76 narrowspec.validatepatterns(set(newincludes))
67 narrowspec.validatepatterns(set(newincludes))
77 narrowspec.validatepatterns(set(oldexcludes))
68 narrowspec.validatepatterns(set(oldexcludes))
78 narrowspec.validatepatterns(set(newexcludes))
69 narrowspec.validatepatterns(set(newexcludes))
79
70
80 common = wireprototypes.decodelist(commonheads)
71 common = wireprototypes.decodelist(commonheads)
81 known = None
72 known = None
82 if known:
73 if known:
83 known = wireprototypes.decodelist(known)
74 known = wireprototypes.decodelist(known)
84 if ellipses == '0':
75 if ellipses == '0':
85 ellipses = False
76 ellipses = False
86 else:
77 else:
87 ellipses = bool(ellipses)
78 ellipses = bool(ellipses)
88 cgversion = cgversion
79 cgversion = cgversion
89 newmatch = narrowspec.match(repo.root, include=newincludes,
80 newmatch = narrowspec.match(repo.root, include=newincludes,
90 exclude=newexcludes)
81 exclude=newexcludes)
91 oldmatch = narrowspec.match(repo.root, include=oldincludes,
82 oldmatch = narrowspec.match(repo.root, include=oldincludes,
92 exclude=oldexcludes)
83 exclude=oldexcludes)
93 diffmatch = matchmod.differencematcher(newmatch, oldmatch)
84 diffmatch = matchmod.differencematcher(newmatch, oldmatch)
94
85
95 bundler = bundle2.widen_bundle(repo, diffmatch, common, known,
86 bundler = bundle2.widen_bundle(repo, diffmatch, common, known,
96 cgversion, ellipses)
87 cgversion, ellipses)
97 except error.Abort as exc:
88 except error.Abort as exc:
98 bundler = bundle2.bundle20(repo.ui)
89 bundler = bundle2.bundle20(repo.ui)
99 manargs = [('message', pycompat.bytestr(exc))]
90 manargs = [('message', pycompat.bytestr(exc))]
100 advargs = []
91 advargs = []
101 if exc.hint is not None:
92 if exc.hint is not None:
102 advargs.append(('hint', exc.hint))
93 advargs.append(('hint', exc.hint))
103 bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs))
94 bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs))
104
95
105 chunks = bundler.getchunks()
96 chunks = bundler.getchunks()
106 return wireprototypes.streamres(gen=chunks)
97 return wireprototypes.streamres(gen=chunks)
107
98
108 def peernarrowwiden(remote, **kwargs):
99 def peernarrowwiden(remote, **kwargs):
109 for ch in ('oldincludes', 'newincludes', 'oldexcludes', 'newexcludes',
100 for ch in ('oldincludes', 'newincludes', 'oldexcludes', 'newexcludes',
110 'commonheads', 'known'):
101 'commonheads', 'known'):
111 kwargs[ch] = wireprototypes.encodelist(kwargs[ch])
102 kwargs[ch] = wireprototypes.encodelist(kwargs[ch])
112
103
113 kwargs['ellipses'] = '%i' % bool(kwargs['ellipses'])
104 kwargs['ellipses'] = '%i' % bool(kwargs['ellipses'])
114 f = remote._callcompressable('narrow_widen', **kwargs)
105 f = remote._callcompressable('narrow_widen', **kwargs)
115 return bundle2.getunbundler(remote.ui, f)
106 return bundle2.getunbundler(remote.ui, f)
@@ -1,668 +1,673
1 # wireprotov1server.py - Wire protocol version 1 server functionality
1 # wireprotov1server.py - Wire protocol version 1 server functionality
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 hex,
14 hex,
15 nullid,
15 nullid,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 bundle2,
19 bundle2,
20 changegroup as changegroupmod,
20 changegroup as changegroupmod,
21 discovery,
21 discovery,
22 encoding,
22 encoding,
23 error,
23 error,
24 exchange,
24 exchange,
25 pushkey as pushkeymod,
25 pushkey as pushkeymod,
26 pycompat,
26 pycompat,
27 streamclone,
27 streamclone,
28 util,
28 util,
29 wireprototypes,
29 wireprototypes,
30 )
30 )
31
31
32 from .utils import (
32 from .utils import (
33 procutil,
33 procutil,
34 stringutil,
34 stringutil,
35 )
35 )
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
40 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
41 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
41 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
42 'IncompatibleClient')
42 'IncompatibleClient')
43 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
43 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
44
44
45 def clientcompressionsupport(proto):
45 def clientcompressionsupport(proto):
46 """Returns a list of compression methods supported by the client.
46 """Returns a list of compression methods supported by the client.
47
47
48 Returns a list of the compression methods supported by the client
48 Returns a list of the compression methods supported by the client
49 according to the protocol capabilities. If no such capability has
49 according to the protocol capabilities. If no such capability has
50 been announced, fallback to the default of zlib and uncompressed.
50 been announced, fallback to the default of zlib and uncompressed.
51 """
51 """
52 for cap in proto.getprotocaps():
52 for cap in proto.getprotocaps():
53 if cap.startswith('comp='):
53 if cap.startswith('comp='):
54 return cap[5:].split(',')
54 return cap[5:].split(',')
55 return ['zlib', 'none']
55 return ['zlib', 'none']
56
56
57 # wire protocol command can either return a string or one of these classes.
57 # wire protocol command can either return a string or one of these classes.
58
58
59 def getdispatchrepo(repo, proto, command):
59 def getdispatchrepo(repo, proto, command):
60 """Obtain the repo used for processing wire protocol commands.
60 """Obtain the repo used for processing wire protocol commands.
61
61
62 The intent of this function is to serve as a monkeypatch point for
62 The intent of this function is to serve as a monkeypatch point for
63 extensions that need commands to operate on different repo views under
63 extensions that need commands to operate on different repo views under
64 specialized circumstances.
64 specialized circumstances.
65 """
65 """
66 return repo.filtered('served')
66 return repo.filtered('served')
67
67
68 def dispatch(repo, proto, command):
68 def dispatch(repo, proto, command):
69 repo = getdispatchrepo(repo, proto, command)
69 repo = getdispatchrepo(repo, proto, command)
70
70
71 func, spec = commands[command]
71 func, spec = commands[command]
72 args = proto.getargs(spec)
72 args = proto.getargs(spec)
73
73
74 return func(repo, proto, *args)
74 return func(repo, proto, *args)
75
75
76 def options(cmd, keys, others):
76 def options(cmd, keys, others):
77 opts = {}
77 opts = {}
78 for k in keys:
78 for k in keys:
79 if k in others:
79 if k in others:
80 opts[k] = others[k]
80 opts[k] = others[k]
81 del others[k]
81 del others[k]
82 if others:
82 if others:
83 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n"
83 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n"
84 % (cmd, ",".join(others)))
84 % (cmd, ",".join(others)))
85 return opts
85 return opts
86
86
87 def bundle1allowed(repo, action):
87 def bundle1allowed(repo, action):
88 """Whether a bundle1 operation is allowed from the server.
88 """Whether a bundle1 operation is allowed from the server.
89
89
90 Priority is:
90 Priority is:
91
91
92 1. server.bundle1gd.<action> (if generaldelta active)
92 1. server.bundle1gd.<action> (if generaldelta active)
93 2. server.bundle1.<action>
93 2. server.bundle1.<action>
94 3. server.bundle1gd (if generaldelta active)
94 3. server.bundle1gd (if generaldelta active)
95 4. server.bundle1
95 4. server.bundle1
96 """
96 """
97 ui = repo.ui
97 ui = repo.ui
98 gd = 'generaldelta' in repo.requirements
98 gd = 'generaldelta' in repo.requirements
99
99
100 if gd:
100 if gd:
101 v = ui.configbool('server', 'bundle1gd.%s' % action)
101 v = ui.configbool('server', 'bundle1gd.%s' % action)
102 if v is not None:
102 if v is not None:
103 return v
103 return v
104
104
105 v = ui.configbool('server', 'bundle1.%s' % action)
105 v = ui.configbool('server', 'bundle1.%s' % action)
106 if v is not None:
106 if v is not None:
107 return v
107 return v
108
108
109 if gd:
109 if gd:
110 v = ui.configbool('server', 'bundle1gd')
110 v = ui.configbool('server', 'bundle1gd')
111 if v is not None:
111 if v is not None:
112 return v
112 return v
113
113
114 return ui.configbool('server', 'bundle1')
114 return ui.configbool('server', 'bundle1')
115
115
116 commands = wireprototypes.commanddict()
116 commands = wireprototypes.commanddict()
117
117
118 def wireprotocommand(name, args=None, permission='push'):
118 def wireprotocommand(name, args=None, permission='push'):
119 """Decorator to declare a wire protocol command.
119 """Decorator to declare a wire protocol command.
120
120
121 ``name`` is the name of the wire protocol command being provided.
121 ``name`` is the name of the wire protocol command being provided.
122
122
123 ``args`` defines the named arguments accepted by the command. It is
123 ``args`` defines the named arguments accepted by the command. It is
124 a space-delimited list of argument names. ``*`` denotes a special value
124 a space-delimited list of argument names. ``*`` denotes a special value
125 that says to accept all named arguments.
125 that says to accept all named arguments.
126
126
127 ``permission`` defines the permission type needed to run this command.
127 ``permission`` defines the permission type needed to run this command.
128 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
128 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
129 respectively. Default is to assume command requires ``push`` permissions
129 respectively. Default is to assume command requires ``push`` permissions
130 because otherwise commands not declaring their permissions could modify
130 because otherwise commands not declaring their permissions could modify
131 a repository that is supposed to be read-only.
131 a repository that is supposed to be read-only.
132 """
132 """
133 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
133 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
134 if v['version'] == 1}
134 if v['version'] == 1}
135
135
136 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to
136 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to
137 # SSHv2.
137 # SSHv2.
138 # TODO undo this hack when SSH is using the unified frame protocol.
138 # TODO undo this hack when SSH is using the unified frame protocol.
139 if name == b'batch':
139 if name == b'batch':
140 transports.add(wireprototypes.SSHV2)
140 transports.add(wireprototypes.SSHV2)
141
141
142 if permission not in ('push', 'pull'):
142 if permission not in ('push', 'pull'):
143 raise error.ProgrammingError('invalid wire protocol permission; '
143 raise error.ProgrammingError('invalid wire protocol permission; '
144 'got %s; expected "push" or "pull"' %
144 'got %s; expected "push" or "pull"' %
145 permission)
145 permission)
146
146
147 if args is None:
147 if args is None:
148 args = ''
148 args = ''
149
149
150 if not isinstance(args, bytes):
150 if not isinstance(args, bytes):
151 raise error.ProgrammingError('arguments for version 1 commands '
151 raise error.ProgrammingError('arguments for version 1 commands '
152 'must be declared as bytes')
152 'must be declared as bytes')
153
153
154 def register(func):
154 def register(func):
155 if name in commands:
155 if name in commands:
156 raise error.ProgrammingError('%s command already registered '
156 raise error.ProgrammingError('%s command already registered '
157 'for version 1' % name)
157 'for version 1' % name)
158 commands[name] = wireprototypes.commandentry(
158 commands[name] = wireprototypes.commandentry(
159 func, args=args, transports=transports, permission=permission)
159 func, args=args, transports=transports, permission=permission)
160
160
161 return func
161 return func
162 return register
162 return register
163
163
164 # TODO define a more appropriate permissions type to use for this.
164 # TODO define a more appropriate permissions type to use for this.
165 @wireprotocommand('batch', 'cmds *', permission='pull')
165 @wireprotocommand('batch', 'cmds *', permission='pull')
166 def batch(repo, proto, cmds, others):
166 def batch(repo, proto, cmds, others):
167 unescapearg = wireprototypes.unescapebatcharg
167 unescapearg = wireprototypes.unescapebatcharg
168 repo = repo.filtered("served")
168 repo = repo.filtered("served")
169 res = []
169 res = []
170 for pair in cmds.split(';'):
170 for pair in cmds.split(';'):
171 op, args = pair.split(' ', 1)
171 op, args = pair.split(' ', 1)
172 vals = {}
172 vals = {}
173 for a in args.split(','):
173 for a in args.split(','):
174 if a:
174 if a:
175 n, v = a.split('=')
175 n, v = a.split('=')
176 vals[unescapearg(n)] = unescapearg(v)
176 vals[unescapearg(n)] = unescapearg(v)
177 func, spec = commands[op]
177 func, spec = commands[op]
178
178
179 # Validate that client has permissions to perform this command.
179 # Validate that client has permissions to perform this command.
180 perm = commands[op].permission
180 perm = commands[op].permission
181 assert perm in ('push', 'pull')
181 assert perm in ('push', 'pull')
182 proto.checkperm(perm)
182 proto.checkperm(perm)
183
183
184 if spec:
184 if spec:
185 keys = spec.split()
185 keys = spec.split()
186 data = {}
186 data = {}
187 for k in keys:
187 for k in keys:
188 if k == '*':
188 if k == '*':
189 star = {}
189 star = {}
190 for key in vals.keys():
190 for key in vals.keys():
191 if key not in keys:
191 if key not in keys:
192 star[key] = vals[key]
192 star[key] = vals[key]
193 data['*'] = star
193 data['*'] = star
194 else:
194 else:
195 data[k] = vals[k]
195 data[k] = vals[k]
196 result = func(repo, proto, *[data[k] for k in keys])
196 result = func(repo, proto, *[data[k] for k in keys])
197 else:
197 else:
198 result = func(repo, proto)
198 result = func(repo, proto)
199 if isinstance(result, wireprototypes.ooberror):
199 if isinstance(result, wireprototypes.ooberror):
200 return result
200 return result
201
201
202 # For now, all batchable commands must return bytesresponse or
202 # For now, all batchable commands must return bytesresponse or
203 # raw bytes (for backwards compatibility).
203 # raw bytes (for backwards compatibility).
204 assert isinstance(result, (wireprototypes.bytesresponse, bytes))
204 assert isinstance(result, (wireprototypes.bytesresponse, bytes))
205 if isinstance(result, wireprototypes.bytesresponse):
205 if isinstance(result, wireprototypes.bytesresponse):
206 result = result.data
206 result = result.data
207 res.append(wireprototypes.escapebatcharg(result))
207 res.append(wireprototypes.escapebatcharg(result))
208
208
209 return wireprototypes.bytesresponse(';'.join(res))
209 return wireprototypes.bytesresponse(';'.join(res))
210
210
211 @wireprotocommand('between', 'pairs', permission='pull')
211 @wireprotocommand('between', 'pairs', permission='pull')
212 def between(repo, proto, pairs):
212 def between(repo, proto, pairs):
213 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")]
213 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")]
214 r = []
214 r = []
215 for b in repo.between(pairs):
215 for b in repo.between(pairs):
216 r.append(wireprototypes.encodelist(b) + "\n")
216 r.append(wireprototypes.encodelist(b) + "\n")
217
217
218 return wireprototypes.bytesresponse(''.join(r))
218 return wireprototypes.bytesresponse(''.join(r))
219
219
220 @wireprotocommand('branchmap', permission='pull')
220 @wireprotocommand('branchmap', permission='pull')
221 def branchmap(repo, proto):
221 def branchmap(repo, proto):
222 branchmap = repo.branchmap()
222 branchmap = repo.branchmap()
223 heads = []
223 heads = []
224 for branch, nodes in branchmap.iteritems():
224 for branch, nodes in branchmap.iteritems():
225 branchname = urlreq.quote(encoding.fromlocal(branch))
225 branchname = urlreq.quote(encoding.fromlocal(branch))
226 branchnodes = wireprototypes.encodelist(nodes)
226 branchnodes = wireprototypes.encodelist(nodes)
227 heads.append('%s %s' % (branchname, branchnodes))
227 heads.append('%s %s' % (branchname, branchnodes))
228
228
229 return wireprototypes.bytesresponse('\n'.join(heads))
229 return wireprototypes.bytesresponse('\n'.join(heads))
230
230
231 @wireprotocommand('branches', 'nodes', permission='pull')
231 @wireprotocommand('branches', 'nodes', permission='pull')
232 def branches(repo, proto, nodes):
232 def branches(repo, proto, nodes):
233 nodes = wireprototypes.decodelist(nodes)
233 nodes = wireprototypes.decodelist(nodes)
234 r = []
234 r = []
235 for b in repo.branches(nodes):
235 for b in repo.branches(nodes):
236 r.append(wireprototypes.encodelist(b) + "\n")
236 r.append(wireprototypes.encodelist(b) + "\n")
237
237
238 return wireprototypes.bytesresponse(''.join(r))
238 return wireprototypes.bytesresponse(''.join(r))
239
239
240 @wireprotocommand('clonebundles', '', permission='pull')
240 @wireprotocommand('clonebundles', '', permission='pull')
241 def clonebundles(repo, proto):
241 def clonebundles(repo, proto):
242 """Server command for returning info for available bundles to seed clones.
242 """Server command for returning info for available bundles to seed clones.
243
243
244 Clients will parse this response and determine what bundle to fetch.
244 Clients will parse this response and determine what bundle to fetch.
245
245
246 Extensions may wrap this command to filter or dynamically emit data
246 Extensions may wrap this command to filter or dynamically emit data
247 depending on the request. e.g. you could advertise URLs for the closest
247 depending on the request. e.g. you could advertise URLs for the closest
248 data center given the client's IP address.
248 data center given the client's IP address.
249 """
249 """
250 return wireprototypes.bytesresponse(
250 return wireprototypes.bytesresponse(
251 repo.vfs.tryread('clonebundles.manifest'))
251 repo.vfs.tryread('clonebundles.manifest'))
252
252
253 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
253 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
254 'known', 'getbundle', 'unbundlehash']
254 'known', 'getbundle', 'unbundlehash']
255
255
256 def _capabilities(repo, proto):
256 def _capabilities(repo, proto):
257 """return a list of capabilities for a repo
257 """return a list of capabilities for a repo
258
258
259 This function exists to allow extensions to easily wrap capabilities
259 This function exists to allow extensions to easily wrap capabilities
260 computation
260 computation
261
261
262 - returns a lists: easy to alter
262 - returns a lists: easy to alter
263 - change done here will be propagated to both `capabilities` and `hello`
263 - change done here will be propagated to both `capabilities` and `hello`
264 command without any other action needed.
264 command without any other action needed.
265 """
265 """
266 # copy to prevent modification of the global list
266 # copy to prevent modification of the global list
267 caps = list(wireprotocaps)
267 caps = list(wireprotocaps)
268
268
269 # Command of same name as capability isn't exposed to version 1 of
269 # Command of same name as capability isn't exposed to version 1 of
270 # transports. So conditionally add it.
270 # transports. So conditionally add it.
271 if commands.commandavailable('changegroupsubset', proto):
271 if commands.commandavailable('changegroupsubset', proto):
272 caps.append('changegroupsubset')
272 caps.append('changegroupsubset')
273
273
274 if streamclone.allowservergeneration(repo):
274 if streamclone.allowservergeneration(repo):
275 if repo.ui.configbool('server', 'preferuncompressed'):
275 if repo.ui.configbool('server', 'preferuncompressed'):
276 caps.append('stream-preferred')
276 caps.append('stream-preferred')
277 requiredformats = repo.requirements & repo.supportedformats
277 requiredformats = repo.requirements & repo.supportedformats
278 # if our local revlogs are just revlogv1, add 'stream' cap
278 # if our local revlogs are just revlogv1, add 'stream' cap
279 if not requiredformats - {'revlogv1'}:
279 if not requiredformats - {'revlogv1'}:
280 caps.append('stream')
280 caps.append('stream')
281 # otherwise, add 'streamreqs' detailing our local revlog format
281 # otherwise, add 'streamreqs' detailing our local revlog format
282 else:
282 else:
283 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
283 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
284 if repo.ui.configbool('experimental', 'bundle2-advertise'):
284 if repo.ui.configbool('experimental', 'bundle2-advertise'):
285 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
285 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
286 caps.append('bundle2=' + urlreq.quote(capsblob))
286 caps.append('bundle2=' + urlreq.quote(capsblob))
287 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
287 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
288
288
289 if repo.ui.configbool('experimental', 'narrow'):
290 caps.append(wireprototypes.NARROWCAP)
291 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
292 caps.append(wireprototypes.ELLIPSESCAP)
293
289 return proto.addcapabilities(repo, caps)
294 return proto.addcapabilities(repo, caps)
290
295
291 # If you are writing an extension and consider wrapping this function. Wrap
296 # If you are writing an extension and consider wrapping this function. Wrap
292 # `_capabilities` instead.
297 # `_capabilities` instead.
293 @wireprotocommand('capabilities', permission='pull')
298 @wireprotocommand('capabilities', permission='pull')
294 def capabilities(repo, proto):
299 def capabilities(repo, proto):
295 caps = _capabilities(repo, proto)
300 caps = _capabilities(repo, proto)
296 return wireprototypes.bytesresponse(' '.join(sorted(caps)))
301 return wireprototypes.bytesresponse(' '.join(sorted(caps)))
297
302
298 @wireprotocommand('changegroup', 'roots', permission='pull')
303 @wireprotocommand('changegroup', 'roots', permission='pull')
299 def changegroup(repo, proto, roots):
304 def changegroup(repo, proto, roots):
300 nodes = wireprototypes.decodelist(roots)
305 nodes = wireprototypes.decodelist(roots)
301 outgoing = discovery.outgoing(repo, missingroots=nodes,
306 outgoing = discovery.outgoing(repo, missingroots=nodes,
302 missingheads=repo.heads())
307 missingheads=repo.heads())
303 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
308 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
304 gen = iter(lambda: cg.read(32768), '')
309 gen = iter(lambda: cg.read(32768), '')
305 return wireprototypes.streamres(gen=gen)
310 return wireprototypes.streamres(gen=gen)
306
311
307 @wireprotocommand('changegroupsubset', 'bases heads',
312 @wireprotocommand('changegroupsubset', 'bases heads',
308 permission='pull')
313 permission='pull')
309 def changegroupsubset(repo, proto, bases, heads):
314 def changegroupsubset(repo, proto, bases, heads):
310 bases = wireprototypes.decodelist(bases)
315 bases = wireprototypes.decodelist(bases)
311 heads = wireprototypes.decodelist(heads)
316 heads = wireprototypes.decodelist(heads)
312 outgoing = discovery.outgoing(repo, missingroots=bases,
317 outgoing = discovery.outgoing(repo, missingroots=bases,
313 missingheads=heads)
318 missingheads=heads)
314 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
319 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
315 gen = iter(lambda: cg.read(32768), '')
320 gen = iter(lambda: cg.read(32768), '')
316 return wireprototypes.streamres(gen=gen)
321 return wireprototypes.streamres(gen=gen)
317
322
318 @wireprotocommand('debugwireargs', 'one two *',
323 @wireprotocommand('debugwireargs', 'one two *',
319 permission='pull')
324 permission='pull')
320 def debugwireargs(repo, proto, one, two, others):
325 def debugwireargs(repo, proto, one, two, others):
321 # only accept optional args from the known set
326 # only accept optional args from the known set
322 opts = options('debugwireargs', ['three', 'four'], others)
327 opts = options('debugwireargs', ['three', 'four'], others)
323 return wireprototypes.bytesresponse(repo.debugwireargs(
328 return wireprototypes.bytesresponse(repo.debugwireargs(
324 one, two, **pycompat.strkwargs(opts)))
329 one, two, **pycompat.strkwargs(opts)))
325
330
326 def find_pullbundle(repo, proto, opts, clheads, heads, common):
331 def find_pullbundle(repo, proto, opts, clheads, heads, common):
327 """Return a file object for the first matching pullbundle.
332 """Return a file object for the first matching pullbundle.
328
333
329 Pullbundles are specified in .hg/pullbundles.manifest similar to
334 Pullbundles are specified in .hg/pullbundles.manifest similar to
330 clonebundles.
335 clonebundles.
331 For each entry, the bundle specification is checked for compatibility:
336 For each entry, the bundle specification is checked for compatibility:
332 - Client features vs the BUNDLESPEC.
337 - Client features vs the BUNDLESPEC.
333 - Revisions shared with the clients vs base revisions of the bundle.
338 - Revisions shared with the clients vs base revisions of the bundle.
334 A bundle can be applied only if all its base revisions are known by
339 A bundle can be applied only if all its base revisions are known by
335 the client.
340 the client.
336 - At least one leaf of the bundle's DAG is missing on the client.
341 - At least one leaf of the bundle's DAG is missing on the client.
337 - Every leaf of the bundle's DAG is part of node set the client wants.
342 - Every leaf of the bundle's DAG is part of node set the client wants.
338 E.g. do not send a bundle of all changes if the client wants only
343 E.g. do not send a bundle of all changes if the client wants only
339 one specific branch of many.
344 one specific branch of many.
340 """
345 """
341 def decodehexstring(s):
346 def decodehexstring(s):
342 return set([h.decode('hex') for h in s.split(';')])
347 return set([h.decode('hex') for h in s.split(';')])
343
348
344 manifest = repo.vfs.tryread('pullbundles.manifest')
349 manifest = repo.vfs.tryread('pullbundles.manifest')
345 if not manifest:
350 if not manifest:
346 return None
351 return None
347 res = exchange.parseclonebundlesmanifest(repo, manifest)
352 res = exchange.parseclonebundlesmanifest(repo, manifest)
348 res = exchange.filterclonebundleentries(repo, res)
353 res = exchange.filterclonebundleentries(repo, res)
349 if not res:
354 if not res:
350 return None
355 return None
351 cl = repo.changelog
356 cl = repo.changelog
352 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
357 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
353 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
358 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
354 compformats = clientcompressionsupport(proto)
359 compformats = clientcompressionsupport(proto)
355 for entry in res:
360 for entry in res:
356 comp = entry.get('COMPRESSION')
361 comp = entry.get('COMPRESSION')
357 altcomp = util.compengines._bundlenames.get(comp)
362 altcomp = util.compengines._bundlenames.get(comp)
358 if comp and comp not in compformats and altcomp not in compformats:
363 if comp and comp not in compformats and altcomp not in compformats:
359 continue
364 continue
360 # No test yet for VERSION, since V2 is supported by any client
365 # No test yet for VERSION, since V2 is supported by any client
361 # that advertises partial pulls
366 # that advertises partial pulls
362 if 'heads' in entry:
367 if 'heads' in entry:
363 try:
368 try:
364 bundle_heads = decodehexstring(entry['heads'])
369 bundle_heads = decodehexstring(entry['heads'])
365 except TypeError:
370 except TypeError:
366 # Bad heads entry
371 # Bad heads entry
367 continue
372 continue
368 if bundle_heads.issubset(common):
373 if bundle_heads.issubset(common):
369 continue # Nothing new
374 continue # Nothing new
370 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
375 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
371 continue # Still nothing new
376 continue # Still nothing new
372 if any(cl.rev(rev) not in heads_anc and
377 if any(cl.rev(rev) not in heads_anc and
373 cl.rev(rev) not in common_anc for rev in bundle_heads):
378 cl.rev(rev) not in common_anc for rev in bundle_heads):
374 continue
379 continue
375 if 'bases' in entry:
380 if 'bases' in entry:
376 try:
381 try:
377 bundle_bases = decodehexstring(entry['bases'])
382 bundle_bases = decodehexstring(entry['bases'])
378 except TypeError:
383 except TypeError:
379 # Bad bases entry
384 # Bad bases entry
380 continue
385 continue
381 if not all(cl.rev(rev) in common_anc for rev in bundle_bases):
386 if not all(cl.rev(rev) in common_anc for rev in bundle_bases):
382 continue
387 continue
383 path = entry['URL']
388 path = entry['URL']
384 repo.ui.debug('sending pullbundle "%s"\n' % path)
389 repo.ui.debug('sending pullbundle "%s"\n' % path)
385 try:
390 try:
386 return repo.vfs.open(path)
391 return repo.vfs.open(path)
387 except IOError:
392 except IOError:
388 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
393 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
389 continue
394 continue
390 return None
395 return None
391
396
392 @wireprotocommand('getbundle', '*', permission='pull')
397 @wireprotocommand('getbundle', '*', permission='pull')
393 def getbundle(repo, proto, others):
398 def getbundle(repo, proto, others):
394 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(),
399 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(),
395 others)
400 others)
396 for k, v in opts.iteritems():
401 for k, v in opts.iteritems():
397 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
402 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
398 if keytype == 'nodes':
403 if keytype == 'nodes':
399 opts[k] = wireprototypes.decodelist(v)
404 opts[k] = wireprototypes.decodelist(v)
400 elif keytype == 'csv':
405 elif keytype == 'csv':
401 opts[k] = list(v.split(','))
406 opts[k] = list(v.split(','))
402 elif keytype == 'scsv':
407 elif keytype == 'scsv':
403 opts[k] = set(v.split(','))
408 opts[k] = set(v.split(','))
404 elif keytype == 'boolean':
409 elif keytype == 'boolean':
405 # Client should serialize False as '0', which is a non-empty string
410 # Client should serialize False as '0', which is a non-empty string
406 # so it evaluates as a True bool.
411 # so it evaluates as a True bool.
407 if v == '0':
412 if v == '0':
408 opts[k] = False
413 opts[k] = False
409 else:
414 else:
410 opts[k] = bool(v)
415 opts[k] = bool(v)
411 elif keytype != 'plain':
416 elif keytype != 'plain':
412 raise KeyError('unknown getbundle option type %s'
417 raise KeyError('unknown getbundle option type %s'
413 % keytype)
418 % keytype)
414
419
415 if not bundle1allowed(repo, 'pull'):
420 if not bundle1allowed(repo, 'pull'):
416 if not exchange.bundle2requested(opts.get('bundlecaps')):
421 if not exchange.bundle2requested(opts.get('bundlecaps')):
417 if proto.name == 'http-v1':
422 if proto.name == 'http-v1':
418 return wireprototypes.ooberror(bundle2required)
423 return wireprototypes.ooberror(bundle2required)
419 raise error.Abort(bundle2requiredmain,
424 raise error.Abort(bundle2requiredmain,
420 hint=bundle2requiredhint)
425 hint=bundle2requiredhint)
421
426
422 prefercompressed = True
427 prefercompressed = True
423
428
424 try:
429 try:
425 clheads = set(repo.changelog.heads())
430 clheads = set(repo.changelog.heads())
426 heads = set(opts.get('heads', set()))
431 heads = set(opts.get('heads', set()))
427 common = set(opts.get('common', set()))
432 common = set(opts.get('common', set()))
428 common.discard(nullid)
433 common.discard(nullid)
429 if (repo.ui.configbool('server', 'pullbundle') and
434 if (repo.ui.configbool('server', 'pullbundle') and
430 'partial-pull' in proto.getprotocaps()):
435 'partial-pull' in proto.getprotocaps()):
431 # Check if a pre-built bundle covers this request.
436 # Check if a pre-built bundle covers this request.
432 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common)
437 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common)
433 if bundle:
438 if bundle:
434 return wireprototypes.streamres(gen=util.filechunkiter(bundle),
439 return wireprototypes.streamres(gen=util.filechunkiter(bundle),
435 prefer_uncompressed=True)
440 prefer_uncompressed=True)
436
441
437 if repo.ui.configbool('server', 'disablefullbundle'):
442 if repo.ui.configbool('server', 'disablefullbundle'):
438 # Check to see if this is a full clone.
443 # Check to see if this is a full clone.
439 changegroup = opts.get('cg', True)
444 changegroup = opts.get('cg', True)
440 if changegroup and not common and clheads == heads:
445 if changegroup and not common and clheads == heads:
441 raise error.Abort(
446 raise error.Abort(
442 _('server has pull-based clones disabled'),
447 _('server has pull-based clones disabled'),
443 hint=_('remove --pull if specified or upgrade Mercurial'))
448 hint=_('remove --pull if specified or upgrade Mercurial'))
444
449
445 info, chunks = exchange.getbundlechunks(repo, 'serve',
450 info, chunks = exchange.getbundlechunks(repo, 'serve',
446 **pycompat.strkwargs(opts))
451 **pycompat.strkwargs(opts))
447 prefercompressed = info.get('prefercompressed', True)
452 prefercompressed = info.get('prefercompressed', True)
448 except error.Abort as exc:
453 except error.Abort as exc:
449 # cleanly forward Abort error to the client
454 # cleanly forward Abort error to the client
450 if not exchange.bundle2requested(opts.get('bundlecaps')):
455 if not exchange.bundle2requested(opts.get('bundlecaps')):
451 if proto.name == 'http-v1':
456 if proto.name == 'http-v1':
452 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n')
457 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n')
453 raise # cannot do better for bundle1 + ssh
458 raise # cannot do better for bundle1 + ssh
454 # bundle2 request expect a bundle2 reply
459 # bundle2 request expect a bundle2 reply
455 bundler = bundle2.bundle20(repo.ui)
460 bundler = bundle2.bundle20(repo.ui)
456 manargs = [('message', pycompat.bytestr(exc))]
461 manargs = [('message', pycompat.bytestr(exc))]
457 advargs = []
462 advargs = []
458 if exc.hint is not None:
463 if exc.hint is not None:
459 advargs.append(('hint', exc.hint))
464 advargs.append(('hint', exc.hint))
460 bundler.addpart(bundle2.bundlepart('error:abort',
465 bundler.addpart(bundle2.bundlepart('error:abort',
461 manargs, advargs))
466 manargs, advargs))
462 chunks = bundler.getchunks()
467 chunks = bundler.getchunks()
463 prefercompressed = False
468 prefercompressed = False
464
469
465 return wireprototypes.streamres(
470 return wireprototypes.streamres(
466 gen=chunks, prefer_uncompressed=not prefercompressed)
471 gen=chunks, prefer_uncompressed=not prefercompressed)
467
472
468 @wireprotocommand('heads', permission='pull')
473 @wireprotocommand('heads', permission='pull')
469 def heads(repo, proto):
474 def heads(repo, proto):
470 h = repo.heads()
475 h = repo.heads()
471 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n')
476 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n')
472
477
473 @wireprotocommand('hello', permission='pull')
478 @wireprotocommand('hello', permission='pull')
474 def hello(repo, proto):
479 def hello(repo, proto):
475 """Called as part of SSH handshake to obtain server info.
480 """Called as part of SSH handshake to obtain server info.
476
481
477 Returns a list of lines describing interesting things about the
482 Returns a list of lines describing interesting things about the
478 server, in an RFC822-like format.
483 server, in an RFC822-like format.
479
484
480 Currently, the only one defined is ``capabilities``, which consists of a
485 Currently, the only one defined is ``capabilities``, which consists of a
481 line of space separated tokens describing server abilities:
486 line of space separated tokens describing server abilities:
482
487
483 capabilities: <token0> <token1> <token2>
488 capabilities: <token0> <token1> <token2>
484 """
489 """
485 caps = capabilities(repo, proto).data
490 caps = capabilities(repo, proto).data
486 return wireprototypes.bytesresponse('capabilities: %s\n' % caps)
491 return wireprototypes.bytesresponse('capabilities: %s\n' % caps)
487
492
488 @wireprotocommand('listkeys', 'namespace', permission='pull')
493 @wireprotocommand('listkeys', 'namespace', permission='pull')
489 def listkeys(repo, proto, namespace):
494 def listkeys(repo, proto, namespace):
490 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
495 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
491 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d))
496 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d))
492
497
493 @wireprotocommand('lookup', 'key', permission='pull')
498 @wireprotocommand('lookup', 'key', permission='pull')
494 def lookup(repo, proto, key):
499 def lookup(repo, proto, key):
495 try:
500 try:
496 k = encoding.tolocal(key)
501 k = encoding.tolocal(key)
497 n = repo.lookup(k)
502 n = repo.lookup(k)
498 r = hex(n)
503 r = hex(n)
499 success = 1
504 success = 1
500 except Exception as inst:
505 except Exception as inst:
501 r = stringutil.forcebytestr(inst)
506 r = stringutil.forcebytestr(inst)
502 success = 0
507 success = 0
503 return wireprototypes.bytesresponse('%d %s\n' % (success, r))
508 return wireprototypes.bytesresponse('%d %s\n' % (success, r))
504
509
505 @wireprotocommand('known', 'nodes *', permission='pull')
510 @wireprotocommand('known', 'nodes *', permission='pull')
506 def known(repo, proto, nodes, others):
511 def known(repo, proto, nodes, others):
507 v = ''.join(b and '1' or '0'
512 v = ''.join(b and '1' or '0'
508 for b in repo.known(wireprototypes.decodelist(nodes)))
513 for b in repo.known(wireprototypes.decodelist(nodes)))
509 return wireprototypes.bytesresponse(v)
514 return wireprototypes.bytesresponse(v)
510
515
511 @wireprotocommand('protocaps', 'caps', permission='pull')
516 @wireprotocommand('protocaps', 'caps', permission='pull')
512 def protocaps(repo, proto, caps):
517 def protocaps(repo, proto, caps):
513 if proto.name == wireprototypes.SSHV1:
518 if proto.name == wireprototypes.SSHV1:
514 proto._protocaps = set(caps.split(' '))
519 proto._protocaps = set(caps.split(' '))
515 return wireprototypes.bytesresponse('OK')
520 return wireprototypes.bytesresponse('OK')
516
521
517 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
522 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
518 def pushkey(repo, proto, namespace, key, old, new):
523 def pushkey(repo, proto, namespace, key, old, new):
519 # compatibility with pre-1.8 clients which were accidentally
524 # compatibility with pre-1.8 clients which were accidentally
520 # sending raw binary nodes rather than utf-8-encoded hex
525 # sending raw binary nodes rather than utf-8-encoded hex
521 if len(new) == 20 and stringutil.escapestr(new) != new:
526 if len(new) == 20 and stringutil.escapestr(new) != new:
522 # looks like it could be a binary node
527 # looks like it could be a binary node
523 try:
528 try:
524 new.decode('utf-8')
529 new.decode('utf-8')
525 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
530 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
526 except UnicodeDecodeError:
531 except UnicodeDecodeError:
527 pass # binary, leave unmodified
532 pass # binary, leave unmodified
528 else:
533 else:
529 new = encoding.tolocal(new) # normal path
534 new = encoding.tolocal(new) # normal path
530
535
531 with proto.mayberedirectstdio() as output:
536 with proto.mayberedirectstdio() as output:
532 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
537 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
533 encoding.tolocal(old), new) or False
538 encoding.tolocal(old), new) or False
534
539
535 output = output.getvalue() if output else ''
540 output = output.getvalue() if output else ''
536 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output))
541 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output))
537
542
538 @wireprotocommand('stream_out', permission='pull')
543 @wireprotocommand('stream_out', permission='pull')
539 def stream(repo, proto):
544 def stream(repo, proto):
540 '''If the server supports streaming clone, it advertises the "stream"
545 '''If the server supports streaming clone, it advertises the "stream"
541 capability with a value representing the version and flags of the repo
546 capability with a value representing the version and flags of the repo
542 it is serving. Client checks to see if it understands the format.
547 it is serving. Client checks to see if it understands the format.
543 '''
548 '''
544 return wireprototypes.streamreslegacy(
549 return wireprototypes.streamreslegacy(
545 streamclone.generatev1wireproto(repo))
550 streamclone.generatev1wireproto(repo))
546
551
547 @wireprotocommand('unbundle', 'heads', permission='push')
552 @wireprotocommand('unbundle', 'heads', permission='push')
548 def unbundle(repo, proto, heads):
553 def unbundle(repo, proto, heads):
549 their_heads = wireprototypes.decodelist(heads)
554 their_heads = wireprototypes.decodelist(heads)
550
555
551 with proto.mayberedirectstdio() as output:
556 with proto.mayberedirectstdio() as output:
552 try:
557 try:
553 exchange.check_heads(repo, their_heads, 'preparing changes')
558 exchange.check_heads(repo, their_heads, 'preparing changes')
554 cleanup = lambda: None
559 cleanup = lambda: None
555 try:
560 try:
556 payload = proto.getpayload()
561 payload = proto.getpayload()
557 if repo.ui.configbool('server', 'streamunbundle'):
562 if repo.ui.configbool('server', 'streamunbundle'):
558 def cleanup():
563 def cleanup():
559 # Ensure that the full payload is consumed, so
564 # Ensure that the full payload is consumed, so
560 # that the connection doesn't contain trailing garbage.
565 # that the connection doesn't contain trailing garbage.
561 for p in payload:
566 for p in payload:
562 pass
567 pass
563 fp = util.chunkbuffer(payload)
568 fp = util.chunkbuffer(payload)
564 else:
569 else:
565 # write bundle data to temporary file as it can be big
570 # write bundle data to temporary file as it can be big
566 fp, tempname = None, None
571 fp, tempname = None, None
567 def cleanup():
572 def cleanup():
568 if fp:
573 if fp:
569 fp.close()
574 fp.close()
570 if tempname:
575 if tempname:
571 os.unlink(tempname)
576 os.unlink(tempname)
572 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
577 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
573 repo.ui.debug('redirecting incoming bundle to %s\n' %
578 repo.ui.debug('redirecting incoming bundle to %s\n' %
574 tempname)
579 tempname)
575 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
580 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
576 r = 0
581 r = 0
577 for p in payload:
582 for p in payload:
578 fp.write(p)
583 fp.write(p)
579 fp.seek(0)
584 fp.seek(0)
580
585
581 gen = exchange.readbundle(repo.ui, fp, None)
586 gen = exchange.readbundle(repo.ui, fp, None)
582 if (isinstance(gen, changegroupmod.cg1unpacker)
587 if (isinstance(gen, changegroupmod.cg1unpacker)
583 and not bundle1allowed(repo, 'push')):
588 and not bundle1allowed(repo, 'push')):
584 if proto.name == 'http-v1':
589 if proto.name == 'http-v1':
585 # need to special case http because stderr do not get to
590 # need to special case http because stderr do not get to
586 # the http client on failed push so we need to abuse
591 # the http client on failed push so we need to abuse
587 # some other error type to make sure the message get to
592 # some other error type to make sure the message get to
588 # the user.
593 # the user.
589 return wireprototypes.ooberror(bundle2required)
594 return wireprototypes.ooberror(bundle2required)
590 raise error.Abort(bundle2requiredmain,
595 raise error.Abort(bundle2requiredmain,
591 hint=bundle2requiredhint)
596 hint=bundle2requiredhint)
592
597
593 r = exchange.unbundle(repo, gen, their_heads, 'serve',
598 r = exchange.unbundle(repo, gen, their_heads, 'serve',
594 proto.client())
599 proto.client())
595 if util.safehasattr(r, 'addpart'):
600 if util.safehasattr(r, 'addpart'):
596 # The return looks streamable, we are in the bundle2 case
601 # The return looks streamable, we are in the bundle2 case
597 # and should return a stream.
602 # and should return a stream.
598 return wireprototypes.streamreslegacy(gen=r.getchunks())
603 return wireprototypes.streamreslegacy(gen=r.getchunks())
599 return wireprototypes.pushres(
604 return wireprototypes.pushres(
600 r, output.getvalue() if output else '')
605 r, output.getvalue() if output else '')
601
606
602 finally:
607 finally:
603 cleanup()
608 cleanup()
604
609
605 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
610 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
606 # handle non-bundle2 case first
611 # handle non-bundle2 case first
607 if not getattr(exc, 'duringunbundle2', False):
612 if not getattr(exc, 'duringunbundle2', False):
608 try:
613 try:
609 raise
614 raise
610 except error.Abort:
615 except error.Abort:
611 # The old code we moved used procutil.stderr directly.
616 # The old code we moved used procutil.stderr directly.
612 # We did not change it to minimise code change.
617 # We did not change it to minimise code change.
613 # This need to be moved to something proper.
618 # This need to be moved to something proper.
614 # Feel free to do it.
619 # Feel free to do it.
615 procutil.stderr.write("abort: %s\n" % exc)
620 procutil.stderr.write("abort: %s\n" % exc)
616 if exc.hint is not None:
621 if exc.hint is not None:
617 procutil.stderr.write("(%s)\n" % exc.hint)
622 procutil.stderr.write("(%s)\n" % exc.hint)
618 procutil.stderr.flush()
623 procutil.stderr.flush()
619 return wireprototypes.pushres(
624 return wireprototypes.pushres(
620 0, output.getvalue() if output else '')
625 0, output.getvalue() if output else '')
621 except error.PushRaced:
626 except error.PushRaced:
622 return wireprototypes.pusherr(
627 return wireprototypes.pusherr(
623 pycompat.bytestr(exc),
628 pycompat.bytestr(exc),
624 output.getvalue() if output else '')
629 output.getvalue() if output else '')
625
630
626 bundler = bundle2.bundle20(repo.ui)
631 bundler = bundle2.bundle20(repo.ui)
627 for out in getattr(exc, '_bundle2salvagedoutput', ()):
632 for out in getattr(exc, '_bundle2salvagedoutput', ()):
628 bundler.addpart(out)
633 bundler.addpart(out)
629 try:
634 try:
630 try:
635 try:
631 raise
636 raise
632 except error.PushkeyFailed as exc:
637 except error.PushkeyFailed as exc:
633 # check client caps
638 # check client caps
634 remotecaps = getattr(exc, '_replycaps', None)
639 remotecaps = getattr(exc, '_replycaps', None)
635 if (remotecaps is not None
640 if (remotecaps is not None
636 and 'pushkey' not in remotecaps.get('error', ())):
641 and 'pushkey' not in remotecaps.get('error', ())):
637 # no support remote side, fallback to Abort handler.
642 # no support remote side, fallback to Abort handler.
638 raise
643 raise
639 part = bundler.newpart('error:pushkey')
644 part = bundler.newpart('error:pushkey')
640 part.addparam('in-reply-to', exc.partid)
645 part.addparam('in-reply-to', exc.partid)
641 if exc.namespace is not None:
646 if exc.namespace is not None:
642 part.addparam('namespace', exc.namespace,
647 part.addparam('namespace', exc.namespace,
643 mandatory=False)
648 mandatory=False)
644 if exc.key is not None:
649 if exc.key is not None:
645 part.addparam('key', exc.key, mandatory=False)
650 part.addparam('key', exc.key, mandatory=False)
646 if exc.new is not None:
651 if exc.new is not None:
647 part.addparam('new', exc.new, mandatory=False)
652 part.addparam('new', exc.new, mandatory=False)
648 if exc.old is not None:
653 if exc.old is not None:
649 part.addparam('old', exc.old, mandatory=False)
654 part.addparam('old', exc.old, mandatory=False)
650 if exc.ret is not None:
655 if exc.ret is not None:
651 part.addparam('ret', exc.ret, mandatory=False)
656 part.addparam('ret', exc.ret, mandatory=False)
652 except error.BundleValueError as exc:
657 except error.BundleValueError as exc:
653 errpart = bundler.newpart('error:unsupportedcontent')
658 errpart = bundler.newpart('error:unsupportedcontent')
654 if exc.parttype is not None:
659 if exc.parttype is not None:
655 errpart.addparam('parttype', exc.parttype)
660 errpart.addparam('parttype', exc.parttype)
656 if exc.params:
661 if exc.params:
657 errpart.addparam('params', '\0'.join(exc.params))
662 errpart.addparam('params', '\0'.join(exc.params))
658 except error.Abort as exc:
663 except error.Abort as exc:
659 manargs = [('message', stringutil.forcebytestr(exc))]
664 manargs = [('message', stringutil.forcebytestr(exc))]
660 advargs = []
665 advargs = []
661 if exc.hint is not None:
666 if exc.hint is not None:
662 advargs.append(('hint', exc.hint))
667 advargs.append(('hint', exc.hint))
663 bundler.addpart(bundle2.bundlepart('error:abort',
668 bundler.addpart(bundle2.bundlepart('error:abort',
664 manargs, advargs))
669 manargs, advargs))
665 except error.PushRaced as exc:
670 except error.PushRaced as exc:
666 bundler.newpart('error:pushraced',
671 bundler.newpart('error:pushraced',
667 [('message', stringutil.forcebytestr(exc))])
672 [('message', stringutil.forcebytestr(exc))])
668 return wireprototypes.streamreslegacy(gen=bundler.getchunks())
673 return wireprototypes.streamreslegacy(gen=bundler.getchunks())
General Comments 0
You need to be logged in to leave comments. Login now