Mercurial > public > mercurial-scm > hg
comparison mercurial/wireprotov2server.py @ 39639: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
39638:d292328e0143 | 39639: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') |