diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2021-06-26 10:09:53 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-26 10:09:53 +0800 |
commit | 2d2ff037f9f7a9ae33e5f4f6bdb75b669a1af19a (patch) | |
tree | 5f4fd00ad13fa5455dc876ab9cb9cc4f9b66bdfc /git/objects | |
parent | 703280b8c3df6f9b1a5cbe0997b717edbcaa8979 (diff) | |
parent | 5d7b8ba9f2e9298496232e4ae66bd904a1d71001 (diff) | |
download | gitpython-2d2ff037f9f7a9ae33e5f4f6bdb75b669a1af19a.tar.gz |
Merge pull request #1279 from Yobmod/main
Finish typing object, improve verious other types.
Diffstat (limited to 'git/objects')
-rw-r--r-- | git/objects/commit.py | 4 | ||||
-rw-r--r-- | git/objects/fun.py | 15 | ||||
-rw-r--r-- | git/objects/submodule/base.py | 21 | ||||
-rw-r--r-- | git/objects/submodule/util.py | 9 | ||||
-rw-r--r-- | git/objects/tree.py | 91 | ||||
-rw-r--r-- | git/objects/util.py | 31 |
6 files changed, 90 insertions, 81 deletions
diff --git a/git/objects/commit.py b/git/objects/commit.py index 26db6e36..0b707450 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -8,7 +8,7 @@ from gitdb import IStream from git.util import ( hex_to_bin, Actor, - Iterable, + IterableObj, Stats, finalize_process ) @@ -47,7 +47,7 @@ log.addHandler(logging.NullHandler()) __all__ = ('Commit', ) -class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): +class Commit(base.Object, IterableObj, Diffable, Traversable, Serializable): """Wraps a git Commit object. diff --git a/git/objects/fun.py b/git/objects/fun.py index 9b36712e..339a53b8 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,10 +1,19 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR + from git.compat import ( safe_decode, defenc ) +# typing ---------------------------------------------- + +from typing import List, Tuple + + +# --------------------------------------------------- + + __all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive', 'traverse_tree_recursive') @@ -38,7 +47,7 @@ def tree_to_stream(entries, write): # END for each item -def tree_entries_from_data(data): +def tree_entries_from_data(data: bytes) -> List[Tuple[bytes, int, str]]: """Reads the binary representation of a tree and returns tuples of Tree items :param data: data block with tree data (as bytes) :return: list(tuple(binsha, mode, tree_relative_path), ...)""" @@ -72,8 +81,8 @@ def tree_entries_from_data(data): # default encoding for strings in git is utf8 # Only use the respective unicode object if the byte stream was encoded - name = data[ns:i] - name = safe_decode(name) + name_bytes = data[ns:i] + name = safe_decode(name_bytes) # byte is NULL, get next 20 i += 1 diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 8cf4dd1e..cbf6cd0d 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -3,7 +3,6 @@ from io import BytesIO import logging import os import stat -from typing import List from unittest import SkipTest import uuid @@ -27,12 +26,13 @@ from git.exc import ( from git.objects.base import IndexObject, Object from git.objects.util import Traversable from git.util import ( - Iterable, + IterableObj, join_path_native, to_native_path_linux, RemoteProgress, rmtree, - unbare_repo + unbare_repo, + IterableList ) from git.util import HIDE_WINDOWS_KNOWN_ERRORS @@ -47,6 +47,11 @@ from .util import ( ) +# typing ---------------------------------------------------------------------- + + +# ----------------------------------------------------------------------------- + __all__ = ["Submodule", "UpdateProgress"] @@ -74,7 +79,7 @@ UPDWKTREE = UpdateProgress.UPDWKTREE # IndexObject comes via util module, its a 'hacky' fix thanks to pythons import # mechanism which cause plenty of trouble of the only reason for packages and # modules is refactoring - subpackages shouldn't depend on parent packages -class Submodule(IndexObject, Iterable, Traversable): +class Submodule(IndexObject, IterableObj, Traversable): """Implements access to a git submodule. They are special in that their sha represents a commit in the submodule's repository which is to be checked out @@ -136,12 +141,12 @@ class Submodule(IndexObject, Iterable, Traversable): # END handle attribute name @classmethod - def _get_intermediate_items(cls, item: 'Submodule') -> List['Submodule']: # type: ignore + def _get_intermediate_items(cls, item: 'Submodule') -> IterableList['Submodule']: """:return: all the submodules of our module repository""" try: return cls.list_items(item.module()) except InvalidGitRepositoryError: - return [] + return IterableList('') # END handle intermediate items @classmethod @@ -1153,7 +1158,7 @@ class Submodule(IndexObject, Iterable, Traversable): """ return self._name - def config_reader(self): + def config_reader(self) -> SectionConstraint: """ :return: ConfigReader instance which allows you to qurey the configuration values of this submodule, as provided by the .gitmodules file @@ -1163,7 +1168,7 @@ class Submodule(IndexObject, Iterable, Traversable): :raise IOError: If the .gitmodules file/blob could not be read""" return self._config_parser_constrained(read_only=True) - def children(self): + def children(self) -> IterableList['Submodule']: """ :return: IterableList(Submodule, ...) an iterable list of submodules instances which are children of this submodule or 0 if the submodule is not checked out""" diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 0b4ce3c5..b4796b30 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -4,6 +4,11 @@ from git.config import GitConfigParser from io import BytesIO import weakref +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .base import Submodule + __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', 'SubmoduleConfigParser') @@ -60,12 +65,12 @@ class SubmoduleConfigParser(GitConfigParser): super(SubmoduleConfigParser, self).__init__(*args, **kwargs) #{ Interface - def set_submodule(self, submodule): + def set_submodule(self, submodule: 'Submodule') -> None: """Set this instance's submodule. It must be called before the first write operation begins""" self._smref = weakref.ref(submodule) - def flush_to_index(self): + def flush_to_index(self) -> None: """Flush changes in our configuration file to the index""" assert self._smref is not None # should always have a file here diff --git a/git/objects/tree.py b/git/objects/tree.py index 29b2a684..191fe27c 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -20,21 +20,27 @@ from .fun import ( # typing ------------------------------------------------- -from typing import Iterable, Iterator, Tuple, Union, cast, TYPE_CHECKING +from typing import Callable, Dict, Generic, Iterable, Iterator, List, Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING + +from git.types import PathLike if TYPE_CHECKING: + from git.repo import Repo from io import BytesIO #-------------------------------------------------------- -cmp = lambda a, b: (a > b) - (a < b) +cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b) __all__ = ("TreeModifier", "Tree") +T_Tree_cache = TypeVar('T_Tree_cache', bound=Union[Tuple[bytes, int, str]]) + -def git_cmp(t1, t2): +def git_cmp(t1: T_Tree_cache, t2: T_Tree_cache) -> int: a, b = t1[2], t2[2] + assert isinstance(a, str) and isinstance(b, str) # Need as mypy 9.0 cannot unpack TypeVar properly len_a, len_b = len(a), len(b) min_len = min(len_a, len_b) min_cmp = cmp(a[:min_len], b[:min_len]) @@ -45,9 +51,10 @@ def git_cmp(t1, t2): return len_a - len_b -def merge_sort(a, cmp): +def merge_sort(a: List[T_Tree_cache], + cmp: Callable[[T_Tree_cache, T_Tree_cache], int]) -> None: if len(a) < 2: - return + return None mid = len(a) // 2 lefthalf = a[:mid] @@ -80,7 +87,7 @@ def merge_sort(a, cmp): k = k + 1 -class TreeModifier(object): +class TreeModifier(Generic[T_Tree_cache], object): """A utility class providing methods to alter the underlying cache in a list-like fashion. @@ -88,10 +95,10 @@ class TreeModifier(object): the cache of a tree, will be sorted. Assuring it will be in a serializable state""" __slots__ = '_cache' - def __init__(self, cache): + def __init__(self, cache: List[T_Tree_cache]) -> None: self._cache = cache - def _index_by_name(self, name): + def _index_by_name(self, name: str) -> int: """:return: index of an item with name, or -1 if not found""" for i, t in enumerate(self._cache): if t[2] == name: @@ -101,7 +108,7 @@ class TreeModifier(object): return -1 #{ Interface - def set_done(self): + def set_done(self) -> 'TreeModifier': """Call this method once you are done modifying the tree information. It may be called several times, but be aware that each call will cause a sort operation @@ -111,7 +118,7 @@ class TreeModifier(object): #} END interface #{ Mutators - def add(self, sha, mode, name, force=False): + def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> 'TreeModifier': """Add the given item to the tree. If an item with the given name already exists, nothing will be done, but a ValueError will be raised if the sha and mode of the existing item do not match the one you add, unless @@ -129,7 +136,9 @@ class TreeModifier(object): sha = to_bin_sha(sha) index = self._index_by_name(name) - item = (sha, mode, name) + + assert isinstance(sha, bytes) and isinstance(mode, int) and isinstance(name, str) + item = cast(T_Tree_cache, (sha, mode, name)) # use Typeguard from typing-extensions 3.10.0 if index == -1: self._cache.append(item) else: @@ -144,14 +153,17 @@ class TreeModifier(object): # END handle name exists return self - def add_unchecked(self, binsha, mode, name): + def add_unchecked(self, binsha: bytes, mode: int, name: str) -> None: """Add the given item to the tree, its correctness is assumed, which puts the caller into responsibility to assure the input is correct. For more information on the parameters, see ``add`` :param binsha: 20 byte binary sha""" - self._cache.append((binsha, mode, name)) + assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str) + tree_cache = cast(T_Tree_cache, (binsha, mode, name)) - def __delitem__(self, name): + self._cache.append(tree_cache) + + def __delitem__(self, name: str) -> None: """Deletes an item with the given name if it exists""" index = self._index_by_name(name) if index > -1: @@ -182,29 +194,29 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): symlink_id = 0o12 tree_id = 0o04 - _map_id_to_type = { + _map_id_to_type: Dict[int, Union[Type[Submodule], Type[Blob], Type['Tree']]] = { commit_id: Submodule, blob_id: Blob, symlink_id: Blob # tree id added once Tree is defined } - def __init__(self, repo, binsha, mode=tree_id << 12, path=None): + def __init__(self, repo: 'Repo', binsha: bytes, mode: int = tree_id << 12, path: Union[PathLike, None] = None): super(Tree, self).__init__(repo, binsha, mode, path) - @classmethod + @ classmethod def _get_intermediate_items(cls, index_object: 'Tree', # type: ignore - ) -> Tuple['Tree', ...]: + ) -> Union[Tuple['Tree', ...], Tuple[()]]: if index_object.type == "tree": index_object = cast('Tree', index_object) return tuple(index_object._iter_convert_to_object(index_object._cache)) return () - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: if attr == "_cache": # Set the data when we need it ostream = self.repo.odb.stream(self.binsha) - self._cache = tree_entries_from_data(ostream.read()) + self._cache: List[Tuple[bytes, int, str]] = tree_entries_from_data(ostream.read()) else: super(Tree, self)._set_cache_(attr) # END handle attribute @@ -221,7 +233,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e # END for each item - def join(self, file): + def join(self, file: str) -> Union[Blob, 'Tree', Submodule]: """Find the named object in this tree's contents :return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule`` @@ -254,26 +266,22 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise KeyError(msg % file) # END handle long paths - def __div__(self, file): - """For PY2 only""" - return self.join(file) - - def __truediv__(self, file): + def __truediv__(self, file: str) -> Union['Tree', Blob, Submodule]: """For PY3 only""" return self.join(file) - @property - def trees(self): + @ property + def trees(self) -> List['Tree']: """:return: list(Tree, ...) list of trees directly below this tree""" return [i for i in self if i.type == "tree"] - @property - def blobs(self): + @ property + def blobs(self) -> List['Blob']: """:return: list(Blob, ...) list of blobs directly below this tree""" return [i for i in self if i.type == "blob"] - @property - def cache(self): + @ property + def cache(self) -> TreeModifier: """ :return: An object allowing to modify the internal cache. This can be used to change the tree's contents. When done, make sure you call ``set_done`` @@ -289,16 +297,16 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): return super(Tree, self).traverse(predicate, prune, depth, branch_first, visit_once, ignore_self) # List protocol - def __getslice__(self, i, j): + def __getslice__(self, i: int, j: int) -> List[Union[Blob, 'Tree', Submodule]]: return list(self._iter_convert_to_object(self._cache[i:j])) - def __iter__(self): + def __iter__(self) -> Iterator[Union[Blob, 'Tree', Submodule]]: return self._iter_convert_to_object(self._cache) - def __len__(self): + def __len__(self) -> int: return len(self._cache) - def __getitem__(self, item): + def __getitem__(self, item: Union[str, int, slice]) -> Union[Blob, 'Tree', Submodule]: if isinstance(item, int): info = self._cache[item] return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2])) @@ -310,7 +318,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise TypeError("Invalid index type: %r" % item) - def __contains__(self, item): + def __contains__(self, item: Union[IndexObject, PathLike]) -> bool: if isinstance(item, IndexObject): for info in self._cache: if item.binsha == info[0]: @@ -321,10 +329,11 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): # compatibility # treat item as repo-relative path - path = self.path - for info in self._cache: - if item == join_path(path, info[2]): - return True + else: + path = self.path + for info in self._cache: + if item == join_path(path, info[2]): + return True # END for each item return False diff --git a/git/objects/util.py b/git/objects/util.py index 087f0166..8b8148a9 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,16 +19,18 @@ import calendar from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import (Any, Callable, Deque, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload) +from typing import (Any, Callable, Deque, Iterator, TypeVar, TYPE_CHECKING, Tuple, Type, Union, cast) if TYPE_CHECKING: from io import BytesIO, StringIO - from .submodule.base import Submodule + from .submodule.base import Submodule # noqa: F401 from .commit import Commit from .blob import Blob from .tag import TagObject from .tree import Tree from subprocess import Popen + +T_Iterableobj = TypeVar('T_Iterableobj') # -------------------------------------------------------------------- @@ -284,29 +286,8 @@ class Traversable(object): """ __slots__ = () - @overload @classmethod - def _get_intermediate_items(cls, item: 'Commit') -> Tuple['Commit', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Submodule') -> Tuple['Submodule', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Tree') -> Tuple['Tree', ...]: - ... - - @overload - @classmethod - def _get_intermediate_items(cls, item: 'Traversable') -> Tuple['Traversable', ...]: - ... - - @classmethod - def _get_intermediate_items(cls, item: 'Traversable' - ) -> Sequence['Traversable']: + def _get_intermediate_items(cls, item): """ Returns: Tuple of items connected to the given item. @@ -322,7 +303,7 @@ class Traversable(object): """ :return: IterableList with the results of the traversal as produced by traverse()""" - out = IterableList(self._id_attribute_) # type: ignore[attr-defined] # defined in sublcasses + out: IterableList = IterableList(self._id_attribute_) # type: ignore[attr-defined] # defined in sublcasses out.extend(self.traverse(*args, **kwargs)) return out |