| |
| |
|
|
| """Functions that are supposed to be as fast as possible.""" |
|
|
| __all__ = [ |
| "tree_to_stream", |
| "tree_entries_from_data", |
| "traverse_trees_recursive", |
| "traverse_tree_recursive", |
| ] |
|
|
| from stat import S_ISDIR |
|
|
| from git.compat import safe_decode, defenc |
|
|
| |
|
|
| from typing import ( |
| Callable, |
| List, |
| MutableSequence, |
| Sequence, |
| Tuple, |
| TYPE_CHECKING, |
| Union, |
| overload, |
| ) |
|
|
| if TYPE_CHECKING: |
| from _typeshed import ReadableBuffer |
|
|
| from git import GitCmdObjectDB |
|
|
| EntryTup = Tuple[bytes, int, str] |
| EntryTupOrNone = Union[EntryTup, None] |
|
|
| |
|
|
|
|
| def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer"], Union[int, None]]) -> None: |
| """Write the given list of entries into a stream using its ``write`` method. |
| |
| :param entries: |
| **Sorted** list of tuples with (binsha, mode, name). |
| |
| :param write: |
| A ``write`` method which takes a data string. |
| """ |
| ord_zero = ord("0") |
| bit_mask = 7 |
|
|
| for binsha, mode, name in entries: |
| mode_str = b"" |
| for i in range(6): |
| mode_str = bytes([((mode >> (i * 3)) & bit_mask) + ord_zero]) + mode_str |
| |
|
|
| |
| if mode_str[0] == ord_zero: |
| mode_str = mode_str[1:] |
| |
|
|
| |
| |
| |
| |
| |
| if isinstance(name, str): |
| name_bytes = name.encode(defenc) |
| else: |
| name_bytes = name |
| write(b"".join((mode_str, b" ", name_bytes, b"\0", binsha))) |
| |
|
|
|
|
| def tree_entries_from_data(data: bytes) -> List[EntryTup]: |
| """Read the binary representation of a tree and returns tuples of |
| :class:`~git.objects.tree.Tree` items. |
| |
| :param data: |
| Data block with tree data (as bytes). |
| |
| :return: |
| list(tuple(binsha, mode, tree_relative_path), ...) |
| """ |
| ord_zero = ord("0") |
| space_ord = ord(" ") |
| len_data = len(data) |
| i = 0 |
| out = [] |
| while i < len_data: |
| mode = 0 |
|
|
| |
| |
| |
| while data[i] != space_ord: |
| |
| |
| mode = (mode << 3) + (data[i] - ord_zero) |
| i += 1 |
| |
|
|
| |
| i += 1 |
|
|
| |
|
|
| ns = i |
| while data[i] != 0: |
| i += 1 |
| |
|
|
| |
| |
| name_bytes = data[ns:i] |
| name = safe_decode(name_bytes) |
|
|
| |
| i += 1 |
| sha = data[i : i + 20] |
| i = i + 20 |
| out.append((sha, mode, name)) |
| |
| return out |
|
|
|
|
| def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int) -> EntryTupOrNone: |
| """Return data entry matching the given name and tree mode or ``None``. |
| |
| Before the item is returned, the respective data item is set None in the `tree_data` |
| list to mark it done. |
| """ |
|
|
| try: |
| item = tree_data[start_at] |
| if item and item[2] == name and S_ISDIR(item[1]) == is_dir: |
| tree_data[start_at] = None |
| return item |
| except IndexError: |
| pass |
| |
| for index, item in enumerate(tree_data): |
| if item and item[2] == name and S_ISDIR(item[1]) == is_dir: |
| tree_data[index] = None |
| return item |
| |
| |
| return None |
|
|
|
|
| @overload |
| def _to_full_path(item: None, path_prefix: str) -> None: ... |
|
|
|
|
| @overload |
| def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup: ... |
|
|
|
|
| def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: |
| """Rebuild entry with given path prefix.""" |
| if not item: |
| return item |
| return (item[0], item[1], path_prefix + item[2]) |
|
|
|
|
| def traverse_trees_recursive( |
| odb: "GitCmdObjectDB", tree_shas: Sequence[Union[bytes, None]], path_prefix: str |
| ) -> List[Tuple[EntryTupOrNone, ...]]: |
| """ |
| :return: |
| List of list with entries according to the given binary tree-shas. |
| |
| The result is encoded in a list |
| of n tuple|None per blob/commit, (n == len(tree_shas)), where: |
| |
| * [0] == 20 byte sha |
| * [1] == mode as int |
| * [2] == path relative to working tree root |
| |
| The entry tuple is ``None`` if the respective blob/commit did not exist in the |
| given tree. |
| |
| :param tree_shas: |
| Iterable of shas pointing to trees. All trees must be on the same level. |
| A tree-sha may be ``None``, in which case ``None``. |
| |
| :param path_prefix: |
| A prefix to be added to the returned paths on this level. |
| Set it ``""`` for the first iteration. |
| |
| :note: |
| The ordering of the returned items will be partially lost. |
| """ |
| trees_data: List[List[EntryTupOrNone]] = [] |
|
|
| nt = len(tree_shas) |
| for tree_sha in tree_shas: |
| if tree_sha is None: |
| data: List[EntryTupOrNone] = [] |
| else: |
| |
| data = list(tree_entries_from_data(odb.stream(tree_sha).read())) |
| |
| trees_data.append(data) |
| |
|
|
| out: List[Tuple[EntryTupOrNone, ...]] = [] |
|
|
| |
| |
| |
| for ti, tree_data in enumerate(trees_data): |
| for ii, item in enumerate(tree_data): |
| if not item: |
| continue |
| |
| entries: List[EntryTupOrNone] |
| entries = [None for _ in range(nt)] |
| entries[ti] = item |
| _sha, mode, name = item |
| is_dir = S_ISDIR(mode) |
|
|
| |
| |
| |
| for tio in range(ti + 1, ti + nt): |
| tio = tio % nt |
| entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii) |
|
|
| |
| |
| if is_dir: |
| out.extend( |
| traverse_trees_recursive( |
| odb, |
| [((ei and ei[0]) or None) for ei in entries], |
| path_prefix + name + "/", |
| ) |
| ) |
| else: |
| out.append(tuple(_to_full_path(e, path_prefix) for e in entries)) |
|
|
| |
| |
| tree_data[ii] = None |
| |
|
|
| |
| del tree_data[:] |
| |
| return out |
|
|
|
|
| def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str) -> List[EntryTup]: |
| """ |
| :return: |
| List of entries of the tree pointed to by the binary `tree_sha`. |
| |
| An entry has the following format: |
| |
| * [0] 20 byte sha |
| * [1] mode as int |
| * [2] path relative to the repository |
| |
| :param path_prefix: |
| Prefix to prepend to the front of all returned paths. |
| """ |
| entries = [] |
| data = tree_entries_from_data(odb.stream(tree_sha).read()) |
|
|
| |
| for sha, mode, name in data: |
| if S_ISDIR(mode): |
| entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + "/")) |
| else: |
| entries.append((sha, mode, path_prefix + name)) |
| |
|
|
| return entries |
|
|