Code Repositories xandikos / wip/nested
Support accessing subdirectories of git repositories. Jelmer Vernooń≥ 3 years ago
1 changed file(s) with 46 addition(s) and 21 deletion(s). Raw diff Collapse all Expand all
2525 import logging
2626 import mimetypes
2727 import os
28 import posixpath
2829 import shutil
2930 import stat
3031 import uuid
3132
33 from dulwich.object_store import (
34 commit_tree_changes,
35 tree_lookup_path,
36 )
3237 from dulwich.objects import Blob, Tree
3338 import dulwich.repo
3439
310315 raise NotImplementedError(self.set_comment)
311316
312317 def destroy(self):
313 """Destroy this store."""
318 """Destroy this store and its contents."""
314319 raise NotImplementedError(self.destroy)
315320
316321 def subdirectories(self):
323328
324329 class GitStore(Store):
325330 """A Store backed by a Git Repository.
331
332 :ivar ref: Git ref this store uses
333 :ivar tree_path: Path inside of the repository to use
334 :ivar repo: Git repository object
335 :ivar path: Repository path
326336 """
327337
328338 def __init__(self, repo, ref=b'refs/heads/master',
329 check_for_duplicate_uids=True):
339 tree_path='', check_for_duplicate_uids=True):
330340 super(GitStore, self).__init__()
331341 self.ref = ref
342 self.tree_path = tree_path
332343 self.repo = repo
333344 # Maps uids to (sha, fname)
334345 self._uid_to_fname = {}
633644 yield (name, old_content_type, old_etag, None)
634645
635646 def destroy(self):
636 """Destroy this store."""
647 """Destroy this store and its contents."""
648 # TODO(jelmer): If self.tree_path != '', just remove the specific path.
637649 shutil.rmtree(self.path)
638650
639651
648660 if isinstance(ref_object, Tree):
649661 return ref_object
650662 else:
651 return self.repo.object_store[ref_object.tree]
663 (mode, sha) = tree_lookup_path(
664 self.repo.object_store.__getitem__, ref_object.tree,
665 self.tree_path.encode(DEFAULT_ENCODING))
666 return self.repo.object_store[sha]
652667
653668 def _get_etag(self, name):
654669 tree = self._get_current_tree()
697712 b.chunked = data
698713 tree = self._get_current_tree()
699714 name_enc = name.encode(DEFAULT_ENCODING)
700 tree[name_enc] = (0o644 | stat.S_IFREG, b.id)
701 self.repo.object_store.add_objects([(tree, ''), (b, name_enc)])
715 mode = 0o644 | stat.S_IFREG
716 path = posixpath.join(self.tree_path.encode(DEFAULT_ENCODING), name_enc)
717 tree = commit_tree_changes(
718 self.repo.object_store, tree, [(path, mode, b.id)])
719
720 self.repo.object_store.add_object(b)
702721 self._commit_tree(tree.id, message.encode(DEFAULT_ENCODING),
703722 author=author)
704723 return b.id
715734 """
716735 tree = self._get_current_tree()
717736 name_enc = name.encode(DEFAULT_ENCODING)
737 path = posixpath.join(self.tree_path, name).encode(DEFAULT_ENCODING)
718738 try:
719739 current_sha = tree[name_enc][1]
720740 except KeyError:
721741 raise NoSuchItem(name)
722742 if etag is not None and current_sha != etag.encode('ascii'):
723743 raise InvalidETag(name, etag, current_sha.decode('ascii'))
724 del tree[name_enc]
725 self.repo.object_store.add_objects([(tree, '')])
744 tree = commit_tree_changes(
745 self.repo.object_store, tree,
746 [(path, None, None)])
726747 if message is None:
727748 fi = open_by_extension(
728749 self.repo.object_store[current_sha].chunked, name,
784805 :param author: Optional author
785806 :return: etag
786807 """
787 p = os.path.join(self.repo.path, name)
788 with open(p, 'wb') as f:
808 path = posixpath.join(self.tree_path, name)
809 abspath = os.path.join(self.repo.path, self.tree_path, name)
810 with open(abspath, 'wb') as f:
789811 f.writelines(data)
790 self.repo.stage(name)
791 etag = self.repo.open_index()[name.encode(DEFAULT_ENCODING)].sha
812 self.repo.stage(path)
813 etag = self.repo.open_index()[path.encode(DEFAULT_ENCODING)].sha
792814 self._commit_tree(message.encode(DEFAULT_ENCODING), author=author)
793815 return etag
794816
802824 :raise NoSuchItem: when the item doesn't exist
803825 :raise InvalidETag: If the specified ETag doesn't match the curren
804826 """
805 p = os.path.join(self.repo.path, name)
806 try:
807 with open(p, 'rb') as f:
827 abspath = os.path.join(self.repo.path, self.tree_path, name)
828 try:
829 with open(abspath, 'rb') as f:
808830 current_blob = Blob.from_string(f.read())
809831 except IOError:
810832 raise NoSuchItem(name)
813835 self.extra_file_handlers)
814836 message = 'Delete ' + fi.describe(name)
815837 if etag is not None:
816 with open(p, 'rb') as f:
838 with open(abspath, 'rb') as f:
817839 current_etag = current_blob.id
818840 if etag.encode('ascii') != current_etag:
819841 raise InvalidETag(name, etag, current_etag.decode('ascii'))
820 os.unlink(p)
821 self.repo.stage(name)
842 os.unlink(abspath)
843 self.repo.stage(posixpath.join(self.tree_path, name))
822844 self._commit_tree(message.encode(DEFAULT_ENCODING), author=author)
823845
824846 def get_ctag(self):
838860 yield (name, mode, sha)
839861 else:
840862 index = self.repo.open_index()
841 for (name, sha, mode) in index.iterblobs():
842 name = name.decode(DEFAULT_ENCODING)
843 yield (name, mode, sha)
863 for (path, sha, mode) in index.iterblobs():
864 (tree_path, basename) = posixpath.split(path)
865 if tree_path != self.tree_path.encode(DEFAULT_ENCODING):
866 continue
867 basename = basename.decode(DEFAULT_ENCODING)
868 yield (basename, mode, sha)
844869
845870 def subdirectories(self):
846871 """Returns subdirectories to probe for other stores.