##// END OF EJS Templates
wireproto: only expose "between" to version 1 of wire protocols...
Gregory Szorc -
r36633:ed770501 default
parent child Browse files
Show More
@@ -1,1093 +1,1091 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
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 hashlib
10 import hashlib
11 import os
11 import os
12 import tempfile
12 import tempfile
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 nullid,
18 nullid,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup as changegroupmod,
23 changegroup as changegroupmod,
24 discovery,
24 discovery,
25 encoding,
25 encoding,
26 error,
26 error,
27 exchange,
27 exchange,
28 peer,
28 peer,
29 pushkey as pushkeymod,
29 pushkey as pushkeymod,
30 pycompat,
30 pycompat,
31 repository,
31 repository,
32 streamclone,
32 streamclone,
33 util,
33 util,
34 wireprototypes,
34 wireprototypes,
35 )
35 )
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 bytesresponse = wireprototypes.bytesresponse
40 bytesresponse = wireprototypes.bytesresponse
41 ooberror = wireprototypes.ooberror
41 ooberror = wireprototypes.ooberror
42 pushres = wireprototypes.pushres
42 pushres = wireprototypes.pushres
43 pusherr = wireprototypes.pusherr
43 pusherr = wireprototypes.pusherr
44 streamres = wireprototypes.streamres
44 streamres = wireprototypes.streamres
45 streamres_legacy = wireprototypes.streamreslegacy
45 streamres_legacy = wireprototypes.streamreslegacy
46
46
47 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
47 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
48 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
48 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
49 'IncompatibleClient')
49 'IncompatibleClient')
50 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
50 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
51
51
52 class remoteiterbatcher(peer.iterbatcher):
52 class remoteiterbatcher(peer.iterbatcher):
53 def __init__(self, remote):
53 def __init__(self, remote):
54 super(remoteiterbatcher, self).__init__()
54 super(remoteiterbatcher, self).__init__()
55 self._remote = remote
55 self._remote = remote
56
56
57 def __getattr__(self, name):
57 def __getattr__(self, name):
58 # Validate this method is batchable, since submit() only supports
58 # Validate this method is batchable, since submit() only supports
59 # batchable methods.
59 # batchable methods.
60 fn = getattr(self._remote, name)
60 fn = getattr(self._remote, name)
61 if not getattr(fn, 'batchable', None):
61 if not getattr(fn, 'batchable', None):
62 raise error.ProgrammingError('Attempted to batch a non-batchable '
62 raise error.ProgrammingError('Attempted to batch a non-batchable '
63 'call to %r' % name)
63 'call to %r' % name)
64
64
65 return super(remoteiterbatcher, self).__getattr__(name)
65 return super(remoteiterbatcher, self).__getattr__(name)
66
66
67 def submit(self):
67 def submit(self):
68 """Break the batch request into many patch calls and pipeline them.
68 """Break the batch request into many patch calls and pipeline them.
69
69
70 This is mostly valuable over http where request sizes can be
70 This is mostly valuable over http where request sizes can be
71 limited, but can be used in other places as well.
71 limited, but can be used in other places as well.
72 """
72 """
73 # 2-tuple of (command, arguments) that represents what will be
73 # 2-tuple of (command, arguments) that represents what will be
74 # sent over the wire.
74 # sent over the wire.
75 requests = []
75 requests = []
76
76
77 # 4-tuple of (command, final future, @batchable generator, remote
77 # 4-tuple of (command, final future, @batchable generator, remote
78 # future).
78 # future).
79 results = []
79 results = []
80
80
81 for command, args, opts, finalfuture in self.calls:
81 for command, args, opts, finalfuture in self.calls:
82 mtd = getattr(self._remote, command)
82 mtd = getattr(self._remote, command)
83 batchable = mtd.batchable(mtd.__self__, *args, **opts)
83 batchable = mtd.batchable(mtd.__self__, *args, **opts)
84
84
85 commandargs, fremote = next(batchable)
85 commandargs, fremote = next(batchable)
86 assert fremote
86 assert fremote
87 requests.append((command, commandargs))
87 requests.append((command, commandargs))
88 results.append((command, finalfuture, batchable, fremote))
88 results.append((command, finalfuture, batchable, fremote))
89
89
90 if requests:
90 if requests:
91 self._resultiter = self._remote._submitbatch(requests)
91 self._resultiter = self._remote._submitbatch(requests)
92
92
93 self._results = results
93 self._results = results
94
94
95 def results(self):
95 def results(self):
96 for command, finalfuture, batchable, remotefuture in self._results:
96 for command, finalfuture, batchable, remotefuture in self._results:
97 # Get the raw result, set it in the remote future, feed it
97 # Get the raw result, set it in the remote future, feed it
98 # back into the @batchable generator so it can be decoded, and
98 # back into the @batchable generator so it can be decoded, and
99 # set the result on the final future to this value.
99 # set the result on the final future to this value.
100 remoteresult = next(self._resultiter)
100 remoteresult = next(self._resultiter)
101 remotefuture.set(remoteresult)
101 remotefuture.set(remoteresult)
102 finalfuture.set(next(batchable))
102 finalfuture.set(next(batchable))
103
103
104 # Verify our @batchable generators only emit 2 values.
104 # Verify our @batchable generators only emit 2 values.
105 try:
105 try:
106 next(batchable)
106 next(batchable)
107 except StopIteration:
107 except StopIteration:
108 pass
108 pass
109 else:
109 else:
110 raise error.ProgrammingError('%s @batchable generator emitted '
110 raise error.ProgrammingError('%s @batchable generator emitted '
111 'unexpected value count' % command)
111 'unexpected value count' % command)
112
112
113 yield finalfuture.value
113 yield finalfuture.value
114
114
115 # Forward a couple of names from peer to make wireproto interactions
115 # Forward a couple of names from peer to make wireproto interactions
116 # slightly more sensible.
116 # slightly more sensible.
117 batchable = peer.batchable
117 batchable = peer.batchable
118 future = peer.future
118 future = peer.future
119
119
120 # list of nodes encoding / decoding
120 # list of nodes encoding / decoding
121
121
122 def decodelist(l, sep=' '):
122 def decodelist(l, sep=' '):
123 if l:
123 if l:
124 return [bin(v) for v in l.split(sep)]
124 return [bin(v) for v in l.split(sep)]
125 return []
125 return []
126
126
127 def encodelist(l, sep=' '):
127 def encodelist(l, sep=' '):
128 try:
128 try:
129 return sep.join(map(hex, l))
129 return sep.join(map(hex, l))
130 except TypeError:
130 except TypeError:
131 raise
131 raise
132
132
133 # batched call argument encoding
133 # batched call argument encoding
134
134
135 def escapearg(plain):
135 def escapearg(plain):
136 return (plain
136 return (plain
137 .replace(':', ':c')
137 .replace(':', ':c')
138 .replace(',', ':o')
138 .replace(',', ':o')
139 .replace(';', ':s')
139 .replace(';', ':s')
140 .replace('=', ':e'))
140 .replace('=', ':e'))
141
141
142 def unescapearg(escaped):
142 def unescapearg(escaped):
143 return (escaped
143 return (escaped
144 .replace(':e', '=')
144 .replace(':e', '=')
145 .replace(':s', ';')
145 .replace(':s', ';')
146 .replace(':o', ',')
146 .replace(':o', ',')
147 .replace(':c', ':'))
147 .replace(':c', ':'))
148
148
149 def encodebatchcmds(req):
149 def encodebatchcmds(req):
150 """Return a ``cmds`` argument value for the ``batch`` command."""
150 """Return a ``cmds`` argument value for the ``batch`` command."""
151 cmds = []
151 cmds = []
152 for op, argsdict in req:
152 for op, argsdict in req:
153 # Old servers didn't properly unescape argument names. So prevent
153 # Old servers didn't properly unescape argument names. So prevent
154 # the sending of argument names that may not be decoded properly by
154 # the sending of argument names that may not be decoded properly by
155 # servers.
155 # servers.
156 assert all(escapearg(k) == k for k in argsdict)
156 assert all(escapearg(k) == k for k in argsdict)
157
157
158 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
158 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
159 for k, v in argsdict.iteritems())
159 for k, v in argsdict.iteritems())
160 cmds.append('%s %s' % (op, args))
160 cmds.append('%s %s' % (op, args))
161
161
162 return ';'.join(cmds)
162 return ';'.join(cmds)
163
163
164 # mapping of options accepted by getbundle and their types
164 # mapping of options accepted by getbundle and their types
165 #
165 #
166 # Meant to be extended by extensions. It is extensions responsibility to ensure
166 # Meant to be extended by extensions. It is extensions responsibility to ensure
167 # such options are properly processed in exchange.getbundle.
167 # such options are properly processed in exchange.getbundle.
168 #
168 #
169 # supported types are:
169 # supported types are:
170 #
170 #
171 # :nodes: list of binary nodes
171 # :nodes: list of binary nodes
172 # :csv: list of comma-separated values
172 # :csv: list of comma-separated values
173 # :scsv: list of comma-separated values return as set
173 # :scsv: list of comma-separated values return as set
174 # :plain: string with no transformation needed.
174 # :plain: string with no transformation needed.
175 gboptsmap = {'heads': 'nodes',
175 gboptsmap = {'heads': 'nodes',
176 'bookmarks': 'boolean',
176 'bookmarks': 'boolean',
177 'common': 'nodes',
177 'common': 'nodes',
178 'obsmarkers': 'boolean',
178 'obsmarkers': 'boolean',
179 'phases': 'boolean',
179 'phases': 'boolean',
180 'bundlecaps': 'scsv',
180 'bundlecaps': 'scsv',
181 'listkeys': 'csv',
181 'listkeys': 'csv',
182 'cg': 'boolean',
182 'cg': 'boolean',
183 'cbattempted': 'boolean',
183 'cbattempted': 'boolean',
184 'stream': 'boolean',
184 'stream': 'boolean',
185 }
185 }
186
186
187 # client side
187 # client side
188
188
189 class wirepeer(repository.legacypeer):
189 class wirepeer(repository.legacypeer):
190 """Client-side interface for communicating with a peer repository.
190 """Client-side interface for communicating with a peer repository.
191
191
192 Methods commonly call wire protocol commands of the same name.
192 Methods commonly call wire protocol commands of the same name.
193
193
194 See also httppeer.py and sshpeer.py for protocol-specific
194 See also httppeer.py and sshpeer.py for protocol-specific
195 implementations of this interface.
195 implementations of this interface.
196 """
196 """
197 # Begin of basewirepeer interface.
197 # Begin of basewirepeer interface.
198
198
199 def iterbatch(self):
199 def iterbatch(self):
200 return remoteiterbatcher(self)
200 return remoteiterbatcher(self)
201
201
202 @batchable
202 @batchable
203 def lookup(self, key):
203 def lookup(self, key):
204 self.requirecap('lookup', _('look up remote revision'))
204 self.requirecap('lookup', _('look up remote revision'))
205 f = future()
205 f = future()
206 yield {'key': encoding.fromlocal(key)}, f
206 yield {'key': encoding.fromlocal(key)}, f
207 d = f.value
207 d = f.value
208 success, data = d[:-1].split(" ", 1)
208 success, data = d[:-1].split(" ", 1)
209 if int(success):
209 if int(success):
210 yield bin(data)
210 yield bin(data)
211 else:
211 else:
212 self._abort(error.RepoError(data))
212 self._abort(error.RepoError(data))
213
213
214 @batchable
214 @batchable
215 def heads(self):
215 def heads(self):
216 f = future()
216 f = future()
217 yield {}, f
217 yield {}, f
218 d = f.value
218 d = f.value
219 try:
219 try:
220 yield decodelist(d[:-1])
220 yield decodelist(d[:-1])
221 except ValueError:
221 except ValueError:
222 self._abort(error.ResponseError(_("unexpected response:"), d))
222 self._abort(error.ResponseError(_("unexpected response:"), d))
223
223
224 @batchable
224 @batchable
225 def known(self, nodes):
225 def known(self, nodes):
226 f = future()
226 f = future()
227 yield {'nodes': encodelist(nodes)}, f
227 yield {'nodes': encodelist(nodes)}, f
228 d = f.value
228 d = f.value
229 try:
229 try:
230 yield [bool(int(b)) for b in d]
230 yield [bool(int(b)) for b in d]
231 except ValueError:
231 except ValueError:
232 self._abort(error.ResponseError(_("unexpected response:"), d))
232 self._abort(error.ResponseError(_("unexpected response:"), d))
233
233
234 @batchable
234 @batchable
235 def branchmap(self):
235 def branchmap(self):
236 f = future()
236 f = future()
237 yield {}, f
237 yield {}, f
238 d = f.value
238 d = f.value
239 try:
239 try:
240 branchmap = {}
240 branchmap = {}
241 for branchpart in d.splitlines():
241 for branchpart in d.splitlines():
242 branchname, branchheads = branchpart.split(' ', 1)
242 branchname, branchheads = branchpart.split(' ', 1)
243 branchname = encoding.tolocal(urlreq.unquote(branchname))
243 branchname = encoding.tolocal(urlreq.unquote(branchname))
244 branchheads = decodelist(branchheads)
244 branchheads = decodelist(branchheads)
245 branchmap[branchname] = branchheads
245 branchmap[branchname] = branchheads
246 yield branchmap
246 yield branchmap
247 except TypeError:
247 except TypeError:
248 self._abort(error.ResponseError(_("unexpected response:"), d))
248 self._abort(error.ResponseError(_("unexpected response:"), d))
249
249
250 @batchable
250 @batchable
251 def listkeys(self, namespace):
251 def listkeys(self, namespace):
252 if not self.capable('pushkey'):
252 if not self.capable('pushkey'):
253 yield {}, None
253 yield {}, None
254 f = future()
254 f = future()
255 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
255 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
256 yield {'namespace': encoding.fromlocal(namespace)}, f
256 yield {'namespace': encoding.fromlocal(namespace)}, f
257 d = f.value
257 d = f.value
258 self.ui.debug('received listkey for "%s": %i bytes\n'
258 self.ui.debug('received listkey for "%s": %i bytes\n'
259 % (namespace, len(d)))
259 % (namespace, len(d)))
260 yield pushkeymod.decodekeys(d)
260 yield pushkeymod.decodekeys(d)
261
261
262 @batchable
262 @batchable
263 def pushkey(self, namespace, key, old, new):
263 def pushkey(self, namespace, key, old, new):
264 if not self.capable('pushkey'):
264 if not self.capable('pushkey'):
265 yield False, None
265 yield False, None
266 f = future()
266 f = future()
267 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
267 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
268 yield {'namespace': encoding.fromlocal(namespace),
268 yield {'namespace': encoding.fromlocal(namespace),
269 'key': encoding.fromlocal(key),
269 'key': encoding.fromlocal(key),
270 'old': encoding.fromlocal(old),
270 'old': encoding.fromlocal(old),
271 'new': encoding.fromlocal(new)}, f
271 'new': encoding.fromlocal(new)}, f
272 d = f.value
272 d = f.value
273 d, output = d.split('\n', 1)
273 d, output = d.split('\n', 1)
274 try:
274 try:
275 d = bool(int(d))
275 d = bool(int(d))
276 except ValueError:
276 except ValueError:
277 raise error.ResponseError(
277 raise error.ResponseError(
278 _('push failed (unexpected response):'), d)
278 _('push failed (unexpected response):'), d)
279 for l in output.splitlines(True):
279 for l in output.splitlines(True):
280 self.ui.status(_('remote: '), l)
280 self.ui.status(_('remote: '), l)
281 yield d
281 yield d
282
282
283 def stream_out(self):
283 def stream_out(self):
284 return self._callstream('stream_out')
284 return self._callstream('stream_out')
285
285
286 def getbundle(self, source, **kwargs):
286 def getbundle(self, source, **kwargs):
287 kwargs = pycompat.byteskwargs(kwargs)
287 kwargs = pycompat.byteskwargs(kwargs)
288 self.requirecap('getbundle', _('look up remote changes'))
288 self.requirecap('getbundle', _('look up remote changes'))
289 opts = {}
289 opts = {}
290 bundlecaps = kwargs.get('bundlecaps')
290 bundlecaps = kwargs.get('bundlecaps')
291 if bundlecaps is not None:
291 if bundlecaps is not None:
292 kwargs['bundlecaps'] = sorted(bundlecaps)
292 kwargs['bundlecaps'] = sorted(bundlecaps)
293 else:
293 else:
294 bundlecaps = () # kwargs could have it to None
294 bundlecaps = () # kwargs could have it to None
295 for key, value in kwargs.iteritems():
295 for key, value in kwargs.iteritems():
296 if value is None:
296 if value is None:
297 continue
297 continue
298 keytype = gboptsmap.get(key)
298 keytype = gboptsmap.get(key)
299 if keytype is None:
299 if keytype is None:
300 raise error.ProgrammingError(
300 raise error.ProgrammingError(
301 'Unexpectedly None keytype for key %s' % key)
301 'Unexpectedly None keytype for key %s' % key)
302 elif keytype == 'nodes':
302 elif keytype == 'nodes':
303 value = encodelist(value)
303 value = encodelist(value)
304 elif keytype in ('csv', 'scsv'):
304 elif keytype in ('csv', 'scsv'):
305 value = ','.join(value)
305 value = ','.join(value)
306 elif keytype == 'boolean':
306 elif keytype == 'boolean':
307 value = '%i' % bool(value)
307 value = '%i' % bool(value)
308 elif keytype != 'plain':
308 elif keytype != 'plain':
309 raise KeyError('unknown getbundle option type %s'
309 raise KeyError('unknown getbundle option type %s'
310 % keytype)
310 % keytype)
311 opts[key] = value
311 opts[key] = value
312 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
312 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
313 if any((cap.startswith('HG2') for cap in bundlecaps)):
313 if any((cap.startswith('HG2') for cap in bundlecaps)):
314 return bundle2.getunbundler(self.ui, f)
314 return bundle2.getunbundler(self.ui, f)
315 else:
315 else:
316 return changegroupmod.cg1unpacker(f, 'UN')
316 return changegroupmod.cg1unpacker(f, 'UN')
317
317
318 def unbundle(self, cg, heads, url):
318 def unbundle(self, cg, heads, url):
319 '''Send cg (a readable file-like object representing the
319 '''Send cg (a readable file-like object representing the
320 changegroup to push, typically a chunkbuffer object) to the
320 changegroup to push, typically a chunkbuffer object) to the
321 remote server as a bundle.
321 remote server as a bundle.
322
322
323 When pushing a bundle10 stream, return an integer indicating the
323 When pushing a bundle10 stream, return an integer indicating the
324 result of the push (see changegroup.apply()).
324 result of the push (see changegroup.apply()).
325
325
326 When pushing a bundle20 stream, return a bundle20 stream.
326 When pushing a bundle20 stream, return a bundle20 stream.
327
327
328 `url` is the url the client thinks it's pushing to, which is
328 `url` is the url the client thinks it's pushing to, which is
329 visible to hooks.
329 visible to hooks.
330 '''
330 '''
331
331
332 if heads != ['force'] and self.capable('unbundlehash'):
332 if heads != ['force'] and self.capable('unbundlehash'):
333 heads = encodelist(['hashed',
333 heads = encodelist(['hashed',
334 hashlib.sha1(''.join(sorted(heads))).digest()])
334 hashlib.sha1(''.join(sorted(heads))).digest()])
335 else:
335 else:
336 heads = encodelist(heads)
336 heads = encodelist(heads)
337
337
338 if util.safehasattr(cg, 'deltaheader'):
338 if util.safehasattr(cg, 'deltaheader'):
339 # this a bundle10, do the old style call sequence
339 # this a bundle10, do the old style call sequence
340 ret, output = self._callpush("unbundle", cg, heads=heads)
340 ret, output = self._callpush("unbundle", cg, heads=heads)
341 if ret == "":
341 if ret == "":
342 raise error.ResponseError(
342 raise error.ResponseError(
343 _('push failed:'), output)
343 _('push failed:'), output)
344 try:
344 try:
345 ret = int(ret)
345 ret = int(ret)
346 except ValueError:
346 except ValueError:
347 raise error.ResponseError(
347 raise error.ResponseError(
348 _('push failed (unexpected response):'), ret)
348 _('push failed (unexpected response):'), ret)
349
349
350 for l in output.splitlines(True):
350 for l in output.splitlines(True):
351 self.ui.status(_('remote: '), l)
351 self.ui.status(_('remote: '), l)
352 else:
352 else:
353 # bundle2 push. Send a stream, fetch a stream.
353 # bundle2 push. Send a stream, fetch a stream.
354 stream = self._calltwowaystream('unbundle', cg, heads=heads)
354 stream = self._calltwowaystream('unbundle', cg, heads=heads)
355 ret = bundle2.getunbundler(self.ui, stream)
355 ret = bundle2.getunbundler(self.ui, stream)
356 return ret
356 return ret
357
357
358 # End of basewirepeer interface.
358 # End of basewirepeer interface.
359
359
360 # Begin of baselegacywirepeer interface.
360 # Begin of baselegacywirepeer interface.
361
361
362 def branches(self, nodes):
362 def branches(self, nodes):
363 n = encodelist(nodes)
363 n = encodelist(nodes)
364 d = self._call("branches", nodes=n)
364 d = self._call("branches", nodes=n)
365 try:
365 try:
366 br = [tuple(decodelist(b)) for b in d.splitlines()]
366 br = [tuple(decodelist(b)) for b in d.splitlines()]
367 return br
367 return br
368 except ValueError:
368 except ValueError:
369 self._abort(error.ResponseError(_("unexpected response:"), d))
369 self._abort(error.ResponseError(_("unexpected response:"), d))
370
370
371 def between(self, pairs):
371 def between(self, pairs):
372 batch = 8 # avoid giant requests
372 batch = 8 # avoid giant requests
373 r = []
373 r = []
374 for i in xrange(0, len(pairs), batch):
374 for i in xrange(0, len(pairs), batch):
375 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
375 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
376 d = self._call("between", pairs=n)
376 d = self._call("between", pairs=n)
377 try:
377 try:
378 r.extend(l and decodelist(l) or [] for l in d.splitlines())
378 r.extend(l and decodelist(l) or [] for l in d.splitlines())
379 except ValueError:
379 except ValueError:
380 self._abort(error.ResponseError(_("unexpected response:"), d))
380 self._abort(error.ResponseError(_("unexpected response:"), d))
381 return r
381 return r
382
382
383 def changegroup(self, nodes, kind):
383 def changegroup(self, nodes, kind):
384 n = encodelist(nodes)
384 n = encodelist(nodes)
385 f = self._callcompressable("changegroup", roots=n)
385 f = self._callcompressable("changegroup", roots=n)
386 return changegroupmod.cg1unpacker(f, 'UN')
386 return changegroupmod.cg1unpacker(f, 'UN')
387
387
388 def changegroupsubset(self, bases, heads, kind):
388 def changegroupsubset(self, bases, heads, kind):
389 self.requirecap('changegroupsubset', _('look up remote changes'))
389 self.requirecap('changegroupsubset', _('look up remote changes'))
390 bases = encodelist(bases)
390 bases = encodelist(bases)
391 heads = encodelist(heads)
391 heads = encodelist(heads)
392 f = self._callcompressable("changegroupsubset",
392 f = self._callcompressable("changegroupsubset",
393 bases=bases, heads=heads)
393 bases=bases, heads=heads)
394 return changegroupmod.cg1unpacker(f, 'UN')
394 return changegroupmod.cg1unpacker(f, 'UN')
395
395
396 # End of baselegacywirepeer interface.
396 # End of baselegacywirepeer interface.
397
397
398 def _submitbatch(self, req):
398 def _submitbatch(self, req):
399 """run batch request <req> on the server
399 """run batch request <req> on the server
400
400
401 Returns an iterator of the raw responses from the server.
401 Returns an iterator of the raw responses from the server.
402 """
402 """
403 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
403 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
404 chunk = rsp.read(1024)
404 chunk = rsp.read(1024)
405 work = [chunk]
405 work = [chunk]
406 while chunk:
406 while chunk:
407 while ';' not in chunk and chunk:
407 while ';' not in chunk and chunk:
408 chunk = rsp.read(1024)
408 chunk = rsp.read(1024)
409 work.append(chunk)
409 work.append(chunk)
410 merged = ''.join(work)
410 merged = ''.join(work)
411 while ';' in merged:
411 while ';' in merged:
412 one, merged = merged.split(';', 1)
412 one, merged = merged.split(';', 1)
413 yield unescapearg(one)
413 yield unescapearg(one)
414 chunk = rsp.read(1024)
414 chunk = rsp.read(1024)
415 work = [merged, chunk]
415 work = [merged, chunk]
416 yield unescapearg(''.join(work))
416 yield unescapearg(''.join(work))
417
417
418 def _submitone(self, op, args):
418 def _submitone(self, op, args):
419 return self._call(op, **pycompat.strkwargs(args))
419 return self._call(op, **pycompat.strkwargs(args))
420
420
421 def debugwireargs(self, one, two, three=None, four=None, five=None):
421 def debugwireargs(self, one, two, three=None, four=None, five=None):
422 # don't pass optional arguments left at their default value
422 # don't pass optional arguments left at their default value
423 opts = {}
423 opts = {}
424 if three is not None:
424 if three is not None:
425 opts[r'three'] = three
425 opts[r'three'] = three
426 if four is not None:
426 if four is not None:
427 opts[r'four'] = four
427 opts[r'four'] = four
428 return self._call('debugwireargs', one=one, two=two, **opts)
428 return self._call('debugwireargs', one=one, two=two, **opts)
429
429
430 def _call(self, cmd, **args):
430 def _call(self, cmd, **args):
431 """execute <cmd> on the server
431 """execute <cmd> on the server
432
432
433 The command is expected to return a simple string.
433 The command is expected to return a simple string.
434
434
435 returns the server reply as a string."""
435 returns the server reply as a string."""
436 raise NotImplementedError()
436 raise NotImplementedError()
437
437
438 def _callstream(self, cmd, **args):
438 def _callstream(self, cmd, **args):
439 """execute <cmd> on the server
439 """execute <cmd> on the server
440
440
441 The command is expected to return a stream. Note that if the
441 The command is expected to return a stream. Note that if the
442 command doesn't return a stream, _callstream behaves
442 command doesn't return a stream, _callstream behaves
443 differently for ssh and http peers.
443 differently for ssh and http peers.
444
444
445 returns the server reply as a file like object.
445 returns the server reply as a file like object.
446 """
446 """
447 raise NotImplementedError()
447 raise NotImplementedError()
448
448
449 def _callcompressable(self, cmd, **args):
449 def _callcompressable(self, cmd, **args):
450 """execute <cmd> on the server
450 """execute <cmd> on the server
451
451
452 The command is expected to return a stream.
452 The command is expected to return a stream.
453
453
454 The stream may have been compressed in some implementations. This
454 The stream may have been compressed in some implementations. This
455 function takes care of the decompression. This is the only difference
455 function takes care of the decompression. This is the only difference
456 with _callstream.
456 with _callstream.
457
457
458 returns the server reply as a file like object.
458 returns the server reply as a file like object.
459 """
459 """
460 raise NotImplementedError()
460 raise NotImplementedError()
461
461
462 def _callpush(self, cmd, fp, **args):
462 def _callpush(self, cmd, fp, **args):
463 """execute a <cmd> on server
463 """execute a <cmd> on server
464
464
465 The command is expected to be related to a push. Push has a special
465 The command is expected to be related to a push. Push has a special
466 return method.
466 return method.
467
467
468 returns the server reply as a (ret, output) tuple. ret is either
468 returns the server reply as a (ret, output) tuple. ret is either
469 empty (error) or a stringified int.
469 empty (error) or a stringified int.
470 """
470 """
471 raise NotImplementedError()
471 raise NotImplementedError()
472
472
473 def _calltwowaystream(self, cmd, fp, **args):
473 def _calltwowaystream(self, cmd, fp, **args):
474 """execute <cmd> on server
474 """execute <cmd> on server
475
475
476 The command will send a stream to the server and get a stream in reply.
476 The command will send a stream to the server and get a stream in reply.
477 """
477 """
478 raise NotImplementedError()
478 raise NotImplementedError()
479
479
480 def _abort(self, exception):
480 def _abort(self, exception):
481 """clearly abort the wire protocol connection and raise the exception
481 """clearly abort the wire protocol connection and raise the exception
482 """
482 """
483 raise NotImplementedError()
483 raise NotImplementedError()
484
484
485 # server side
485 # server side
486
486
487 # wire protocol command can either return a string or one of these classes.
487 # wire protocol command can either return a string or one of these classes.
488
488
489 def getdispatchrepo(repo, proto, command):
489 def getdispatchrepo(repo, proto, command):
490 """Obtain the repo used for processing wire protocol commands.
490 """Obtain the repo used for processing wire protocol commands.
491
491
492 The intent of this function is to serve as a monkeypatch point for
492 The intent of this function is to serve as a monkeypatch point for
493 extensions that need commands to operate on different repo views under
493 extensions that need commands to operate on different repo views under
494 specialized circumstances.
494 specialized circumstances.
495 """
495 """
496 return repo.filtered('served')
496 return repo.filtered('served')
497
497
498 def dispatch(repo, proto, command):
498 def dispatch(repo, proto, command):
499 repo = getdispatchrepo(repo, proto, command)
499 repo = getdispatchrepo(repo, proto, command)
500 func, spec = commands[command]
500 func, spec = commands[command]
501 args = proto.getargs(spec)
501 args = proto.getargs(spec)
502 return func(repo, proto, *args)
502 return func(repo, proto, *args)
503
503
504 def options(cmd, keys, others):
504 def options(cmd, keys, others):
505 opts = {}
505 opts = {}
506 for k in keys:
506 for k in keys:
507 if k in others:
507 if k in others:
508 opts[k] = others[k]
508 opts[k] = others[k]
509 del others[k]
509 del others[k]
510 if others:
510 if others:
511 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
511 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
512 % (cmd, ",".join(others)))
512 % (cmd, ",".join(others)))
513 return opts
513 return opts
514
514
515 def bundle1allowed(repo, action):
515 def bundle1allowed(repo, action):
516 """Whether a bundle1 operation is allowed from the server.
516 """Whether a bundle1 operation is allowed from the server.
517
517
518 Priority is:
518 Priority is:
519
519
520 1. server.bundle1gd.<action> (if generaldelta active)
520 1. server.bundle1gd.<action> (if generaldelta active)
521 2. server.bundle1.<action>
521 2. server.bundle1.<action>
522 3. server.bundle1gd (if generaldelta active)
522 3. server.bundle1gd (if generaldelta active)
523 4. server.bundle1
523 4. server.bundle1
524 """
524 """
525 ui = repo.ui
525 ui = repo.ui
526 gd = 'generaldelta' in repo.requirements
526 gd = 'generaldelta' in repo.requirements
527
527
528 if gd:
528 if gd:
529 v = ui.configbool('server', 'bundle1gd.%s' % action)
529 v = ui.configbool('server', 'bundle1gd.%s' % action)
530 if v is not None:
530 if v is not None:
531 return v
531 return v
532
532
533 v = ui.configbool('server', 'bundle1.%s' % action)
533 v = ui.configbool('server', 'bundle1.%s' % action)
534 if v is not None:
534 if v is not None:
535 return v
535 return v
536
536
537 if gd:
537 if gd:
538 v = ui.configbool('server', 'bundle1gd')
538 v = ui.configbool('server', 'bundle1gd')
539 if v is not None:
539 if v is not None:
540 return v
540 return v
541
541
542 return ui.configbool('server', 'bundle1')
542 return ui.configbool('server', 'bundle1')
543
543
544 def supportedcompengines(ui, role):
544 def supportedcompengines(ui, role):
545 """Obtain the list of supported compression engines for a request."""
545 """Obtain the list of supported compression engines for a request."""
546 assert role in (util.CLIENTROLE, util.SERVERROLE)
546 assert role in (util.CLIENTROLE, util.SERVERROLE)
547
547
548 compengines = util.compengines.supportedwireengines(role)
548 compengines = util.compengines.supportedwireengines(role)
549
549
550 # Allow config to override default list and ordering.
550 # Allow config to override default list and ordering.
551 if role == util.SERVERROLE:
551 if role == util.SERVERROLE:
552 configengines = ui.configlist('server', 'compressionengines')
552 configengines = ui.configlist('server', 'compressionengines')
553 config = 'server.compressionengines'
553 config = 'server.compressionengines'
554 else:
554 else:
555 # This is currently implemented mainly to facilitate testing. In most
555 # This is currently implemented mainly to facilitate testing. In most
556 # cases, the server should be in charge of choosing a compression engine
556 # cases, the server should be in charge of choosing a compression engine
557 # because a server has the most to lose from a sub-optimal choice. (e.g.
557 # because a server has the most to lose from a sub-optimal choice. (e.g.
558 # CPU DoS due to an expensive engine or a network DoS due to poor
558 # CPU DoS due to an expensive engine or a network DoS due to poor
559 # compression ratio).
559 # compression ratio).
560 configengines = ui.configlist('experimental',
560 configengines = ui.configlist('experimental',
561 'clientcompressionengines')
561 'clientcompressionengines')
562 config = 'experimental.clientcompressionengines'
562 config = 'experimental.clientcompressionengines'
563
563
564 # No explicit config. Filter out the ones that aren't supposed to be
564 # No explicit config. Filter out the ones that aren't supposed to be
565 # advertised and return default ordering.
565 # advertised and return default ordering.
566 if not configengines:
566 if not configengines:
567 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
567 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
568 return [e for e in compengines
568 return [e for e in compengines
569 if getattr(e.wireprotosupport(), attr) > 0]
569 if getattr(e.wireprotosupport(), attr) > 0]
570
570
571 # If compression engines are listed in the config, assume there is a good
571 # If compression engines are listed in the config, assume there is a good
572 # reason for it (like server operators wanting to achieve specific
572 # reason for it (like server operators wanting to achieve specific
573 # performance characteristics). So fail fast if the config references
573 # performance characteristics). So fail fast if the config references
574 # unusable compression engines.
574 # unusable compression engines.
575 validnames = set(e.name() for e in compengines)
575 validnames = set(e.name() for e in compengines)
576 invalidnames = set(e for e in configengines if e not in validnames)
576 invalidnames = set(e for e in configengines if e not in validnames)
577 if invalidnames:
577 if invalidnames:
578 raise error.Abort(_('invalid compression engine defined in %s: %s') %
578 raise error.Abort(_('invalid compression engine defined in %s: %s') %
579 (config, ', '.join(sorted(invalidnames))))
579 (config, ', '.join(sorted(invalidnames))))
580
580
581 compengines = [e for e in compengines if e.name() in configengines]
581 compengines = [e for e in compengines if e.name() in configengines]
582 compengines = sorted(compengines,
582 compengines = sorted(compengines,
583 key=lambda e: configengines.index(e.name()))
583 key=lambda e: configengines.index(e.name()))
584
584
585 if not compengines:
585 if not compengines:
586 raise error.Abort(_('%s config option does not specify any known '
586 raise error.Abort(_('%s config option does not specify any known '
587 'compression engines') % config,
587 'compression engines') % config,
588 hint=_('usable compression engines: %s') %
588 hint=_('usable compression engines: %s') %
589 ', '.sorted(validnames))
589 ', '.sorted(validnames))
590
590
591 return compengines
591 return compengines
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 self.func = func
596 self.func = func
597 self.args = args
597 self.args = args
598 self.transports = transports or set()
598 self.transports = transports or set()
599
599
600 def _merge(self, func, args):
600 def _merge(self, func, args):
601 """Merge this instance with an incoming 2-tuple.
601 """Merge this instance with an incoming 2-tuple.
602
602
603 This is called when a caller using the old 2-tuple API attempts
603 This is called when a caller using the old 2-tuple API attempts
604 to replace an instance. The incoming values are merged with
604 to replace an instance. The incoming values are merged with
605 data not captured by the 2-tuple and a new instance containing
605 data not captured by the 2-tuple and a new instance containing
606 the union of the two objects is returned.
606 the union of the two objects is returned.
607 """
607 """
608 return commandentry(func, args=args, transports=set(self.transports))
608 return commandentry(func, args=args, transports=set(self.transports))
609
609
610 # Old code treats instances as 2-tuples. So expose that interface.
610 # Old code treats instances as 2-tuples. So expose that interface.
611 def __iter__(self):
611 def __iter__(self):
612 yield self.func
612 yield self.func
613 yield self.args
613 yield self.args
614
614
615 def __getitem__(self, i):
615 def __getitem__(self, i):
616 if i == 0:
616 if i == 0:
617 return self.func
617 return self.func
618 elif i == 1:
618 elif i == 1:
619 return self.args
619 return self.args
620 else:
620 else:
621 raise IndexError('can only access elements 0 and 1')
621 raise IndexError('can only access elements 0 and 1')
622
622
623 class commanddict(dict):
623 class commanddict(dict):
624 """Container for registered wire protocol commands.
624 """Container for registered wire protocol commands.
625
625
626 It behaves like a dict. But __setitem__ is overwritten to allow silent
626 It behaves like a dict. But __setitem__ is overwritten to allow silent
627 coercion of values from 2-tuples for API compatibility.
627 coercion of values from 2-tuples for API compatibility.
628 """
628 """
629 def __setitem__(self, k, v):
629 def __setitem__(self, k, v):
630 if isinstance(v, commandentry):
630 if isinstance(v, commandentry):
631 pass
631 pass
632 # Cast 2-tuples to commandentry instances.
632 # Cast 2-tuples to commandentry instances.
633 elif isinstance(v, tuple):
633 elif isinstance(v, tuple):
634 if len(v) != 2:
634 if len(v) != 2:
635 raise ValueError('command tuples must have exactly 2 elements')
635 raise ValueError('command tuples must have exactly 2 elements')
636
636
637 # It is common for extensions to wrap wire protocol commands via
637 # It is common for extensions to wrap wire protocol commands via
638 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
638 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
639 # doing this aren't aware of the new API that uses objects to store
639 # doing this aren't aware of the new API that uses objects to store
640 # command entries, we automatically merge old state with new.
640 # command entries, we automatically merge old state with new.
641 if k in self:
641 if k in self:
642 v = self[k]._merge(v[0], v[1])
642 v = self[k]._merge(v[0], v[1])
643 else:
643 else:
644 # Use default values from @wireprotocommand.
644 # Use default values from @wireprotocommand.
645 v = commandentry(v[0], args=v[1],
645 v = commandentry(v[0], args=v[1],
646 transports=set(wireprototypes.TRANSPORTS))
646 transports=set(wireprototypes.TRANSPORTS))
647 else:
647 else:
648 raise ValueError('command entries must be commandentry instances '
648 raise ValueError('command entries must be commandentry instances '
649 'or 2-tuples')
649 'or 2-tuples')
650
650
651 return super(commanddict, self).__setitem__(k, v)
651 return super(commanddict, self).__setitem__(k, v)
652
652
653 def commandavailable(self, command, proto):
653 def commandavailable(self, command, proto):
654 """Determine if a command is available for the requested protocol."""
654 """Determine if a command is available for the requested protocol."""
655 assert proto.name in wireprototypes.TRANSPORTS
655 assert proto.name in wireprototypes.TRANSPORTS
656
656
657 entry = self.get(command)
657 entry = self.get(command)
658
658
659 if not entry:
659 if not entry:
660 return False
660 return False
661
661
662 if proto.name not in entry.transports:
662 if proto.name not in entry.transports:
663 return False
663 return False
664
664
665 return True
665 return True
666
666
667 # Constants specifying which transports a wire protocol command should be
667 # Constants specifying which transports a wire protocol command should be
668 # available on. For use with @wireprotocommand.
668 # available on. For use with @wireprotocommand.
669 POLICY_ALL = 'all'
669 POLICY_ALL = 'all'
670 POLICY_V1_ONLY = 'v1-only'
670 POLICY_V1_ONLY = 'v1-only'
671 POLICY_V2_ONLY = 'v2-only'
671 POLICY_V2_ONLY = 'v2-only'
672
672
673 commands = commanddict()
673 commands = commanddict()
674
674
675 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL):
675 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL):
676 """Decorator to declare a wire protocol command.
676 """Decorator to declare a wire protocol command.
677
677
678 ``name`` is the name of the wire protocol command being provided.
678 ``name`` is the name of the wire protocol command being provided.
679
679
680 ``args`` is a space-delimited list of named arguments that the command
680 ``args`` is a space-delimited list of named arguments that the command
681 accepts. ``*`` is a special value that says to accept all arguments.
681 accepts. ``*`` is a special value that says to accept all arguments.
682
682
683 ``transportpolicy`` is a POLICY_* constant denoting which transports
683 ``transportpolicy`` is a POLICY_* constant denoting which transports
684 this wire protocol command should be exposed to. By default, commands
684 this wire protocol command should be exposed to. By default, commands
685 are exposed to all wire protocol transports.
685 are exposed to all wire protocol transports.
686 """
686 """
687 if transportpolicy == POLICY_ALL:
687 if transportpolicy == POLICY_ALL:
688 transports = set(wireprototypes.TRANSPORTS)
688 transports = set(wireprototypes.TRANSPORTS)
689 elif transportpolicy == POLICY_V1_ONLY:
689 elif transportpolicy == POLICY_V1_ONLY:
690 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
690 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
691 if v['version'] == 1}
691 if v['version'] == 1}
692 elif transportpolicy == POLICY_V2_ONLY:
692 elif transportpolicy == POLICY_V2_ONLY:
693 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
693 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
694 if v['version'] == 2}
694 if v['version'] == 2}
695 else:
695 else:
696 raise error.Abort(_('invalid transport policy value: %s') %
696 raise error.Abort(_('invalid transport policy value: %s') %
697 transportpolicy)
697 transportpolicy)
698
698
699 def register(func):
699 def register(func):
700 commands[name] = commandentry(func, args=args, transports=transports)
700 commands[name] = commandentry(func, args=args, transports=transports)
701 return func
701 return func
702 return register
702 return register
703
703
704 @wireprotocommand('batch', 'cmds *')
704 @wireprotocommand('batch', 'cmds *')
705 def batch(repo, proto, cmds, others):
705 def batch(repo, proto, cmds, others):
706 repo = repo.filtered("served")
706 repo = repo.filtered("served")
707 res = []
707 res = []
708 for pair in cmds.split(';'):
708 for pair in cmds.split(';'):
709 op, args = pair.split(' ', 1)
709 op, args = pair.split(' ', 1)
710 vals = {}
710 vals = {}
711 for a in args.split(','):
711 for a in args.split(','):
712 if a:
712 if a:
713 n, v = a.split('=')
713 n, v = a.split('=')
714 vals[unescapearg(n)] = unescapearg(v)
714 vals[unescapearg(n)] = unescapearg(v)
715 func, spec = commands[op]
715 func, spec = commands[op]
716 if spec:
716 if spec:
717 keys = spec.split()
717 keys = spec.split()
718 data = {}
718 data = {}
719 for k in keys:
719 for k in keys:
720 if k == '*':
720 if k == '*':
721 star = {}
721 star = {}
722 for key in vals.keys():
722 for key in vals.keys():
723 if key not in keys:
723 if key not in keys:
724 star[key] = vals[key]
724 star[key] = vals[key]
725 data['*'] = star
725 data['*'] = star
726 else:
726 else:
727 data[k] = vals[k]
727 data[k] = vals[k]
728 result = func(repo, proto, *[data[k] for k in keys])
728 result = func(repo, proto, *[data[k] for k in keys])
729 else:
729 else:
730 result = func(repo, proto)
730 result = func(repo, proto)
731 if isinstance(result, ooberror):
731 if isinstance(result, ooberror):
732 return result
732 return result
733
733
734 # For now, all batchable commands must return bytesresponse or
734 # For now, all batchable commands must return bytesresponse or
735 # raw bytes (for backwards compatibility).
735 # raw bytes (for backwards compatibility).
736 assert isinstance(result, (bytesresponse, bytes))
736 assert isinstance(result, (bytesresponse, bytes))
737 if isinstance(result, bytesresponse):
737 if isinstance(result, bytesresponse):
738 result = result.data
738 result = result.data
739 res.append(escapearg(result))
739 res.append(escapearg(result))
740
740
741 return bytesresponse(';'.join(res))
741 return bytesresponse(';'.join(res))
742
742
743 # TODO mark as version 1 transport only once interaction with
743 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY)
744 # SSH handshake mechanism is figured out.
745 @wireprotocommand('between', 'pairs')
746 def between(repo, proto, pairs):
744 def between(repo, proto, pairs):
747 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
745 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
748 r = []
746 r = []
749 for b in repo.between(pairs):
747 for b in repo.between(pairs):
750 r.append(encodelist(b) + "\n")
748 r.append(encodelist(b) + "\n")
751
749
752 return bytesresponse(''.join(r))
750 return bytesresponse(''.join(r))
753
751
754 @wireprotocommand('branchmap')
752 @wireprotocommand('branchmap')
755 def branchmap(repo, proto):
753 def branchmap(repo, proto):
756 branchmap = repo.branchmap()
754 branchmap = repo.branchmap()
757 heads = []
755 heads = []
758 for branch, nodes in branchmap.iteritems():
756 for branch, nodes in branchmap.iteritems():
759 branchname = urlreq.quote(encoding.fromlocal(branch))
757 branchname = urlreq.quote(encoding.fromlocal(branch))
760 branchnodes = encodelist(nodes)
758 branchnodes = encodelist(nodes)
761 heads.append('%s %s' % (branchname, branchnodes))
759 heads.append('%s %s' % (branchname, branchnodes))
762
760
763 return bytesresponse('\n'.join(heads))
761 return bytesresponse('\n'.join(heads))
764
762
765 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY)
763 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY)
766 def branches(repo, proto, nodes):
764 def branches(repo, proto, nodes):
767 nodes = decodelist(nodes)
765 nodes = decodelist(nodes)
768 r = []
766 r = []
769 for b in repo.branches(nodes):
767 for b in repo.branches(nodes):
770 r.append(encodelist(b) + "\n")
768 r.append(encodelist(b) + "\n")
771
769
772 return bytesresponse(''.join(r))
770 return bytesresponse(''.join(r))
773
771
774 @wireprotocommand('clonebundles', '')
772 @wireprotocommand('clonebundles', '')
775 def clonebundles(repo, proto):
773 def clonebundles(repo, proto):
776 """Server command for returning info for available bundles to seed clones.
774 """Server command for returning info for available bundles to seed clones.
777
775
778 Clients will parse this response and determine what bundle to fetch.
776 Clients will parse this response and determine what bundle to fetch.
779
777
780 Extensions may wrap this command to filter or dynamically emit data
778 Extensions may wrap this command to filter or dynamically emit data
781 depending on the request. e.g. you could advertise URLs for the closest
779 depending on the request. e.g. you could advertise URLs for the closest
782 data center given the client's IP address.
780 data center given the client's IP address.
783 """
781 """
784 return bytesresponse(repo.vfs.tryread('clonebundles.manifest'))
782 return bytesresponse(repo.vfs.tryread('clonebundles.manifest'))
785
783
786 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
784 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
787 'known', 'getbundle', 'unbundlehash', 'batch']
785 'known', 'getbundle', 'unbundlehash', 'batch']
788
786
789 def _capabilities(repo, proto):
787 def _capabilities(repo, proto):
790 """return a list of capabilities for a repo
788 """return a list of capabilities for a repo
791
789
792 This function exists to allow extensions to easily wrap capabilities
790 This function exists to allow extensions to easily wrap capabilities
793 computation
791 computation
794
792
795 - returns a lists: easy to alter
793 - returns a lists: easy to alter
796 - change done here will be propagated to both `capabilities` and `hello`
794 - change done here will be propagated to both `capabilities` and `hello`
797 command without any other action needed.
795 command without any other action needed.
798 """
796 """
799 # copy to prevent modification of the global list
797 # copy to prevent modification of the global list
800 caps = list(wireprotocaps)
798 caps = list(wireprotocaps)
801
799
802 # Command of same name as capability isn't exposed to version 1 of
800 # Command of same name as capability isn't exposed to version 1 of
803 # transports. So conditionally add it.
801 # transports. So conditionally add it.
804 if commands.commandavailable('changegroupsubset', proto):
802 if commands.commandavailable('changegroupsubset', proto):
805 caps.append('changegroupsubset')
803 caps.append('changegroupsubset')
806
804
807 if streamclone.allowservergeneration(repo):
805 if streamclone.allowservergeneration(repo):
808 if repo.ui.configbool('server', 'preferuncompressed'):
806 if repo.ui.configbool('server', 'preferuncompressed'):
809 caps.append('stream-preferred')
807 caps.append('stream-preferred')
810 requiredformats = repo.requirements & repo.supportedformats
808 requiredformats = repo.requirements & repo.supportedformats
811 # if our local revlogs are just revlogv1, add 'stream' cap
809 # if our local revlogs are just revlogv1, add 'stream' cap
812 if not requiredformats - {'revlogv1'}:
810 if not requiredformats - {'revlogv1'}:
813 caps.append('stream')
811 caps.append('stream')
814 # otherwise, add 'streamreqs' detailing our local revlog format
812 # otherwise, add 'streamreqs' detailing our local revlog format
815 else:
813 else:
816 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
814 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
817 if repo.ui.configbool('experimental', 'bundle2-advertise'):
815 if repo.ui.configbool('experimental', 'bundle2-advertise'):
818 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
816 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
819 caps.append('bundle2=' + urlreq.quote(capsblob))
817 caps.append('bundle2=' + urlreq.quote(capsblob))
820 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
818 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
821
819
822 return proto.addcapabilities(repo, caps)
820 return proto.addcapabilities(repo, caps)
823
821
824 # If you are writing an extension and consider wrapping this function. Wrap
822 # If you are writing an extension and consider wrapping this function. Wrap
825 # `_capabilities` instead.
823 # `_capabilities` instead.
826 @wireprotocommand('capabilities')
824 @wireprotocommand('capabilities')
827 def capabilities(repo, proto):
825 def capabilities(repo, proto):
828 return bytesresponse(' '.join(_capabilities(repo, proto)))
826 return bytesresponse(' '.join(_capabilities(repo, proto)))
829
827
830 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY)
828 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY)
831 def changegroup(repo, proto, roots):
829 def changegroup(repo, proto, roots):
832 nodes = decodelist(roots)
830 nodes = decodelist(roots)
833 outgoing = discovery.outgoing(repo, missingroots=nodes,
831 outgoing = discovery.outgoing(repo, missingroots=nodes,
834 missingheads=repo.heads())
832 missingheads=repo.heads())
835 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
833 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
836 gen = iter(lambda: cg.read(32768), '')
834 gen = iter(lambda: cg.read(32768), '')
837 return streamres(gen=gen)
835 return streamres(gen=gen)
838
836
839 @wireprotocommand('changegroupsubset', 'bases heads',
837 @wireprotocommand('changegroupsubset', 'bases heads',
840 transportpolicy=POLICY_V1_ONLY)
838 transportpolicy=POLICY_V1_ONLY)
841 def changegroupsubset(repo, proto, bases, heads):
839 def changegroupsubset(repo, proto, bases, heads):
842 bases = decodelist(bases)
840 bases = decodelist(bases)
843 heads = decodelist(heads)
841 heads = decodelist(heads)
844 outgoing = discovery.outgoing(repo, missingroots=bases,
842 outgoing = discovery.outgoing(repo, missingroots=bases,
845 missingheads=heads)
843 missingheads=heads)
846 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
844 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
847 gen = iter(lambda: cg.read(32768), '')
845 gen = iter(lambda: cg.read(32768), '')
848 return streamres(gen=gen)
846 return streamres(gen=gen)
849
847
850 @wireprotocommand('debugwireargs', 'one two *')
848 @wireprotocommand('debugwireargs', 'one two *')
851 def debugwireargs(repo, proto, one, two, others):
849 def debugwireargs(repo, proto, one, two, others):
852 # only accept optional args from the known set
850 # only accept optional args from the known set
853 opts = options('debugwireargs', ['three', 'four'], others)
851 opts = options('debugwireargs', ['three', 'four'], others)
854 return bytesresponse(repo.debugwireargs(one, two,
852 return bytesresponse(repo.debugwireargs(one, two,
855 **pycompat.strkwargs(opts)))
853 **pycompat.strkwargs(opts)))
856
854
857 @wireprotocommand('getbundle', '*')
855 @wireprotocommand('getbundle', '*')
858 def getbundle(repo, proto, others):
856 def getbundle(repo, proto, others):
859 opts = options('getbundle', gboptsmap.keys(), others)
857 opts = options('getbundle', gboptsmap.keys(), others)
860 for k, v in opts.iteritems():
858 for k, v in opts.iteritems():
861 keytype = gboptsmap[k]
859 keytype = gboptsmap[k]
862 if keytype == 'nodes':
860 if keytype == 'nodes':
863 opts[k] = decodelist(v)
861 opts[k] = decodelist(v)
864 elif keytype == 'csv':
862 elif keytype == 'csv':
865 opts[k] = list(v.split(','))
863 opts[k] = list(v.split(','))
866 elif keytype == 'scsv':
864 elif keytype == 'scsv':
867 opts[k] = set(v.split(','))
865 opts[k] = set(v.split(','))
868 elif keytype == 'boolean':
866 elif keytype == 'boolean':
869 # Client should serialize False as '0', which is a non-empty string
867 # Client should serialize False as '0', which is a non-empty string
870 # so it evaluates as a True bool.
868 # so it evaluates as a True bool.
871 if v == '0':
869 if v == '0':
872 opts[k] = False
870 opts[k] = False
873 else:
871 else:
874 opts[k] = bool(v)
872 opts[k] = bool(v)
875 elif keytype != 'plain':
873 elif keytype != 'plain':
876 raise KeyError('unknown getbundle option type %s'
874 raise KeyError('unknown getbundle option type %s'
877 % keytype)
875 % keytype)
878
876
879 if not bundle1allowed(repo, 'pull'):
877 if not bundle1allowed(repo, 'pull'):
880 if not exchange.bundle2requested(opts.get('bundlecaps')):
878 if not exchange.bundle2requested(opts.get('bundlecaps')):
881 if proto.name == 'http-v1':
879 if proto.name == 'http-v1':
882 return ooberror(bundle2required)
880 return ooberror(bundle2required)
883 raise error.Abort(bundle2requiredmain,
881 raise error.Abort(bundle2requiredmain,
884 hint=bundle2requiredhint)
882 hint=bundle2requiredhint)
885
883
886 prefercompressed = True
884 prefercompressed = True
887
885
888 try:
886 try:
889 if repo.ui.configbool('server', 'disablefullbundle'):
887 if repo.ui.configbool('server', 'disablefullbundle'):
890 # Check to see if this is a full clone.
888 # Check to see if this is a full clone.
891 clheads = set(repo.changelog.heads())
889 clheads = set(repo.changelog.heads())
892 changegroup = opts.get('cg', True)
890 changegroup = opts.get('cg', True)
893 heads = set(opts.get('heads', set()))
891 heads = set(opts.get('heads', set()))
894 common = set(opts.get('common', set()))
892 common = set(opts.get('common', set()))
895 common.discard(nullid)
893 common.discard(nullid)
896 if changegroup and not common and clheads == heads:
894 if changegroup and not common and clheads == heads:
897 raise error.Abort(
895 raise error.Abort(
898 _('server has pull-based clones disabled'),
896 _('server has pull-based clones disabled'),
899 hint=_('remove --pull if specified or upgrade Mercurial'))
897 hint=_('remove --pull if specified or upgrade Mercurial'))
900
898
901 info, chunks = exchange.getbundlechunks(repo, 'serve',
899 info, chunks = exchange.getbundlechunks(repo, 'serve',
902 **pycompat.strkwargs(opts))
900 **pycompat.strkwargs(opts))
903 prefercompressed = info.get('prefercompressed', True)
901 prefercompressed = info.get('prefercompressed', True)
904 except error.Abort as exc:
902 except error.Abort as exc:
905 # cleanly forward Abort error to the client
903 # cleanly forward Abort error to the client
906 if not exchange.bundle2requested(opts.get('bundlecaps')):
904 if not exchange.bundle2requested(opts.get('bundlecaps')):
907 if proto.name == 'http-v1':
905 if proto.name == 'http-v1':
908 return ooberror(pycompat.bytestr(exc) + '\n')
906 return ooberror(pycompat.bytestr(exc) + '\n')
909 raise # cannot do better for bundle1 + ssh
907 raise # cannot do better for bundle1 + ssh
910 # bundle2 request expect a bundle2 reply
908 # bundle2 request expect a bundle2 reply
911 bundler = bundle2.bundle20(repo.ui)
909 bundler = bundle2.bundle20(repo.ui)
912 manargs = [('message', pycompat.bytestr(exc))]
910 manargs = [('message', pycompat.bytestr(exc))]
913 advargs = []
911 advargs = []
914 if exc.hint is not None:
912 if exc.hint is not None:
915 advargs.append(('hint', exc.hint))
913 advargs.append(('hint', exc.hint))
916 bundler.addpart(bundle2.bundlepart('error:abort',
914 bundler.addpart(bundle2.bundlepart('error:abort',
917 manargs, advargs))
915 manargs, advargs))
918 chunks = bundler.getchunks()
916 chunks = bundler.getchunks()
919 prefercompressed = False
917 prefercompressed = False
920
918
921 return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
919 return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
922
920
923 @wireprotocommand('heads')
921 @wireprotocommand('heads')
924 def heads(repo, proto):
922 def heads(repo, proto):
925 h = repo.heads()
923 h = repo.heads()
926 return bytesresponse(encodelist(h) + '\n')
924 return bytesresponse(encodelist(h) + '\n')
927
925
928 @wireprotocommand('hello')
926 @wireprotocommand('hello')
929 def hello(repo, proto):
927 def hello(repo, proto):
930 """Called as part of SSH handshake to obtain server info.
928 """Called as part of SSH handshake to obtain server info.
931
929
932 Returns a list of lines describing interesting things about the
930 Returns a list of lines describing interesting things about the
933 server, in an RFC822-like format.
931 server, in an RFC822-like format.
934
932
935 Currently, the only one defined is ``capabilities``, which consists of a
933 Currently, the only one defined is ``capabilities``, which consists of a
936 line of space separated tokens describing server abilities:
934 line of space separated tokens describing server abilities:
937
935
938 capabilities: <token0> <token1> <token2>
936 capabilities: <token0> <token1> <token2>
939 """
937 """
940 caps = capabilities(repo, proto).data
938 caps = capabilities(repo, proto).data
941 return bytesresponse('capabilities: %s\n' % caps)
939 return bytesresponse('capabilities: %s\n' % caps)
942
940
943 @wireprotocommand('listkeys', 'namespace')
941 @wireprotocommand('listkeys', 'namespace')
944 def listkeys(repo, proto, namespace):
942 def listkeys(repo, proto, namespace):
945 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
943 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
946 return bytesresponse(pushkeymod.encodekeys(d))
944 return bytesresponse(pushkeymod.encodekeys(d))
947
945
948 @wireprotocommand('lookup', 'key')
946 @wireprotocommand('lookup', 'key')
949 def lookup(repo, proto, key):
947 def lookup(repo, proto, key):
950 try:
948 try:
951 k = encoding.tolocal(key)
949 k = encoding.tolocal(key)
952 c = repo[k]
950 c = repo[k]
953 r = c.hex()
951 r = c.hex()
954 success = 1
952 success = 1
955 except Exception as inst:
953 except Exception as inst:
956 r = util.forcebytestr(inst)
954 r = util.forcebytestr(inst)
957 success = 0
955 success = 0
958 return bytesresponse('%d %s\n' % (success, r))
956 return bytesresponse('%d %s\n' % (success, r))
959
957
960 @wireprotocommand('known', 'nodes *')
958 @wireprotocommand('known', 'nodes *')
961 def known(repo, proto, nodes, others):
959 def known(repo, proto, nodes, others):
962 v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
960 v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
963 return bytesresponse(v)
961 return bytesresponse(v)
964
962
965 @wireprotocommand('pushkey', 'namespace key old new')
963 @wireprotocommand('pushkey', 'namespace key old new')
966 def pushkey(repo, proto, namespace, key, old, new):
964 def pushkey(repo, proto, namespace, key, old, new):
967 # compatibility with pre-1.8 clients which were accidentally
965 # compatibility with pre-1.8 clients which were accidentally
968 # sending raw binary nodes rather than utf-8-encoded hex
966 # sending raw binary nodes rather than utf-8-encoded hex
969 if len(new) == 20 and util.escapestr(new) != new:
967 if len(new) == 20 and util.escapestr(new) != new:
970 # looks like it could be a binary node
968 # looks like it could be a binary node
971 try:
969 try:
972 new.decode('utf-8')
970 new.decode('utf-8')
973 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
971 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
974 except UnicodeDecodeError:
972 except UnicodeDecodeError:
975 pass # binary, leave unmodified
973 pass # binary, leave unmodified
976 else:
974 else:
977 new = encoding.tolocal(new) # normal path
975 new = encoding.tolocal(new) # normal path
978
976
979 with proto.mayberedirectstdio() as output:
977 with proto.mayberedirectstdio() as output:
980 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
978 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
981 encoding.tolocal(old), new) or False
979 encoding.tolocal(old), new) or False
982
980
983 output = output.getvalue() if output else ''
981 output = output.getvalue() if output else ''
984 return bytesresponse('%d\n%s' % (int(r), output))
982 return bytesresponse('%d\n%s' % (int(r), output))
985
983
986 @wireprotocommand('stream_out')
984 @wireprotocommand('stream_out')
987 def stream(repo, proto):
985 def stream(repo, proto):
988 '''If the server supports streaming clone, it advertises the "stream"
986 '''If the server supports streaming clone, it advertises the "stream"
989 capability with a value representing the version and flags of the repo
987 capability with a value representing the version and flags of the repo
990 it is serving. Client checks to see if it understands the format.
988 it is serving. Client checks to see if it understands the format.
991 '''
989 '''
992 return streamres_legacy(streamclone.generatev1wireproto(repo))
990 return streamres_legacy(streamclone.generatev1wireproto(repo))
993
991
994 @wireprotocommand('unbundle', 'heads')
992 @wireprotocommand('unbundle', 'heads')
995 def unbundle(repo, proto, heads):
993 def unbundle(repo, proto, heads):
996 their_heads = decodelist(heads)
994 their_heads = decodelist(heads)
997
995
998 with proto.mayberedirectstdio() as output:
996 with proto.mayberedirectstdio() as output:
999 try:
997 try:
1000 exchange.check_heads(repo, their_heads, 'preparing changes')
998 exchange.check_heads(repo, their_heads, 'preparing changes')
1001
999
1002 # write bundle data to temporary file because it can be big
1000 # write bundle data to temporary file because it can be big
1003 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1001 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1004 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
1002 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
1005 r = 0
1003 r = 0
1006 try:
1004 try:
1007 proto.forwardpayload(fp)
1005 proto.forwardpayload(fp)
1008 fp.seek(0)
1006 fp.seek(0)
1009 gen = exchange.readbundle(repo.ui, fp, None)
1007 gen = exchange.readbundle(repo.ui, fp, None)
1010 if (isinstance(gen, changegroupmod.cg1unpacker)
1008 if (isinstance(gen, changegroupmod.cg1unpacker)
1011 and not bundle1allowed(repo, 'push')):
1009 and not bundle1allowed(repo, 'push')):
1012 if proto.name == 'http-v1':
1010 if proto.name == 'http-v1':
1013 # need to special case http because stderr do not get to
1011 # need to special case http because stderr do not get to
1014 # the http client on failed push so we need to abuse
1012 # the http client on failed push so we need to abuse
1015 # some other error type to make sure the message get to
1013 # some other error type to make sure the message get to
1016 # the user.
1014 # the user.
1017 return ooberror(bundle2required)
1015 return ooberror(bundle2required)
1018 raise error.Abort(bundle2requiredmain,
1016 raise error.Abort(bundle2requiredmain,
1019 hint=bundle2requiredhint)
1017 hint=bundle2requiredhint)
1020
1018
1021 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1019 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1022 proto.client())
1020 proto.client())
1023 if util.safehasattr(r, 'addpart'):
1021 if util.safehasattr(r, 'addpart'):
1024 # The return looks streamable, we are in the bundle2 case
1022 # The return looks streamable, we are in the bundle2 case
1025 # and should return a stream.
1023 # and should return a stream.
1026 return streamres_legacy(gen=r.getchunks())
1024 return streamres_legacy(gen=r.getchunks())
1027 return pushres(r, output.getvalue() if output else '')
1025 return pushres(r, output.getvalue() if output else '')
1028
1026
1029 finally:
1027 finally:
1030 fp.close()
1028 fp.close()
1031 os.unlink(tempname)
1029 os.unlink(tempname)
1032
1030
1033 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1031 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1034 # handle non-bundle2 case first
1032 # handle non-bundle2 case first
1035 if not getattr(exc, 'duringunbundle2', False):
1033 if not getattr(exc, 'duringunbundle2', False):
1036 try:
1034 try:
1037 raise
1035 raise
1038 except error.Abort:
1036 except error.Abort:
1039 # The old code we moved used util.stderr directly.
1037 # The old code we moved used util.stderr directly.
1040 # We did not change it to minimise code change.
1038 # We did not change it to minimise code change.
1041 # This need to be moved to something proper.
1039 # This need to be moved to something proper.
1042 # Feel free to do it.
1040 # Feel free to do it.
1043 util.stderr.write("abort: %s\n" % exc)
1041 util.stderr.write("abort: %s\n" % exc)
1044 if exc.hint is not None:
1042 if exc.hint is not None:
1045 util.stderr.write("(%s)\n" % exc.hint)
1043 util.stderr.write("(%s)\n" % exc.hint)
1046 return pushres(0, output.getvalue() if output else '')
1044 return pushres(0, output.getvalue() if output else '')
1047 except error.PushRaced:
1045 except error.PushRaced:
1048 return pusherr(str(exc),
1046 return pusherr(str(exc),
1049 output.getvalue() if output else '')
1047 output.getvalue() if output else '')
1050
1048
1051 bundler = bundle2.bundle20(repo.ui)
1049 bundler = bundle2.bundle20(repo.ui)
1052 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1050 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1053 bundler.addpart(out)
1051 bundler.addpart(out)
1054 try:
1052 try:
1055 try:
1053 try:
1056 raise
1054 raise
1057 except error.PushkeyFailed as exc:
1055 except error.PushkeyFailed as exc:
1058 # check client caps
1056 # check client caps
1059 remotecaps = getattr(exc, '_replycaps', None)
1057 remotecaps = getattr(exc, '_replycaps', None)
1060 if (remotecaps is not None
1058 if (remotecaps is not None
1061 and 'pushkey' not in remotecaps.get('error', ())):
1059 and 'pushkey' not in remotecaps.get('error', ())):
1062 # no support remote side, fallback to Abort handler.
1060 # no support remote side, fallback to Abort handler.
1063 raise
1061 raise
1064 part = bundler.newpart('error:pushkey')
1062 part = bundler.newpart('error:pushkey')
1065 part.addparam('in-reply-to', exc.partid)
1063 part.addparam('in-reply-to', exc.partid)
1066 if exc.namespace is not None:
1064 if exc.namespace is not None:
1067 part.addparam('namespace', exc.namespace,
1065 part.addparam('namespace', exc.namespace,
1068 mandatory=False)
1066 mandatory=False)
1069 if exc.key is not None:
1067 if exc.key is not None:
1070 part.addparam('key', exc.key, mandatory=False)
1068 part.addparam('key', exc.key, mandatory=False)
1071 if exc.new is not None:
1069 if exc.new is not None:
1072 part.addparam('new', exc.new, mandatory=False)
1070 part.addparam('new', exc.new, mandatory=False)
1073 if exc.old is not None:
1071 if exc.old is not None:
1074 part.addparam('old', exc.old, mandatory=False)
1072 part.addparam('old', exc.old, mandatory=False)
1075 if exc.ret is not None:
1073 if exc.ret is not None:
1076 part.addparam('ret', exc.ret, mandatory=False)
1074 part.addparam('ret', exc.ret, mandatory=False)
1077 except error.BundleValueError as exc:
1075 except error.BundleValueError as exc:
1078 errpart = bundler.newpart('error:unsupportedcontent')
1076 errpart = bundler.newpart('error:unsupportedcontent')
1079 if exc.parttype is not None:
1077 if exc.parttype is not None:
1080 errpart.addparam('parttype', exc.parttype)
1078 errpart.addparam('parttype', exc.parttype)
1081 if exc.params:
1079 if exc.params:
1082 errpart.addparam('params', '\0'.join(exc.params))
1080 errpart.addparam('params', '\0'.join(exc.params))
1083 except error.Abort as exc:
1081 except error.Abort as exc:
1084 manargs = [('message', util.forcebytestr(exc))]
1082 manargs = [('message', util.forcebytestr(exc))]
1085 advargs = []
1083 advargs = []
1086 if exc.hint is not None:
1084 if exc.hint is not None:
1087 advargs.append(('hint', exc.hint))
1085 advargs.append(('hint', exc.hint))
1088 bundler.addpart(bundle2.bundlepart('error:abort',
1086 bundler.addpart(bundle2.bundlepart('error:abort',
1089 manargs, advargs))
1087 manargs, advargs))
1090 except error.PushRaced as exc:
1088 except error.PushRaced as exc:
1091 bundler.newpart('error:pushraced',
1089 bundler.newpart('error:pushraced',
1092 [('message', util.forcebytestr(exc))])
1090 [('message', util.forcebytestr(exc))])
1093 return streamres_legacy(gen=bundler.getchunks())
1091 return streamres_legacy(gen=bundler.getchunks())
General Comments 0
You need to be logged in to leave comments. Login now