comparison mercurial/wireprototypes.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 9668744c9122
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
8 from .node import ( 8 from .node import (
9 bin, 9 bin,
10 hex, 10 hex,
11 ) 11 )
12 from .i18n import _ 12 from .i18n import _
13 from .thirdparty import ( 13 from .thirdparty import attr
14 attr,
15 )
16 from . import ( 14 from . import (
17 error, 15 error,
18 util, 16 util,
19 ) 17 )
20 from .interfaces import ( 18 from .interfaces import util as interfaceutil
21 util as interfaceutil, 19 from .utils import compression
22 )
23 from .utils import (
24 compression,
25 )
26 20
27 # Names of the SSH protocol implementations. 21 # Names of the SSH protocol implementations.
28 SSHV1 = 'ssh-v1' 22 SSHV1 = 'ssh-v1'
29 # These are advertised over the wire. Increment the counters at the end 23 # These are advertised over the wire. Increment the counters at the end
30 # to reflect BC breakages. 24 # to reflect BC breakages.
36 ELLIPSESCAP = 'exp-ellipses-2' 30 ELLIPSESCAP = 'exp-ellipses-2'
37 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP) 31 SUPPORTED_ELLIPSESCAP = (ELLIPSESCAP1, ELLIPSESCAP)
38 32
39 # All available wire protocol transports. 33 # All available wire protocol transports.
40 TRANSPORTS = { 34 TRANSPORTS = {
41 SSHV1: { 35 SSHV1: {'transport': 'ssh', 'version': 1,},
42 'transport': 'ssh',
43 'version': 1,
44 },
45 SSHV2: { 36 SSHV2: {
46 'transport': 'ssh', 37 'transport': 'ssh',
47 # TODO mark as version 2 once all commands are implemented. 38 # TODO mark as version 2 once all commands are implemented.
48 'version': 1, 39 'version': 1,
49 }, 40 },
50 'http-v1': { 41 'http-v1': {'transport': 'http', 'version': 1,},
51 'transport': 'http', 42 HTTP_WIREPROTO_V2: {'transport': 'http', 'version': 2,},
52 'version': 1,
53 },
54 HTTP_WIREPROTO_V2: {
55 'transport': 'http',
56 'version': 2,
57 }
58 } 43 }
44
59 45
60 class bytesresponse(object): 46 class bytesresponse(object):
61 """A wire protocol response consisting of raw bytes.""" 47 """A wire protocol response consisting of raw bytes."""
48
62 def __init__(self, data): 49 def __init__(self, data):
63 self.data = data 50 self.data = data
64 51
52
65 class ooberror(object): 53 class ooberror(object):
66 """wireproto reply: failure of a batch of operation 54 """wireproto reply: failure of a batch of operation
67 55
68 Something failed during a batch call. The error message is stored in 56 Something failed during a batch call. The error message is stored in
69 `self.message`. 57 `self.message`.
70 """ 58 """
59
71 def __init__(self, message): 60 def __init__(self, message):
72 self.message = message 61 self.message = message
73 62
63
74 class pushres(object): 64 class pushres(object):
75 """wireproto reply: success with simple integer return 65 """wireproto reply: success with simple integer return
76 66
77 The call was successful and returned an integer contained in `self.res`. 67 The call was successful and returned an integer contained in `self.res`.
78 """ 68 """
69
79 def __init__(self, res, output): 70 def __init__(self, res, output):
80 self.res = res 71 self.res = res
81 self.output = output 72 self.output = output
82 73
74
83 class pusherr(object): 75 class pusherr(object):
84 """wireproto reply: failure 76 """wireproto reply: failure
85 77
86 The call failed. The `self.res` attribute contains the error message. 78 The call failed. The `self.res` attribute contains the error message.
87 """ 79 """
80
88 def __init__(self, res, output): 81 def __init__(self, res, output):
89 self.res = res 82 self.res = res
90 self.output = output 83 self.output = output
91 84
85
92 class streamres(object): 86 class streamres(object):
93 """wireproto reply: binary stream 87 """wireproto reply: binary stream
94 88
95 The call was successful and the result is a stream. 89 The call was successful and the result is a stream.
96 90
98 92
99 ``prefer_uncompressed`` indicates that the data is expected to be 93 ``prefer_uncompressed`` indicates that the data is expected to be
100 uncompressable and that the stream should therefore use the ``none`` 94 uncompressable and that the stream should therefore use the ``none``
101 engine. 95 engine.
102 """ 96 """
97
103 def __init__(self, gen=None, prefer_uncompressed=False): 98 def __init__(self, gen=None, prefer_uncompressed=False):
104 self.gen = gen 99 self.gen = gen
105 self.prefer_uncompressed = prefer_uncompressed 100 self.prefer_uncompressed = prefer_uncompressed
106 101
102
107 class streamreslegacy(object): 103 class streamreslegacy(object):
108 """wireproto reply: uncompressed binary stream 104 """wireproto reply: uncompressed binary stream
109 105
110 The call was successful and the result is a stream. 106 The call was successful and the result is a stream.
111 107
112 Accepts a generator containing chunks of data to be sent to the client. 108 Accepts a generator containing chunks of data to be sent to the client.
113 109
114 Like ``streamres``, but sends an uncompressed data for "version 1" clients 110 Like ``streamres``, but sends an uncompressed data for "version 1" clients
115 using the application/mercurial-0.1 media type. 111 using the application/mercurial-0.1 media type.
116 """ 112 """
113
117 def __init__(self, gen=None): 114 def __init__(self, gen=None):
118 self.gen = gen 115 self.gen = gen
116
119 117
120 # list of nodes encoding / decoding 118 # list of nodes encoding / decoding
121 def decodelist(l, sep=' '): 119 def decodelist(l, sep=' '):
122 if l: 120 if l:
123 return [bin(v) for v in l.split(sep)] 121 return [bin(v) for v in l.split(sep)]
124 return [] 122 return []
123
125 124
126 def encodelist(l, sep=' '): 125 def encodelist(l, sep=' '):
127 try: 126 try:
128 return sep.join(map(hex, l)) 127 return sep.join(map(hex, l))
129 except TypeError: 128 except TypeError:
130 raise 129 raise
131 130
131
132 # batched call argument encoding 132 # batched call argument encoding
133 133
134
134 def escapebatcharg(plain): 135 def escapebatcharg(plain):
135 return (plain 136 return (
136 .replace(':', ':c') 137 plain.replace(':', ':c')
137 .replace(',', ':o') 138 .replace(',', ':o')
138 .replace(';', ':s') 139 .replace(';', ':s')
139 .replace('=', ':e')) 140 .replace('=', ':e')
141 )
142
140 143
141 def unescapebatcharg(escaped): 144 def unescapebatcharg(escaped):
142 return (escaped 145 return (
143 .replace(':e', '=') 146 escaped.replace(':e', '=')
144 .replace(':s', ';') 147 .replace(':s', ';')
145 .replace(':o', ',') 148 .replace(':o', ',')
146 .replace(':c', ':')) 149 .replace(':c', ':')
150 )
151
147 152
148 # mapping of options accepted by getbundle and their types 153 # mapping of options accepted by getbundle and their types
149 # 154 #
150 # Meant to be extended by extensions. It is the extension's responsibility to 155 # Meant to be extended by extensions. It is the extension's responsibility to
151 # ensure such options are properly processed in exchange.getbundle. 156 # ensure such options are properly processed in exchange.getbundle.
155 # :nodes: list of binary nodes, transmitted as space-separated hex nodes 160 # :nodes: list of binary nodes, transmitted as space-separated hex nodes
156 # :csv: list of values, transmitted as comma-separated values 161 # :csv: list of values, transmitted as comma-separated values
157 # :scsv: set of values, transmitted as comma-separated values 162 # :scsv: set of values, transmitted as comma-separated values
158 # :plain: string with no transformation needed. 163 # :plain: string with no transformation needed.
159 GETBUNDLE_ARGUMENTS = { 164 GETBUNDLE_ARGUMENTS = {
160 'heads': 'nodes', 165 'heads': 'nodes',
161 'bookmarks': 'boolean', 166 'bookmarks': 'boolean',
162 'common': 'nodes', 167 'common': 'nodes',
163 'obsmarkers': 'boolean', 168 'obsmarkers': 'boolean',
164 'phases': 'boolean', 169 'phases': 'boolean',
165 'bundlecaps': 'scsv', 170 'bundlecaps': 'scsv',
169 'stream': 'boolean', 174 'stream': 'boolean',
170 'includepats': 'csv', 175 'includepats': 'csv',
171 'excludepats': 'csv', 176 'excludepats': 'csv',
172 } 177 }
173 178
179
174 class baseprotocolhandler(interfaceutil.Interface): 180 class baseprotocolhandler(interfaceutil.Interface):
175 """Abstract base class for wire protocol handlers. 181 """Abstract base class for wire protocol handlers.
176 182
177 A wire protocol handler serves as an interface between protocol command 183 A wire protocol handler serves as an interface between protocol command
178 handlers and the wire protocol transport layer. Protocol handlers provide 184 handlers and the wire protocol transport layer. Protocol handlers provide
182 188
183 name = interfaceutil.Attribute( 189 name = interfaceutil.Attribute(
184 """The name of the protocol implementation. 190 """The name of the protocol implementation.
185 191
186 Used for uniquely identifying the transport type. 192 Used for uniquely identifying the transport type.
187 """) 193 """
194 )
188 195
189 def getargs(args): 196 def getargs(args):
190 """return the value for arguments in <args> 197 """return the value for arguments in <args>
191 198
192 For version 1 transports, returns a list of values in the same 199 For version 1 transports, returns a list of values in the same
237 The argument is the permission required to proceed. If the client 244 The argument is the permission required to proceed. If the client
238 doesn't have that permission, the exception should raise or abort 245 doesn't have that permission, the exception should raise or abort
239 in a protocol specific manner. 246 in a protocol specific manner.
240 """ 247 """
241 248
249
242 class commandentry(object): 250 class commandentry(object):
243 """Represents a declared wire protocol command.""" 251 """Represents a declared wire protocol command."""
244 def __init__(self, func, args='', transports=None, 252
245 permission='push', cachekeyfn=None, extracapabilitiesfn=None): 253 def __init__(
254 self,
255 func,
256 args='',
257 transports=None,
258 permission='push',
259 cachekeyfn=None,
260 extracapabilitiesfn=None,
261 ):
246 self.func = func 262 self.func = func
247 self.args = args 263 self.args = args
248 self.transports = transports or set() 264 self.transports = transports or set()
249 self.permission = permission 265 self.permission = permission
250 self.cachekeyfn = cachekeyfn 266 self.cachekeyfn = cachekeyfn
256 This is called when a caller using the old 2-tuple API attempts 272 This is called when a caller using the old 2-tuple API attempts
257 to replace an instance. The incoming values are merged with 273 to replace an instance. The incoming values are merged with
258 data not captured by the 2-tuple and a new instance containing 274 data not captured by the 2-tuple and a new instance containing
259 the union of the two objects is returned. 275 the union of the two objects is returned.
260 """ 276 """
261 return commandentry(func, args=args, transports=set(self.transports), 277 return commandentry(
262 permission=self.permission) 278 func,
279 args=args,
280 transports=set(self.transports),
281 permission=self.permission,
282 )
263 283
264 # Old code treats instances as 2-tuples. So expose that interface. 284 # Old code treats instances as 2-tuples. So expose that interface.
265 def __iter__(self): 285 def __iter__(self):
266 yield self.func 286 yield self.func
267 yield self.args 287 yield self.args
272 elif i == 1: 292 elif i == 1:
273 return self.args 293 return self.args
274 else: 294 else:
275 raise IndexError('can only access elements 0 and 1') 295 raise IndexError('can only access elements 0 and 1')
276 296
297
277 class commanddict(dict): 298 class commanddict(dict):
278 """Container for registered wire protocol commands. 299 """Container for registered wire protocol commands.
279 300
280 It behaves like a dict. But __setitem__ is overwritten to allow silent 301 It behaves like a dict. But __setitem__ is overwritten to allow silent
281 coercion of values from 2-tuples for API compatibility. 302 coercion of values from 2-tuples for API compatibility.
282 """ 303 """
304
283 def __setitem__(self, k, v): 305 def __setitem__(self, k, v):
284 if isinstance(v, commandentry): 306 if isinstance(v, commandentry):
285 pass 307 pass
286 # Cast 2-tuples to commandentry instances. 308 # Cast 2-tuples to commandentry instances.
287 elif isinstance(v, tuple): 309 elif isinstance(v, tuple):
294 # command entries, we automatically merge old state with new. 316 # command entries, we automatically merge old state with new.
295 if k in self: 317 if k in self:
296 v = self[k]._merge(v[0], v[1]) 318 v = self[k]._merge(v[0], v[1])
297 else: 319 else:
298 # Use default values from @wireprotocommand. 320 # Use default values from @wireprotocommand.
299 v = commandentry(v[0], args=v[1], 321 v = commandentry(
300 transports=set(TRANSPORTS), 322 v[0],
301 permission='push') 323 args=v[1],
324 transports=set(TRANSPORTS),
325 permission='push',
326 )
302 else: 327 else:
303 raise ValueError('command entries must be commandentry instances ' 328 raise ValueError(
304 'or 2-tuples') 329 'command entries must be commandentry instances ' 'or 2-tuples'
330 )
305 331
306 return super(commanddict, self).__setitem__(k, v) 332 return super(commanddict, self).__setitem__(k, v)
307 333
308 def commandavailable(self, command, proto): 334 def commandavailable(self, command, proto):
309 """Determine if a command is available for the requested protocol.""" 335 """Determine if a command is available for the requested protocol."""
316 342
317 if proto.name not in entry.transports: 343 if proto.name not in entry.transports:
318 return False 344 return False
319 345
320 return True 346 return True
347
321 348
322 def supportedcompengines(ui, role): 349 def supportedcompengines(ui, role):
323 """Obtain the list of supported compression engines for a request.""" 350 """Obtain the list of supported compression engines for a request."""
324 assert role in (compression.CLIENTROLE, compression.SERVERROLE) 351 assert role in (compression.CLIENTROLE, compression.SERVERROLE)
325 352
333 # This is currently implemented mainly to facilitate testing. In most 360 # This is currently implemented mainly to facilitate testing. In most
334 # cases, the server should be in charge of choosing a compression engine 361 # cases, the server should be in charge of choosing a compression engine
335 # because a server has the most to lose from a sub-optimal choice. (e.g. 362 # because a server has the most to lose from a sub-optimal choice. (e.g.
336 # CPU DoS due to an expensive engine or a network DoS due to poor 363 # CPU DoS due to an expensive engine or a network DoS due to poor
337 # compression ratio). 364 # compression ratio).
338 configengines = ui.configlist('experimental', 365 configengines = ui.configlist(
339 'clientcompressionengines') 366 'experimental', 'clientcompressionengines'
367 )
340 config = 'experimental.clientcompressionengines' 368 config = 'experimental.clientcompressionengines'
341 369
342 # No explicit config. Filter out the ones that aren't supposed to be 370 # No explicit config. Filter out the ones that aren't supposed to be
343 # advertised and return default ordering. 371 # advertised and return default ordering.
344 if not configengines: 372 if not configengines:
345 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority' 373 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
346 return [e for e in compengines 374 return [
347 if getattr(e.wireprotosupport(), attr) > 0] 375 e for e in compengines if getattr(e.wireprotosupport(), attr) > 0
376 ]
348 377
349 # If compression engines are listed in the config, assume there is a good 378 # If compression engines are listed in the config, assume there is a good
350 # reason for it (like server operators wanting to achieve specific 379 # reason for it (like server operators wanting to achieve specific
351 # performance characteristics). So fail fast if the config references 380 # performance characteristics). So fail fast if the config references
352 # unusable compression engines. 381 # unusable compression engines.
353 validnames = set(e.name() for e in compengines) 382 validnames = set(e.name() for e in compengines)
354 invalidnames = set(e for e in configengines if e not in validnames) 383 invalidnames = set(e for e in configengines if e not in validnames)
355 if invalidnames: 384 if invalidnames:
356 raise error.Abort(_('invalid compression engine defined in %s: %s') % 385 raise error.Abort(
357 (config, ', '.join(sorted(invalidnames)))) 386 _('invalid compression engine defined in %s: %s')
387 % (config, ', '.join(sorted(invalidnames)))
388 )
358 389
359 compengines = [e for e in compengines if e.name() in configengines] 390 compengines = [e for e in compengines if e.name() in configengines]
360 compengines = sorted(compengines, 391 compengines = sorted(
361 key=lambda e: configengines.index(e.name())) 392 compengines, key=lambda e: configengines.index(e.name())
393 )
362 394
363 if not compengines: 395 if not compengines:
364 raise error.Abort(_('%s config option does not specify any known ' 396 raise error.Abort(
365 'compression engines') % config, 397 _(
366 hint=_('usable compression engines: %s') % 398 '%s config option does not specify any known '
367 ', '.sorted(validnames)) 399 'compression engines'
400 )
401 % config,
402 hint=_('usable compression engines: %s') % ', '.sorted(validnames),
403 )
368 404
369 return compengines 405 return compengines
406
370 407
371 @attr.s 408 @attr.s
372 class encodedresponse(object): 409 class encodedresponse(object):
373 """Represents response data that is already content encoded. 410 """Represents response data that is already content encoded.
374 411
376 413
377 Commands typically emit Python objects that are encoded and sent over the 414 Commands typically emit Python objects that are encoded and sent over the
378 wire. If commands emit an object of this type, the encoding step is bypassed 415 wire. If commands emit an object of this type, the encoding step is bypassed
379 and the content from this object is used instead. 416 and the content from this object is used instead.
380 """ 417 """
418
381 data = attr.ib() 419 data = attr.ib()
420
382 421
383 @attr.s 422 @attr.s
384 class alternatelocationresponse(object): 423 class alternatelocationresponse(object):
385 """Represents a response available at an alternate location. 424 """Represents a response available at an alternate location.
386 425
387 Instances are sent in place of actual response objects when the server 426 Instances are sent in place of actual response objects when the server
388 is sending a "content redirect" response. 427 is sending a "content redirect" response.
389 428
390 Only compatible with wire protocol version 2. 429 Only compatible with wire protocol version 2.
391 """ 430 """
431
392 url = attr.ib() 432 url = attr.ib()
393 mediatype = attr.ib() 433 mediatype = attr.ib()
394 size = attr.ib(default=None) 434 size = attr.ib(default=None)
395 fullhashes = attr.ib(default=None) 435 fullhashes = attr.ib(default=None)
396 fullhashseed = attr.ib(default=None) 436 fullhashseed = attr.ib(default=None)
397 serverdercerts = attr.ib(default=None) 437 serverdercerts = attr.ib(default=None)
398 servercadercerts = attr.ib(default=None) 438 servercadercerts = attr.ib(default=None)
399 439
440
400 @attr.s 441 @attr.s
401 class indefinitebytestringresponse(object): 442 class indefinitebytestringresponse(object):
402 """Represents an object to be encoded to an indefinite length bytestring. 443 """Represents an object to be encoded to an indefinite length bytestring.
403 444
404 Instances are initialized from an iterable of chunks, with each chunk being 445 Instances are initialized from an iterable of chunks, with each chunk being
405 a bytes instance. 446 a bytes instance.
406 """ 447 """
448
407 chunks = attr.ib() 449 chunks = attr.ib()