comparison 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
comparison
equal deleted inserted replaced
39654:d292328e0143 39655:0e03e6a44dee
8 8
9 import contextlib 9 import contextlib
10 10
11 from .i18n import _ 11 from .i18n import _
12 from .node import ( 12 from .node import (
13 hex,
13 nullid, 14 nullid,
14 nullrev, 15 nullrev,
15 ) 16 )
16 from . import ( 17 from . import (
17 changegroup, 18 changegroup,
646 yield { 647 yield {
647 b'node': node, 648 b'node': node,
648 b'bookmarks': sorted(marks), 649 b'bookmarks': sorted(marks),
649 } 650 }
650 651
652 class FileAccessError(Exception):
653 """Represents an error accessing a specific file."""
654
655 def __init__(self, path, msg, args):
656 self.path = path
657 self.msg = msg
658 self.args = args
659
660 def getfilestore(repo, proto, path):
661 """Obtain a file storage object for use with wire protocol.
662
663 Exists as a standalone function so extensions can monkeypatch to add
664 access control.
665 """
666 # This seems to work even if the file doesn't exist. So catch
667 # "empty" files and return an error.
668 fl = repo.file(path)
669
670 if not len(fl):
671 raise FileAccessError(path, 'unknown file: %s', (path,))
672
673 return fl
674
675 @wireprotocommand('filedata',
676 args={
677 'nodes': [b'0123456...'],
678 'fields': [b'parents', b'revision'],
679 'path': b'foo.txt',
680 },
681 permission='pull')
682 def filedata(repo, proto, nodes=None, fields=None, path=None):
683 fields = fields or set()
684
685 if nodes is None:
686 raise error.WireprotoCommandError('nodes argument must be defined')
687
688 if path is None:
689 raise error.WireprotoCommandError('path argument must be defined')
690
691 try:
692 # Extensions may wish to access the protocol handler.
693 store = getfilestore(repo, proto, path)
694 except FileAccessError as e:
695 raise error.WireprotoCommandError(e.msg, e.args)
696
697 # Validate requested nodes.
698 for node in nodes:
699 try:
700 store.rev(node)
701 except error.LookupError:
702 raise error.WireprotoCommandError('unknown file node: %s',
703 (hex(node),))
704
705 revs, requests = builddeltarequests(store, nodes)
706
707 yield {
708 b'totalitems': len(revs),
709 }
710
711 if b'revision' in fields:
712 deltas = store.emitrevisiondeltas(requests)
713 else:
714 deltas = None
715
716 for rev in revs:
717 node = store.node(rev)
718
719 if deltas is not None:
720 delta = next(deltas)
721 else:
722 delta = None
723
724 d = {
725 b'node': node,
726 }
727
728 if b'parents' in fields:
729 d[b'parents'] = store.parents(node)
730
731 if b'revision' in fields:
732 assert delta is not None
733 assert delta.flags == 0
734 assert d[b'node'] == delta.node
735
736 if delta.revision is not None:
737 revisiondata = delta.revision
738 d[b'revisionsize'] = len(revisiondata)
739 else:
740 d[b'deltabasenode'] = delta.basenode
741 revisiondata = delta.delta
742 d[b'deltasize'] = len(revisiondata)
743 else:
744 revisiondata = None
745
746 yield d
747
748 if revisiondata is not None:
749 yield revisiondata
750
751 if deltas is not None:
752 try:
753 next(deltas)
754 raise error.ProgrammingError('should not have more deltas')
755 except GeneratorExit:
756 pass
757
651 @wireprotocommand('heads', 758 @wireprotocommand('heads',
652 args={ 759 args={
653 'publiconly': False, 760 'publiconly': False,
654 }, 761 },
655 permission='pull') 762 permission='pull')