##// END OF EJS Templates
localrepo: extract handling of some special value in __getitem__...
localrepo: extract handling of some special value in __getitem__ The value "null" will always be present in a repository. So this lookup should always succeed and do not need to be in the general try/catch. Differential Revision: https://phab.mercurial-scm.org/D7474

File last commit:

r43387:8ff1ecfa default
r44139:b7d0aa52 default
Show More
wireprotov2server.py
1573 lines | 48.0 KiB | text/x-python | PythonLexer
/ mercurial / wireprotov2server.py
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 import collections
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 import contextlib
Gregory Szorc
wireprotov2: support response caching...
r40057 import hashlib
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
from .i18n import _
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 from .node import (
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 hex,
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 nullid,
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 from . import (
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 discovery,
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 encoding,
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 error,
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 match as matchmod,
Gregory Szorc
wireprotov2: advertise recognized path filter prefixes...
r39836 narrowspec,
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 pycompat,
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 streamclone,
Gregory Szorc
wireprotov2server: use our JSON encoder...
r41448 templatefilters,
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 util,
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 wireprotoframing,
wireprototypes,
)
Augie Fackler
formatting: blacken the codebase...
r43346 from .interfaces import util as interfaceutil
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 from .utils import (
Gregory Szorc
wireprotov2: support response caching...
r40057 cborutil,
Gregory Szorc
py3: cast exception to bytes...
r39869 stringutil,
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Gregory Szorc
wireprotov2: send protocol settings frame from client...
r40168 FRAMINGTYPE = b'application/mercurial-exp-framing-0006'
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Gregory Szorc
wireproto: rename HTTPV2 so it less like HTTP/2...
r37662 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Gregory Szorc
wireproto: move version 2 commands dict to wireprotov2server...
r37802 COMMANDS = wireprototypes.commanddict()
Gregory Szorc
wireprotov2: support response caching...
r40057 # Value inserted into cache key computation function. Change the value to
# force new cache keys for every command request. This should be done when
# there is a change to how caching works, etc.
GLOBAL_CACHE_VERSION = 1
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
from .hgweb import common as hgwebcommon
# URL space looks like: <permissions>/<command>, where <permission> can
# be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
# Root URL does nothing meaningful... yet.
if not urlparts:
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'HTTP version 2 API handler'))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
if len(urlparts) == 1:
res.status = b'404 Not Found'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'do not know how to process %s\n') % req.dispatchpath
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
permission, command = urlparts[0:2]
if permission not in (b'ro', b'rw'):
res.status = b'404 Not Found'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'unknown permission: %s') % permission)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if req.method != b'POST':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 res.status = b'405 Method Not Allowed'
res.headers[b'Allow'] = b'POST'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'commands require POST requests'))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
# At some point we'll want to use our own API instead of recycling the
# behavior of version 1 of the wire protocol...
# TODO return reasonable responses - not responses that overload the
# HTTP status line message for error reporting.
try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 checkperm(rctx, req, b'pull' if permission == b'ro' else b'push')
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 except hgwebcommon.ErrorResponse as e:
res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
for k, v in e.headers:
res.headers[k] = v
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(b'permission denied')
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
# We have a special endpoint to reflect the request back at the client.
if command == b'debugreflect':
_processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
return
# Extra commands that we handle that aren't really wire protocol
# commands. Think extra hard before making this hackery available to
# extension.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 extracommands = {b'multirequest'}
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Gregory Szorc
wireproto: move version 2 commands dict to wireprotov2server...
r37802 if command not in COMMANDS and command not in extracommands:
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 res.status = b'404 Not Found'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'unknown wire protocol command: %s\n') % command)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
repo = rctx.repo
ui = repo.ui
proto = httpv2protocolhandler(req, ui)
Augie Fackler
formatting: blacken the codebase...
r43346 if (
not COMMANDS.commandavailable(command, proto)
and command not in extracommands
):
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 res.status = b'404 Not Found'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'invalid wire protocol command: %s') % command)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
# TODO consider cases where proxies may add additional Accept headers.
if req.headers.get(b'Accept') != FRAMINGTYPE:
res.status = b'406 Not Acceptable'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'client MUST specify Accept header with value: %s\n')
Augie Fackler
formatting: blacken the codebase...
r43346 % FRAMINGTYPE
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
if req.headers.get(b'Content-Type') != FRAMINGTYPE:
res.status = b'415 Unsupported Media Type'
# TODO we should send a response with appropriate media type,
# since client does Accept it.
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'client MUST send Content-Type header with value: %s\n')
Augie Fackler
formatting: blacken the codebase...
r43346 % FRAMINGTYPE
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
_processhttpv2request(ui, repo, req, res, permission, command, proto)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 def _processhttpv2reflectrequest(ui, repo, req, res):
"""Reads unified frame protocol request and dumps out state to client.
This special endpoint can be used to help debug the wire protocol.
Instead of routing the request through the normal dispatch mechanism,
we instead read all frames, decode them, and feed them into our state
tracker. We then dump the log of all that activity back out to the
client.
"""
# Reflection APIs have a history of being abused, accidentally disclosing
# sensitive data, etc. So we have a config knob.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not ui.configbool(b'experimental', b'web.api.debugreflect'):
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 res.status = b'404 Not Found'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'debugreflect service not available'))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
# We assume we have a unified framing protocol request body.
Gregory Szorc
wireprotov2: pass ui into clientreactor and serverreactor...
r40165 reactor = wireprotoframing.serverreactor(ui)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 states = []
while True:
frame = wireprotoframing.readframe(req.bodyfh)
if not frame:
states.append(b'received: <no frame>')
break
Augie Fackler
formatting: blacken the codebase...
r43346 states.append(
b'received: %d %d %d %s'
% (frame.typeid, frame.flags, frame.requestid, frame.payload)
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
action, meta = reactor.onframerecv(frame)
Gregory Szorc
wireprotov2server: use our JSON encoder...
r41448 states.append(templatefilters.json((action, meta)))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
action, meta = reactor.oninputeof()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 meta[b'action'] = action
Gregory Szorc
wireprotov2server: use our JSON encoder...
r41448 states.append(templatefilters.json(meta))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
res.setbodybytes(b'\n'.join(states))
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
"""Post-validation handler for HTTPv2 requests.
Called when the HTTP request contains unified frame-based protocol
frames for evaluation.
"""
# TODO Some HTTP clients are full duplex and can receive data before
# the entire request is transmitted. Figure out a way to indicate support
# for that so we can opt into full duplex mode.
Gregory Szorc
wireprotov2: pass ui into clientreactor and serverreactor...
r40165 reactor = wireprotoframing.serverreactor(ui, deferoutput=True)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 seencommand = False
Gregory Szorc
wireprotov2: send content encoded frames from server...
r40173 outstream = None
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
while True:
frame = wireprotoframing.readframe(req.bodyfh)
if not frame:
break
action, meta = reactor.onframerecv(frame)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if action == b'wantframe':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # Need more data before we can do anything.
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif action == b'runcommand':
Gregory Szorc
wireprotov2: send content encoded frames from server...
r40173 # Defer creating output stream because we need to wait for
# protocol settings frames so proper encoding can be applied.
if not outstream:
outstream = reactor.makeoutputstream()
Augie Fackler
formatting: blacken the codebase...
r43346 sentoutput = _httpv2runcommand(
ui,
repo,
req,
res,
authedperm,
reqcommand,
reactor,
outstream,
meta,
issubsequent=seencommand,
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
if sentoutput:
return
seencommand = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif action == b'error':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # TODO define proper error mechanism.
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(meta[b'message'] + b'\n')
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return
else:
raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unhandled action from frame processor: %s' % action
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
action, meta = reactor.oninputeof()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if action == b'sendframes':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # We assume we haven't started sending the response yet. If we're
# wrong, the response type will raise an exception.
res.status = b'200 OK'
res.headers[b'Content-Type'] = FRAMINGTYPE
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodygen(meta[b'framegen'])
elif action == b'noop':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 pass
else:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unhandled action from frame processor: %s' % action
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Augie Fackler
formatting: blacken the codebase...
r43346 def _httpv2runcommand(
ui,
repo,
req,
res,
authedperm,
reqcommand,
reactor,
outstream,
command,
issubsequent,
):
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 """Dispatch a wire protocol command made from HTTPv2 requests.
The authenticated permission (``authedperm``) along with the original
command from the URL (``reqcommand``) are passed in.
"""
# We already validated that the session has permissions to perform the
# actions in ``authedperm``. In the unified frame protocol, the canonical
# command to run is expressed in a frame. However, the URL also requested
# to run a specific command. We need to be careful that the command we
# run doesn't have permissions requirements greater than what was granted
# by ``authedperm``.
#
# Our rule for this is we only allow one command per HTTP request and
# that command must match the command in the URL. However, we make
# an exception for the ``multirequest`` URL. This URL is allowed to
# execute multiple commands. We double check permissions of each command
# as it is invoked to ensure there is no privilege escalation.
# TODO consider allowing multiple commands to regular command URLs
# iff each command is the same.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 proto = httpv2protocolhandler(req, ui, args=command[b'args'])
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
if reqcommand == b'multirequest':
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not COMMANDS.commandavailable(command[b'command'], proto):
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # TODO proper error mechanism
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'wire protocol command not available: %s')
% command[b'command']
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return True
# TODO don't use assert here, since it may be elided by -O.
assert authedperm in (b'ro', b'rw')
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 wirecommand = COMMANDS[command[b'command']]
assert wirecommand.permission in (b'push', b'pull')
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if authedperm == b'ro' and wirecommand.permission != b'pull':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # TODO proper error mechanism
res.status = b'403 Forbidden'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'insufficient permissions to execute command: %s')
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 % command[b'command']
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return True
# TODO should we also call checkperm() here? Maybe not if we're going
# to overhaul that API. The granted scope from the URL check should
# be good enough.
else:
# Don't allow multiple commands outside of ``multirequest`` URL.
if issubsequent:
# TODO proper error mechanism
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: blacken the codebase...
r43346 res.setbodybytes(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'multiple commands cannot be issued to this URL')
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if reqcommand != command[b'command']:
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 # TODO define proper error mechanism
res.status = b'200 OK'
res.headers[b'Content-Type'] = b'text/plain'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 res.setbodybytes(_(b'command in frame must match command in URL'))
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return True
res.status = b'200 OK'
res.headers[b'Content-Type'] = FRAMINGTYPE
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 objs = dispatch(repo, proto, command[b'command'], command[b'redirect'])
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595
action, meta = reactor.oncommandresponsereadyobjects(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 outstream, command[b'requestid'], objs
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 except error.WireprotoCommandError as e:
action, meta = reactor.oncommanderror(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 outstream, command[b'requestid'], e.message, e.messageargs
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 except Exception as e:
Gregory Szorc
wireprotov2: change behavior of error frame...
r37744 action, meta = reactor.onservererror(
Augie Fackler
formatting: blacken the codebase...
r43346 outstream,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 command[b'requestid'],
_(b'exception when invoking command: %s')
Augie Fackler
formatting: blacken the codebase...
r43346 % stringutil.forcebytestr(e),
)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if action == b'sendframes':
res.setbodygen(meta[b'framegen'])
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif action == b'noop':
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 return False
else:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unhandled event from reactor: %s' % action
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
Gregory Szorc
wireproto: reimplement dispatch() for version 2 server...
r37800 def getdispatchrepo(repo, proto, command):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 viewconfig = repo.ui.config(b'server', b'view')
Joerg Sonnenberger
server: allow customizing the default repo filter...
r42006 return repo.filtered(viewconfig)
Gregory Szorc
wireproto: reimplement dispatch() for version 2 server...
r37800
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: server support for sending content redirects...
r40061 def dispatch(repo, proto, command, redirect):
Gregory Szorc
wireprotov2: support response caching...
r40057 """Run a wire protocol command.
Returns an iterable of objects that will be sent to the client.
"""
Gregory Szorc
wireproto: reimplement dispatch() for version 2 server...
r37800 repo = getdispatchrepo(repo, proto, command)
Gregory Szorc
wireprotov2: support response caching...
r40057 entry = COMMANDS[command]
func = entry.func
spec = entry.args
Gregory Szorc
wireproto: reimplement dispatch() for version 2 server...
r37800 args = proto.getargs(spec)
Gregory Szorc
wireprotov2: support response caching...
r40057 # There is some duplicate boilerplate code here for calling the command and
# emitting objects. It is either that or a lot of indented code that looks
# like a pyramid (since there are a lot of code paths that result in not
# using the cacher).
callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args))
# Request is not cacheable. Don't bother instantiating a cacher.
if not entry.cachekeyfn:
for o in callcommand():
yield o
return
Gregory Szorc
wireprotov2: server support for sending content redirects...
r40061 if redirect:
redirecttargets = redirect[b'targets']
redirecthashes = redirect[b'hashes']
else:
redirecttargets = []
redirecthashes = []
Augie Fackler
formatting: blacken the codebase...
r43346 cacher = makeresponsecacher(
repo,
proto,
command,
args,
cborutil.streamencode,
redirecttargets=redirecttargets,
redirecthashes=redirecthashes,
)
Gregory Szorc
wireprotov2: support response caching...
r40057
# But we have no cacher. Do default handling.
if not cacher:
for o in callcommand():
yield o
return
with cacher:
Augie Fackler
formatting: blacken the codebase...
r43346 cachekey = entry.cachekeyfn(
repo, proto, cacher, **pycompat.strkwargs(args)
)
Gregory Szorc
wireprotov2: support response caching...
r40057
# No cache key or the cacher doesn't like it. Do default handling.
if cachekey is None or not cacher.setcachekey(cachekey):
for o in callcommand():
yield o
return
# Serve it from the cache, if possible.
cached = cacher.lookup()
if cached:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for o in cached[b'objs']:
Gregory Szorc
wireprotov2: support response caching...
r40057 yield o
return
# Else call the command and feed its output into the cacher, allowing
# the cacher to buffer/mutate objects as it desires.
for o in callcommand():
for o in cacher.onobject(o):
yield o
for o in cacher.onfinished():
yield o
Gregory Szorc
wireproto: reimplement dispatch() for version 2 server...
r37800
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
interfaceutil: module to stub out zope.interface...
r37828 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 class httpv2protocolhandler(object):
def __init__(self, req, ui, args=None):
self._req = req
self._ui = ui
self._args = args
@property
def name(self):
Gregory Szorc
wireproto: rename HTTPV2 so it less like HTTP/2...
r37662 return HTTP_WIREPROTO_V2
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
def getargs(self, args):
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 # First look for args that were passed but aren't registered on this
# command.
extra = set(self._args) - set(args)
if extra:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unsupported argument to command: %s'
% b', '.join(sorted(extra))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
# And look for required arguments that are missing.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 missing = {a for a in args if args[a][b'required']} - set(self._args)
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
if missing:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'missing required arguments: %s' % b', '.join(sorted(missing))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
# Now derive the arguments to pass to the command, taking into
# account the arguments specified by the client.
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563 data = {}
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 for k, meta in sorted(args.items()):
# This argument wasn't passed by the client.
if k not in self._args:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 data[k] = meta[b'default']()
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 continue
v = self._args[k]
# Sets may be expressed as lists. Silently normalize.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if meta[b'type'] == b'set' and isinstance(v, list):
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 v = set(v)
# TODO consider more/stronger type validation.
data[k] = v
Gregory Szorc
wireproto: extract HTTP version 2 code to own module...
r37563
return data
def getprotocaps(self):
# Protocol capabilities are currently not implemented for HTTP V2.
return set()
def getpayload(self):
raise NotImplementedError
@contextlib.contextmanager
def mayberedirectstdio(self):
raise NotImplementedError
def client(self):
raise NotImplementedError
def addcapabilities(self, repo, caps):
return caps
def checkperm(self, perm):
raise NotImplementedError
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: define and implement HTTP handshake to upgrade protocol...
r37575 def httpv2apidescriptor(req, repo):
proto = httpv2protocolhandler(req, repo.ui)
return _capabilitiesv2(repo, proto)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def _capabilitiesv2(repo, proto):
"""Obtain the set of capabilities for version 2 transports.
These capabilities are distinct from the capabilities for version 1
transports.
"""
caps = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'commands': {},
b'framingmediatypes': [FRAMINGTYPE],
b'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 }
Gregory Szorc
wireproto: move version 2 commands dict to wireprotov2server...
r37802 for command, entry in COMMANDS.items():
Gregory Szorc
wireprotov2: expose rich arguments metadata...
r39837 args = {}
for arg, meta in entry.args.items():
args[arg] = {
# TODO should this be a normalized type using CBOR's
# terminology?
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'type': meta[b'type'],
b'required': meta[b'required'],
Gregory Szorc
wireprotov2: expose rich arguments metadata...
r39837 }
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not meta[b'required']:
args[arg][b'default'] = meta[b'default']()
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if meta[b'validvalues']:
args[arg][b'validvalues'] = meta[b'validvalues']
Gregory Szorc
wireprotov2: advertise set of valid values for requestable fields...
r39838
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 # TODO this type of check should be defined in a per-command callback.
Augie Fackler
formatting: blacken the codebase...
r43346 if (
command == b'rawstorefiledata'
and not streamclone.allowservergeneration(repo)
):
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 caps[b'commands'][command] = {
b'args': args,
b'permissions': [entry.permission],
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 }
Gregory Szorc
wireprotov2: advertise recommended batch size for requests...
r40208 if entry.extracapabilitiesfn:
extracaps = entry.extracapabilitiesfn(repo, proto)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 caps[b'commands'][command].update(extracaps)
Gregory Szorc
wireprotov2: advertise recommended batch size for requests...
r40208
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 caps[b'rawrepoformats'] = sorted(repo.requirements & repo.supportedformats)
Gregory Szorc
wireproto: expose repository formats via capabilities...
r37675
Gregory Szorc
wireprotov2: advertise redirect targets in capabilities...
r40059 targets = getadvertisedredirecttargets(repo, proto)
if targets:
caps[b'redirect'] = {
b'targets': [],
b'hashes': [b'sha256', b'sha1'],
}
for target in targets:
entry = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'name': target[b'name'],
b'protocol': target[b'protocol'],
b'uris': target[b'uris'],
Gregory Szorc
wireprotov2: advertise redirect targets in capabilities...
r40059 }
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for key in (b'snirequired', b'tlsversions'):
Gregory Szorc
wireprotov2: advertise redirect targets in capabilities...
r40059 if key in target:
entry[key] = target[key]
caps[b'redirect'][b'targets'].append(entry)
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 return proto.addcapabilities(repo, caps)
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: advertise redirect targets in capabilities...
r40059 def getadvertisedredirecttargets(repo, proto):
"""Obtain a list of content redirect targets.
Returns a list containing potential redirect targets that will be
advertised in capabilities data. Each dict MUST have the following
keys:
name
The name of this redirect target. This is the identifier clients use
to refer to a target. It is transferred as part of every command
request.
protocol
Network protocol used by this target. Typically this is the string
in front of the ``://`` in a URL. e.g. ``https``.
uris
List of representative URIs for this target. Clients can use the
URIs to test parsing for compatibility or for ordering preference
for which target to use.
The following optional keys are recognized:
snirequired
Bool indicating if Server Name Indication (SNI) is required to
connect to this target.
tlsversions
List of bytes indicating which TLS versions are supported by this
target.
By default, clients reflect the target order advertised by servers
and servers will use the first client-advertised target when picking
a redirect target. So targets should be advertised in the order the
server prefers they be used.
"""
return []
Augie Fackler
formatting: blacken the codebase...
r43346
def wireprotocommand(
name,
args=None,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'push',
Augie Fackler
formatting: blacken the codebase...
r43346 cachekeyfn=None,
extracapabilitiesfn=None,
):
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798 """Decorator to declare a wire protocol command.
``name`` is the name of the wire protocol command being provided.
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 ``args`` is a dict defining arguments accepted by the command. Keys are
the argument name. Values are dicts with the following keys:
``type``
The argument data type. Must be one of the following string
literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
or ``bool``.
``default``
A callable returning the default value for this argument. If not
specified, ``None`` will be the default value.
``example``
An example value for this argument.
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
Gregory Szorc
wireprotov2: advertise set of valid values for requestable fields...
r39838 ``validvalues``
Set of recognized values for this argument.
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798 ``permission`` defines the permission type needed to run this command.
Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
respectively. Default is to assume command requires ``push`` permissions
because otherwise commands not declaring their permissions could modify
a repository that is supposed to be read-only.
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595
Gregory Szorc
wireprotov2: support response caching...
r40057 ``cachekeyfn`` defines an optional callable that can derive the
cache key for this request.
Gregory Szorc
wireprotov2: advertise recommended batch size for requests...
r40208 ``extracapabilitiesfn`` defines an optional callable that defines extra
command capabilities/parameters that are advertised next to the command
in the capabilities data structure describing the server. The callable
receives as arguments the repository and protocol objects. It returns
a dict of extra fields to add to the command descriptor.
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 Wire protocol commands are generators of objects to be serialized and
sent to the client.
If a command raises an uncaught exception, this will be translated into
a command error.
Gregory Szorc
wireprotov2: support response caching...
r40057
All commands can opt in to being cacheable by defining a function
(``cachekeyfn``) that is called to derive a cache key. This function
receives the same arguments as the command itself plus a ``cacher``
argument containing the active cacher for the request and returns a bytes
containing the key in a cache the response to this command may be cached
under.
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798 """
Augie Fackler
formatting: blacken the codebase...
r43346 transports = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 k for k, v in wireprototypes.TRANSPORTS.items() if v[b'version'] == 2
Augie Fackler
formatting: blacken the codebase...
r43346 }
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if permission not in (b'push', b'pull'):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'invalid wire protocol permission; '
b'got %s; expected "push" or "pull"' % permission
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
if args is None:
args = {}
if not isinstance(args, dict):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'arguments for version 2 commands must be declared as dicts'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 for arg, meta in args.items():
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if arg == b'*':
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'* argument name not allowed on version 2 commands'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
if not isinstance(meta, dict):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'arguments for version 2 commands '
b'must declare metadata as a dict'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'type' not in meta:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s argument for command %s does not '
b'declare type field' % (arg, name)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if meta[b'type'] not in (
b'bytes',
b'int',
b'list',
b'dict',
b'set',
b'bool',
):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s argument for command %s has '
b'illegal type: %s' % (arg, name, meta[b'type'])
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'example' not in meta:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s argument for command %s does not '
b'declare example field' % (arg, name)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 meta[b'required'] = b'default' not in meta
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 meta.setdefault(b'default', lambda: None)
meta.setdefault(b'validvalues', None)
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def register(func):
Gregory Szorc
wireproto: move version 2 commands dict to wireprotov2server...
r37802 if name in COMMANDS:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'%s command already registered for version 2' % name
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
Gregory Szorc
wireproto: move version 2 commands dict to wireprotov2server...
r37802 COMMANDS[name] = wireprototypes.commandentry(
Augie Fackler
formatting: blacken the codebase...
r43346 func,
args=args,
transports=transports,
permission=permission,
cachekeyfn=cachekeyfn,
extracapabilitiesfn=extracapabilitiesfn,
)
Gregory Szorc
wireproto: make version 2 @wireprotocommand an independent function...
r37798
return func
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
return register
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: support response caching...
r40057 def makecommandcachekeyfn(command, localversion=None, allargs=False):
"""Construct a cache key derivation function with common features.
By default, the cache key is a hash of:
* The command name.
* A global cache version number.
* A local cache version number (passed via ``localversion``).
* All the arguments passed to the command.
* The media type used.
* Wire protocol version string.
* The repository path.
"""
if not allargs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ProgrammingError(
b'only allargs=True is currently supported'
)
Gregory Szorc
wireprotov2: support response caching...
r40057
if localversion is None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ProgrammingError(b'must set localversion argument value')
Gregory Szorc
wireprotov2: support response caching...
r40057
def cachekeyfn(repo, proto, cacher, **args):
spec = COMMANDS[command]
# Commands that mutate the repo can not be cached.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if spec.permission == b'push':
Gregory Szorc
wireprotov2: support response caching...
r40057 return None
# TODO config option to disable caching.
# Our key derivation strategy is to construct a data structure
# holding everything that could influence cacheability and to hash
# the CBOR representation of that. Using CBOR seems like it might
# be overkill. However, simpler hashing mechanisms are prone to
# duplicate input issues. e.g. if you just concatenate two values,
# "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides
# "padding" between values and prevents these problems.
# Seed the hash with various data.
state = {
# To invalidate all cache keys.
b'globalversion': GLOBAL_CACHE_VERSION,
# More granular cache key invalidation.
b'localversion': localversion,
# Cache keys are segmented by command.
Gregory Szorc
wireprotov2server: don't attempt to cast command name...
r41422 b'command': command,
Gregory Szorc
wireprotov2: support response caching...
r40057 # Throw in the media type and API version strings so changes
# to exchange semantics invalid cache.
b'mediatype': FRAMINGTYPE,
b'version': HTTP_WIREPROTO_V2,
# So same requests for different repos don't share cache keys.
b'repo': repo.root,
}
# The arguments passed to us will have already been normalized.
# Default values will be set, etc. This is important because it
# means that it doesn't matter if clients send an explicit argument
# or rely on the default value: it will all normalize to the same
# set of arguments on the server and therefore the same cache key.
#
# Arguments by their very nature must support being encoded to CBOR.
# And the CBOR encoder is deterministic. So we hash the arguments
# by feeding the CBOR of their representation into the hasher.
if allargs:
state[b'args'] = pycompat.byteskwargs(args)
cacher.adjustcachekeystate(state)
hasher = hashlib.sha1()
for chunk in cborutil.streamencode(state):
hasher.update(chunk)
return pycompat.sysbytes(hasher.hexdigest())
return cachekeyfn
Augie Fackler
formatting: blacken the codebase...
r43346
def makeresponsecacher(
repo, proto, command, args, objencoderfn, redirecttargets, redirecthashes
):
Gregory Szorc
wireprotov2: support response caching...
r40057 """Construct a cacher for a cacheable command.
Returns an ``iwireprotocolcommandcacher`` instance.
Extensions can monkeypatch this function to provide custom caching
backends.
"""
return None
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212 def resolvenodes(repo, revisions):
"""Resolve nodes from a revisions specifier data structure."""
cl = repo.changelog
clhasnode = cl.hasnode
seen = set()
nodes = []
if not isinstance(revisions, list):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.WireprotoCommandError(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 b'revisions must be defined as an array'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
for spec in revisions:
if b'type' not in spec:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'type key not present in revision specifier'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
typ = spec[b'type']
if typ == b'changesetexplicit':
if b'nodes' not in spec:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'nodes key not present in changesetexplicit revision '
b'specifier'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
for node in spec[b'nodes']:
if node not in seen:
nodes.append(node)
seen.add(node)
elif typ == b'changesetexplicitdepth':
for key in (b'nodes', b'depth'):
if key not in spec:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s key not present in changesetexplicitdepth revision '
b'specifier',
Augie Fackler
formatting: blacken the codebase...
r43346 (key,),
)
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
Augie Fackler
formatting: blacken the codebase...
r43346 for rev in repo.revs(
b'ancestors(%ln, %s)', spec[b'nodes'], spec[b'depth'] - 1
):
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212 node = cl.node(rev)
if node not in seen:
nodes.append(node)
seen.add(node)
elif typ == b'changesetdagrange':
for key in (b'roots', b'heads'):
if key not in spec:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s key not present in changesetdagrange revision '
b'specifier',
Augie Fackler
formatting: blacken the codebase...
r43346 (key,),
)
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
if not spec[b'heads']:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'heads key in changesetdagrange cannot be empty'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
if spec[b'roots']:
common = [n for n in spec[b'roots'] if clhasnode(n)]
else:
common = [nullid]
for n in discovery.outgoing(repo, common, spec[b'heads']).missing:
if n not in seen:
nodes.append(n)
seen.add(n)
else:
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unknown revision specifier type: %s', (typ,)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212
return nodes
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 @wireprotocommand(b'branchmap', permission=b'pull')
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def branchmapv2(repo, proto):
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 yield {
encoding.fromlocal(k): v
for k, v in pycompat.iteritems(repo.branchmap())
}
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 @wireprotocommand(b'capabilities', permission=b'pull')
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def capabilitiesv2(repo, proto):
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 yield _capabilitiesv2(repo, proto)
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'changesetdata',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'revisions': {
b'type': b'list',
b'example': [
Augie Fackler
formatting: blacken the codebase...
r43346 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],}
],
Gregory Szorc
wireprotov2: teach changesetdata to fetch ancestors until depth...
r39840 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fields': {
b'type': b'set',
b'default': set,
b'example': {b'parents', b'revision'},
b'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
},
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212 def changesetdata(repo, proto, revisions, fields):
Gregory Szorc
wireprotov2: add TODOs around extending changesetdata fields...
r39672 # TODO look for unknown fields and abort when they can't be serviced.
Gregory Szorc
wireprotov2: advertise set of valid values for requestable fields...
r39838 # This could probably be validated by dispatcher using validvalues.
Gregory Szorc
wireprotov2: add TODOs around extending changesetdata fields...
r39672
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 cl = repo.changelog
Gregory Szorc
wireprotov2: change how revisions are specified to changesetdata...
r40212 outgoing = resolvenodes(repo, revisions)
Gregory Szorc
wireprotov2: add phases to "changesetdata" command...
r39668 publishing = repo.publishing()
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666
if outgoing:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo.hook(b'preoutgoing', throw=True, source=b'serve')
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666
yield {
b'totalitems': len(outgoing),
}
Gregory Szorc
wireprotov2: add phases to "changesetdata" command...
r39668 # The phases of nodes already transferred to the client may have changed
# since the client last requested data. We send phase-only records
# for these revisions, if requested.
Gregory Szorc
wireprotov2: stop sending phase updates for base revisions...
r40211 # TODO actually do this. We'll probably want to emit phase heads
# in the ancestry set of the outgoing revisions. This will ensure
# that phase updates within that set are seen.
if b'phase' in fields:
pass
Gregory Szorc
wireprotov2: add phases to "changesetdata" command...
r39668
Gregory Szorc
wireprotov2: add bookmarks to "changesetdata" command...
r39670 nodebookmarks = {}
for mark, node in repo._bookmarks.items():
nodebookmarks.setdefault(node, set()).add(mark)
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 # It is already topologically sorted by revision number.
for node in outgoing:
d = {
b'node': node,
}
if b'parents' in fields:
d[b'parents'] = cl.parents(node)
Gregory Szorc
wireprotov2: add phases to "changesetdata" command...
r39668 if b'phase' in fields:
if publishing:
d[b'phase'] = b'public'
else:
ctx = repo[node]
d[b'phase'] = ctx.phasestr()
Gregory Szorc
wireprotov2: add bookmarks to "changesetdata" command...
r39670 if b'bookmarks' in fields and node in nodebookmarks:
d[b'bookmarks'] = sorted(nodebookmarks[node])
del nodebookmarks[node]
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 followingmeta = []
followingdata = []
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666
if b'revision' in fields:
rawdata: update caller in wireprotov2server...
r43042 revisiondata = cl.rawdata(node)
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 followingmeta.append((b'revision', len(revisiondata)))
followingdata.append(revisiondata)
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666
Gregory Szorc
wireprotov2: add TODOs around extending changesetdata fields...
r39672 # TODO make it possible for extensions to wrap a function or register
# a handler to service custom fields.
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 if followingmeta:
d[b'fieldsfollowing'] = followingmeta
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666 yield d
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 for extra in followingdata:
yield extra
Gregory Szorc
wireprotov2: define and implement "changesetdata" command...
r39666
Gregory Szorc
wireprotov2: add bookmarks to "changesetdata" command...
r39670 # If requested, send bookmarks from nodes that didn't have revision
# data sent so receiver is aware of any bookmark updates.
if b'bookmarks' in fields:
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for node, marks in sorted(pycompat.iteritems(nodebookmarks)):
Gregory Szorc
wireprotov2: add bookmarks to "changesetdata" command...
r39670 yield {
b'node': node,
b'bookmarks': sorted(marks),
}
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 class FileAccessError(Exception):
"""Represents an error accessing a specific file."""
def __init__(self, path, msg, args):
self.path = path
self.msg = msg
self.args = args
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 def getfilestore(repo, proto, path):
"""Obtain a file storage object for use with wire protocol.
Exists as a standalone function so extensions can monkeypatch to add
access control.
"""
# This seems to work even if the file doesn't exist. So catch
# "empty" files and return an error.
fl = repo.file(path)
if not len(fl):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise FileAccessError(path, b'unknown file: %s', (path,))
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675
return fl
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 def emitfilerevisions(repo, path, revisions, linknodes, fields):
Gregory Szorc
wireprotov2: extract file object emission to own function...
r40213 for revision in revisions:
d = {
b'node': revision.node,
}
if b'parents' in fields:
d[b'parents'] = [revision.p1node, revision.p2node]
Gregory Szorc
wireprotov2: support exposing linknode of file revisions...
r40427 if b'linknode' in fields:
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 d[b'linknode'] = linknodes[revision.node]
Gregory Szorc
wireprotov2: support exposing linknode of file revisions...
r40427
Gregory Szorc
wireprotov2: extract file object emission to own function...
r40213 followingmeta = []
followingdata = []
if b'revision' in fields:
if revision.revision is not None:
followingmeta.append((b'revision', len(revision.revision)))
followingdata.append(revision.revision)
else:
d[b'deltabasenode'] = revision.basenode
followingmeta.append((b'delta', len(revision.delta)))
followingdata.append(revision.delta)
if followingmeta:
d[b'fieldsfollowing'] = followingmeta
yield d
for extra in followingdata:
yield extra
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 def makefilematcher(repo, pathfilter):
"""Construct a matcher from a path filter dict."""
# Validate values.
if pathfilter:
for key in (b'include', b'exclude'):
for pattern in pathfilter.get(key, []):
if not pattern.startswith((b'path:', b'rootfilesin:')):
raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s pattern must begin with `path:` or `rootfilesin:`; '
b'got %s',
Augie Fackler
formatting: blacken the codebase...
r43346 (key, pattern),
)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
if pathfilter:
Augie Fackler
formatting: blacken the codebase...
r43346 matcher = matchmod.match(
repo.root,
b'',
include=pathfilter.get(b'include', []),
exclude=pathfilter.get(b'exclude', []),
)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 else:
matcher = matchmod.match(repo.root, b'')
# Requested patterns could include files not in the local store. So
# filter those out.
Martin von Zweigbergk
wireprotov2server: let repo.narrowmatch(match) do matcher intersection...
r40685 return repo.narrowmatch(matcher)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'filedata',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'haveparents': {
b'type': b'bool',
b'default': lambda: False,
b'example': True,
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'nodes': {b'type': b'list', b'example': [b'0123456...'],},
b'fields': {
b'type': b'set',
b'default': set,
b'example': {b'parents', b'revision'},
b'validvalues': {b'parents', b'revision', b'linknode'},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'path': {b'type': b'bytes', b'example': b'foo.txt',},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Gregory Szorc
wireprotov2: support response caching...
r40057 # TODO censoring a file revision won't invalidate the cache.
# Figure out a way to take censoring into account when deriving
# the cache key.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachekeyfn=makecommandcachekeyfn(b'filedata', 1, allargs=True),
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 def filedata(repo, proto, haveparents, nodes, fields, path):
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 # TODO this API allows access to file revisions that are attached to
# secret changesets. filesdata does not have this problem. Maybe this
# API should be deleted?
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 try:
# Extensions may wish to access the protocol handler.
store = getfilestore(repo, proto, path)
except FileAccessError as e:
raise error.WireprotoCommandError(e.msg, e.args)
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 clnode = repo.changelog.node
linknodes = {}
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 # Validate requested nodes.
for node in nodes:
try:
store.rev(node)
except error.LookupError:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.WireprotoCommandError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'unknown file node: %s', (hex(node),)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 # TODO by creating the filectx against a specific file revision
# instead of changeset, linkrev() is always used. This is wrong for
# cases where linkrev() may refer to a hidden changeset. But since this
# API doesn't know anything about changesets, we're not sure how to
# disambiguate the linknode. Perhaps we should delete this API?
fctx = repo.filectx(path, fileid=node)
linknodes[node] = clnode(fctx.introrev())
Augie Fackler
formatting: blacken the codebase...
r43346 revisions = store.emitrevisions(
nodes,
revisiondata=b'revision' in fields,
assumehaveparentrevisions=haveparents,
)
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675
yield {
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 b'totalitems': len(nodes),
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675 }
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 for o in emitfilerevisions(repo, path, revisions, linknodes, fields):
Gregory Szorc
wireprotov2: extract file object emission to own function...
r40213 yield o
Gregory Szorc
wireprotov2: define and implement "filedata" command...
r39675
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 def filesdatacapabilities(repo, proto):
batchsize = repo.ui.configint(
Augie Fackler
formatting: blacken the codebase...
r43346 b'experimental', b'server.filesdata.recommended-batch-size'
)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 return {
b'recommendedbatchsize': batchsize,
}
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'filesdata',
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'haveparents': {
b'type': b'bool',
b'default': lambda: False,
b'example': True,
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fields': {
b'type': b'set',
b'default': set,
b'example': {b'parents', b'revision'},
b'validvalues': {
Augie Fackler
formatting: blacken the codebase...
r43346 b'firstchangeset',
b'linknode',
b'parents',
b'revision',
},
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'pathfilter': {
b'type': b'dict',
b'default': lambda: None,
b'example': {b'include': [b'path:tests']},
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'revisions': {
b'type': b'list',
b'example': [
Augie Fackler
formatting: blacken the codebase...
r43346 {b'type': b'changesetexplicit', b'nodes': [b'abcdef...'],}
],
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 },
},
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 # TODO censoring a file revision won't invalidate the cache.
# Figure out a way to take censoring into account when deriving
# the cache key.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cachekeyfn=makecommandcachekeyfn(b'filesdata', 1, allargs=True),
Augie Fackler
formatting: blacken the codebase...
r43346 extracapabilitiesfn=filesdatacapabilities,
)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 def filesdata(repo, proto, haveparents, fields, pathfilter, revisions):
# TODO This should operate on a repo that exposes obsolete changesets. There
# is a race between a client making a push that obsoletes a changeset and
# another client fetching files data for that changeset. If a client has a
# changeset, it should probably be allowed to access files data for that
# changeset.
outgoing = resolvenodes(repo, revisions)
filematcher = makefilematcher(repo, pathfilter)
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 # path -> {fnode: linknode}
fnodes = collections.defaultdict(dict)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 # We collect the set of relevant file revisions by iterating the changeset
# revisions and either walking the set of files recorded in the changeset
# or by walking the manifest at that revision. There is probably room for a
# storage-level API to request this data, as it can be expensive to compute
# and would benefit from caching or alternate storage from what revlogs
# provide.
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 for node in outgoing:
ctx = repo[node]
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 mctx = ctx.manifestctx()
md = mctx.read()
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 if haveparents:
checkpaths = ctx.files()
else:
checkpaths = md.keys()
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 for path in checkpaths:
fnode = md[path]
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 if path in fnodes and fnode in fnodes[path]:
continue
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: unify file revision collection and linknode derivation...
r40960 if not filematcher(path):
continue
fnodes[path].setdefault(fnode, node)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
yield {
b'totalpaths': len(fnodes),
Augie Fackler
formatting: blacken the codebase...
r43346 b'totalitems': sum(len(v) for v in fnodes.values()),
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 }
for path, filenodes in sorted(fnodes.items()):
try:
store = getfilestore(repo, proto, path)
except FileAccessError as e:
raise error.WireprotoCommandError(e.msg, e.args)
yield {
b'path': path,
b'totalitems': len(filenodes),
}
Augie Fackler
formatting: blacken the codebase...
r43346 revisions = store.emitrevisions(
filenodes.keys(),
revisiondata=b'revision' in fields,
assumehaveparentrevisions=haveparents,
)
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214
Gregory Szorc
wireprotov2: send linknodes to emitfilerevisions()...
r40959 for o in emitfilerevisions(repo, path, revisions, filenodes, fields):
Gregory Szorc
wireprotov2: define and implement "filesdata" command...
r40214 yield o
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'heads',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'publiconly': {
b'type': b'bool',
b'default': lambda: False,
b'example': False,
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
},
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 def headsv2(repo, proto, publiconly):
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 if publiconly:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo = repo.filtered(b'immutable')
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 yield repo.heads()
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'known',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'nodes': {
b'type': b'list',
b'default': list,
b'example': [b'deadbeef'],
},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 def knownv2(repo, proto, nodes):
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 yield result
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'listkeys',
args={b'namespace': {b'type': b'bytes', b'example': b'ns',},},
permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 def listkeysv2(repo, proto, namespace):
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 keys = repo.listkeys(encoding.tolocal(namespace))
Augie Fackler
formatting: blacken the codebase...
r43346 keys = {
encoding.fromlocal(k): encoding.fromlocal(v)
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for k, v in pycompat.iteritems(keys)
Augie Fackler
formatting: blacken the codebase...
r43346 }
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 yield keys
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'lookup',
args={b'key': {b'type': b'bytes', b'example': b'foo',},},
permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def lookupv2(repo, proto, key):
key = encoding.tolocal(key)
# TODO handle exception.
node = repo.lookup(key)
Gregory Szorc
wireprotov2: implement commands as a generator of objects...
r39595 yield node
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: advertise recommended batch size for requests...
r40208 def manifestdatacapabilities(repo, proto):
batchsize = repo.ui.configint(
Augie Fackler
formatting: blacken the codebase...
r43346 b'experimental', b'server.manifestdata.recommended-batch-size'
)
Gregory Szorc
wireprotov2: advertise recommended batch size for requests...
r40208
return {
b'recommendedbatchsize': batchsize,
}
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'manifestdata',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'nodes': {b'type': b'list', b'example': [b'0123456...'],},
b'haveparents': {
b'type': b'bool',
b'default': lambda: False,
b'example': True,
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'fields': {
b'type': b'set',
b'default': set,
b'example': {b'parents', b'revision'},
b'validvalues': {b'parents', b'revision'},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'tree': {b'type': b'bytes', b'example': b'',},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
cachekeyfn=makecommandcachekeyfn(b'manifestdata', 1, allargs=True),
Augie Fackler
formatting: blacken the codebase...
r43346 extracapabilitiesfn=manifestdatacapabilities,
)
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 def manifestdata(repo, proto, haveparents, nodes, fields, tree):
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 store = repo.manifestlog.getstorage(tree)
# Validate the node is known and abort on unknown revisions.
for node in nodes:
try:
store.rev(node)
except error.LookupError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.WireprotoCommandError(b'unknown node: %s', (node,))
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673
Augie Fackler
formatting: blacken the codebase...
r43346 revisions = store.emitrevisions(
nodes,
revisiondata=b'revision' in fields,
assumehaveparentrevisions=haveparents,
)
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673
yield {
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 b'totalitems': len(nodes),
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 }
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 for revision in revisions:
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 d = {
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 b'node': revision.node,
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 }
if b'parents' in fields:
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 d[b'parents'] = [revision.p1node, revision.p2node]
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 followingmeta = []
followingdata = []
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 if b'revision' in fields:
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 if revision.revision is not None:
followingmeta.append((b'revision', len(revision.revision)))
followingdata.append(revision.revision)
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673 else:
Gregory Szorc
wireprotov2server: port to emitrevisions()...
r39900 d[b'deltabasenode'] = revision.basenode
followingmeta.append((b'delta', len(revision.delta)))
followingdata.append(revision.delta)
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839
if followingmeta:
d[b'fieldsfollowing'] = followingmeta
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673
yield d
Gregory Szorc
wireprotov2: allow multiple fields to follow revision maps...
r39839 for extra in followingdata:
yield extra
Gregory Szorc
wireprotov2: define and implement "manifestdata" command...
r39673
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 @wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'pushkey',
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'namespace': {b'type': b'bytes', b'example': b'ns',},
b'key': {b'type': b'bytes', b'example': b'key',},
b'old': {b'type': b'bytes', b'example': b'old',},
b'new': {b'type': b'bytes', b'example': b'new',},
Gregory Szorc
wireprotov2: declare command arguments richly...
r39835 },
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'push',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireproto: move version 2 command handlers to wireprotov2server...
r37564 def pushkeyv2(repo, proto, namespace, key, old, new):
# TODO handle ui output redirection
Augie Fackler
formatting: blacken the codebase...
r43346 yield repo.pushkey(
encoding.tolocal(namespace),
encoding.tolocal(key),
encoding.tolocal(old),
encoding.tolocal(new),
)
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365
@wireprotocommand(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'rawstorefiledata',
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 args={
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'files': {
b'type': b'list',
b'example': [b'changelog', b'manifestlog'],
},
b'pathfilter': {
b'type': b'list',
b'default': lambda: None,
b'example': {b'include': [b'path:tests']},
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 },
},
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 permission=b'pull',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 def rawstorefiledata(repo, proto, files, pathfilter):
if not streamclone.allowservergeneration(repo):
raise error.WireprotoCommandError(b'stream clone is disabled')
# TODO support dynamically advertising what store files "sets" are
# available. For now, we support changelog, manifestlog, and files.
files = set(files)
allowedfiles = {b'changelog', b'manifestlog'}
unsupported = files - allowedfiles
if unsupported:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.WireprotoCommandError(
b'unknown file type: %s', (b', '.join(sorted(unsupported)),)
)
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365
with repo.lock():
topfiles = list(repo.store.topfiles())
sendfiles = []
totalsize = 0
# TODO this is a bunch of storage layer interface abstractions because
# it assumes revlogs.
for name, encodedname, size in topfiles:
if b'changelog' in files and name.startswith(b'00changelog'):
pass
elif b'manifestlog' in files and name.startswith(b'00manifest'):
pass
else:
continue
sendfiles.append((b'store', name, size))
totalsize += size
yield {
b'filecount': len(sendfiles),
b'totalsize': totalsize,
}
for location, name, size in sendfiles:
yield {
b'location': location,
b'path': name,
b'size': size,
}
# We have to use a closure for this to ensure the context manager is
# closed only after sending the final chunk.
def getfiledata():
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 with repo.svfs(name, b'rb', auditpath=False) as fh:
Gregory Szorc
wireprotov2: implement command for retrieving raw store files...
r40365 for chunk in util.filechunkiter(fh, limit=size):
yield chunk
Augie Fackler
formatting: blacken the codebase...
r43346 yield wireprototypes.indefinitebytestringresponse(getfiledata())