Mercurial > public > mercurial-scm > hg
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() |