Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/objects/tree.py: 38%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
2#
3# This module is part of GitPython and is released under the
4# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
6__all__ = ["TreeModifier", "Tree"]
8import os
10import git.diff as git_diff
11from git.util import IterableList, join_path, to_bin_sha
13from . import util
14from .base import IndexObjUnion, IndexObject
15from .blob import Blob
16from .fun import tree_entries_from_data, tree_to_stream, _validate_tree_entry_name
17from .submodule.base import Submodule
19# typing -------------------------------------------------
21from typing import (
22 Any,
23 Callable,
24 Dict,
25 Iterable,
26 Iterator,
27 List,
28 Literal,
29 Tuple,
30 TYPE_CHECKING,
31 Type,
32 Union,
33 cast,
34)
36from git.types import PathLike
38if TYPE_CHECKING:
39 from io import BytesIO
41 from git.repo import Repo
43TreeCacheTup = Tuple[bytes, int, str]
45TraversedTreeTup = Union[Tuple[Union["Tree", None], IndexObjUnion, Tuple["Submodule", "Submodule"]]]
47# --------------------------------------------------------
50def cmp(a: str, b: str) -> int:
51 return (a > b) - (a < b)
54class TreeModifier:
55 """A utility class providing methods to alter the underlying cache in a list-like
56 fashion.
58 Once all adjustments are complete, the :attr:`_cache`, which really is a reference
59 to the cache of a tree, will be sorted. This ensures it will be in a serializable
60 state.
61 """
63 __slots__ = ("_cache",)
65 def __init__(self, cache: List[TreeCacheTup]) -> None:
66 self._cache = cache
68 def _index_by_name(self, name: str) -> int:
69 """:return: index of an item with name, or -1 if not found"""
70 for i, t in enumerate(self._cache):
71 if t[2] == name:
72 return i
73 # END found item
74 # END for each item in cache
75 return -1
77 # { Interface
78 def set_done(self) -> "TreeModifier":
79 """Call this method once you are done modifying the tree information.
81 This may be called several times, but be aware that each call will cause a sort
82 operation.
84 :return:
85 self
86 """
87 self._cache.sort(key=lambda x: (x[2] + "/") if x[1] == Tree.tree_id << 12 else x[2])
88 return self
90 # } END interface
92 # { Mutators
93 def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> "TreeModifier":
94 """Add the given item to the tree.
96 If an item with the given name already exists, nothing will be done, but a
97 :exc:`ValueError` will be raised if the sha and mode of the existing item do not
98 match the one you add, unless `force` is ``True``.
100 :param sha:
101 The 20 or 40 byte sha of the item to add.
103 :param mode:
104 :class:`int` representing the stat-compatible mode of the item.
106 :param force:
107 If ``True``, an item with your name and information will overwrite any
108 existing item with the same name, no matter which information it has.
110 :return:
111 self
112 """
113 _validate_tree_entry_name(name, mode)
114 if (mode >> 12) not in Tree._map_id_to_type:
115 raise ValueError("Invalid object type according to mode %o" % mode)
117 sha = to_bin_sha(sha)
118 index = self._index_by_name(name)
120 item = (sha, mode, name)
122 if index == -1:
123 self._cache.append(item)
124 else:
125 if force:
126 self._cache[index] = item
127 else:
128 ex_item = self._cache[index]
129 if ex_item[0] != sha or ex_item[1] != mode:
130 raise ValueError("Item %r existed with different properties" % name)
131 # END handle mismatch
132 # END handle force
133 # END handle name exists
134 return self
136 def add_unchecked(self, binsha: bytes, mode: int, name: str) -> None:
137 """Add the given item to the tree. Its correctness is assumed, so it is the
138 caller's responsibility to ensure that the input is correct.
140 For more information on the parameters, see :meth:`add`.
142 :param binsha:
143 20 byte binary sha.
144 """
145 assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str)
146 tree_cache = (binsha, mode, name)
148 self._cache.append(tree_cache)
150 def __delitem__(self, name: str) -> None:
151 """Delete an item with the given name if it exists."""
152 index = self._index_by_name(name)
153 if index > -1:
154 del self._cache[index]
156 # } END mutators
159class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
160 R"""Tree objects represent an ordered list of :class:`~git.objects.blob.Blob`\s and
161 other :class:`Tree`\s.
163 See :manpage:`gitglossary(7)` on "tree object":
164 https://git-scm.com/docs/gitglossary#def_tree_object
166 Subscripting is supported, as with a list or dict:
168 * Access a specific blob using the ``tree["filename"]`` notation.
169 * You may likewise access by index, like ``blob = tree[0]``.
170 """
172 type: Literal["tree"] = "tree"
174 __slots__ = ("_cache",)
176 # Actual integer IDs for comparison.
177 commit_id = 0o16 # Equals stat.S_IFDIR | stat.S_IFLNK - a directory link.
178 blob_id = 0o10
179 symlink_id = 0o12
180 tree_id = 0o04
182 _map_id_to_type: Dict[int, Type[IndexObjUnion]] = {
183 commit_id: Submodule,
184 blob_id: Blob,
185 symlink_id: Blob,
186 # Tree ID added once Tree is defined.
187 }
189 def __init__(
190 self,
191 repo: "Repo",
192 binsha: bytes,
193 mode: int = tree_id << 12,
194 path: Union[PathLike, None] = None,
195 ):
196 super().__init__(repo, binsha, mode, path)
198 @classmethod
199 def _get_intermediate_items(
200 cls,
201 index_object: IndexObjUnion,
202 ) -> Union[Tuple["Tree", ...], Tuple[()]]:
203 if index_object.type == "tree":
204 return tuple(index_object._iter_convert_to_object(index_object._cache))
205 return ()
207 def _set_cache_(self, attr: str) -> None:
208 if attr == "_cache":
209 # Set the data when we need it.
210 ostream = self.repo.odb.stream(self.binsha)
211 self._cache: List[TreeCacheTup] = tree_entries_from_data(ostream.read())
212 else:
213 super()._set_cache_(attr)
214 # END handle attribute
216 def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup]) -> Iterator[IndexObjUnion]:
217 """Iterable yields tuples of (binsha, mode, name), which will be converted to
218 the respective object representation.
219 """
220 for binsha, mode, name in iterable:
221 path = join_path(self.path, name)
222 try:
223 yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
224 except KeyError as e:
225 raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e
226 # END for each item
228 def join(self, file: PathLike) -> IndexObjUnion:
229 """Find the named object in this tree's contents.
231 :return:
232 :class:`~git.objects.blob.Blob`, :class:`Tree`, or
233 :class:`~git.objects.submodule.base.Submodule`
235 :raise KeyError:
236 If the given file or tree does not exist in this tree.
237 """
238 msg = "Blob or Tree named %r not found"
239 file = os.fspath(file)
240 if "/" in file:
241 tree = self
242 item = self
243 tokens = file.split("/")
244 for i, token in enumerate(tokens):
245 item = tree[token]
246 if item.type == "tree":
247 tree = item
248 else:
249 # Safety assertion - blobs are at the end of the path.
250 if i != len(tokens) - 1:
251 raise KeyError(msg % file)
252 return item
253 # END handle item type
254 # END for each token of split path
255 if item == self:
256 raise KeyError(msg % file)
257 return item
258 else:
259 for info in self._cache:
260 if info[2] == file: # [2] == name
261 return self._map_id_to_type[info[1] >> 12](
262 self.repo, info[0], info[1], join_path(self.path, info[2])
263 )
264 # END for each obj
265 raise KeyError(msg % file)
266 # END handle long paths
268 def __truediv__(self, file: PathLike) -> IndexObjUnion:
269 """The ``/`` operator is another syntax for joining.
271 See :meth:`join` for details.
272 """
273 return self.join(file)
275 @property
276 def trees(self) -> List["Tree"]:
277 """:return: list(Tree, ...) List of trees directly below this tree"""
278 return [i for i in self if i.type == "tree"]
280 @property
281 def blobs(self) -> List[Blob]:
282 """:return: list(Blob, ...) List of blobs directly below this tree"""
283 return [i for i in self if i.type == "blob"]
285 @property
286 def cache(self) -> TreeModifier:
287 """
288 :return:
289 An object allowing modification of the internal cache. This can be used to
290 change the tree's contents. When done, make sure you call
291 :meth:`~TreeModifier.set_done` on the tree modifier, or serialization
292 behaviour will be incorrect.
294 :note:
295 See :class:`TreeModifier` for more information on how to alter the cache.
296 """
297 return TreeModifier(self._cache)
299 def traverse(
300 self,
301 predicate: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: True,
302 prune: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: False,
303 depth: int = -1,
304 branch_first: bool = True,
305 visit_once: bool = False,
306 ignore_self: int = 1,
307 as_edge: bool = False,
308 ) -> Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]]:
309 """For documentation, see
310 `Traversable._traverse() <git.objects.util.Traversable._traverse>`.
312 Trees are set to ``visit_once = False`` to gain more performance in the
313 traversal.
314 """
316 # # To typecheck instead of using cast.
317 # import itertools
318 # def is_tree_traversed(inp: Tuple) -> TypeGuard[Tuple[Iterator[Union['Tree', 'Blob', 'Submodule']]]]:
319 # return all(isinstance(x, (Blob, Tree, Submodule)) for x in inp[1])
321 # ret = super().traverse(predicate, prune, depth, branch_first, visit_once, ignore_self)
322 # ret_tup = itertools.tee(ret, 2)
323 # assert is_tree_traversed(ret_tup), f"Type is {[type(x) for x in list(ret_tup[0])]}"
324 # return ret_tup[0]
326 return cast(
327 Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]],
328 super()._traverse(
329 predicate, # type: ignore[arg-type]
330 prune, # type: ignore[arg-type]
331 depth,
332 branch_first,
333 visit_once,
334 ignore_self,
335 ),
336 )
338 def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]:
339 """
340 :return:
341 :class:`~git.util.IterableList` with the results of the traversal as
342 produced by :meth:`traverse`
344 Tree -> IterableList[Union[Submodule, Tree, Blob]]
345 """
346 return super()._list_traverse(*args, **kwargs)
348 # List protocol
350 def __getslice__(self, i: int, j: int) -> List[IndexObjUnion]:
351 return list(self._iter_convert_to_object(self._cache[i:j]))
353 def __iter__(self) -> Iterator[IndexObjUnion]:
354 return self._iter_convert_to_object(self._cache)
356 def __len__(self) -> int:
357 return len(self._cache)
359 def __getitem__(self, item: Union[str, int, slice]) -> IndexObjUnion:
360 if isinstance(item, int):
361 info = self._cache[item]
362 return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
364 if isinstance(item, str):
365 # compatibility
366 return self.join(item)
367 # END index is basestring
369 raise TypeError("Invalid index type: %r" % item)
371 def __contains__(self, item: Union[IndexObjUnion, PathLike]) -> bool:
372 if isinstance(item, IndexObject):
373 for info in self._cache:
374 if item.binsha == info[0]:
375 return True
376 # END compare sha
377 # END for each entry
378 # END handle item is index object
379 # compatibility
381 # Treat item as repo-relative path.
382 else:
383 path = self.path
384 for info in self._cache:
385 if item == join_path(path, info[2]):
386 return True
387 # END for each item
388 return False
390 def __reversed__(self) -> Iterator[IndexObjUnion]:
391 return reversed(self._iter_convert_to_object(self._cache)) # type: ignore[call-overload]
393 def _serialize(self, stream: "BytesIO") -> "Tree":
394 """Serialize this tree into the stream. Assumes sorted tree data.
396 :note:
397 We will assume our tree data to be in a sorted state. If this is not the
398 case, serialization will not generate a correct tree representation as these
399 are assumed to be sorted by algorithms.
400 """
401 tree_to_stream(self._cache, stream.write)
402 return self
404 def _deserialize(self, stream: "BytesIO") -> "Tree":
405 self._cache = tree_entries_from_data(stream.read())
406 return self
409# END tree
411# Finalize map definition.
412Tree._map_id_to_type[Tree.tree_id] = Tree