Mercurial > public > mercurial-scm > hg-stable
diff mercurial/wireprotov2server.py @ 39655:0e03e6a44dee
wireprotov2: define and implement "filedata" command
Continuing our trend of implementing *data commands for retrieving
information about specific repository data primitives, this commit
implements a command for retrieving data about an individual tracked
file.
The command is very similar to "manifestdata." The only significant
difference is that we have a standalone function for obtaining
storage for a tracked file. This is to provide a monkeypatch point
for extensions to implement path-based access control.
With this API available, wire protocol version 2 now exposes all
data primitives necessary to implement a full clone. Of course,
since "filedata" can only resolve data for a single path at a time,
clients would need to issue N commands to perform a full clone. On
the Firefox repository, this would be ~461k commands. We'll likely
need to implement a file data retrieval command that supports
multiple paths. But that can be implemented later.
Differential Revision: https://phab.mercurial-scm.org/D4490
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Wed, 05 Sep 2018 09:10:17 -0700 |
parents | c7a7c7e844e5 |
children | aa7e312375cf |
line wrap: on
line diff
--- a/mercurial/wireprotov2server.py Wed Sep 05 09:09:57 2018 -0700 +++ b/mercurial/wireprotov2server.py Wed Sep 05 09:10:17 2018 -0700 @@ -10,6 +10,7 @@ from .i18n import _ from .node import ( + hex, nullid, nullrev, ) @@ -648,6 +649,112 @@ b'bookmarks': sorted(marks), } +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 + +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): + raise FileAccessError(path, 'unknown file: %s', (path,)) + + return fl + +@wireprotocommand('filedata', + args={ + 'nodes': [b'0123456...'], + 'fields': [b'parents', b'revision'], + 'path': b'foo.txt', + }, + permission='pull') +def filedata(repo, proto, nodes=None, fields=None, path=None): + fields = fields or set() + + if nodes is None: + raise error.WireprotoCommandError('nodes argument must be defined') + + if path is None: + raise error.WireprotoCommandError('path argument must be defined') + + 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) + + # Validate requested nodes. + for node in nodes: + try: + store.rev(node) + except error.LookupError: + raise error.WireprotoCommandError('unknown file node: %s', + (hex(node),)) + + revs, requests = builddeltarequests(store, nodes) + + yield { + b'totalitems': len(revs), + } + + if b'revision' in fields: + deltas = store.emitrevisiondeltas(requests) + else: + deltas = None + + for rev in revs: + node = store.node(rev) + + if deltas is not None: + delta = next(deltas) + else: + delta = None + + d = { + b'node': node, + } + + if b'parents' in fields: + d[b'parents'] = store.parents(node) + + if b'revision' in fields: + assert delta is not None + assert delta.flags == 0 + assert d[b'node'] == delta.node + + if delta.revision is not None: + revisiondata = delta.revision + d[b'revisionsize'] = len(revisiondata) + else: + d[b'deltabasenode'] = delta.basenode + revisiondata = delta.delta + d[b'deltasize'] = len(revisiondata) + else: + revisiondata = None + + yield d + + if revisiondata is not None: + yield revisiondata + + if deltas is not None: + try: + next(deltas) + raise error.ProgrammingError('should not have more deltas') + except GeneratorExit: + pass + @wireprotocommand('heads', args={ 'publiconly': False,