Mercurial > hg
comparison mercurial/wireprotov2server.py @ 43077:687b865b95ad
formatting: byteify all mercurial/ and hgext/ string literals
Done with
python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py')
black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**')
# skip-blame mass-reformatting only
Differential Revision: https://phab.mercurial-scm.org/D6972
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:48:39 -0400 |
parents | 2372284d9457 |
children | d783f945a701 |
comparison
equal
deleted
inserted
replaced
43076:2372284d9457 | 43077:687b865b95ad |
---|---|
54 | 54 |
55 # Root URL does nothing meaningful... yet. | 55 # Root URL does nothing meaningful... yet. |
56 if not urlparts: | 56 if not urlparts: |
57 res.status = b'200 OK' | 57 res.status = b'200 OK' |
58 res.headers[b'Content-Type'] = b'text/plain' | 58 res.headers[b'Content-Type'] = b'text/plain' |
59 res.setbodybytes(_('HTTP version 2 API handler')) | 59 res.setbodybytes(_(b'HTTP version 2 API handler')) |
60 return | 60 return |
61 | 61 |
62 if len(urlparts) == 1: | 62 if len(urlparts) == 1: |
63 res.status = b'404 Not Found' | 63 res.status = b'404 Not Found' |
64 res.headers[b'Content-Type'] = b'text/plain' | 64 res.headers[b'Content-Type'] = b'text/plain' |
65 res.setbodybytes( | 65 res.setbodybytes( |
66 _('do not know how to process %s\n') % req.dispatchpath | 66 _(b'do not know how to process %s\n') % req.dispatchpath |
67 ) | 67 ) |
68 return | 68 return |
69 | 69 |
70 permission, command = urlparts[0:2] | 70 permission, command = urlparts[0:2] |
71 | 71 |
72 if permission not in (b'ro', b'rw'): | 72 if permission not in (b'ro', b'rw'): |
73 res.status = b'404 Not Found' | 73 res.status = b'404 Not Found' |
74 res.headers[b'Content-Type'] = b'text/plain' | 74 res.headers[b'Content-Type'] = b'text/plain' |
75 res.setbodybytes(_('unknown permission: %s') % permission) | 75 res.setbodybytes(_(b'unknown permission: %s') % permission) |
76 return | 76 return |
77 | 77 |
78 if req.method != 'POST': | 78 if req.method != b'POST': |
79 res.status = b'405 Method Not Allowed' | 79 res.status = b'405 Method Not Allowed' |
80 res.headers[b'Allow'] = b'POST' | 80 res.headers[b'Allow'] = b'POST' |
81 res.setbodybytes(_('commands require POST requests')) | 81 res.setbodybytes(_(b'commands require POST requests')) |
82 return | 82 return |
83 | 83 |
84 # At some point we'll want to use our own API instead of recycling the | 84 # At some point we'll want to use our own API instead of recycling the |
85 # behavior of version 1 of the wire protocol... | 85 # behavior of version 1 of the wire protocol... |
86 # TODO return reasonable responses - not responses that overload the | 86 # TODO return reasonable responses - not responses that overload the |
87 # HTTP status line message for error reporting. | 87 # HTTP status line message for error reporting. |
88 try: | 88 try: |
89 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push') | 89 checkperm(rctx, req, b'pull' if permission == b'ro' else b'push') |
90 except hgwebcommon.ErrorResponse as e: | 90 except hgwebcommon.ErrorResponse as e: |
91 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) | 91 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) |
92 for k, v in e.headers: | 92 for k, v in e.headers: |
93 res.headers[k] = v | 93 res.headers[k] = v |
94 res.setbodybytes('permission denied') | 94 res.setbodybytes(b'permission denied') |
95 return | 95 return |
96 | 96 |
97 # We have a special endpoint to reflect the request back at the client. | 97 # We have a special endpoint to reflect the request back at the client. |
98 if command == b'debugreflect': | 98 if command == b'debugreflect': |
99 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res) | 99 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res) |
100 return | 100 return |
101 | 101 |
102 # Extra commands that we handle that aren't really wire protocol | 102 # Extra commands that we handle that aren't really wire protocol |
103 # commands. Think extra hard before making this hackery available to | 103 # commands. Think extra hard before making this hackery available to |
104 # extension. | 104 # extension. |
105 extracommands = {'multirequest'} | 105 extracommands = {b'multirequest'} |
106 | 106 |
107 if command not in COMMANDS and command not in extracommands: | 107 if command not in COMMANDS and command not in extracommands: |
108 res.status = b'404 Not Found' | 108 res.status = b'404 Not Found' |
109 res.headers[b'Content-Type'] = b'text/plain' | 109 res.headers[b'Content-Type'] = b'text/plain' |
110 res.setbodybytes(_('unknown wire protocol command: %s\n') % command) | 110 res.setbodybytes(_(b'unknown wire protocol command: %s\n') % command) |
111 return | 111 return |
112 | 112 |
113 repo = rctx.repo | 113 repo = rctx.repo |
114 ui = repo.ui | 114 ui = repo.ui |
115 | 115 |
119 not COMMANDS.commandavailable(command, proto) | 119 not COMMANDS.commandavailable(command, proto) |
120 and command not in extracommands | 120 and command not in extracommands |
121 ): | 121 ): |
122 res.status = b'404 Not Found' | 122 res.status = b'404 Not Found' |
123 res.headers[b'Content-Type'] = b'text/plain' | 123 res.headers[b'Content-Type'] = b'text/plain' |
124 res.setbodybytes(_('invalid wire protocol command: %s') % command) | 124 res.setbodybytes(_(b'invalid wire protocol command: %s') % command) |
125 return | 125 return |
126 | 126 |
127 # TODO consider cases where proxies may add additional Accept headers. | 127 # TODO consider cases where proxies may add additional Accept headers. |
128 if req.headers.get(b'Accept') != FRAMINGTYPE: | 128 if req.headers.get(b'Accept') != FRAMINGTYPE: |
129 res.status = b'406 Not Acceptable' | 129 res.status = b'406 Not Acceptable' |
130 res.headers[b'Content-Type'] = b'text/plain' | 130 res.headers[b'Content-Type'] = b'text/plain' |
131 res.setbodybytes( | 131 res.setbodybytes( |
132 _('client MUST specify Accept header with value: %s\n') | 132 _(b'client MUST specify Accept header with value: %s\n') |
133 % FRAMINGTYPE | 133 % FRAMINGTYPE |
134 ) | 134 ) |
135 return | 135 return |
136 | 136 |
137 if req.headers.get(b'Content-Type') != FRAMINGTYPE: | 137 if req.headers.get(b'Content-Type') != FRAMINGTYPE: |
138 res.status = b'415 Unsupported Media Type' | 138 res.status = b'415 Unsupported Media Type' |
139 # TODO we should send a response with appropriate media type, | 139 # TODO we should send a response with appropriate media type, |
140 # since client does Accept it. | 140 # since client does Accept it. |
141 res.headers[b'Content-Type'] = b'text/plain' | 141 res.headers[b'Content-Type'] = b'text/plain' |
142 res.setbodybytes( | 142 res.setbodybytes( |
143 _('client MUST send Content-Type header with ' 'value: %s\n') | 143 _(b'client MUST send Content-Type header with ' b'value: %s\n') |
144 % FRAMINGTYPE | 144 % FRAMINGTYPE |
145 ) | 145 ) |
146 return | 146 return |
147 | 147 |
148 _processhttpv2request(ui, repo, req, res, permission, command, proto) | 148 _processhttpv2request(ui, repo, req, res, permission, command, proto) |
158 tracker. We then dump the log of all that activity back out to the | 158 tracker. We then dump the log of all that activity back out to the |
159 client. | 159 client. |
160 """ | 160 """ |
161 # Reflection APIs have a history of being abused, accidentally disclosing | 161 # Reflection APIs have a history of being abused, accidentally disclosing |
162 # sensitive data, etc. So we have a config knob. | 162 # sensitive data, etc. So we have a config knob. |
163 if not ui.configbool('experimental', 'web.api.debugreflect'): | 163 if not ui.configbool(b'experimental', b'web.api.debugreflect'): |
164 res.status = b'404 Not Found' | 164 res.status = b'404 Not Found' |
165 res.headers[b'Content-Type'] = b'text/plain' | 165 res.headers[b'Content-Type'] = b'text/plain' |
166 res.setbodybytes(_('debugreflect service not available')) | 166 res.setbodybytes(_(b'debugreflect service not available')) |
167 return | 167 return |
168 | 168 |
169 # We assume we have a unified framing protocol request body. | 169 # We assume we have a unified framing protocol request body. |
170 | 170 |
171 reactor = wireprotoframing.serverreactor(ui) | 171 reactor = wireprotoframing.serverreactor(ui) |
185 | 185 |
186 action, meta = reactor.onframerecv(frame) | 186 action, meta = reactor.onframerecv(frame) |
187 states.append(templatefilters.json((action, meta))) | 187 states.append(templatefilters.json((action, meta))) |
188 | 188 |
189 action, meta = reactor.oninputeof() | 189 action, meta = reactor.oninputeof() |
190 meta['action'] = action | 190 meta[b'action'] = action |
191 states.append(templatefilters.json(meta)) | 191 states.append(templatefilters.json(meta)) |
192 | 192 |
193 res.status = b'200 OK' | 193 res.status = b'200 OK' |
194 res.headers[b'Content-Type'] = b'text/plain' | 194 res.headers[b'Content-Type'] = b'text/plain' |
195 res.setbodybytes(b'\n'.join(states)) | 195 res.setbodybytes(b'\n'.join(states)) |
214 if not frame: | 214 if not frame: |
215 break | 215 break |
216 | 216 |
217 action, meta = reactor.onframerecv(frame) | 217 action, meta = reactor.onframerecv(frame) |
218 | 218 |
219 if action == 'wantframe': | 219 if action == b'wantframe': |
220 # Need more data before we can do anything. | 220 # Need more data before we can do anything. |
221 continue | 221 continue |
222 elif action == 'runcommand': | 222 elif action == b'runcommand': |
223 # Defer creating output stream because we need to wait for | 223 # Defer creating output stream because we need to wait for |
224 # protocol settings frames so proper encoding can be applied. | 224 # protocol settings frames so proper encoding can be applied. |
225 if not outstream: | 225 if not outstream: |
226 outstream = reactor.makeoutputstream() | 226 outstream = reactor.makeoutputstream() |
227 | 227 |
241 if sentoutput: | 241 if sentoutput: |
242 return | 242 return |
243 | 243 |
244 seencommand = True | 244 seencommand = True |
245 | 245 |
246 elif action == 'error': | 246 elif action == b'error': |
247 # TODO define proper error mechanism. | 247 # TODO define proper error mechanism. |
248 res.status = b'200 OK' | 248 res.status = b'200 OK' |
249 res.headers[b'Content-Type'] = b'text/plain' | 249 res.headers[b'Content-Type'] = b'text/plain' |
250 res.setbodybytes(meta['message'] + b'\n') | 250 res.setbodybytes(meta[b'message'] + b'\n') |
251 return | 251 return |
252 else: | 252 else: |
253 raise error.ProgrammingError( | 253 raise error.ProgrammingError( |
254 'unhandled action from frame processor: %s' % action | 254 b'unhandled action from frame processor: %s' % action |
255 ) | 255 ) |
256 | 256 |
257 action, meta = reactor.oninputeof() | 257 action, meta = reactor.oninputeof() |
258 if action == 'sendframes': | 258 if action == b'sendframes': |
259 # We assume we haven't started sending the response yet. If we're | 259 # We assume we haven't started sending the response yet. If we're |
260 # wrong, the response type will raise an exception. | 260 # wrong, the response type will raise an exception. |
261 res.status = b'200 OK' | 261 res.status = b'200 OK' |
262 res.headers[b'Content-Type'] = FRAMINGTYPE | 262 res.headers[b'Content-Type'] = FRAMINGTYPE |
263 res.setbodygen(meta['framegen']) | 263 res.setbodygen(meta[b'framegen']) |
264 elif action == 'noop': | 264 elif action == b'noop': |
265 pass | 265 pass |
266 else: | 266 else: |
267 raise error.ProgrammingError( | 267 raise error.ProgrammingError( |
268 'unhandled action from frame processor: %s' % action | 268 b'unhandled action from frame processor: %s' % action |
269 ) | 269 ) |
270 | 270 |
271 | 271 |
272 def _httpv2runcommand( | 272 def _httpv2runcommand( |
273 ui, | 273 ui, |
299 # execute multiple commands. We double check permissions of each command | 299 # execute multiple commands. We double check permissions of each command |
300 # as it is invoked to ensure there is no privilege escalation. | 300 # as it is invoked to ensure there is no privilege escalation. |
301 # TODO consider allowing multiple commands to regular command URLs | 301 # TODO consider allowing multiple commands to regular command URLs |
302 # iff each command is the same. | 302 # iff each command is the same. |
303 | 303 |
304 proto = httpv2protocolhandler(req, ui, args=command['args']) | 304 proto = httpv2protocolhandler(req, ui, args=command[b'args']) |
305 | 305 |
306 if reqcommand == b'multirequest': | 306 if reqcommand == b'multirequest': |
307 if not COMMANDS.commandavailable(command['command'], proto): | 307 if not COMMANDS.commandavailable(command[b'command'], proto): |
308 # TODO proper error mechanism | 308 # TODO proper error mechanism |
309 res.status = b'200 OK' | 309 res.status = b'200 OK' |
310 res.headers[b'Content-Type'] = b'text/plain' | 310 res.headers[b'Content-Type'] = b'text/plain' |
311 res.setbodybytes( | 311 res.setbodybytes( |
312 _('wire protocol command not available: %s') | 312 _(b'wire protocol command not available: %s') |
313 % command['command'] | 313 % command[b'command'] |
314 ) | 314 ) |
315 return True | 315 return True |
316 | 316 |
317 # TODO don't use assert here, since it may be elided by -O. | 317 # TODO don't use assert here, since it may be elided by -O. |
318 assert authedperm in (b'ro', b'rw') | 318 assert authedperm in (b'ro', b'rw') |
319 wirecommand = COMMANDS[command['command']] | 319 wirecommand = COMMANDS[command[b'command']] |
320 assert wirecommand.permission in ('push', 'pull') | 320 assert wirecommand.permission in (b'push', b'pull') |
321 | 321 |
322 if authedperm == b'ro' and wirecommand.permission != 'pull': | 322 if authedperm == b'ro' and wirecommand.permission != b'pull': |
323 # TODO proper error mechanism | 323 # TODO proper error mechanism |
324 res.status = b'403 Forbidden' | 324 res.status = b'403 Forbidden' |
325 res.headers[b'Content-Type'] = b'text/plain' | 325 res.headers[b'Content-Type'] = b'text/plain' |
326 res.setbodybytes( | 326 res.setbodybytes( |
327 _('insufficient permissions to execute ' 'command: %s') | 327 _(b'insufficient permissions to execute ' b'command: %s') |
328 % command['command'] | 328 % command[b'command'] |
329 ) | 329 ) |
330 return True | 330 return True |
331 | 331 |
332 # TODO should we also call checkperm() here? Maybe not if we're going | 332 # TODO should we also call checkperm() here? Maybe not if we're going |
333 # to overhaul that API. The granted scope from the URL check should | 333 # to overhaul that API. The granted scope from the URL check should |
338 if issubsequent: | 338 if issubsequent: |
339 # TODO proper error mechanism | 339 # TODO proper error mechanism |
340 res.status = b'200 OK' | 340 res.status = b'200 OK' |
341 res.headers[b'Content-Type'] = b'text/plain' | 341 res.headers[b'Content-Type'] = b'text/plain' |
342 res.setbodybytes( | 342 res.setbodybytes( |
343 _('multiple commands cannot be issued to this ' 'URL') | 343 _(b'multiple commands cannot be issued to this ' b'URL') |
344 ) | 344 ) |
345 return True | 345 return True |
346 | 346 |
347 if reqcommand != command['command']: | 347 if reqcommand != command[b'command']: |
348 # TODO define proper error mechanism | 348 # TODO define proper error mechanism |
349 res.status = b'200 OK' | 349 res.status = b'200 OK' |
350 res.headers[b'Content-Type'] = b'text/plain' | 350 res.headers[b'Content-Type'] = b'text/plain' |
351 res.setbodybytes(_('command in frame must match command in URL')) | 351 res.setbodybytes(_(b'command in frame must match command in URL')) |
352 return True | 352 return True |
353 | 353 |
354 res.status = b'200 OK' | 354 res.status = b'200 OK' |
355 res.headers[b'Content-Type'] = FRAMINGTYPE | 355 res.headers[b'Content-Type'] = FRAMINGTYPE |
356 | 356 |
357 try: | 357 try: |
358 objs = dispatch(repo, proto, command['command'], command['redirect']) | 358 objs = dispatch(repo, proto, command[b'command'], command[b'redirect']) |
359 | 359 |
360 action, meta = reactor.oncommandresponsereadyobjects( | 360 action, meta = reactor.oncommandresponsereadyobjects( |
361 outstream, command['requestid'], objs | 361 outstream, command[b'requestid'], objs |
362 ) | 362 ) |
363 | 363 |
364 except error.WireprotoCommandError as e: | 364 except error.WireprotoCommandError as e: |
365 action, meta = reactor.oncommanderror( | 365 action, meta = reactor.oncommanderror( |
366 outstream, command['requestid'], e.message, e.messageargs | 366 outstream, command[b'requestid'], e.message, e.messageargs |
367 ) | 367 ) |
368 | 368 |
369 except Exception as e: | 369 except Exception as e: |
370 action, meta = reactor.onservererror( | 370 action, meta = reactor.onservererror( |
371 outstream, | 371 outstream, |
372 command['requestid'], | 372 command[b'requestid'], |
373 _('exception when invoking command: %s') | 373 _(b'exception when invoking command: %s') |
374 % stringutil.forcebytestr(e), | 374 % stringutil.forcebytestr(e), |
375 ) | 375 ) |
376 | 376 |
377 if action == 'sendframes': | 377 if action == b'sendframes': |
378 res.setbodygen(meta['framegen']) | 378 res.setbodygen(meta[b'framegen']) |
379 return True | 379 return True |
380 elif action == 'noop': | 380 elif action == b'noop': |
381 return False | 381 return False |
382 else: | 382 else: |
383 raise error.ProgrammingError( | 383 raise error.ProgrammingError( |
384 'unhandled event from reactor: %s' % action | 384 b'unhandled event from reactor: %s' % action |
385 ) | 385 ) |
386 | 386 |
387 | 387 |
388 def getdispatchrepo(repo, proto, command): | 388 def getdispatchrepo(repo, proto, command): |
389 viewconfig = repo.ui.config('server', 'view') | 389 viewconfig = repo.ui.config(b'server', b'view') |
390 return repo.filtered(viewconfig) | 390 return repo.filtered(viewconfig) |
391 | 391 |
392 | 392 |
393 def dispatch(repo, proto, command, redirect): | 393 def dispatch(repo, proto, command, redirect): |
394 """Run a wire protocol command. | 394 """Run a wire protocol command. |
451 | 451 |
452 # Serve it from the cache, if possible. | 452 # Serve it from the cache, if possible. |
453 cached = cacher.lookup() | 453 cached = cacher.lookup() |
454 | 454 |
455 if cached: | 455 if cached: |
456 for o in cached['objs']: | 456 for o in cached[b'objs']: |
457 yield o | 457 yield o |
458 return | 458 return |
459 | 459 |
460 # Else call the command and feed its output into the cacher, allowing | 460 # Else call the command and feed its output into the cacher, allowing |
461 # the cacher to buffer/mutate objects as it desires. | 461 # the cacher to buffer/mutate objects as it desires. |
482 # First look for args that were passed but aren't registered on this | 482 # First look for args that were passed but aren't registered on this |
483 # command. | 483 # command. |
484 extra = set(self._args) - set(args) | 484 extra = set(self._args) - set(args) |
485 if extra: | 485 if extra: |
486 raise error.WireprotoCommandError( | 486 raise error.WireprotoCommandError( |
487 'unsupported argument to command: %s' % ', '.join(sorted(extra)) | 487 b'unsupported argument to command: %s' |
488 % b', '.join(sorted(extra)) | |
488 ) | 489 ) |
489 | 490 |
490 # And look for required arguments that are missing. | 491 # And look for required arguments that are missing. |
491 missing = {a for a in args if args[a]['required']} - set(self._args) | 492 missing = {a for a in args if args[a][b'required']} - set(self._args) |
492 | 493 |
493 if missing: | 494 if missing: |
494 raise error.WireprotoCommandError( | 495 raise error.WireprotoCommandError( |
495 'missing required arguments: %s' % ', '.join(sorted(missing)) | 496 b'missing required arguments: %s' % b', '.join(sorted(missing)) |
496 ) | 497 ) |
497 | 498 |
498 # Now derive the arguments to pass to the command, taking into | 499 # Now derive the arguments to pass to the command, taking into |
499 # account the arguments specified by the client. | 500 # account the arguments specified by the client. |
500 data = {} | 501 data = {} |
501 for k, meta in sorted(args.items()): | 502 for k, meta in sorted(args.items()): |
502 # This argument wasn't passed by the client. | 503 # This argument wasn't passed by the client. |
503 if k not in self._args: | 504 if k not in self._args: |
504 data[k] = meta['default']() | 505 data[k] = meta[b'default']() |
505 continue | 506 continue |
506 | 507 |
507 v = self._args[k] | 508 v = self._args[k] |
508 | 509 |
509 # Sets may be expressed as lists. Silently normalize. | 510 # Sets may be expressed as lists. Silently normalize. |
510 if meta['type'] == 'set' and isinstance(v, list): | 511 if meta[b'type'] == b'set' and isinstance(v, list): |
511 v = set(v) | 512 v = set(v) |
512 | 513 |
513 # TODO consider more/stronger type validation. | 514 # TODO consider more/stronger type validation. |
514 | 515 |
515 data[k] = v | 516 data[k] = v |
548 | 549 |
549 These capabilities are distinct from the capabilities for version 1 | 550 These capabilities are distinct from the capabilities for version 1 |
550 transports. | 551 transports. |
551 """ | 552 """ |
552 caps = { | 553 caps = { |
553 'commands': {}, | 554 b'commands': {}, |
554 'framingmediatypes': [FRAMINGTYPE], | 555 b'framingmediatypes': [FRAMINGTYPE], |
555 'pathfilterprefixes': set(narrowspec.VALID_PREFIXES), | 556 b'pathfilterprefixes': set(narrowspec.VALID_PREFIXES), |
556 } | 557 } |
557 | 558 |
558 for command, entry in COMMANDS.items(): | 559 for command, entry in COMMANDS.items(): |
559 args = {} | 560 args = {} |
560 | 561 |
561 for arg, meta in entry.args.items(): | 562 for arg, meta in entry.args.items(): |
562 args[arg] = { | 563 args[arg] = { |
563 # TODO should this be a normalized type using CBOR's | 564 # TODO should this be a normalized type using CBOR's |
564 # terminology? | 565 # terminology? |
565 b'type': meta['type'], | 566 b'type': meta[b'type'], |
566 b'required': meta['required'], | 567 b'required': meta[b'required'], |
567 } | 568 } |
568 | 569 |
569 if not meta['required']: | 570 if not meta[b'required']: |
570 args[arg][b'default'] = meta['default']() | 571 args[arg][b'default'] = meta[b'default']() |
571 | 572 |
572 if meta['validvalues']: | 573 if meta[b'validvalues']: |
573 args[arg][b'validvalues'] = meta['validvalues'] | 574 args[arg][b'validvalues'] = meta[b'validvalues'] |
574 | 575 |
575 # TODO this type of check should be defined in a per-command callback. | 576 # TODO this type of check should be defined in a per-command callback. |
576 if ( | 577 if ( |
577 command == b'rawstorefiledata' | 578 command == b'rawstorefiledata' |
578 and not streamclone.allowservergeneration(repo) | 579 and not streamclone.allowservergeneration(repo) |
579 ): | 580 ): |
580 continue | 581 continue |
581 | 582 |
582 caps['commands'][command] = { | 583 caps[b'commands'][command] = { |
583 'args': args, | 584 b'args': args, |
584 'permissions': [entry.permission], | 585 b'permissions': [entry.permission], |
585 } | 586 } |
586 | 587 |
587 if entry.extracapabilitiesfn: | 588 if entry.extracapabilitiesfn: |
588 extracaps = entry.extracapabilitiesfn(repo, proto) | 589 extracaps = entry.extracapabilitiesfn(repo, proto) |
589 caps['commands'][command].update(extracaps) | 590 caps[b'commands'][command].update(extracaps) |
590 | 591 |
591 caps['rawrepoformats'] = sorted(repo.requirements & repo.supportedformats) | 592 caps[b'rawrepoformats'] = sorted(repo.requirements & repo.supportedformats) |
592 | 593 |
593 targets = getadvertisedredirecttargets(repo, proto) | 594 targets = getadvertisedredirecttargets(repo, proto) |
594 if targets: | 595 if targets: |
595 caps[b'redirect'] = { | 596 caps[b'redirect'] = { |
596 b'targets': [], | 597 b'targets': [], |
597 b'hashes': [b'sha256', b'sha1'], | 598 b'hashes': [b'sha256', b'sha1'], |
598 } | 599 } |
599 | 600 |
600 for target in targets: | 601 for target in targets: |
601 entry = { | 602 entry = { |
602 b'name': target['name'], | 603 b'name': target[b'name'], |
603 b'protocol': target['protocol'], | 604 b'protocol': target[b'protocol'], |
604 b'uris': target['uris'], | 605 b'uris': target[b'uris'], |
605 } | 606 } |
606 | 607 |
607 for key in ('snirequired', 'tlsversions'): | 608 for key in (b'snirequired', b'tlsversions'): |
608 if key in target: | 609 if key in target: |
609 entry[key] = target[key] | 610 entry[key] = target[key] |
610 | 611 |
611 caps[b'redirect'][b'targets'].append(entry) | 612 caps[b'redirect'][b'targets'].append(entry) |
612 | 613 |
653 | 654 |
654 | 655 |
655 def wireprotocommand( | 656 def wireprotocommand( |
656 name, | 657 name, |
657 args=None, | 658 args=None, |
658 permission='push', | 659 permission=b'push', |
659 cachekeyfn=None, | 660 cachekeyfn=None, |
660 extracapabilitiesfn=None, | 661 extracapabilitiesfn=None, |
661 ): | 662 ): |
662 """Decorator to declare a wire protocol command. | 663 """Decorator to declare a wire protocol command. |
663 | 664 |
708 argument containing the active cacher for the request and returns a bytes | 709 argument containing the active cacher for the request and returns a bytes |
709 containing the key in a cache the response to this command may be cached | 710 containing the key in a cache the response to this command may be cached |
710 under. | 711 under. |
711 """ | 712 """ |
712 transports = { | 713 transports = { |
713 k for k, v in wireprototypes.TRANSPORTS.items() if v['version'] == 2 | 714 k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 2 |
714 } | 715 } |
715 | 716 |
716 if permission not in ('push', 'pull'): | 717 if permission not in (b'push', b'pull'): |
717 raise error.ProgrammingError( | 718 raise error.ProgrammingError( |
718 'invalid wire protocol permission; ' | 719 b'invalid wire protocol permission; ' |
719 'got %s; expected "push" or "pull"' % permission | 720 b'got %s; expected "push" or "pull"' % permission |
720 ) | 721 ) |
721 | 722 |
722 if args is None: | 723 if args is None: |
723 args = {} | 724 args = {} |
724 | 725 |
725 if not isinstance(args, dict): | 726 if not isinstance(args, dict): |
726 raise error.ProgrammingError( | 727 raise error.ProgrammingError( |
727 'arguments for version 2 commands ' 'must be declared as dicts' | 728 b'arguments for version 2 commands ' b'must be declared as dicts' |
728 ) | 729 ) |
729 | 730 |
730 for arg, meta in args.items(): | 731 for arg, meta in args.items(): |
731 if arg == '*': | 732 if arg == b'*': |
732 raise error.ProgrammingError( | 733 raise error.ProgrammingError( |
733 '* argument name not allowed on ' 'version 2 commands' | 734 b'* argument name not allowed on ' b'version 2 commands' |
734 ) | 735 ) |
735 | 736 |
736 if not isinstance(meta, dict): | 737 if not isinstance(meta, dict): |
737 raise error.ProgrammingError( | 738 raise error.ProgrammingError( |
738 'arguments for version 2 commands ' | 739 b'arguments for version 2 commands ' |
739 'must declare metadata as a dict' | 740 b'must declare metadata as a dict' |
740 ) | 741 ) |
741 | 742 |
742 if 'type' not in meta: | 743 if b'type' not in meta: |
743 raise error.ProgrammingError( | 744 raise error.ProgrammingError( |
744 '%s argument for command %s does not ' | 745 b'%s argument for command %s does not ' |
745 'declare type field' % (arg, name) | 746 b'declare type field' % (arg, name) |
746 ) | 747 ) |
747 | 748 |
748 if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'): | 749 if meta[b'type'] not in ( |
750 b'bytes', | |
751 b'int', | |
752 b'list', | |
753 b'dict', | |
754 b'set', | |
755 b'bool', | |
756 ): | |
749 raise error.ProgrammingError( | 757 raise error.ProgrammingError( |
750 '%s argument for command %s has ' | 758 b'%s argument for command %s has ' |
751 'illegal type: %s' % (arg, name, meta['type']) | 759 b'illegal type: %s' % (arg, name, meta[b'type']) |
752 ) | 760 ) |
753 | 761 |
754 if 'example' not in meta: | 762 if b'example' not in meta: |
755 raise error.ProgrammingError( | 763 raise error.ProgrammingError( |
756 '%s argument for command %s does not ' | 764 b'%s argument for command %s does not ' |
757 'declare example field' % (arg, name) | 765 b'declare example field' % (arg, name) |
758 ) | 766 ) |
759 | 767 |
760 meta['required'] = 'default' not in meta | 768 meta[b'required'] = b'default' not in meta |
761 | 769 |
762 meta.setdefault('default', lambda: None) | 770 meta.setdefault(b'default', lambda: None) |
763 meta.setdefault('validvalues', None) | 771 meta.setdefault(b'validvalues', None) |
764 | 772 |
765 def register(func): | 773 def register(func): |
766 if name in COMMANDS: | 774 if name in COMMANDS: |
767 raise error.ProgrammingError( | 775 raise error.ProgrammingError( |
768 '%s command already registered ' 'for version 2' % name | 776 b'%s command already registered ' b'for version 2' % name |
769 ) | 777 ) |
770 | 778 |
771 COMMANDS[name] = wireprototypes.commandentry( | 779 COMMANDS[name] = wireprototypes.commandentry( |
772 func, | 780 func, |
773 args=args, | 781 args=args, |
794 * The media type used. | 802 * The media type used. |
795 * Wire protocol version string. | 803 * Wire protocol version string. |
796 * The repository path. | 804 * The repository path. |
797 """ | 805 """ |
798 if not allargs: | 806 if not allargs: |
799 raise error.ProgrammingError('only allargs=True is currently supported') | 807 raise error.ProgrammingError( |
808 b'only allargs=True is currently supported' | |
809 ) | |
800 | 810 |
801 if localversion is None: | 811 if localversion is None: |
802 raise error.ProgrammingError('must set localversion argument value') | 812 raise error.ProgrammingError(b'must set localversion argument value') |
803 | 813 |
804 def cachekeyfn(repo, proto, cacher, **args): | 814 def cachekeyfn(repo, proto, cacher, **args): |
805 spec = COMMANDS[command] | 815 spec = COMMANDS[command] |
806 | 816 |
807 # Commands that mutate the repo can not be cached. | 817 # Commands that mutate the repo can not be cached. |
808 if spec.permission == 'push': | 818 if spec.permission == b'push': |
809 return None | 819 return None |
810 | 820 |
811 # TODO config option to disable caching. | 821 # TODO config option to disable caching. |
812 | 822 |
813 # Our key derivation strategy is to construct a data structure | 823 # Our key derivation strategy is to construct a data structure |
878 seen = set() | 888 seen = set() |
879 nodes = [] | 889 nodes = [] |
880 | 890 |
881 if not isinstance(revisions, list): | 891 if not isinstance(revisions, list): |
882 raise error.WireprotoCommandError( | 892 raise error.WireprotoCommandError( |
883 'revisions must be defined as an ' 'array' | 893 b'revisions must be defined as an ' b'array' |
884 ) | 894 ) |
885 | 895 |
886 for spec in revisions: | 896 for spec in revisions: |
887 if b'type' not in spec: | 897 if b'type' not in spec: |
888 raise error.WireprotoCommandError( | 898 raise error.WireprotoCommandError( |
889 'type key not present in revision specifier' | 899 b'type key not present in revision specifier' |
890 ) | 900 ) |
891 | 901 |
892 typ = spec[b'type'] | 902 typ = spec[b'type'] |
893 | 903 |
894 if typ == b'changesetexplicit': | 904 if typ == b'changesetexplicit': |
895 if b'nodes' not in spec: | 905 if b'nodes' not in spec: |
896 raise error.WireprotoCommandError( | 906 raise error.WireprotoCommandError( |
897 'nodes key not present in changesetexplicit revision ' | 907 b'nodes key not present in changesetexplicit revision ' |
898 'specifier' | 908 b'specifier' |
899 ) | 909 ) |
900 | 910 |
901 for node in spec[b'nodes']: | 911 for node in spec[b'nodes']: |
902 if node not in seen: | 912 if node not in seen: |
903 nodes.append(node) | 913 nodes.append(node) |
905 | 915 |
906 elif typ == b'changesetexplicitdepth': | 916 elif typ == b'changesetexplicitdepth': |
907 for key in (b'nodes', b'depth'): | 917 for key in (b'nodes', b'depth'): |
908 if key not in spec: | 918 if key not in spec: |
909 raise error.WireprotoCommandError( | 919 raise error.WireprotoCommandError( |
910 '%s key not present in changesetexplicitdepth revision ' | 920 b'%s key not present in changesetexplicitdepth revision ' |
911 'specifier', | 921 b'specifier', |
912 (key,), | 922 (key,), |
913 ) | 923 ) |
914 | 924 |
915 for rev in repo.revs( | 925 for rev in repo.revs( |
916 b'ancestors(%ln, %s)', spec[b'nodes'], spec[b'depth'] - 1 | 926 b'ancestors(%ln, %s)', spec[b'nodes'], spec[b'depth'] - 1 |
923 | 933 |
924 elif typ == b'changesetdagrange': | 934 elif typ == b'changesetdagrange': |
925 for key in (b'roots', b'heads'): | 935 for key in (b'roots', b'heads'): |
926 if key not in spec: | 936 if key not in spec: |
927 raise error.WireprotoCommandError( | 937 raise error.WireprotoCommandError( |
928 '%s key not present in changesetdagrange revision ' | 938 b'%s key not present in changesetdagrange revision ' |
929 'specifier', | 939 b'specifier', |
930 (key,), | 940 (key,), |
931 ) | 941 ) |
932 | 942 |
933 if not spec[b'heads']: | 943 if not spec[b'heads']: |
934 raise error.WireprotoCommandError( | 944 raise error.WireprotoCommandError( |
935 'heads key in changesetdagrange cannot be empty' | 945 b'heads key in changesetdagrange cannot be empty' |
936 ) | 946 ) |
937 | 947 |
938 if spec[b'roots']: | 948 if spec[b'roots']: |
939 common = [n for n in spec[b'roots'] if clhasnode(n)] | 949 common = [n for n in spec[b'roots'] if clhasnode(n)] |
940 else: | 950 else: |
945 nodes.append(n) | 955 nodes.append(n) |
946 seen.add(n) | 956 seen.add(n) |
947 | 957 |
948 else: | 958 else: |
949 raise error.WireprotoCommandError( | 959 raise error.WireprotoCommandError( |
950 'unknown revision specifier type: %s', (typ,) | 960 b'unknown revision specifier type: %s', (typ,) |
951 ) | 961 ) |
952 | 962 |
953 return nodes | 963 return nodes |
954 | 964 |
955 | 965 |
956 @wireprotocommand('branchmap', permission='pull') | 966 @wireprotocommand(b'branchmap', permission=b'pull') |
957 def branchmapv2(repo, proto): | 967 def branchmapv2(repo, proto): |
958 yield {encoding.fromlocal(k): v for k, v in repo.branchmap().iteritems()} | 968 yield {encoding.fromlocal(k): v for k, v in repo.branchmap().iteritems()} |
959 | 969 |
960 | 970 |
961 @wireprotocommand('capabilities', permission='pull') | 971 @wireprotocommand(b'capabilities', permission=b'pull') |
962 def capabilitiesv2(repo, proto): | 972 def capabilitiesv2(repo, proto): |
963 yield _capabilitiesv2(repo, proto) | 973 yield _capabilitiesv2(repo, proto) |
964 | 974 |
965 | 975 |
966 @wireprotocommand( | 976 @wireprotocommand( |
967 'changesetdata', | 977 b'changesetdata', |
968 args={ | 978 args={ |
969 'revisions': { | 979 b'revisions': { |
970 'type': 'list', | 980 b'type': b'list', |
971 'example': [ | 981 b'example': [ |
972 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],} | 982 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],} |
973 ], | 983 ], |
974 }, | 984 }, |
975 'fields': { | 985 b'fields': { |
976 'type': 'set', | 986 b'type': b'set', |
977 'default': set, | 987 b'default': set, |
978 'example': {b'parents', b'revision'}, | 988 b'example': {b'parents', b'revision'}, |
979 'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'}, | 989 b'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'}, |
980 }, | 990 }, |
981 }, | 991 }, |
982 permission='pull', | 992 permission=b'pull', |
983 ) | 993 ) |
984 def changesetdata(repo, proto, revisions, fields): | 994 def changesetdata(repo, proto, revisions, fields): |
985 # TODO look for unknown fields and abort when they can't be serviced. | 995 # TODO look for unknown fields and abort when they can't be serviced. |
986 # This could probably be validated by dispatcher using validvalues. | 996 # This could probably be validated by dispatcher using validvalues. |
987 | 997 |
988 cl = repo.changelog | 998 cl = repo.changelog |
989 outgoing = resolvenodes(repo, revisions) | 999 outgoing = resolvenodes(repo, revisions) |
990 publishing = repo.publishing() | 1000 publishing = repo.publishing() |
991 | 1001 |
992 if outgoing: | 1002 if outgoing: |
993 repo.hook('preoutgoing', throw=True, source='serve') | 1003 repo.hook(b'preoutgoing', throw=True, source=b'serve') |
994 | 1004 |
995 yield { | 1005 yield { |
996 b'totalitems': len(outgoing), | 1006 b'totalitems': len(outgoing), |
997 } | 1007 } |
998 | 1008 |
1076 # This seems to work even if the file doesn't exist. So catch | 1086 # This seems to work even if the file doesn't exist. So catch |
1077 # "empty" files and return an error. | 1087 # "empty" files and return an error. |
1078 fl = repo.file(path) | 1088 fl = repo.file(path) |
1079 | 1089 |
1080 if not len(fl): | 1090 if not len(fl): |
1081 raise FileAccessError(path, 'unknown file: %s', (path,)) | 1091 raise FileAccessError(path, b'unknown file: %s', (path,)) |
1082 | 1092 |
1083 return fl | 1093 return fl |
1084 | 1094 |
1085 | 1095 |
1086 def emitfilerevisions(repo, path, revisions, linknodes, fields): | 1096 def emitfilerevisions(repo, path, revisions, linknodes, fields): |
1123 if pathfilter: | 1133 if pathfilter: |
1124 for key in (b'include', b'exclude'): | 1134 for key in (b'include', b'exclude'): |
1125 for pattern in pathfilter.get(key, []): | 1135 for pattern in pathfilter.get(key, []): |
1126 if not pattern.startswith((b'path:', b'rootfilesin:')): | 1136 if not pattern.startswith((b'path:', b'rootfilesin:')): |
1127 raise error.WireprotoCommandError( | 1137 raise error.WireprotoCommandError( |
1128 '%s pattern must begin with `path:` or `rootfilesin:`; ' | 1138 b'%s pattern must begin with `path:` or `rootfilesin:`; ' |
1129 'got %s', | 1139 b'got %s', |
1130 (key, pattern), | 1140 (key, pattern), |
1131 ) | 1141 ) |
1132 | 1142 |
1133 if pathfilter: | 1143 if pathfilter: |
1134 matcher = matchmod.match( | 1144 matcher = matchmod.match( |
1144 # filter those out. | 1154 # filter those out. |
1145 return repo.narrowmatch(matcher) | 1155 return repo.narrowmatch(matcher) |
1146 | 1156 |
1147 | 1157 |
1148 @wireprotocommand( | 1158 @wireprotocommand( |
1149 'filedata', | 1159 b'filedata', |
1150 args={ | 1160 args={ |
1151 'haveparents': { | 1161 b'haveparents': { |
1152 'type': 'bool', | 1162 b'type': b'bool', |
1153 'default': lambda: False, | 1163 b'default': lambda: False, |
1154 'example': True, | 1164 b'example': True, |
1155 }, | 1165 }, |
1156 'nodes': {'type': 'list', 'example': [b'0123456...'],}, | 1166 b'nodes': {b'type': b'list', b'example': [b'0123456...'],}, |
1157 'fields': { | 1167 b'fields': { |
1158 'type': 'set', | 1168 b'type': b'set', |
1159 'default': set, | 1169 b'default': set, |
1160 'example': {b'parents', b'revision'}, | 1170 b'example': {b'parents', b'revision'}, |
1161 'validvalues': {b'parents', b'revision', b'linknode'}, | 1171 b'validvalues': {b'parents', b'revision', b'linknode'}, |
1162 }, | 1172 }, |
1163 'path': {'type': 'bytes', 'example': b'foo.txt',}, | 1173 b'path': {b'type': b'bytes', b'example': b'foo.txt',}, |
1164 }, | 1174 }, |
1165 permission='pull', | 1175 permission=b'pull', |
1166 # TODO censoring a file revision won't invalidate the cache. | 1176 # TODO censoring a file revision won't invalidate the cache. |
1167 # Figure out a way to take censoring into account when deriving | 1177 # Figure out a way to take censoring into account when deriving |
1168 # the cache key. | 1178 # the cache key. |
1169 cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True), | 1179 cachekeyfn=makecommandcachekeyfn(b'filedata', 1, allargs=True), |
1170 ) | 1180 ) |
1171 def filedata(repo, proto, haveparents, nodes, fields, path): | 1181 def filedata(repo, proto, haveparents, nodes, fields, path): |
1172 # TODO this API allows access to file revisions that are attached to | 1182 # TODO this API allows access to file revisions that are attached to |
1173 # secret changesets. filesdata does not have this problem. Maybe this | 1183 # secret changesets. filesdata does not have this problem. Maybe this |
1174 # API should be deleted? | 1184 # API should be deleted? |
1186 for node in nodes: | 1196 for node in nodes: |
1187 try: | 1197 try: |
1188 store.rev(node) | 1198 store.rev(node) |
1189 except error.LookupError: | 1199 except error.LookupError: |
1190 raise error.WireprotoCommandError( | 1200 raise error.WireprotoCommandError( |
1191 'unknown file node: %s', (hex(node),) | 1201 b'unknown file node: %s', (hex(node),) |
1192 ) | 1202 ) |
1193 | 1203 |
1194 # TODO by creating the filectx against a specific file revision | 1204 # TODO by creating the filectx against a specific file revision |
1195 # instead of changeset, linkrev() is always used. This is wrong for | 1205 # instead of changeset, linkrev() is always used. This is wrong for |
1196 # cases where linkrev() may refer to a hidden changeset. But since this | 1206 # cases where linkrev() may refer to a hidden changeset. But since this |
1221 b'recommendedbatchsize': batchsize, | 1231 b'recommendedbatchsize': batchsize, |
1222 } | 1232 } |
1223 | 1233 |
1224 | 1234 |
1225 @wireprotocommand( | 1235 @wireprotocommand( |
1226 'filesdata', | 1236 b'filesdata', |
1227 args={ | 1237 args={ |
1228 'haveparents': { | 1238 b'haveparents': { |
1229 'type': 'bool', | 1239 b'type': b'bool', |
1230 'default': lambda: False, | 1240 b'default': lambda: False, |
1231 'example': True, | 1241 b'example': True, |
1232 }, | 1242 }, |
1233 'fields': { | 1243 b'fields': { |
1234 'type': 'set', | 1244 b'type': b'set', |
1235 'default': set, | 1245 b'default': set, |
1236 'example': {b'parents', b'revision'}, | 1246 b'example': {b'parents', b'revision'}, |
1237 'validvalues': { | 1247 b'validvalues': { |
1238 b'firstchangeset', | 1248 b'firstchangeset', |
1239 b'linknode', | 1249 b'linknode', |
1240 b'parents', | 1250 b'parents', |
1241 b'revision', | 1251 b'revision', |
1242 }, | 1252 }, |
1243 }, | 1253 }, |
1244 'pathfilter': { | 1254 b'pathfilter': { |
1245 'type': 'dict', | 1255 b'type': b'dict', |
1246 'default': lambda: None, | 1256 b'default': lambda: None, |
1247 'example': {b'include': [b'path:tests']}, | 1257 b'example': {b'include': [b'path:tests']}, |
1248 }, | 1258 }, |
1249 'revisions': { | 1259 b'revisions': { |
1250 'type': 'list', | 1260 b'type': b'list', |
1251 'example': [ | 1261 b'example': [ |
1252 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],} | 1262 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],} |
1253 ], | 1263 ], |
1254 }, | 1264 }, |
1255 }, | 1265 }, |
1256 permission='pull', | 1266 permission=b'pull', |
1257 # TODO censoring a file revision won't invalidate the cache. | 1267 # TODO censoring a file revision won't invalidate the cache. |
1258 # Figure out a way to take censoring into account when deriving | 1268 # Figure out a way to take censoring into account when deriving |
1259 # the cache key. | 1269 # the cache key. |
1260 cachekeyfn=makecommandcachekeyfn('filesdata', 1, allargs=True), | 1270 cachekeyfn=makecommandcachekeyfn(b'filesdata', 1, allargs=True), |
1261 extracapabilitiesfn=filesdatacapabilities, | 1271 extracapabilitiesfn=filesdatacapabilities, |
1262 ) | 1272 ) |
1263 def filesdata(repo, proto, haveparents, fields, pathfilter, revisions): | 1273 def filesdata(repo, proto, haveparents, fields, pathfilter, revisions): |
1264 # TODO This should operate on a repo that exposes obsolete changesets. There | 1274 # TODO This should operate on a repo that exposes obsolete changesets. There |
1265 # is a race between a client making a push that obsoletes a changeset and | 1275 # is a race between a client making a push that obsoletes a changeset and |
1325 for o in emitfilerevisions(repo, path, revisions, filenodes, fields): | 1335 for o in emitfilerevisions(repo, path, revisions, filenodes, fields): |
1326 yield o | 1336 yield o |
1327 | 1337 |
1328 | 1338 |
1329 @wireprotocommand( | 1339 @wireprotocommand( |
1330 'heads', | 1340 b'heads', |
1331 args={ | 1341 args={ |
1332 'publiconly': { | 1342 b'publiconly': { |
1333 'type': 'bool', | 1343 b'type': b'bool', |
1334 'default': lambda: False, | 1344 b'default': lambda: False, |
1335 'example': False, | 1345 b'example': False, |
1336 }, | 1346 }, |
1337 }, | 1347 }, |
1338 permission='pull', | 1348 permission=b'pull', |
1339 ) | 1349 ) |
1340 def headsv2(repo, proto, publiconly): | 1350 def headsv2(repo, proto, publiconly): |
1341 if publiconly: | 1351 if publiconly: |
1342 repo = repo.filtered('immutable') | 1352 repo = repo.filtered(b'immutable') |
1343 | 1353 |
1344 yield repo.heads() | 1354 yield repo.heads() |
1345 | 1355 |
1346 | 1356 |
1347 @wireprotocommand( | 1357 @wireprotocommand( |
1348 'known', | 1358 b'known', |
1349 args={ | 1359 args={ |
1350 'nodes': {'type': 'list', 'default': list, 'example': [b'deadbeef'],}, | 1360 b'nodes': { |
1361 b'type': b'list', | |
1362 b'default': list, | |
1363 b'example': [b'deadbeef'], | |
1364 }, | |
1351 }, | 1365 }, |
1352 permission='pull', | 1366 permission=b'pull', |
1353 ) | 1367 ) |
1354 def knownv2(repo, proto, nodes): | 1368 def knownv2(repo, proto, nodes): |
1355 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes)) | 1369 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes)) |
1356 yield result | 1370 yield result |
1357 | 1371 |
1358 | 1372 |
1359 @wireprotocommand( | 1373 @wireprotocommand( |
1360 'listkeys', | 1374 b'listkeys', |
1361 args={'namespace': {'type': 'bytes', 'example': b'ns',},}, | 1375 args={b'namespace': {b'type': b'bytes', b'example': b'ns',},}, |
1362 permission='pull', | 1376 permission=b'pull', |
1363 ) | 1377 ) |
1364 def listkeysv2(repo, proto, namespace): | 1378 def listkeysv2(repo, proto, namespace): |
1365 keys = repo.listkeys(encoding.tolocal(namespace)) | 1379 keys = repo.listkeys(encoding.tolocal(namespace)) |
1366 keys = { | 1380 keys = { |
1367 encoding.fromlocal(k): encoding.fromlocal(v) | 1381 encoding.fromlocal(k): encoding.fromlocal(v) |
1370 | 1384 |
1371 yield keys | 1385 yield keys |
1372 | 1386 |
1373 | 1387 |
1374 @wireprotocommand( | 1388 @wireprotocommand( |
1375 'lookup', | 1389 b'lookup', |
1376 args={'key': {'type': 'bytes', 'example': b'foo',},}, | 1390 args={b'key': {b'type': b'bytes', b'example': b'foo',},}, |
1377 permission='pull', | 1391 permission=b'pull', |
1378 ) | 1392 ) |
1379 def lookupv2(repo, proto, key): | 1393 def lookupv2(repo, proto, key): |
1380 key = encoding.tolocal(key) | 1394 key = encoding.tolocal(key) |
1381 | 1395 |
1382 # TODO handle exception. | 1396 # TODO handle exception. |
1394 b'recommendedbatchsize': batchsize, | 1408 b'recommendedbatchsize': batchsize, |
1395 } | 1409 } |
1396 | 1410 |
1397 | 1411 |
1398 @wireprotocommand( | 1412 @wireprotocommand( |
1399 'manifestdata', | 1413 b'manifestdata', |
1400 args={ | 1414 args={ |
1401 'nodes': {'type': 'list', 'example': [b'0123456...'],}, | 1415 b'nodes': {b'type': b'list', b'example': [b'0123456...'],}, |
1402 'haveparents': { | 1416 b'haveparents': { |
1403 'type': 'bool', | 1417 b'type': b'bool', |
1404 'default': lambda: False, | 1418 b'default': lambda: False, |
1405 'example': True, | 1419 b'example': True, |
1406 }, | 1420 }, |
1407 'fields': { | 1421 b'fields': { |
1408 'type': 'set', | 1422 b'type': b'set', |
1409 'default': set, | 1423 b'default': set, |
1410 'example': {b'parents', b'revision'}, | 1424 b'example': {b'parents', b'revision'}, |
1411 'validvalues': {b'parents', b'revision'}, | 1425 b'validvalues': {b'parents', b'revision'}, |
1412 }, | 1426 }, |
1413 'tree': {'type': 'bytes', 'example': b'',}, | 1427 b'tree': {b'type': b'bytes', b'example': b'',}, |
1414 }, | 1428 }, |
1415 permission='pull', | 1429 permission=b'pull', |
1416 cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True), | 1430 cachekeyfn=makecommandcachekeyfn(b'manifestdata', 1, allargs=True), |
1417 extracapabilitiesfn=manifestdatacapabilities, | 1431 extracapabilitiesfn=manifestdatacapabilities, |
1418 ) | 1432 ) |
1419 def manifestdata(repo, proto, haveparents, nodes, fields, tree): | 1433 def manifestdata(repo, proto, haveparents, nodes, fields, tree): |
1420 store = repo.manifestlog.getstorage(tree) | 1434 store = repo.manifestlog.getstorage(tree) |
1421 | 1435 |
1422 # Validate the node is known and abort on unknown revisions. | 1436 # Validate the node is known and abort on unknown revisions. |
1423 for node in nodes: | 1437 for node in nodes: |
1424 try: | 1438 try: |
1425 store.rev(node) | 1439 store.rev(node) |
1426 except error.LookupError: | 1440 except error.LookupError: |
1427 raise error.WireprotoCommandError('unknown node: %s', (node,)) | 1441 raise error.WireprotoCommandError(b'unknown node: %s', (node,)) |
1428 | 1442 |
1429 revisions = store.emitrevisions( | 1443 revisions = store.emitrevisions( |
1430 nodes, | 1444 nodes, |
1431 revisiondata=b'revision' in fields, | 1445 revisiondata=b'revision' in fields, |
1432 assumehaveparentrevisions=haveparents, | 1446 assumehaveparentrevisions=haveparents, |
1464 for extra in followingdata: | 1478 for extra in followingdata: |
1465 yield extra | 1479 yield extra |
1466 | 1480 |
1467 | 1481 |
1468 @wireprotocommand( | 1482 @wireprotocommand( |
1469 'pushkey', | 1483 b'pushkey', |
1470 args={ | 1484 args={ |
1471 'namespace': {'type': 'bytes', 'example': b'ns',}, | 1485 b'namespace': {b'type': b'bytes', b'example': b'ns',}, |
1472 'key': {'type': 'bytes', 'example': b'key',}, | 1486 b'key': {b'type': b'bytes', b'example': b'key',}, |
1473 'old': {'type': 'bytes', 'example': b'old',}, | 1487 b'old': {b'type': b'bytes', b'example': b'old',}, |
1474 'new': {'type': 'bytes', 'example': 'new',}, | 1488 b'new': {b'type': b'bytes', b'example': b'new',}, |
1475 }, | 1489 }, |
1476 permission='push', | 1490 permission=b'push', |
1477 ) | 1491 ) |
1478 def pushkeyv2(repo, proto, namespace, key, old, new): | 1492 def pushkeyv2(repo, proto, namespace, key, old, new): |
1479 # TODO handle ui output redirection | 1493 # TODO handle ui output redirection |
1480 yield repo.pushkey( | 1494 yield repo.pushkey( |
1481 encoding.tolocal(namespace), | 1495 encoding.tolocal(namespace), |
1484 encoding.tolocal(new), | 1498 encoding.tolocal(new), |
1485 ) | 1499 ) |
1486 | 1500 |
1487 | 1501 |
1488 @wireprotocommand( | 1502 @wireprotocommand( |
1489 'rawstorefiledata', | 1503 b'rawstorefiledata', |
1490 args={ | 1504 args={ |
1491 'files': {'type': 'list', 'example': [b'changelog', b'manifestlog'],}, | 1505 b'files': { |
1492 'pathfilter': { | 1506 b'type': b'list', |
1493 'type': 'list', | 1507 b'example': [b'changelog', b'manifestlog'], |
1494 'default': lambda: None, | 1508 }, |
1495 'example': {b'include': [b'path:tests']}, | 1509 b'pathfilter': { |
1510 b'type': b'list', | |
1511 b'default': lambda: None, | |
1512 b'example': {b'include': [b'path:tests']}, | |
1496 }, | 1513 }, |
1497 }, | 1514 }, |
1498 permission='pull', | 1515 permission=b'pull', |
1499 ) | 1516 ) |
1500 def rawstorefiledata(repo, proto, files, pathfilter): | 1517 def rawstorefiledata(repo, proto, files, pathfilter): |
1501 if not streamclone.allowservergeneration(repo): | 1518 if not streamclone.allowservergeneration(repo): |
1502 raise error.WireprotoCommandError(b'stream clone is disabled') | 1519 raise error.WireprotoCommandError(b'stream clone is disabled') |
1503 | 1520 |
1544 } | 1561 } |
1545 | 1562 |
1546 # We have to use a closure for this to ensure the context manager is | 1563 # We have to use a closure for this to ensure the context manager is |
1547 # closed only after sending the final chunk. | 1564 # closed only after sending the final chunk. |
1548 def getfiledata(): | 1565 def getfiledata(): |
1549 with repo.svfs(name, 'rb', auditpath=False) as fh: | 1566 with repo.svfs(name, b'rb', auditpath=False) as fh: |
1550 for chunk in util.filechunkiter(fh, limit=size): | 1567 for chunk in util.filechunkiter(fh, limit=size): |
1551 yield chunk | 1568 yield chunk |
1552 | 1569 |
1553 yield wireprototypes.indefinitebytestringresponse(getfiledata()) | 1570 yield wireprototypes.indefinitebytestringresponse(getfiledata()) |