codekingpro/portable-devtools
115k
1import os2 3import pygit24 5from fsspec.spec import AbstractFileSystem6 7from .memory import MemoryFile8 9 10class GitFileSystem(AbstractFileSystem):11 """Browse the files of a local git repo at any hash/tag/branch12 13 (experimental backend)14 """15 16 root_marker = ""17 cachable = True18 19 def __init__(self, path=None, fo=None, ref=None, **kwargs):20 """21 22 Parameters23 ----------24 path: str (optional)25 Local location of the repo (uses current directory if not given).26 May be deprecated in favour of ``fo``. When used with a higher27 level function such as fsspec.open(), may be of the form28 "git://[path-to-repo[:]][ref@]path/to/file" (but the actual29 file path should not contain "@" or ":").30 fo: str (optional)31 Same as ``path``, but passed as part of a chained URL. This one32 takes precedence if both are given.33 ref: str (optional)34 Reference to work with, could be a hash, tag or branch name. Defaults35 to current working tree. Note that ``ls`` and ``open`` also take hash,36 so this becomes the default for those operations37 kwargs38 """39 super().__init__(**kwargs)40 self.repo = pygit2.Repository(fo or path or os.getcwd())41 self.ref = ref or "master"42 43 @classmethod44 def _strip_protocol(cls, path):45 path = super()._strip_protocol(path).lstrip("/")46 if ":" in path:47 path = path.split(":", 1)[1]48 if "@" in path:49 path = path.split("@", 1)[1]50 return path.lstrip("/")51 52 def _path_to_object(self, path, ref):53 comm, ref = self.repo.resolve_refish(ref or self.ref)54 parts = path.split("/")55 tree = comm.tree56 for part in parts:57 if part and isinstance(tree, pygit2.Tree):58 if part not in tree:59 raise FileNotFoundError(path)60 tree = tree[part]61 return tree62 63 @staticmethod64 def _get_kwargs_from_urls(path):65 path = path.removeprefix("git://")66 out = {}67 if ":" in path:68 out["path"], path = path.split(":", 1)69 if "@" in path:70 out["ref"], path = path.split("@", 1)71 return out72 73 @staticmethod74 def _object_to_info(obj, path=None):75 # obj.name and obj.filemode are None for the root tree!76 is_dir = isinstance(obj, pygit2.Tree)77 return {78 "type": "directory" if is_dir else "file",79 "name": (80 "/".join([path, obj.name or ""]).lstrip("/") if path else obj.name81 ),82 "hex": str(obj.id),83 "mode": "100644" if obj.filemode is None else f"{obj.filemode:o}",84 "size": 0 if is_dir else obj.size,85 }86 87 def ls(self, path, detail=True, ref=None, **kwargs):88 tree = self._path_to_object(self._strip_protocol(path), ref)89 return [90 GitFileSystem._object_to_info(obj, path)91 if detail92 else GitFileSystem._object_to_info(obj, path)["name"]93 for obj in (tree if isinstance(tree, pygit2.Tree) else [tree])94 ]95 96 def info(self, path, ref=None, **kwargs):97 tree = self._path_to_object(self._strip_protocol(path), ref)98 return GitFileSystem._object_to_info(tree, path)99 100 def ukey(self, path, ref=None):101 return self.info(path, ref=ref)["hex"]102 103 def _open(104 self,105 path,106 mode="rb",107 block_size=None,108 autocommit=True,109 cache_options=None,110 ref=None,111 **kwargs,112 ):113 obj = self._path_to_object(path, ref or self.ref)114 return MemoryFile(data=obj.data)115 