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())