From 16f0607ed29f20c09e89f2cacc0e28e982309d60 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 12:31:00 +0100 Subject: Improve typing of config_levels, add assert_never() --- git/config.py | 31 ++++++++++++++++--------------- git/objects/submodule/base.py | 27 +++++++++++++++------------ git/objects/submodule/util.py | 9 ++++++++- git/refs/symbolic.py | 3 ++- git/repo/base.py | 19 ++++++++++--------- git/types.py | 29 ++++++++++++++++++++++++++++- 6 files changed, 79 insertions(+), 39 deletions(-) (limited to 'git') diff --git a/git/config.py b/git/config.py index 5c5ceea8..4cb13bdf 100644 --- a/git/config.py +++ b/git/config.py @@ -31,9 +31,10 @@ import configparser as cp # typing------------------------------------------------------- -from typing import Any, Callable, IO, List, Dict, Sequence, TYPE_CHECKING, Tuple, Union, cast, overload +from typing import (Any, Callable, IO, List, Dict, Sequence, + TYPE_CHECKING, Tuple, Union, cast, overload) -from git.types import Literal, Lit_config_levels, PathLike, TBD +from git.types import Lit_config_levels, ConfigLevels_Tup, ConfigLevels_NT, PathLike, TBD, assert_never if TYPE_CHECKING: from git.repo.base import Repo @@ -48,8 +49,9 @@ log.addHandler(logging.NullHandler()) # invariants # represents the configuration level of a configuration file -CONFIG_LEVELS = ("system", "user", "global", "repository" - ) # type: Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']] + + +CONFIG_LEVELS: ConfigLevels_Tup = ConfigLevels_NT("system", "user", "global", "repository") # Section pattern to detect conditional includes. # https://git-scm.com/docs/git-config#_conditional_includes @@ -229,8 +231,9 @@ def get_config_path(config_level: Lit_config_levels) -> str: return osp.normpath(osp.expanduser("~/.gitconfig")) elif config_level == "repository": raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path") - - raise ValueError("Invalid configuration level: %r" % config_level) + else: + # Should not reach here. Will raise ValueError if does. Static typing will warn about extra and missing elifs + assert_never(config_level, ValueError("Invalid configuration level: %r" % config_level)) class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501 @@ -300,12 +303,12 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje self._proxies = self._dict() if file_or_files is not None: - self._file_or_files = file_or_files # type: Union[PathLike, IO, Sequence[Union[PathLike, IO]]] + self._file_or_files: Union[PathLike, IO, Sequence[Union[PathLike, IO]]] = file_or_files else: if config_level is None: if read_only: - self._file_or_files = [get_config_path(f) # type: ignore - for f in CONFIG_LEVELS # Can type f properly when 3.5 dropped + self._file_or_files = [get_config_path(f) + for f in CONFIG_LEVELS if f != 'repository'] else: raise ValueError("No configuration level or configuration files specified") @@ -323,15 +326,13 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje def _acquire_lock(self) -> None: if not self._read_only: if not self._lock: - if isinstance(self._file_or_files, (tuple, list)): - raise ValueError( - "Write-ConfigParsers can operate on a single file only, multiple files have been passed") - # END single file check - if isinstance(self._file_or_files, (str, os.PathLike)): file_or_files = self._file_or_files + elif isinstance(self._file_or_files, (tuple, list, Sequence)): + raise ValueError( + "Write-ConfigParsers can operate on a single file only, multiple files have been passed") else: - file_or_files = cast(IO, self._file_or_files).name + file_or_files = self._file_or_files.name # END get filename from handle/stream # initialize lock base - we want to write diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index c95b66f2..6824528d 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -7,6 +7,8 @@ import stat from unittest import SkipTest import uuid +from git import IndexFile + import git from git.cmd import Git from git.compat import ( @@ -49,7 +51,7 @@ from .util import ( # typing ---------------------------------------------------------------------- -from typing import Dict, TYPE_CHECKING +from typing import Callable, Dict, TYPE_CHECKING from typing import Any, Iterator, Union from git.types import Commit_ish, PathLike @@ -131,14 +133,14 @@ class Submodule(IndexObject, TraversableIterableObj): if url is not None: self._url = url if branch_path is not None: - assert isinstance(branch_path, str) + # assert isinstance(branch_path, str) self._branch_path = branch_path if name is not None: self._name = name def _set_cache_(self, attr: str) -> None: if attr in ('path', '_url', '_branch_path'): - reader = self.config_reader() + reader: SectionConstraint = self.config_reader() # default submodule values try: self.path = reader.get('path') @@ -807,7 +809,8 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def remove(self, module=True, force=False, configuration=True, dry_run=False): + def remove(self, module: bool = True, force: bool = False, + configuration: bool = True, dry_run: bool = False) -> 'Submodule': """Remove this submodule from the repository. This will remove our entry from the .gitmodules file and the entry in the .git / config file. @@ -861,7 +864,7 @@ class Submodule(IndexObject, TraversableIterableObj): # TODO: If we run into permission problems, we have a highly inconsistent # state. Delete the .git folders last, start with the submodules first mp = self.abspath - method = None + method: Union[None, Callable[[PathLike], None]] = None if osp.islink(mp): method = os.remove elif osp.isdir(mp): @@ -928,7 +931,7 @@ class Submodule(IndexObject, TraversableIterableObj): rmtree(git_dir) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: - raise SkipTest("FIXME: fails with: PermissionError\n %s", ex) from ex + raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex else: raise # end handle separate bare repository @@ -961,7 +964,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self - def set_parent_commit(self, commit: Union[Commit_ish, None], check=True): + def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> 'Submodule': """Set this instance to use the given commit whose tree is supposed to contain the .gitmodules blob. @@ -1009,7 +1012,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def config_writer(self, index=None, write=True): + def config_writer(self, index: Union[IndexFile, None] = None, write: bool = True) -> SectionConstraint: """:return: a config writer instance allowing you to read and write the data belonging to this submodule into the .gitmodules file. @@ -1030,7 +1033,7 @@ class Submodule(IndexObject, TraversableIterableObj): return writer @unbare_repo - def rename(self, new_name): + def rename(self, new_name: str) -> 'Submodule': """Rename this submodule :note: This method takes care of renaming the submodule in various places, such as @@ -1081,7 +1084,7 @@ class Submodule(IndexObject, TraversableIterableObj): #{ Query Interface @unbare_repo - def module(self): + def module(self) -> 'Repo': """:return: Repo instance initialized from the repository at our submodule path :raise InvalidGitRepositoryError: if a repository was not available. This could also mean that it was not yet initialized""" @@ -1098,7 +1101,7 @@ class Submodule(IndexObject, TraversableIterableObj): raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath) # END handle exceptions - def module_exists(self): + def module_exists(self) -> bool: """:return: True if our module exists and is a valid git repository. See module() method""" try: self.module() @@ -1107,7 +1110,7 @@ class Submodule(IndexObject, TraversableIterableObj): return False # END handle exception - def exists(self): + def exists(self) -> bool: """ :return: True if the submodule exists, False otherwise. Please note that a submodule may exist ( in the .gitmodules file) even though its module diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 5290000b..1db473df 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -5,11 +5,18 @@ from io import BytesIO import weakref +# typing ----------------------------------------------------------------------- + from typing import Any, TYPE_CHECKING, Union +from git.types import PathLike + if TYPE_CHECKING: from .base import Submodule from weakref import ReferenceType + from git.repo import Repo + from git.refs import Head + __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', 'SubmoduleConfigParser') @@ -28,7 +35,7 @@ def sm_name(section): return section[11:-1] -def mkhead(repo, path): +def mkhead(repo: 'Repo', path: PathLike) -> 'Head': """:return: New branch/head instance""" return git.Head(repo, git.Head.to_full_path(path)) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index ca0691d9..f0bd9316 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -1,3 +1,4 @@ +from git.types import PathLike import os from git.compat import defenc @@ -408,7 +409,7 @@ class SymbolicReference(object): return RefLog.entry_at(RefLog.path(self), index) @classmethod - def to_full_path(cls, path): + def to_full_path(cls, path) -> PathLike: """ :return: string with a full repository-relative path which can be used to initialize a Reference instance, for instance by using ``Reference.from_path``""" diff --git a/git/repo/base.py b/git/repo/base.py index d77b19c1..e60b6f6c 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -36,13 +36,15 @@ import gitdb # typing ------------------------------------------------------ -from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish +from git.types import ConfigLevels_NT, TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish from typing import (Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, Union, NamedTuple, cast, TYPE_CHECKING) -if TYPE_CHECKING: # only needed for types +from git.types import ConfigLevels_Tup + +if TYPE_CHECKING: from git.util import IterableList from git.refs.symbolic import SymbolicReference from git.objects import Tree @@ -55,12 +57,11 @@ log = logging.getLogger(__name__) __all__ = ('Repo',) -BlameEntry = NamedTuple('BlameEntry', [ - ('commit', Dict[str, TBD]), - ('linenos', range), - ('orig_path', Optional[str]), - ('orig_linenos', range)] -) +class BlameEntry(NamedTuple): + commit: Dict[str, TBD] + linenos: range + orig_path: Optional[str] + orig_linenos: range class Repo(object): @@ -95,7 +96,7 @@ class Repo(object): # invariants # represents the configuration level of a configuration file - config_level = ("system", "user", "global", "repository") # type: Tuple[Lit_config_levels, ...] + config_level: ConfigLevels_Tup = ConfigLevels_NT("system", "user", "global", "repository") # Subclass configuration # Subclasses may easily bring in their own custom types by placing a constructor or type here diff --git a/git/types.py b/git/types.py index fb63f46e..79f86f04 100644 --- a/git/types.py +++ b/git/types.py @@ -4,7 +4,8 @@ import os import sys -from typing import Dict, Union, Any, TYPE_CHECKING +from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 + NamedTuple, TYPE_CHECKING, get_args, TypeVar) # noqa: F401 if sys.version_info[:2] >= (3, 8): @@ -35,6 +36,32 @@ Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] Lit_config_levels = Literal['system', 'global', 'user', 'repository'] +T = TypeVar('T', bound=Literal['system', 'global', 'user', 'repository'], covariant=True) + + +class ConfigLevels_NT(NamedTuple): + """NamedTuple of allowed CONFIG_LEVELS""" + # works for pylance, but not mypy + system: Literal['system'] + user: Literal['user'] + global_: Literal['global'] + repository: Literal['repository'] + + +ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels, Lit_config_levels] +# Typing this as specific literals breaks for mypy + + +def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: + return inp in get_args(Lit_config_levels) + + +def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: + if exc is None: + assert False, f"An unhandled Literal ({inp}) in an if else chain was found" + else: + raise exc + class Files_TD(TypedDict): insertions: int -- cgit v1.2.1 From 9400246e9255cc8aef83fe950cf200724790d431 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 13:55:48 +0100 Subject: Fix IndexFile forwardref --- git/objects/submodule/base.py | 5 ++--- git/types.py | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 6824528d..25f88b37 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -7,8 +7,6 @@ import stat from unittest import SkipTest import uuid -from git import IndexFile - import git from git.cmd import Git from git.compat import ( @@ -58,6 +56,7 @@ from git.types import Commit_ish, PathLike if TYPE_CHECKING: from git.repo import Repo + from git.index import IndexFile # ----------------------------------------------------------------------------- @@ -1012,7 +1011,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def config_writer(self, index: Union[IndexFile, None] = None, write: bool = True) -> SectionConstraint: + def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True) -> SectionConstraint: """:return: a config writer instance allowing you to read and write the data belonging to this submodule into the .gitmodules file. diff --git a/git/types.py b/git/types.py index 79f86f04..0f288e10 100644 --- a/git/types.py +++ b/git/types.py @@ -36,8 +36,6 @@ Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] Lit_config_levels = Literal['system', 'global', 'user', 'repository'] -T = TypeVar('T', bound=Literal['system', 'global', 'user', 'repository'], covariant=True) - class ConfigLevels_NT(NamedTuple): """NamedTuple of allowed CONFIG_LEVELS""" -- cgit v1.2.1 From e4caa80cc6b6ee2b9c031a7d743d61b4830f2a7e Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 14:03:23 +0100 Subject: put typing_extensions.get_types() behind python version guard --- git/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/types.py b/git/types.py index 0f288e10..6604a243 100644 --- a/git/types.py +++ b/git/types.py @@ -5,13 +5,13 @@ import os import sys from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 - NamedTuple, TYPE_CHECKING, get_args, TypeVar) # noqa: F401 + NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 + from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, get_args # noqa: F401 else: - from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 + from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol, get_args # noqa: F401 if sys.version_info[:2] >= (3, 10): from typing import TypeGuard # noqa: F401 -- cgit v1.2.1 From 0939e38fd8fdb0567762b8a68190f7f762cf9756 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 14:11:30 +0100 Subject: fix is_config_level for < 3.8 --- git/types.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/types.py b/git/types.py index 6604a243..69286191 100644 --- a/git/types.py +++ b/git/types.py @@ -11,7 +11,7 @@ from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, if sys.version_info[:2] >= (3, 8): from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, get_args # noqa: F401 else: - from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol, get_args # noqa: F401 + from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 if sys.version_info[:2] >= (3, 10): from typing import TypeGuard # noqa: F401 @@ -51,7 +51,10 @@ ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: - return inp in get_args(Lit_config_levels) + try: + return inp in get_args(Lit_config_levels) + except NameError: # get_args added in py 3.8 + return True def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: -- cgit v1.2.1 From 0fc93b5da3459023de391f14532542f2bae61439 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 14:15:35 +0100 Subject: Rmv is_config_level() and get_args(), not worth the trouble --- git/types.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/types.py b/git/types.py index 69286191..9de4449b 100644 --- a/git/types.py +++ b/git/types.py @@ -9,7 +9,7 @@ from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, get_args # noqa: F401 + from typing import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 else: from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 @@ -46,15 +46,8 @@ class ConfigLevels_NT(NamedTuple): repository: Literal['repository'] -ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels, Lit_config_levels] # Typing this as specific literals breaks for mypy - - -def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: - try: - return inp in get_args(Lit_config_levels) - except NameError: # get_args added in py 3.8 - return True +ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels, Lit_config_levels] def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: -- cgit v1.2.1 From 53f1195b7e279a0a3d783dff3b4ec68b47261d96 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 14:20:42 +0100 Subject: Add Literal_config_levels.__args__ --- git/types.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'git') diff --git a/git/types.py b/git/types.py index 9de4449b..f20221b9 100644 --- a/git/types.py +++ b/git/types.py @@ -46,8 +46,12 @@ class ConfigLevels_NT(NamedTuple): repository: Literal['repository'] -# Typing this as specific literals breaks for mypy ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels, Lit_config_levels] +# Typing this as specific literals breaks for mypy + + +def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: + return inp in Lit_config_levels.__args__ # type: ignore # mypy lies about __args__ def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: -- cgit v1.2.1 From 41e9781b640983cd3f38223e5b349eb299a0e4f6 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 14:32:57 +0100 Subject: Improve BlameEntry.commit typing --- git/config.py | 4 ++-- git/repo/base.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'git') diff --git a/git/config.py b/git/config.py index 4cb13bdf..6931dd12 100644 --- a/git/config.py +++ b/git/config.py @@ -34,7 +34,7 @@ import configparser as cp from typing import (Any, Callable, IO, List, Dict, Sequence, TYPE_CHECKING, Tuple, Union, cast, overload) -from git.types import Lit_config_levels, ConfigLevels_Tup, ConfigLevels_NT, PathLike, TBD, assert_never +from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never if TYPE_CHECKING: from git.repo.base import Repo @@ -51,7 +51,7 @@ log.addHandler(logging.NullHandler()) # represents the configuration level of a configuration file -CONFIG_LEVELS: ConfigLevels_Tup = ConfigLevels_NT("system", "user", "global", "repository") +CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository") # Section pattern to detect conditional includes. # https://git-scm.com/docs/git-config#_conditional_includes diff --git a/git/repo/base.py b/git/repo/base.py index e60b6f6c..e1b1fc76 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -36,7 +36,7 @@ import gitdb # typing ------------------------------------------------------ -from git.types import ConfigLevels_NT, TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish +from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish from typing import (Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, Union, @@ -58,7 +58,7 @@ __all__ = ('Repo',) class BlameEntry(NamedTuple): - commit: Dict[str, TBD] + commit: Dict[str, 'Commit'] linenos: range orig_path: Optional[str] orig_linenos: range @@ -96,7 +96,7 @@ class Repo(object): # invariants # represents the configuration level of a configuration file - config_level: ConfigLevels_Tup = ConfigLevels_NT("system", "user", "global", "repository") + config_level: ConfigLevels_Tup = ("system", "user", "global", "repository") # Subclass configuration # Subclasses may easily bring in their own custom types by placing a constructor or type here @@ -802,7 +802,7 @@ class Repo(object): should get a continuous range spanning all line numbers in the file. """ data = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs) - commits = {} # type: Dict[str, TBD] + commits: Dict[str, Commit] = {} stream = (line for line in data.split(b'\n') if line) while True: -- cgit v1.2.1 From 23b5d6b434551e1df1c954ab5d2c0166f080fba8 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 15:42:46 +0100 Subject: Add types to submodule.util.py --- git/config.py | 9 +++++---- git/objects/submodule/util.py | 14 ++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'git') diff --git a/git/config.py b/git/config.py index 6931dd12..bfdfd916 100644 --- a/git/config.py +++ b/git/config.py @@ -38,6 +38,7 @@ from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert if TYPE_CHECKING: from git.repo.base import Repo + from io import BytesIO # ------------------------------------------------------------- @@ -274,7 +275,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # list of RawConfigParser methods able to change the instance _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set") - def __init__(self, file_or_files: Union[None, PathLike, IO, Sequence[Union[PathLike, IO]]] = None, + def __init__(self, file_or_files: Union[None, PathLike, BytesIO, Sequence[Union[PathLike, BytesIO]]] = None, read_only: bool = True, merge_includes: bool = True, config_level: Union[Lit_config_levels, None] = None, repo: Union['Repo', None] = None) -> None: @@ -303,7 +304,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje self._proxies = self._dict() if file_or_files is not None: - self._file_or_files: Union[PathLike, IO, Sequence[Union[PathLike, IO]]] = file_or_files + self._file_or_files: Union[PathLike, 'BytesIO', Sequence[Union[PathLike, 'BytesIO']]] = file_or_files else: if config_level is None: if read_only: @@ -650,7 +651,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje a file lock""" self._assure_writable("write") if not self._dirty: - return + return None if isinstance(self._file_or_files, (list, tuple)): raise AssertionError("Cannot write back if there is not exactly a single file to write to, have %i files" @@ -675,7 +676,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje with open(fp, "wb") as fp_open: self._write(fp_open) else: - fp = cast(IO, fp) + fp = cast(BytesIO, fp) fp.seek(0) # make sure we do not overwrite into an existing file if hasattr(fp, 'truncate'): diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 1db473df..cde18d08 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -7,7 +7,7 @@ import weakref # typing ----------------------------------------------------------------------- -from typing import Any, TYPE_CHECKING, Union +from typing import Any, Sequence, TYPE_CHECKING, Union from git.types import PathLike @@ -16,6 +16,8 @@ if TYPE_CHECKING: from weakref import ReferenceType from git.repo import Repo from git.refs import Head + from git import Remote + from git.refs import RemoteReference __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', @@ -24,12 +26,12 @@ __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', #{ Utilities -def sm_section(name): +def sm_section(name: str) -> str: """:return: section title used in .gitmodules configuration file""" - return 'submodule "%s"' % name + return f'submodule {name}' -def sm_name(section): +def sm_name(section: str) -> str: """:return: name of the submodule as parsed from the section name""" section = section.strip() return section[11:-1] @@ -40,7 +42,7 @@ def mkhead(repo: 'Repo', path: PathLike) -> 'Head': return git.Head(repo, git.Head.to_full_path(path)) -def find_first_remote_branch(remotes, branch_name): +def find_first_remote_branch(remotes: Sequence['Remote'], branch_name: str) -> 'RemoteReference': """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" for remote in remotes: try: @@ -99,7 +101,7 @@ class SubmoduleConfigParser(GitConfigParser): #{ Overridden Methods def write(self) -> None: - rval = super(SubmoduleConfigParser, self).write() + rval: None = super(SubmoduleConfigParser, self).write() self.flush_to_index() return rval # END overridden methods -- cgit v1.2.1 From c2317a768f4d6b72b9c20d4fbe455af8a0d77c36 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 18:47:44 +0100 Subject: Make bytesIO forwardref --- git/config.py | 6 +++--- git/objects/submodule/base.py | 2 +- git/objects/submodule/root.py | 15 ++++++++++++--- git/refs/head.py | 11 ++++++++--- 4 files changed, 24 insertions(+), 10 deletions(-) (limited to 'git') diff --git a/git/config.py b/git/config.py index bfdfd916..0ce3e831 100644 --- a/git/config.py +++ b/git/config.py @@ -275,7 +275,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # list of RawConfigParser methods able to change the instance _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set") - def __init__(self, file_or_files: Union[None, PathLike, BytesIO, Sequence[Union[PathLike, BytesIO]]] = None, + def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Union[PathLike, 'BytesIO']]] = None, read_only: bool = True, merge_includes: bool = True, config_level: Union[Lit_config_levels, None] = None, repo: Union['Repo', None] = None) -> None: @@ -667,7 +667,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje fp = self._file_or_files # we have a physical file on disk, so get a lock - is_file_lock = isinstance(fp, (str, IOBase)) # can't use Pathlike until 3.5 dropped + is_file_lock = isinstance(fp, (str, os.PathLike, IOBase)) # can't use Pathlike until 3.5 dropped if is_file_lock and self._lock is not None: # else raise Error? self._lock._obtain_lock() @@ -676,7 +676,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje with open(fp, "wb") as fp_open: self._write(fp_open) else: - fp = cast(BytesIO, fp) + fp = cast('BytesIO', fp) fp.seek(0) # make sure we do not overwrite into an existing file if hasattr(fp, 'truncate'): diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 25f88b37..7cd4356e 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -392,7 +392,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[path] + sm = repo.head.commit.tree[str(path)] sm._name = name return sm except KeyError: diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index 0af48710..c6746ad8 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -10,6 +10,15 @@ import git import logging +# typing ------------------------------------------------------------------- + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from git.repo import Repo + +# ---------------------------------------------------------------------------- + __all__ = ["RootModule", "RootUpdateProgress"] log = logging.getLogger('git.objects.submodule.root') @@ -42,7 +51,7 @@ class RootModule(Submodule): k_root_name = '__ROOT__' - def __init__(self, repo): + def __init__(self, repo: 'Repo'): # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) super(RootModule, self).__init__( repo, @@ -55,7 +64,7 @@ class RootModule(Submodule): branch_path=git.Head.to_full_path(self.k_head_default) ) - def _clear_cache(self): + def _clear_cache(self) -> None: """May not do anything""" pass @@ -343,7 +352,7 @@ class RootModule(Submodule): return self - def module(self): + def module(self) -> 'Repo': """:return: the actual repository containing the submodules""" return self.repo #} END interface diff --git a/git/refs/head.py b/git/refs/head.py index c698004d..97c8e6a1 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -5,9 +5,13 @@ from git.exc import GitCommandError from .symbolic import SymbolicReference from .reference import Reference -from typing import Union +from typing import Union, TYPE_CHECKING + from git.types import Commit_ish +if TYPE_CHECKING: + from git.repo import Repo + __all__ = ["HEAD", "Head"] @@ -25,12 +29,13 @@ class HEAD(SymbolicReference): _ORIG_HEAD_NAME = 'ORIG_HEAD' __slots__ = () - def __init__(self, repo, path=_HEAD_NAME): + def __init__(self, repo: 'Repo', path=_HEAD_NAME): if path != self._HEAD_NAME: raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)) super(HEAD, self).__init__(repo, path) + self.commit: 'Commit_ish' - def orig_head(self): + def orig_head(self) -> 'SymbolicReference': """ :return: SymbolicReference pointing at the ORIG_HEAD, which is maintained to contain the previous value of HEAD""" -- cgit v1.2.1 From a9351347d704db02bd3d1103e9715ff6a999e3f9 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 5 Jul 2021 19:10:21 +0100 Subject: Add types to submodule.root.py --- git/objects/submodule/root.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index c6746ad8..bcac5419 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -12,10 +12,13 @@ import logging # typing ------------------------------------------------------------------- -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union + +from git.types import Commit_ish if TYPE_CHECKING: from git.repo import Repo + from git.util import IterableList # ---------------------------------------------------------------------------- @@ -70,9 +73,11 @@ class RootModule(Submodule): #{ Interface - def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, - to_latest_revision=False, progress=None, dry_run=False, force_reset=False, - keep_going=False): + def update(self, previous_commit: Union[Commit_ish, None] = None, # type: ignore[override] + recursive: bool = True, force_remove: bool = False, init: bool = True, + to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None, + dry_run: bool = False, force_reset: bool = False, keep_going: bool = False + ) -> 'RootModule': """Update the submodules of this repository to the current HEAD commit. This method behaves smartly by determining changes of the path of a submodules repository, next to changes to the to-be-checked-out commit or the branch to be @@ -137,8 +142,8 @@ class RootModule(Submodule): previous_commit = repo.commit(previous_commit) # obtain commit object # END handle previous commit - psms = self.list_items(repo, parent_commit=previous_commit) - sms = self.list_items(repo) + psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit) + sms: 'IterableList[Submodule]' = self.list_items(repo) spsms = set(psms) ssms = set(sms) @@ -171,8 +176,8 @@ class RootModule(Submodule): csms = (spsms & ssms) len_csms = len(csms) for i, csm in enumerate(csms): - psm = psms[csm.name] - sm = sms[csm.name] + psm: 'Submodule' = psms[csm.name] + sm: 'Submodule' = sms[csm.name] # PATH CHANGES ############## -- cgit v1.2.1 From 3ce319f1296a5402079e9280500e96cc1d12fd04 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 14:06:59 +0100 Subject: Add types to submodule.update() --- git/objects/submodule/base.py | 58 ++++++++++++++++++++++++++----------------- git/remote.py | 11 +++++--- git/repo/base.py | 9 ++++--- git/util.py | 6 +++-- 4 files changed, 52 insertions(+), 32 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 7cd4356e..c975c0f5 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -49,10 +49,10 @@ from .util import ( # typing ---------------------------------------------------------------------- -from typing import Callable, Dict, TYPE_CHECKING +from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING from typing import Any, Iterator, Union -from git.types import Commit_ish, PathLike +from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: from git.repo import Repo @@ -227,7 +227,7 @@ class Submodule(IndexObject, TraversableIterableObj): return SubmoduleConfigParser(fp_module, read_only=read_only) - def _clear_cache(self): + def _clear_cache(self) -> None: # clear the possibly changed values for name in self._cache_attrs: try: @@ -247,7 +247,7 @@ class Submodule(IndexObject, TraversableIterableObj): def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: """:return: Config Parser constrained to our submodule in read or write mode""" try: - pc = self.parent_commit + pc: Union['Commit_ish', None] = self.parent_commit except ValueError: pc = None # end handle empty parent repository @@ -256,10 +256,12 @@ class Submodule(IndexObject, TraversableIterableObj): return SectionConstraint(parser, sm_section(self.name)) @classmethod - def _module_abspath(cls, parent_repo, path, name): + def _module_abspath(cls, parent_repo: 'Repo', path: PathLike, name: str) -> PathLike: if cls._need_gitfile_submodules(parent_repo.git): return osp.join(parent_repo.git_dir, 'modules', name) - return osp.join(parent_repo.working_tree_dir, path) + if parent_repo.working_tree_dir: + return osp.join(parent_repo.working_tree_dir, path) + raise NotADirectoryError() # end @classmethod @@ -287,7 +289,7 @@ class Submodule(IndexObject, TraversableIterableObj): return clone @classmethod - def _to_relative_path(cls, parent_repo, path): + def _to_relative_path(cls, parent_repo: 'Repo', path: PathLike) -> PathLike: """:return: a path guaranteed to be relative to the given parent - repository :raise ValueError: if path is not contained in the parent repository's working tree""" path = to_native_path_linux(path) @@ -295,7 +297,7 @@ class Submodule(IndexObject, TraversableIterableObj): path = path[:-1] # END handle trailing slash - if osp.isabs(path): + if osp.isabs(path) and parent_repo.working_tree_dir: working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) if not path.startswith(working_tree_linux): raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'" @@ -309,7 +311,7 @@ class Submodule(IndexObject, TraversableIterableObj): return path @classmethod - def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath): + def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: """Writes a .git file containing a(preferably) relative path to the actual git module repository. It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir :note: will overwrite existing files ! @@ -336,7 +338,8 @@ class Submodule(IndexObject, TraversableIterableObj): @classmethod def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None, - branch=None, no_checkout: bool = False, depth=None, env=None, clone_multi_options=None + branch: Union[str, None] = None, no_checkout: bool = False, depth: Union[int, None] = None, + env: Mapping[str, str] = None, clone_multi_options: Union[Sequence[TBD], None] = None ) -> 'Submodule': """Add a new submodule to the given repository. This will alter the index as well as the .gitmodules file, but will not create a new commit. @@ -415,7 +418,8 @@ class Submodule(IndexObject, TraversableIterableObj): # END check url # END verify urls match - mrepo = None + # mrepo: Union[Repo, None] = None + if url is None: if not has_module: raise ValueError("A URL was not given and a repository did not exist at %s" % path) @@ -428,7 +432,7 @@ class Submodule(IndexObject, TraversableIterableObj): url = urls[0] else: # clone new repo - kwargs: Dict[str, Union[bool, int]] = {'n': no_checkout} + kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout} if not branch_is_default: kwargs['b'] = br.name # END setup checkout-branch @@ -452,6 +456,8 @@ class Submodule(IndexObject, TraversableIterableObj): # otherwise there is a '-' character in front of the submodule listing # a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one + writer: Union[GitConfigParser, SectionConstraint] + with sm.repo.config_writer() as writer: writer.set_value(sm_section(name), 'url', url) @@ -473,8 +479,10 @@ class Submodule(IndexObject, TraversableIterableObj): return sm - def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False, - force=False, keep_going=False, env=None, clone_multi_options=None): + def update(self, recursive: bool = False, init: bool = True, to_latest_revision: bool = False, + progress: Union['UpdateProgress', None] = None, dry_run: bool = False, + force: bool = False, keep_going: bool = False, env: Mapping[str, str] = None, + clone_multi_options: Union[Sequence[TBD], None] = None): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. @@ -581,6 +589,7 @@ class Submodule(IndexObject, TraversableIterableObj): if not dry_run: # see whether we have a valid branch to checkout try: + assert isinstance(mrepo, Repo) # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) @@ -641,7 +650,7 @@ class Submodule(IndexObject, TraversableIterableObj): may_reset = True if mrepo.head.commit.binsha != self.NULL_BIN_SHA: base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) - if len(base_commit) == 0 or base_commit[0].hexsha == hexsha: + if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha): if force: msg = "Will force checkout or reset on local branch that is possibly in the future of" msg += "the commit it will be checked out to, effectively 'forgetting' new commits" @@ -916,7 +925,7 @@ class Submodule(IndexObject, TraversableIterableObj): import gc gc.collect() try: - rmtree(wtd) + rmtree(str(wtd)) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex @@ -954,6 +963,8 @@ class Submodule(IndexObject, TraversableIterableObj): # now git config - need the config intact, otherwise we can't query # information anymore + writer: Union[GitConfigParser, SectionConstraint] + with self.repo.config_writer() as writer: writer.remove_section(sm_section(self.name)) @@ -1067,13 +1078,14 @@ class Submodule(IndexObject, TraversableIterableObj): destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) source_dir = mod.git_dir # Let's be sure the submodule name is not so obviously tied to a directory - if destination_module_abspath.startswith(mod.git_dir): + if str(destination_module_abspath).startswith(str(mod.git_dir)): tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) os.renames(source_dir, tmp_dir) source_dir = tmp_dir # end handle self-containment os.renames(source_dir, destination_module_abspath) - self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) + if mod.working_tree_dir: + self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) # end move separate git repository return self @@ -1150,26 +1162,26 @@ class Submodule(IndexObject, TraversableIterableObj): return mkhead(self.module(), self._branch_path) @property - def branch_path(self): + def branch_path(self) -> PathLike: """ :return: full(relative) path as string to the branch we would checkout from the remote and track""" return self._branch_path @property - def branch_name(self): + def branch_name(self) -> str: """:return: the name of the branch, which is the shortest possible branch name""" # use an instance method, for this we create a temporary Head instance # which uses a repository that is available at least ( it makes no difference ) return git.Head(self.repo, self._branch_path).name @property - def url(self): + def url(self) -> str: """:return: The url to the repository which our module - repository refers to""" return self._url @property - def parent_commit(self): + def parent_commit(self) -> 'Commit_ish': """:return: Commit instance with the tree containing the .gitmodules file :note: will always point to the current head's commit if it was not set explicitly""" if self._parent_commit is None: @@ -1177,7 +1189,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self._parent_commit @property - def name(self): + def name(self) -> str: """:return: The name of this submodule. It is used to identify it within the .gitmodules file. :note: by default, the name is the path at which to find the submodule, but diff --git a/git/remote.py b/git/remote.py index 0ef54ea7..739424ee 100644 --- a/git/remote.py +++ b/git/remote.py @@ -42,6 +42,7 @@ from git.types import PathLike, Literal, TBD, TypeGuard, Commit_ish if TYPE_CHECKING: from git.repo.base import Repo + from git.objects.submodule.base import UpdateProgress # from git.objects.commit import Commit # from git.objects import Blob, Tree, TagObject @@ -64,7 +65,9 @@ __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote') #{ Utilities -def add_progress(kwargs: Any, git: Git, progress: Union[Callable[..., Any], None]) -> Any: +def add_progress(kwargs: Any, git: Git, + progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] + ) -> Any: """Add the --progress flag to the given kwargs dict if supported by the git command. If the actual progress in the given progress instance is not given, we do not request any progress @@ -794,7 +797,7 @@ class Remote(LazyMixin, IterableObj): config.release() def fetch(self, refspec: Union[str, List[str], None] = None, - progress: Union[Callable[..., Any], None] = None, + progress: Union[RemoteProgress, None, 'UpdateProgress'] = None, verbose: bool = True, **kwargs: Any) -> IterableList[FetchInfo]: """Fetch the latest changes for this remote @@ -841,7 +844,7 @@ class Remote(LazyMixin, IterableObj): return res def pull(self, refspec: Union[str, List[str], None] = None, - progress: Union[Callable[..., Any], None] = None, + progress: Union[RemoteProgress, 'UpdateProgress', None] = None, **kwargs: Any) -> IterableList[FetchInfo]: """Pull changes from the given branch, being the same as a fetch followed by a merge of branch with your local branch. @@ -862,7 +865,7 @@ class Remote(LazyMixin, IterableObj): return res def push(self, refspec: Union[str, List[str], None] = None, - progress: Union[Callable[..., Any], None] = None, + progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] = None, **kwargs: Any) -> IterableList[PushInfo]: """Push changes from source branch in refspec to target branch in refspec. diff --git a/git/repo/base.py b/git/repo/base.py index e1b1fc76..ea86139b 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -48,6 +48,8 @@ if TYPE_CHECKING: from git.util import IterableList from git.refs.symbolic import SymbolicReference from git.objects import Tree + from git.objects.submodule.base import UpdateProgress + from git.remote import RemoteProgress # ----------------------------------------------------------- @@ -575,7 +577,7 @@ class Repo(object): return Commit.iter_items(self, rev, paths, **kwargs) def merge_base(self, *rev: TBD, **kwargs: Any - ) -> List[Union['SymbolicReference', Commit_ish, None]]: + ) -> List[Union[Commit_ish, None]]: """Find the closest common ancestor for the given revision (e.g. Commits, Tags, References, etc) :param rev: At least two revs to find the common ancestor for. @@ -588,7 +590,7 @@ class Repo(object): raise ValueError("Please specify at least two revs, got only %i" % len(rev)) # end handle input - res = [] # type: List[Union['SymbolicReference', Commit_ish, None]] + res = [] # type: List[Union[Commit_ish, None]] try: lines = self.git.merge_base(*rev, **kwargs).splitlines() # List[str] except GitCommandError as err: @@ -1014,7 +1016,8 @@ class Repo(object): @classmethod def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB], - progress: Optional[Callable], multi_options: Optional[List[str]] = None, **kwargs: Any + progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None], + multi_options: Optional[List[str]] = None, **kwargs: Any ) -> 'Repo': odbt = kwargs.pop('odbt', odb_default_type) diff --git a/git/util.py b/git/util.py index abc82bd3..b13af358 100644 --- a/git/util.py +++ b/git/util.py @@ -82,13 +82,15 @@ HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRO #{ Utility Methods +T = TypeVar('T') -def unbare_repo(func: Callable) -> Callable: + +def unbare_repo(func: Callable[..., T]) -> Callable[..., T]: """Methods with this decorator raise InvalidGitRepositoryError if they encounter a bare repository""" @wraps(func) - def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> Callable: + def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> T: if self.repo.bare: raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__) # END bare method -- cgit v1.2.1 From 647101833ae276f3b923583e202faa3f7d78e218 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 14:25:23 +0100 Subject: Improve types of @unbare_repo and @git_working_dir decorators --- git/index/base.py | 6 +++--- git/index/util.py | 6 +++--- git/types.py | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index f4ffba7b..8346d24a 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -410,7 +410,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # whose name contains wildcard characters. if abs_path not in resolved_paths: for f in self._iter_expand_paths(glob.glob(abs_path)): - yield f.replace(rs, '') + yield str(f).replace(rs, '') continue # END glob handling try: @@ -635,7 +635,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): @git_working_dir def _entries_for_paths(self, paths: List[str], path_rewriter: Callable, fprogress: Callable, entries: List[BaseIndexEntry]) -> List[BaseIndexEntry]: - entries_added = [] # type: List[BaseIndexEntry] + entries_added: List[BaseIndexEntry] = [] if path_rewriter: for path in paths: if osp.isabs(path): @@ -769,7 +769,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # automatically # paths can be git-added, for everything else we use git-update-index paths, entries = self._preprocess_add_items(items) - entries_added = [] + entries_added: List[BaseIndexEntry] = [] # This code needs a working tree, therefore we try not to run it unless required. # That way, we are OK on a bare repository as well. # If there are no paths, the rewriter has nothing to do either diff --git a/git/index/util.py b/git/index/util.py index 471e9262..e0daef0c 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -13,7 +13,7 @@ import os.path as osp from typing import (Any, Callable) -from git.types import PathLike +from git.types import PathLike, _T # --------------------------------------------------------------------------------- @@ -88,12 +88,12 @@ def default_index(func: Callable[..., Any]) -> Callable[..., Any]: return check_default_index -def git_working_dir(func: Callable[..., Any]) -> Callable[..., None]: +def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]: """Decorator which changes the current working dir to the one of the git repository in order to assure relative paths are handled correctly""" @wraps(func) - def set_git_working_dir(self, *args: Any, **kwargs: Any) -> None: + def set_git_working_dir(self, *args: Any, **kwargs: Any) -> _T: cur_wd = os.getcwd() os.chdir(self.repo.working_tree_dir) try: diff --git a/git/types.py b/git/types.py index f20221b9..7b87dd18 100644 --- a/git/types.py +++ b/git/types.py @@ -30,6 +30,7 @@ if TYPE_CHECKING: # from git.refs import SymbolicReference TBD = Any +_T = TypeVar('_T') Tree_ish = Union['Commit', 'Tree'] Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] -- cgit v1.2.1 From fb09bfacd449ac7b5d2f20b9dbe123d61d004cde Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 14:39:28 +0100 Subject: Improve types of diff.py --- git/diff.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 346a2ca7..21b66640 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,7 +16,7 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING -from git.types import PathLike, TBD, Literal +from git.types import PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree @@ -200,7 +200,8 @@ class DiffIndex(list): if change_type not in self.change_type: raise ValueError("Invalid change type: %s" % change_type) - for diff in self: # type: 'Diff' + # diff: 'Diff' + for diff in self: if diff.change_type == change_type: yield diff elif change_type == "A" and diff.new_file: @@ -281,7 +282,8 @@ class Diff(object): a_mode: Union[bytes, str, None], b_mode: Union[bytes, str, None], new_file: bool, deleted_file: bool, copied_file: bool, raw_rename_from: Optional[bytes], raw_rename_to: Optional[bytes], - diff: Union[str, bytes, None], change_type: Optional[str], score: Optional[int]) -> None: + diff: Union[str, bytes, None], change_type: Union[Lit_change_type, None], + score: Optional[int]) -> None: assert a_rawpath is None or isinstance(a_rawpath, bytes) assert b_rawpath is None or isinstance(b_rawpath, bytes) @@ -498,12 +500,18 @@ class Diff(object): for line in lines.split(':')[1:]: meta, _, path = line.partition('\x00') path = path.rstrip('\x00') - a_blob_id, b_blob_id = None, None # Type: Optional[str] + a_blob_id: Union[str, None] + b_blob_id: Union[str, None] old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) # Change type can be R100 # R: status letter # 100: score (in case of copy and rename) - change_type = _change_type[0] + + def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: + return inp in Lit_change_type.__args__ # type: ignore + + assert is_change_type(_change_type[0]) + change_type: Lit_change_type = _change_type[0] score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None path = path.strip() @@ -518,7 +526,7 @@ class Diff(object): # NOTE: We cannot conclude from the existence of a blob to change type # as diffs with the working do not have blobs yet if change_type == 'D': - b_blob_id = None # Optional[str] + b_blob_id = None deleted_file = True elif change_type == 'A': a_blob_id = None -- cgit v1.2.1 From 2a6a2e2e44b6220f4cbc7d1672e0cfb1c13926c2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 14:45:30 +0100 Subject: Improve types of diff.py --- git/diff.py | 10 ++++++---- git/objects/submodule/base.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 21b66640..f07bd93a 100644 --- a/git/diff.py +++ b/git/diff.py @@ -26,8 +26,13 @@ if TYPE_CHECKING: Lit_change_type = Literal['A', 'D', 'M', 'R', 'T'] + +def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: + return inp in Lit_change_type.__args__ # type: ignore + # ------------------------------------------------------------------------ + __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') # Special object to compare against the empty tree in diffs @@ -503,13 +508,10 @@ class Diff(object): a_blob_id: Union[str, None] b_blob_id: Union[str, None] old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) - # Change type can be R100 + # _Change_type can be R100 # R: status letter # 100: score (in case of copy and rename) - def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return inp in Lit_change_type.__args__ # type: ignore - assert is_change_type(_change_type[0]) change_type: Lit_change_type = _change_type[0] score_str = ''.join(_change_type[1:]) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index c975c0f5..401ef54e 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -47,6 +47,7 @@ from .util import ( find_first_remote_branch ) +from git.repo import Repo # typing ---------------------------------------------------------------------- from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING @@ -55,7 +56,6 @@ from typing import Any, Iterator, Union from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: - from git.repo import Repo from git.index import IndexFile -- cgit v1.2.1 From 35783557c418921641be47f95e12c80d50b20d22 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 14:55:44 +0100 Subject: Add cast(Repo, mrepo) in try block --- git/objects/submodule/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 401ef54e..2ace1f03 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -47,15 +47,15 @@ from .util import ( find_first_remote_branch ) -from git.repo import Repo # typing ---------------------------------------------------------------------- -from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING +from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING, cast from typing import Any, Iterator, Union from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: + from git.repo import Repo from git.index import IndexFile @@ -589,7 +589,8 @@ class Submodule(IndexObject, TraversableIterableObj): if not dry_run: # see whether we have a valid branch to checkout try: - assert isinstance(mrepo, Repo) + # assert isinstance(mrepo, Repo) # cant do this cos of circular import + mrepo = cast('Repo', mrepo) # Try TypeGuard wirh hasattr? # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) -- cgit v1.2.1 From 1bcccd55e78d1412903c2af59ccc895cca85e153 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:01:15 +0100 Subject: Fix Literal Typeguards --- git/diff.py | 2 +- git/types.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index f07bd93a..611194a8 100644 --- a/git/diff.py +++ b/git/diff.py @@ -28,7 +28,7 @@ Lit_change_type = Literal['A', 'D', 'M', 'R', 'T'] def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return inp in Lit_change_type.__args__ # type: ignore + return inp in ('A', 'D', 'M', 'R', 'T') # ------------------------------------------------------------------------ diff --git a/git/types.py b/git/types.py index 7b87dd18..b2a555b0 100644 --- a/git/types.py +++ b/git/types.py @@ -38,6 +38,10 @@ Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] Lit_config_levels = Literal['system', 'global', 'user', 'repository'] +def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: + return inp in ('system', 'global', 'user', 'repository') + + class ConfigLevels_NT(NamedTuple): """NamedTuple of allowed CONFIG_LEVELS""" # works for pylance, but not mypy @@ -51,10 +55,6 @@ ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels # Typing this as specific literals breaks for mypy -def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: - return inp in Lit_config_levels.__args__ # type: ignore # mypy lies about __args__ - - def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: if exc is None: assert False, f"An unhandled Literal ({inp}) in an if else chain was found" -- cgit v1.2.1 From 278a3713a0a560cbc5b1515c86f8852cd3119e6b Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:09:36 +0100 Subject: Fix for mrepo --- git/objects/submodule/base.py | 9 +++++---- git/types.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 2ace1f03..53e89dba 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -418,7 +418,7 @@ class Submodule(IndexObject, TraversableIterableObj): # END check url # END verify urls match - # mrepo: Union[Repo, None] = None + mrepo: Union[Repo, None] = None if url is None: if not has_module: @@ -474,7 +474,8 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - sm.binsha = mrepo.head.commit.binsha + if mrepo is not None: + sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) return sm @@ -589,8 +590,8 @@ class Submodule(IndexObject, TraversableIterableObj): if not dry_run: # see whether we have a valid branch to checkout try: - # assert isinstance(mrepo, Repo) # cant do this cos of circular import - mrepo = cast('Repo', mrepo) # Try TypeGuard wirh hasattr? + # assert isinstance(mrepo, Repo) # cant do this cos of circular import + mrepo = cast('Repo', mrepo) # Try TypeGuard wirh hasattr, or has_remotes&_head protocol? # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) diff --git a/git/types.py b/git/types.py index b2a555b0..36ebbb31 100644 --- a/git/types.py +++ b/git/types.py @@ -57,7 +57,7 @@ ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: if exc is None: - assert False, f"An unhandled Literal ({inp}) in an if else chain was found" + assert False, f"An unhandled Literal ({inp}) in an if/else chain was found" else: raise exc -- cgit v1.2.1 From deafa6a0ab837dffb8f78177f7c22d21ac65bf62 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:40:06 +0100 Subject: Fix for mrepo2 --- git/diff.py | 2 +- git/objects/submodule/base.py | 12 ++++++------ git/repo/base.py | 2 +- git/types.py | 10 +++++++++- 4 files changed, 17 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 611194a8..c5e231b2 100644 --- a/git/diff.py +++ b/git/diff.py @@ -512,7 +512,7 @@ class Diff(object): # R: status letter # 100: score (in case of copy and rename) - assert is_change_type(_change_type[0]) + assert is_change_type(_change_type[0]), "Unexpected _change_type recieved in Diff" change_type: Lit_change_type = _change_type[0] score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 53e89dba..3df2b41a 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -474,8 +474,8 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - if mrepo is not None: - sm.binsha = mrepo.head.commit.binsha + mrepo = cast('Repo', mrepo) + sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) return sm @@ -652,7 +652,7 @@ class Submodule(IndexObject, TraversableIterableObj): may_reset = True if mrepo.head.commit.binsha != self.NULL_BIN_SHA: base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) - if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha): + if len(base_commit) == 0 or base_commit[0].hexsha == hexsha: # type: ignore if force: msg = "Will force checkout or reset on local branch that is possibly in the future of" msg += "the commit it will be checked out to, effectively 'forgetting' new commits" @@ -927,7 +927,7 @@ class Submodule(IndexObject, TraversableIterableObj): import gc gc.collect() try: - rmtree(str(wtd)) + rmtree(wtd) # type: ignore ## str()? except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex @@ -1015,7 +1015,7 @@ class Submodule(IndexObject, TraversableIterableObj): # If check is False, we might see a parent-commit that doesn't even contain the submodule anymore. # in that case, mark our sha as being NULL try: - self.binsha = pctree[str(self.path)].binsha + self.binsha = pctree[self.path].binsha # type: ignore # str()? except KeyError: self.binsha = self.NULL_BIN_SHA # end @@ -1080,7 +1080,7 @@ class Submodule(IndexObject, TraversableIterableObj): destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) source_dir = mod.git_dir # Let's be sure the submodule name is not so obviously tied to a directory - if str(destination_module_abspath).startswith(str(mod.git_dir)): + if destination_module_abspath.startswith(str(mod.git_dir)): # type: ignore # str()? tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) os.renames(source_dir, tmp_dir) source_dir = tmp_dir diff --git a/git/repo/base.py b/git/repo/base.py index ea86139b..a6f91aee 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -1016,7 +1016,7 @@ class Repo(object): @classmethod def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB], - progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None], + progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None] = None, multi_options: Optional[List[str]] = None, **kwargs: Any ) -> 'Repo': odbt = kwargs.pop('odbt', odb_default_type) diff --git a/git/types.py b/git/types.py index 36ebbb31..0e085075 100644 --- a/git/types.py +++ b/git/types.py @@ -5,7 +5,7 @@ import os import sys from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 - NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 + NamedTuple, TYPE_CHECKING, TypeVar, runtime_checkable) # noqa: F401 if sys.version_info[:2] >= (3, 8): @@ -78,3 +78,11 @@ class Total_TD(TypedDict): class HSH_TD(TypedDict): total: Total_TD files: Dict[PathLike, Files_TD] + + +@runtime_checkable +class RepoLike(Protocol): + """Protocol class to allow structural type-checking of Repo + e.g. when cannot import due to circular imports""" + + def remotes(self): ... # NOQA: E704 -- cgit v1.2.1 From e6f340cf8617ceb99f6da5f3db902a69308cdec7 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:42:02 +0100 Subject: Rmv runtime_checkable < py3.8 --- git/objects/submodule/base.py | 2 +- git/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 3df2b41a..514fcfea 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -927,7 +927,7 @@ class Submodule(IndexObject, TraversableIterableObj): import gc gc.collect() try: - rmtree(wtd) # type: ignore ## str()? + rmtree(str(wtd)) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex diff --git a/git/types.py b/git/types.py index 0e085075..0852905b 100644 --- a/git/types.py +++ b/git/types.py @@ -80,7 +80,7 @@ class HSH_TD(TypedDict): files: Dict[PathLike, Files_TD] -@runtime_checkable +# @runtime_checkable class RepoLike(Protocol): """Protocol class to allow structural type-checking of Repo e.g. when cannot import due to circular imports""" -- cgit v1.2.1 From ed58e2f840749bb4dabd384b812ecb259dc60304 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:43:56 +0100 Subject: Rmv runtime_checkable < py3.8 pt2 --- git/objects/submodule/base.py | 2 +- git/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 514fcfea..ca408338 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -142,7 +142,7 @@ class Submodule(IndexObject, TraversableIterableObj): reader: SectionConstraint = self.config_reader() # default submodule values try: - self.path = reader.get('path') + self.path: PathLike = reader.get('path') except cp.NoSectionError as e: if self.repo.working_tree_dir is not None: raise ValueError("This submodule instance does not exist anymore in '%s' file" diff --git a/git/types.py b/git/types.py index 0852905b..00f1ae57 100644 --- a/git/types.py +++ b/git/types.py @@ -5,7 +5,7 @@ import os import sys from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 - NamedTuple, TYPE_CHECKING, TypeVar, runtime_checkable) # noqa: F401 + NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 if sys.version_info[:2] >= (3, 8): -- cgit v1.2.1 From eecf1486cf445e2f23585b1bb65097dfebbc9545 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:50:37 +0100 Subject: Rmv root.py types --- git/objects/submodule/base.py | 4 ++-- git/objects/submodule/root.py | 34 ++++++++++------------------------ 2 files changed, 12 insertions(+), 26 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index ca408338..76769cad 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -1015,7 +1015,7 @@ class Submodule(IndexObject, TraversableIterableObj): # If check is False, we might see a parent-commit that doesn't even contain the submodule anymore. # in that case, mark our sha as being NULL try: - self.binsha = pctree[self.path].binsha # type: ignore # str()? + self.binsha = pctree[str(self.path)].binsha except KeyError: self.binsha = self.NULL_BIN_SHA # end @@ -1080,7 +1080,7 @@ class Submodule(IndexObject, TraversableIterableObj): destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) source_dir = mod.git_dir # Let's be sure the submodule name is not so obviously tied to a directory - if destination_module_abspath.startswith(str(mod.git_dir)): # type: ignore # str()? + if str(destination_module_abspath).startswith(str(mod.git_dir)): tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) os.renames(source_dir, tmp_dir) source_dir = tmp_dir diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index bcac5419..0af48710 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -10,18 +10,6 @@ import git import logging -# typing ------------------------------------------------------------------- - -from typing import TYPE_CHECKING, Union - -from git.types import Commit_ish - -if TYPE_CHECKING: - from git.repo import Repo - from git.util import IterableList - -# ---------------------------------------------------------------------------- - __all__ = ["RootModule", "RootUpdateProgress"] log = logging.getLogger('git.objects.submodule.root') @@ -54,7 +42,7 @@ class RootModule(Submodule): k_root_name = '__ROOT__' - def __init__(self, repo: 'Repo'): + def __init__(self, repo): # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) super(RootModule, self).__init__( repo, @@ -67,17 +55,15 @@ class RootModule(Submodule): branch_path=git.Head.to_full_path(self.k_head_default) ) - def _clear_cache(self) -> None: + def _clear_cache(self): """May not do anything""" pass #{ Interface - def update(self, previous_commit: Union[Commit_ish, None] = None, # type: ignore[override] - recursive: bool = True, force_remove: bool = False, init: bool = True, - to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None, - dry_run: bool = False, force_reset: bool = False, keep_going: bool = False - ) -> 'RootModule': + def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, + to_latest_revision=False, progress=None, dry_run=False, force_reset=False, + keep_going=False): """Update the submodules of this repository to the current HEAD commit. This method behaves smartly by determining changes of the path of a submodules repository, next to changes to the to-be-checked-out commit or the branch to be @@ -142,8 +128,8 @@ class RootModule(Submodule): previous_commit = repo.commit(previous_commit) # obtain commit object # END handle previous commit - psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit) - sms: 'IterableList[Submodule]' = self.list_items(repo) + psms = self.list_items(repo, parent_commit=previous_commit) + sms = self.list_items(repo) spsms = set(psms) ssms = set(sms) @@ -176,8 +162,8 @@ class RootModule(Submodule): csms = (spsms & ssms) len_csms = len(csms) for i, csm in enumerate(csms): - psm: 'Submodule' = psms[csm.name] - sm: 'Submodule' = sms[csm.name] + psm = psms[csm.name] + sm = sms[csm.name] # PATH CHANGES ############## @@ -357,7 +343,7 @@ class RootModule(Submodule): return self - def module(self) -> 'Repo': + def module(self): """:return: the actual repository containing the submodules""" return self.repo #} END interface -- cgit v1.2.1 From b78cca1854e24de3558b43880586dbf9632831ad Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 15:56:23 +0100 Subject: Rmv base.py types --- git/objects/submodule/base.py | 101 ++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 62 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 76769cad..960ff045 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -49,14 +49,13 @@ from .util import ( # typing ---------------------------------------------------------------------- -from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING, cast +from typing import Dict, TYPE_CHECKING from typing import Any, Iterator, Union -from git.types import Commit_ish, PathLike, TBD +from git.types import Commit_ish, PathLike if TYPE_CHECKING: from git.repo import Repo - from git.index import IndexFile # ----------------------------------------------------------------------------- @@ -132,17 +131,17 @@ class Submodule(IndexObject, TraversableIterableObj): if url is not None: self._url = url if branch_path is not None: - # assert isinstance(branch_path, str) + assert isinstance(branch_path, str) self._branch_path = branch_path if name is not None: self._name = name def _set_cache_(self, attr: str) -> None: if attr in ('path', '_url', '_branch_path'): - reader: SectionConstraint = self.config_reader() + reader = self.config_reader() # default submodule values try: - self.path: PathLike = reader.get('path') + self.path = reader.get('path') except cp.NoSectionError as e: if self.repo.working_tree_dir is not None: raise ValueError("This submodule instance does not exist anymore in '%s' file" @@ -227,7 +226,7 @@ class Submodule(IndexObject, TraversableIterableObj): return SubmoduleConfigParser(fp_module, read_only=read_only) - def _clear_cache(self) -> None: + def _clear_cache(self): # clear the possibly changed values for name in self._cache_attrs: try: @@ -247,7 +246,7 @@ class Submodule(IndexObject, TraversableIterableObj): def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: """:return: Config Parser constrained to our submodule in read or write mode""" try: - pc: Union['Commit_ish', None] = self.parent_commit + pc = self.parent_commit except ValueError: pc = None # end handle empty parent repository @@ -256,12 +255,10 @@ class Submodule(IndexObject, TraversableIterableObj): return SectionConstraint(parser, sm_section(self.name)) @classmethod - def _module_abspath(cls, parent_repo: 'Repo', path: PathLike, name: str) -> PathLike: + def _module_abspath(cls, parent_repo, path, name): if cls._need_gitfile_submodules(parent_repo.git): return osp.join(parent_repo.git_dir, 'modules', name) - if parent_repo.working_tree_dir: - return osp.join(parent_repo.working_tree_dir, path) - raise NotADirectoryError() + return osp.join(parent_repo.working_tree_dir, path) # end @classmethod @@ -289,7 +286,7 @@ class Submodule(IndexObject, TraversableIterableObj): return clone @classmethod - def _to_relative_path(cls, parent_repo: 'Repo', path: PathLike) -> PathLike: + def _to_relative_path(cls, parent_repo, path): """:return: a path guaranteed to be relative to the given parent - repository :raise ValueError: if path is not contained in the parent repository's working tree""" path = to_native_path_linux(path) @@ -297,7 +294,7 @@ class Submodule(IndexObject, TraversableIterableObj): path = path[:-1] # END handle trailing slash - if osp.isabs(path) and parent_repo.working_tree_dir: + if osp.isabs(path): working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) if not path.startswith(working_tree_linux): raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'" @@ -311,7 +308,7 @@ class Submodule(IndexObject, TraversableIterableObj): return path @classmethod - def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: + def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath): """Writes a .git file containing a(preferably) relative path to the actual git module repository. It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir :note: will overwrite existing files ! @@ -338,8 +335,7 @@ class Submodule(IndexObject, TraversableIterableObj): @classmethod def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None, - branch: Union[str, None] = None, no_checkout: bool = False, depth: Union[int, None] = None, - env: Mapping[str, str] = None, clone_multi_options: Union[Sequence[TBD], None] = None + branch=None, no_checkout: bool = False, depth=None, env=None ) -> 'Submodule': """Add a new submodule to the given repository. This will alter the index as well as the .gitmodules file, but will not create a new commit. @@ -373,8 +369,6 @@ class Submodule(IndexObject, TraversableIterableObj): and is defined in `os.environ`, value from `os.environ` will be used. If you want to unset some variable, consider providing empty string as its value. - :param clone_multi_options: A list of Clone options. Please see ``git.repo.base.Repo.clone`` - for details. :return: The newly created submodule instance :note: works atomically, such that no change will be done if the repository update fails for instance""" @@ -387,7 +381,7 @@ class Submodule(IndexObject, TraversableIterableObj): # assure we never put backslashes into the url, as some operating systems # like it ... if url is not None: - url = to_native_path_linux(url) + url = to_native_path_linux(url) # to_native_path_linux does nothing?? # END assure url correctness # INSTANTIATE INTERMEDIATE SM @@ -395,7 +389,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[str(path)] + sm = repo.head.commit.tree[path] # type: ignore sm._name = name return sm except KeyError: @@ -418,8 +412,7 @@ class Submodule(IndexObject, TraversableIterableObj): # END check url # END verify urls match - mrepo: Union[Repo, None] = None - + mrepo = None if url is None: if not has_module: raise ValueError("A URL was not given and a repository did not exist at %s" % path) @@ -432,7 +425,7 @@ class Submodule(IndexObject, TraversableIterableObj): url = urls[0] else: # clone new repo - kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout} + kwargs: Dict[str, Union[bool, int]] = {'n': no_checkout} if not branch_is_default: kwargs['b'] = br.name # END setup checkout-branch @@ -442,8 +435,6 @@ class Submodule(IndexObject, TraversableIterableObj): kwargs['depth'] = depth else: raise ValueError("depth should be an integer") - if clone_multi_options: - kwargs['multi_options'] = clone_multi_options # _clone_repo(cls, repo, url, path, name, **kwargs): mrepo = cls._clone_repo(repo, url, path, name, env=env, **kwargs) @@ -456,8 +447,6 @@ class Submodule(IndexObject, TraversableIterableObj): # otherwise there is a '-' character in front of the submodule listing # a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one - writer: Union[GitConfigParser, SectionConstraint] - with sm.repo.config_writer() as writer: writer.set_value(sm_section(name), 'url', url) @@ -474,16 +463,13 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - mrepo = cast('Repo', mrepo) sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) return sm - def update(self, recursive: bool = False, init: bool = True, to_latest_revision: bool = False, - progress: Union['UpdateProgress', None] = None, dry_run: bool = False, - force: bool = False, keep_going: bool = False, env: Mapping[str, str] = None, - clone_multi_options: Union[Sequence[TBD], None] = None): + def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False, + force=False, keep_going=False, env=None): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. @@ -514,8 +500,6 @@ class Submodule(IndexObject, TraversableIterableObj): and is defined in `os.environ`, value from `os.environ` will be used. If you want to unset some variable, consider providing empty string as its value. - :param clone_multi_options: list of Clone options. Please see ``git.repo.base.Repo.clone`` - for details. Only take effect with `init` option. :note: does nothing in bare repositories :note: method is definitely not atomic if recurisve is True :return: self""" @@ -582,16 +566,13 @@ class Submodule(IndexObject, TraversableIterableObj): progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name)) if not dry_run: - mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True, env=env, - multi_options=clone_multi_options) + mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True, env=env) # END handle dry-run progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath) if not dry_run: # see whether we have a valid branch to checkout try: - # assert isinstance(mrepo, Repo) # cant do this cos of circular import - mrepo = cast('Repo', mrepo) # Try TypeGuard wirh hasattr, or has_remotes&_head protocol? # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) @@ -652,7 +633,7 @@ class Submodule(IndexObject, TraversableIterableObj): may_reset = True if mrepo.head.commit.binsha != self.NULL_BIN_SHA: base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) - if len(base_commit) == 0 or base_commit[0].hexsha == hexsha: # type: ignore + if len(base_commit) == 0 or base_commit[0].hexsha == hexsha: if force: msg = "Will force checkout or reset on local branch that is possibly in the future of" msg += "the commit it will be checked out to, effectively 'forgetting' new commits" @@ -819,8 +800,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def remove(self, module: bool = True, force: bool = False, - configuration: bool = True, dry_run: bool = False) -> 'Submodule': + def remove(self, module=True, force=False, configuration=True, dry_run=False): """Remove this submodule from the repository. This will remove our entry from the .gitmodules file and the entry in the .git / config file. @@ -874,7 +854,7 @@ class Submodule(IndexObject, TraversableIterableObj): # TODO: If we run into permission problems, we have a highly inconsistent # state. Delete the .git folders last, start with the submodules first mp = self.abspath - method: Union[None, Callable[[PathLike], None]] = None + method = None if osp.islink(mp): method = os.remove elif osp.isdir(mp): @@ -927,7 +907,7 @@ class Submodule(IndexObject, TraversableIterableObj): import gc gc.collect() try: - rmtree(str(wtd)) + rmtree(wtd) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex @@ -941,7 +921,7 @@ class Submodule(IndexObject, TraversableIterableObj): rmtree(git_dir) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: - raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex + raise SkipTest("FIXME: fails with: PermissionError\n %s", ex) from ex else: raise # end handle separate bare repository @@ -965,8 +945,6 @@ class Submodule(IndexObject, TraversableIterableObj): # now git config - need the config intact, otherwise we can't query # information anymore - writer: Union[GitConfigParser, SectionConstraint] - with self.repo.config_writer() as writer: writer.remove_section(sm_section(self.name)) @@ -976,7 +954,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self - def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> 'Submodule': + def set_parent_commit(self, commit: Union[Commit_ish, None], check=True): """Set this instance to use the given commit whose tree is supposed to contain the .gitmodules blob. @@ -1015,7 +993,7 @@ class Submodule(IndexObject, TraversableIterableObj): # If check is False, we might see a parent-commit that doesn't even contain the submodule anymore. # in that case, mark our sha as being NULL try: - self.binsha = pctree[str(self.path)].binsha + self.binsha = pctree[self.path].binsha # type: ignore except KeyError: self.binsha = self.NULL_BIN_SHA # end @@ -1024,7 +1002,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True) -> SectionConstraint: + def config_writer(self, index=None, write=True): """:return: a config writer instance allowing you to read and write the data belonging to this submodule into the .gitmodules file. @@ -1045,7 +1023,7 @@ class Submodule(IndexObject, TraversableIterableObj): return writer @unbare_repo - def rename(self, new_name: str) -> 'Submodule': + def rename(self, new_name): """Rename this submodule :note: This method takes care of renaming the submodule in various places, such as @@ -1080,14 +1058,13 @@ class Submodule(IndexObject, TraversableIterableObj): destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) source_dir = mod.git_dir # Let's be sure the submodule name is not so obviously tied to a directory - if str(destination_module_abspath).startswith(str(mod.git_dir)): + if destination_module_abspath.startswith(mod.git_dir): tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) os.renames(source_dir, tmp_dir) source_dir = tmp_dir # end handle self-containment os.renames(source_dir, destination_module_abspath) - if mod.working_tree_dir: - self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) + self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) # end move separate git repository return self @@ -1097,7 +1074,7 @@ class Submodule(IndexObject, TraversableIterableObj): #{ Query Interface @unbare_repo - def module(self) -> 'Repo': + def module(self): """:return: Repo instance initialized from the repository at our submodule path :raise InvalidGitRepositoryError: if a repository was not available. This could also mean that it was not yet initialized""" @@ -1114,7 +1091,7 @@ class Submodule(IndexObject, TraversableIterableObj): raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath) # END handle exceptions - def module_exists(self) -> bool: + def module_exists(self): """:return: True if our module exists and is a valid git repository. See module() method""" try: self.module() @@ -1123,7 +1100,7 @@ class Submodule(IndexObject, TraversableIterableObj): return False # END handle exception - def exists(self) -> bool: + def exists(self): """ :return: True if the submodule exists, False otherwise. Please note that a submodule may exist ( in the .gitmodules file) even though its module @@ -1164,26 +1141,26 @@ class Submodule(IndexObject, TraversableIterableObj): return mkhead(self.module(), self._branch_path) @property - def branch_path(self) -> PathLike: + def branch_path(self): """ :return: full(relative) path as string to the branch we would checkout from the remote and track""" return self._branch_path @property - def branch_name(self) -> str: + def branch_name(self): """:return: the name of the branch, which is the shortest possible branch name""" # use an instance method, for this we create a temporary Head instance # which uses a repository that is available at least ( it makes no difference ) return git.Head(self.repo, self._branch_path).name @property - def url(self) -> str: + def url(self): """:return: The url to the repository which our module - repository refers to""" return self._url @property - def parent_commit(self) -> 'Commit_ish': + def parent_commit(self): """:return: Commit instance with the tree containing the .gitmodules file :note: will always point to the current head's commit if it was not set explicitly""" if self._parent_commit is None: @@ -1191,7 +1168,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self._parent_commit @property - def name(self) -> str: + def name(self): """:return: The name of this submodule. It is used to identify it within the .gitmodules file. :note: by default, the name is the path at which to find the submodule, but -- cgit v1.2.1 From 6aebb73bb818d91c275b94b6052d8ce4ddc113c6 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:01:18 +0100 Subject: Rmv submodule types --- git/objects/submodule/base.py | 19 +++++++++++++------ git/objects/submodule/util.py | 23 +++++++---------------- 2 files changed, 20 insertions(+), 22 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 960ff045..c95b66f2 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -335,7 +335,7 @@ class Submodule(IndexObject, TraversableIterableObj): @classmethod def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None, - branch=None, no_checkout: bool = False, depth=None, env=None + branch=None, no_checkout: bool = False, depth=None, env=None, clone_multi_options=None ) -> 'Submodule': """Add a new submodule to the given repository. This will alter the index as well as the .gitmodules file, but will not create a new commit. @@ -369,6 +369,8 @@ class Submodule(IndexObject, TraversableIterableObj): and is defined in `os.environ`, value from `os.environ` will be used. If you want to unset some variable, consider providing empty string as its value. + :param clone_multi_options: A list of Clone options. Please see ``git.repo.base.Repo.clone`` + for details. :return: The newly created submodule instance :note: works atomically, such that no change will be done if the repository update fails for instance""" @@ -381,7 +383,7 @@ class Submodule(IndexObject, TraversableIterableObj): # assure we never put backslashes into the url, as some operating systems # like it ... if url is not None: - url = to_native_path_linux(url) # to_native_path_linux does nothing?? + url = to_native_path_linux(url) # END assure url correctness # INSTANTIATE INTERMEDIATE SM @@ -389,7 +391,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[path] # type: ignore + sm = repo.head.commit.tree[path] sm._name = name return sm except KeyError: @@ -435,6 +437,8 @@ class Submodule(IndexObject, TraversableIterableObj): kwargs['depth'] = depth else: raise ValueError("depth should be an integer") + if clone_multi_options: + kwargs['multi_options'] = clone_multi_options # _clone_repo(cls, repo, url, path, name, **kwargs): mrepo = cls._clone_repo(repo, url, path, name, env=env, **kwargs) @@ -469,7 +473,7 @@ class Submodule(IndexObject, TraversableIterableObj): return sm def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False, - force=False, keep_going=False, env=None): + force=False, keep_going=False, env=None, clone_multi_options=None): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. @@ -500,6 +504,8 @@ class Submodule(IndexObject, TraversableIterableObj): and is defined in `os.environ`, value from `os.environ` will be used. If you want to unset some variable, consider providing empty string as its value. + :param clone_multi_options: list of Clone options. Please see ``git.repo.base.Repo.clone`` + for details. Only take effect with `init` option. :note: does nothing in bare repositories :note: method is definitely not atomic if recurisve is True :return: self""" @@ -566,7 +572,8 @@ class Submodule(IndexObject, TraversableIterableObj): progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name)) if not dry_run: - mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True, env=env) + mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True, env=env, + multi_options=clone_multi_options) # END handle dry-run progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath) @@ -993,7 +1000,7 @@ class Submodule(IndexObject, TraversableIterableObj): # If check is False, we might see a parent-commit that doesn't even contain the submodule anymore. # in that case, mark our sha as being NULL try: - self.binsha = pctree[self.path].binsha # type: ignore + self.binsha = pctree[str(self.path)].binsha except KeyError: self.binsha = self.NULL_BIN_SHA # end diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index cde18d08..5290000b 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -5,20 +5,11 @@ from io import BytesIO import weakref -# typing ----------------------------------------------------------------------- - -from typing import Any, Sequence, TYPE_CHECKING, Union - -from git.types import PathLike +from typing import Any, TYPE_CHECKING, Union if TYPE_CHECKING: from .base import Submodule from weakref import ReferenceType - from git.repo import Repo - from git.refs import Head - from git import Remote - from git.refs import RemoteReference - __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', 'SubmoduleConfigParser') @@ -26,23 +17,23 @@ __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', #{ Utilities -def sm_section(name: str) -> str: +def sm_section(name): """:return: section title used in .gitmodules configuration file""" - return f'submodule {name}' + return 'submodule "%s"' % name -def sm_name(section: str) -> str: +def sm_name(section): """:return: name of the submodule as parsed from the section name""" section = section.strip() return section[11:-1] -def mkhead(repo: 'Repo', path: PathLike) -> 'Head': +def mkhead(repo, path): """:return: New branch/head instance""" return git.Head(repo, git.Head.to_full_path(path)) -def find_first_remote_branch(remotes: Sequence['Remote'], branch_name: str) -> 'RemoteReference': +def find_first_remote_branch(remotes, branch_name): """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" for remote in remotes: try: @@ -101,7 +92,7 @@ class SubmoduleConfigParser(GitConfigParser): #{ Overridden Methods def write(self) -> None: - rval: None = super(SubmoduleConfigParser, self).write() + rval = super(SubmoduleConfigParser, self).write() self.flush_to_index() return rval # END overridden methods -- cgit v1.2.1 From c0ab23e5d0afce4a85a8af7ec2a360bf6c71c4ac Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:04:06 +0100 Subject: Rmv submodule types2 --- git/diff.py | 4 ++-- git/objects/submodule/base.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index c5e231b2..ac744999 100644 --- a/git/diff.py +++ b/git/diff.py @@ -24,11 +24,11 @@ if TYPE_CHECKING: from subprocess import Popen -Lit_change_type = Literal['A', 'D', 'M', 'R', 'T'] +Lit_change_type = Literal['A', 'C', 'D', 'M', 'R', 'T'] def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return inp in ('A', 'D', 'M', 'R', 'T') + return inp in ('A', 'D', 'C', 'M', 'R', 'T') # ------------------------------------------------------------------------ diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index c95b66f2..499b2b30 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -391,7 +391,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[path] + sm = repo.head.commit.tree[path] # type: ignore sm._name = name return sm except KeyError: -- cgit v1.2.1 From 8d2a7703259967f0438a18b5cbc80ee060e15866 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:16:35 +0100 Subject: Rmv diff typeguard --- git/diff.py | 28 ++++++++++------------------ git/objects/submodule/base.py | 2 +- 2 files changed, 11 insertions(+), 19 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index ac744999..879d5b55 100644 --- a/git/diff.py +++ b/git/diff.py @@ -15,8 +15,8 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ -from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING -from git.types import PathLike, TBD, Literal, TypeGuard +from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING, cast +from git.types import PathLike, TBD, Literal if TYPE_CHECKING: from .objects.tree import Tree @@ -24,15 +24,10 @@ if TYPE_CHECKING: from subprocess import Popen -Lit_change_type = Literal['A', 'C', 'D', 'M', 'R', 'T'] - - -def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return inp in ('A', 'D', 'C', 'M', 'R', 'T') +Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T'] # ------------------------------------------------------------------------ - __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') # Special object to compare against the empty tree in diffs @@ -205,8 +200,8 @@ class DiffIndex(list): if change_type not in self.change_type: raise ValueError("Invalid change type: %s" % change_type) - # diff: 'Diff' for diff in self: + diff = cast('Diff', diff) if diff.change_type == change_type: yield diff elif change_type == "A" and diff.new_file: @@ -287,8 +282,7 @@ class Diff(object): a_mode: Union[bytes, str, None], b_mode: Union[bytes, str, None], new_file: bool, deleted_file: bool, copied_file: bool, raw_rename_from: Optional[bytes], raw_rename_to: Optional[bytes], - diff: Union[str, bytes, None], change_type: Union[Lit_change_type, None], - score: Optional[int]) -> None: + diff: Union[str, bytes, None], change_type: Optional[str], score: Optional[int]) -> None: assert a_rawpath is None or isinstance(a_rawpath, bytes) assert b_rawpath is None or isinstance(b_rawpath, bytes) @@ -505,15 +499,13 @@ class Diff(object): for line in lines.split(':')[1:]: meta, _, path = line.partition('\x00') path = path.rstrip('\x00') - a_blob_id: Union[str, None] - b_blob_id: Union[str, None] + a_blob_id: Optional[str] + b_blob_id: Optional[str] old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) - # _Change_type can be R100 + # Change type can be R100 # R: status letter # 100: score (in case of copy and rename) - - assert is_change_type(_change_type[0]), "Unexpected _change_type recieved in Diff" - change_type: Lit_change_type = _change_type[0] + change_type: Lit_change_type = _change_type[0] # type: ignore score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None path = path.strip() @@ -528,7 +520,7 @@ class Diff(object): # NOTE: We cannot conclude from the existence of a blob to change type # as diffs with the working do not have blobs yet if change_type == 'D': - b_blob_id = None + b_blob_id = None # Optional[str] deleted_file = True elif change_type == 'A': a_blob_id = None diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 499b2b30..60ff1155 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -391,7 +391,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[path] # type: ignore + sm = repo.head.commit.tree[path] # type: ignore sm._name = name return sm except KeyError: -- cgit v1.2.1 From 1fd9e8c43cadb6459438a9405cd993c005689f53 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:20:52 +0100 Subject: Re-add submodule.util.py types --- git/objects/submodule/util.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 5290000b..cde18d08 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -5,11 +5,20 @@ from io import BytesIO import weakref -from typing import Any, TYPE_CHECKING, Union +# typing ----------------------------------------------------------------------- + +from typing import Any, Sequence, TYPE_CHECKING, Union + +from git.types import PathLike if TYPE_CHECKING: from .base import Submodule from weakref import ReferenceType + from git.repo import Repo + from git.refs import Head + from git import Remote + from git.refs import RemoteReference + __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', 'SubmoduleConfigParser') @@ -17,23 +26,23 @@ __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', #{ Utilities -def sm_section(name): +def sm_section(name: str) -> str: """:return: section title used in .gitmodules configuration file""" - return 'submodule "%s"' % name + return f'submodule {name}' -def sm_name(section): +def sm_name(section: str) -> str: """:return: name of the submodule as parsed from the section name""" section = section.strip() return section[11:-1] -def mkhead(repo, path): +def mkhead(repo: 'Repo', path: PathLike) -> 'Head': """:return: New branch/head instance""" return git.Head(repo, git.Head.to_full_path(path)) -def find_first_remote_branch(remotes, branch_name): +def find_first_remote_branch(remotes: Sequence['Remote'], branch_name: str) -> 'RemoteReference': """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" for remote in remotes: try: @@ -92,7 +101,7 @@ class SubmoduleConfigParser(GitConfigParser): #{ Overridden Methods def write(self) -> None: - rval = super(SubmoduleConfigParser, self).write() + rval: None = super(SubmoduleConfigParser, self).write() self.flush_to_index() return rval # END overridden methods -- cgit v1.2.1 From 1eceb8938ec98fad3a3aa2b8ffae5be8b7653976 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:29:02 +0100 Subject: Fix submodule.util.py types --- git/objects/submodule/root.py | 34 ++++++++++++++++++++++++---------- git/objects/submodule/util.py | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index 0af48710..bcac5419 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -10,6 +10,18 @@ import git import logging +# typing ------------------------------------------------------------------- + +from typing import TYPE_CHECKING, Union + +from git.types import Commit_ish + +if TYPE_CHECKING: + from git.repo import Repo + from git.util import IterableList + +# ---------------------------------------------------------------------------- + __all__ = ["RootModule", "RootUpdateProgress"] log = logging.getLogger('git.objects.submodule.root') @@ -42,7 +54,7 @@ class RootModule(Submodule): k_root_name = '__ROOT__' - def __init__(self, repo): + def __init__(self, repo: 'Repo'): # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) super(RootModule, self).__init__( repo, @@ -55,15 +67,17 @@ class RootModule(Submodule): branch_path=git.Head.to_full_path(self.k_head_default) ) - def _clear_cache(self): + def _clear_cache(self) -> None: """May not do anything""" pass #{ Interface - def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, - to_latest_revision=False, progress=None, dry_run=False, force_reset=False, - keep_going=False): + def update(self, previous_commit: Union[Commit_ish, None] = None, # type: ignore[override] + recursive: bool = True, force_remove: bool = False, init: bool = True, + to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None, + dry_run: bool = False, force_reset: bool = False, keep_going: bool = False + ) -> 'RootModule': """Update the submodules of this repository to the current HEAD commit. This method behaves smartly by determining changes of the path of a submodules repository, next to changes to the to-be-checked-out commit or the branch to be @@ -128,8 +142,8 @@ class RootModule(Submodule): previous_commit = repo.commit(previous_commit) # obtain commit object # END handle previous commit - psms = self.list_items(repo, parent_commit=previous_commit) - sms = self.list_items(repo) + psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit) + sms: 'IterableList[Submodule]' = self.list_items(repo) spsms = set(psms) ssms = set(sms) @@ -162,8 +176,8 @@ class RootModule(Submodule): csms = (spsms & ssms) len_csms = len(csms) for i, csm in enumerate(csms): - psm = psms[csm.name] - sm = sms[csm.name] + psm: 'Submodule' = psms[csm.name] + sm: 'Submodule' = sms[csm.name] # PATH CHANGES ############## @@ -343,7 +357,7 @@ class RootModule(Submodule): return self - def module(self): + def module(self) -> 'Repo': """:return: the actual repository containing the submodules""" return self.repo #} END interface diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index cde18d08..a776af88 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -28,7 +28,7 @@ __all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch', def sm_section(name: str) -> str: """:return: section title used in .gitmodules configuration file""" - return f'submodule {name}' + return f'submodule "{name}"' def sm_name(section: str) -> str: -- cgit v1.2.1 From 215abfda39c34aa125f9405d9bb848eb45ee7ac6 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:36:16 +0100 Subject: Readd typeguard to Diff.py --- git/diff.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 879d5b55..a90b7758 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,7 +16,7 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING, cast -from git.types import PathLike, TBD, Literal +from git.types import PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree @@ -26,8 +26,13 @@ if TYPE_CHECKING: Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T'] + +def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: + return inp in ['A', 'D', 'C', 'M', 'R', 'T'] + # ------------------------------------------------------------------------ + __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') # Special object to compare against the empty tree in diffs @@ -202,6 +207,7 @@ class DiffIndex(list): for diff in self: diff = cast('Diff', diff) + if diff.change_type == change_type: yield diff elif change_type == "A" and diff.new_file: @@ -505,7 +511,8 @@ class Diff(object): # Change type can be R100 # R: status letter # 100: score (in case of copy and rename) - change_type: Lit_change_type = _change_type[0] # type: ignore + assert is_change_type(_change_type[0]) + change_type: Lit_change_type = _change_type[0] score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None path = path.strip() -- cgit v1.2.1 From 94c2ae405ba635e801ff7a1ea00300e51f3a70db Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:41:14 +0100 Subject: Readd submodule.base.py types --- git/diff.py | 5 ++- git/objects/submodule/base.py | 88 +++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 39 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index a90b7758..7de4276a 100644 --- a/git/diff.py +++ b/git/diff.py @@ -28,7 +28,8 @@ Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T'] def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return inp in ['A', 'D', 'C', 'M', 'R', 'T'] + return True + # return inp in ['A', 'D', 'C', 'M', 'R', 'T'] # ------------------------------------------------------------------------ @@ -511,7 +512,7 @@ class Diff(object): # Change type can be R100 # R: status letter # 100: score (in case of copy and rename) - assert is_change_type(_change_type[0]) + assert is_change_type(_change_type[0]), f"Unexpected value for change_type received: {_change_type[0]}" change_type: Lit_change_type = _change_type[0] score_str = ''.join(_change_type[1:]) score = int(score_str) if score_str.isdigit() else None diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 60ff1155..87a86749 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -47,15 +47,16 @@ from .util import ( find_first_remote_branch ) +from git.repo import Repo # typing ---------------------------------------------------------------------- -from typing import Dict, TYPE_CHECKING +from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING from typing import Any, Iterator, Union -from git.types import Commit_ish, PathLike +from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: - from git.repo import Repo + from git.index import IndexFile # ----------------------------------------------------------------------------- @@ -131,14 +132,14 @@ class Submodule(IndexObject, TraversableIterableObj): if url is not None: self._url = url if branch_path is not None: - assert isinstance(branch_path, str) + # assert isinstance(branch_path, str) self._branch_path = branch_path if name is not None: self._name = name def _set_cache_(self, attr: str) -> None: if attr in ('path', '_url', '_branch_path'): - reader = self.config_reader() + reader: SectionConstraint = self.config_reader() # default submodule values try: self.path = reader.get('path') @@ -226,7 +227,7 @@ class Submodule(IndexObject, TraversableIterableObj): return SubmoduleConfigParser(fp_module, read_only=read_only) - def _clear_cache(self): + def _clear_cache(self) -> None: # clear the possibly changed values for name in self._cache_attrs: try: @@ -246,7 +247,7 @@ class Submodule(IndexObject, TraversableIterableObj): def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: """:return: Config Parser constrained to our submodule in read or write mode""" try: - pc = self.parent_commit + pc: Union['Commit_ish', None] = self.parent_commit except ValueError: pc = None # end handle empty parent repository @@ -255,10 +256,12 @@ class Submodule(IndexObject, TraversableIterableObj): return SectionConstraint(parser, sm_section(self.name)) @classmethod - def _module_abspath(cls, parent_repo, path, name): + def _module_abspath(cls, parent_repo: 'Repo', path: PathLike, name: str) -> PathLike: if cls._need_gitfile_submodules(parent_repo.git): return osp.join(parent_repo.git_dir, 'modules', name) - return osp.join(parent_repo.working_tree_dir, path) + if parent_repo.working_tree_dir: + return osp.join(parent_repo.working_tree_dir, path) + raise NotADirectoryError() # end @classmethod @@ -286,7 +289,7 @@ class Submodule(IndexObject, TraversableIterableObj): return clone @classmethod - def _to_relative_path(cls, parent_repo, path): + def _to_relative_path(cls, parent_repo: 'Repo', path: PathLike) -> PathLike: """:return: a path guaranteed to be relative to the given parent - repository :raise ValueError: if path is not contained in the parent repository's working tree""" path = to_native_path_linux(path) @@ -294,7 +297,7 @@ class Submodule(IndexObject, TraversableIterableObj): path = path[:-1] # END handle trailing slash - if osp.isabs(path): + if osp.isabs(path) and parent_repo.working_tree_dir: working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) if not path.startswith(working_tree_linux): raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'" @@ -308,7 +311,7 @@ class Submodule(IndexObject, TraversableIterableObj): return path @classmethod - def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath): + def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: """Writes a .git file containing a(preferably) relative path to the actual git module repository. It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir :note: will overwrite existing files ! @@ -335,7 +338,8 @@ class Submodule(IndexObject, TraversableIterableObj): @classmethod def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None, - branch=None, no_checkout: bool = False, depth=None, env=None, clone_multi_options=None + branch: Union[str, None] = None, no_checkout: bool = False, depth: Union[int, None] = None, + env: Mapping[str, str] = None, clone_multi_options: Union[Sequence[TBD], None] = None ) -> 'Submodule': """Add a new submodule to the given repository. This will alter the index as well as the .gitmodules file, but will not create a new commit. @@ -391,7 +395,7 @@ class Submodule(IndexObject, TraversableIterableObj): if sm.exists(): # reretrieve submodule from tree try: - sm = repo.head.commit.tree[path] # type: ignore + sm = repo.head.commit.tree[str(path)] sm._name = name return sm except KeyError: @@ -414,7 +418,8 @@ class Submodule(IndexObject, TraversableIterableObj): # END check url # END verify urls match - mrepo = None + mrepo: Union[Repo, None] = None + if url is None: if not has_module: raise ValueError("A URL was not given and a repository did not exist at %s" % path) @@ -427,7 +432,7 @@ class Submodule(IndexObject, TraversableIterableObj): url = urls[0] else: # clone new repo - kwargs: Dict[str, Union[bool, int]] = {'n': no_checkout} + kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout} if not branch_is_default: kwargs['b'] = br.name # END setup checkout-branch @@ -451,6 +456,8 @@ class Submodule(IndexObject, TraversableIterableObj): # otherwise there is a '-' character in front of the submodule listing # a38efa84daef914e4de58d1905a500d8d14aaf45 mymodule (v0.9.0-1-ga38efa8) # -a38efa84daef914e4de58d1905a500d8d14aaf45 submodules/intermediate/one + writer: Union[GitConfigParser, SectionConstraint] + with sm.repo.config_writer() as writer: writer.set_value(sm_section(name), 'url', url) @@ -467,13 +474,15 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - sm.binsha = mrepo.head.commit.binsha + sm.binsha = mrepo.head.commit.binsha # type: ignore index.add([sm], write=True) return sm - def update(self, recursive=False, init=True, to_latest_revision=False, progress=None, dry_run=False, - force=False, keep_going=False, env=None, clone_multi_options=None): + def update(self, recursive: bool = False, init: bool = True, to_latest_revision: bool = False, + progress: Union['UpdateProgress', None] = None, dry_run: bool = False, + force: bool = False, keep_going: bool = False, env: Mapping[str, str] = None, + clone_multi_options: Union[Sequence[TBD], None] = None): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. @@ -580,6 +589,7 @@ class Submodule(IndexObject, TraversableIterableObj): if not dry_run: # see whether we have a valid branch to checkout try: + assert isinstance(mrepo, Repo) # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) @@ -640,7 +650,7 @@ class Submodule(IndexObject, TraversableIterableObj): may_reset = True if mrepo.head.commit.binsha != self.NULL_BIN_SHA: base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) - if len(base_commit) == 0 or base_commit[0].hexsha == hexsha: + if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha): if force: msg = "Will force checkout or reset on local branch that is possibly in the future of" msg += "the commit it will be checked out to, effectively 'forgetting' new commits" @@ -807,7 +817,8 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def remove(self, module=True, force=False, configuration=True, dry_run=False): + def remove(self, module: bool = True, force: bool = False, + configuration: bool = True, dry_run: bool = False) -> 'Submodule': """Remove this submodule from the repository. This will remove our entry from the .gitmodules file and the entry in the .git / config file. @@ -861,7 +872,7 @@ class Submodule(IndexObject, TraversableIterableObj): # TODO: If we run into permission problems, we have a highly inconsistent # state. Delete the .git folders last, start with the submodules first mp = self.abspath - method = None + method: Union[None, Callable[[PathLike], None]] = None if osp.islink(mp): method = os.remove elif osp.isdir(mp): @@ -914,7 +925,7 @@ class Submodule(IndexObject, TraversableIterableObj): import gc gc.collect() try: - rmtree(wtd) + rmtree(str(wtd)) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex @@ -928,7 +939,7 @@ class Submodule(IndexObject, TraversableIterableObj): rmtree(git_dir) except Exception as ex: if HIDE_WINDOWS_KNOWN_ERRORS: - raise SkipTest("FIXME: fails with: PermissionError\n %s", ex) from ex + raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex else: raise # end handle separate bare repository @@ -952,6 +963,8 @@ class Submodule(IndexObject, TraversableIterableObj): # now git config - need the config intact, otherwise we can't query # information anymore + writer: Union[GitConfigParser, SectionConstraint] + with self.repo.config_writer() as writer: writer.remove_section(sm_section(self.name)) @@ -961,7 +974,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self - def set_parent_commit(self, commit: Union[Commit_ish, None], check=True): + def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> 'Submodule': """Set this instance to use the given commit whose tree is supposed to contain the .gitmodules blob. @@ -1009,7 +1022,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self @unbare_repo - def config_writer(self, index=None, write=True): + def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True) -> SectionConstraint: """:return: a config writer instance allowing you to read and write the data belonging to this submodule into the .gitmodules file. @@ -1030,7 +1043,7 @@ class Submodule(IndexObject, TraversableIterableObj): return writer @unbare_repo - def rename(self, new_name): + def rename(self, new_name: str) -> 'Submodule': """Rename this submodule :note: This method takes care of renaming the submodule in various places, such as @@ -1065,13 +1078,14 @@ class Submodule(IndexObject, TraversableIterableObj): destination_module_abspath = self._module_abspath(self.repo, self.path, new_name) source_dir = mod.git_dir # Let's be sure the submodule name is not so obviously tied to a directory - if destination_module_abspath.startswith(mod.git_dir): + if str(destination_module_abspath).startswith(str(mod.git_dir)): tmp_dir = self._module_abspath(self.repo, self.path, str(uuid.uuid4())) os.renames(source_dir, tmp_dir) source_dir = tmp_dir # end handle self-containment os.renames(source_dir, destination_module_abspath) - self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) + if mod.working_tree_dir: + self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath) # end move separate git repository return self @@ -1081,7 +1095,7 @@ class Submodule(IndexObject, TraversableIterableObj): #{ Query Interface @unbare_repo - def module(self): + def module(self) -> 'Repo': """:return: Repo instance initialized from the repository at our submodule path :raise InvalidGitRepositoryError: if a repository was not available. This could also mean that it was not yet initialized""" @@ -1098,7 +1112,7 @@ class Submodule(IndexObject, TraversableIterableObj): raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath) # END handle exceptions - def module_exists(self): + def module_exists(self) -> bool: """:return: True if our module exists and is a valid git repository. See module() method""" try: self.module() @@ -1107,7 +1121,7 @@ class Submodule(IndexObject, TraversableIterableObj): return False # END handle exception - def exists(self): + def exists(self) -> bool: """ :return: True if the submodule exists, False otherwise. Please note that a submodule may exist ( in the .gitmodules file) even though its module @@ -1148,26 +1162,26 @@ class Submodule(IndexObject, TraversableIterableObj): return mkhead(self.module(), self._branch_path) @property - def branch_path(self): + def branch_path(self) -> PathLike: """ :return: full(relative) path as string to the branch we would checkout from the remote and track""" return self._branch_path @property - def branch_name(self): + def branch_name(self) -> str: """:return: the name of the branch, which is the shortest possible branch name""" # use an instance method, for this we create a temporary Head instance # which uses a repository that is available at least ( it makes no difference ) return git.Head(self.repo, self._branch_path).name @property - def url(self): + def url(self) -> str: """:return: The url to the repository which our module - repository refers to""" return self._url @property - def parent_commit(self): + def parent_commit(self) -> 'Commit_ish': """:return: Commit instance with the tree containing the .gitmodules file :note: will always point to the current head's commit if it was not set explicitly""" if self._parent_commit is None: @@ -1175,7 +1189,7 @@ class Submodule(IndexObject, TraversableIterableObj): return self._parent_commit @property - def name(self): + def name(self) -> str: """:return: The name of this submodule. It is used to identify it within the .gitmodules file. :note: by default, the name is the path at which to find the submodule, but -- cgit v1.2.1 From 06eca0b84a4538c642c5e1afa2f3441a96bef444 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:47:39 +0100 Subject: Make subodule a forward ref in Index.base --- git/index/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 8346d24a..b37883a6 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -22,7 +22,6 @@ from git.exc import ( ) from git.objects import ( Blob, - Submodule, Tree, Object, Commit, @@ -76,6 +75,7 @@ if TYPE_CHECKING: from git.repo import Repo from git.refs.reference import Reference from git.util import Actor + from git.objects.submodule.base import Submodule StageType = int @@ -842,7 +842,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): items = [items] for item in items: - if isinstance(item, (BaseIndexEntry, (Blob, Submodule))): + if isinstance(item, (BaseIndexEntry, (Blob, 'Submodule'))): paths.append(self._to_relative_path(item.path)) elif isinstance(item, str): paths.append(self._to_relative_path(item)) @@ -853,7 +853,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): @post_clear_cache @default_index - def remove(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, Submodule]], working_tree: bool = False, + def remove(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], working_tree: bool = False, **kwargs: Any) -> List[str]: """Remove the given items from the index and optionally from the working tree as well. @@ -905,7 +905,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): @post_clear_cache @default_index - def move(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, Submodule]], skip_errors: bool = False, + def move(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], skip_errors: bool = False, **kwargs: Any) -> List[Tuple[str, str]]: """Rename/move the items, whereas the last item is considered the destination of the move operation. If the destination is a file, the first item ( of two ) -- cgit v1.2.1 From 33ffd0b2ed117d043fe828e5f2eabe5c8f8b0b66 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:51:34 +0100 Subject: Make subodule a forward ref in Index.base2 --- git/index/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index b37883a6..5a564b8c 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -75,7 +75,7 @@ if TYPE_CHECKING: from git.repo import Repo from git.refs.reference import Reference from git.util import Actor - from git.objects.submodule.base import Submodule + from git.objects import Submodule StageType = int -- cgit v1.2.1 From af7cee514632a4a3825dbb5655a208d2ebd1f67f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 16:57:12 +0100 Subject: Make Repo a forward ref in Submodule.base --- git/objects/submodule/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 87a86749..847b4325 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -47,16 +47,16 @@ from .util import ( find_first_remote_branch ) -from git.repo import Repo # typing ---------------------------------------------------------------------- -from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING +from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING, cast from typing import Any, Iterator, Union from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: from git.index import IndexFile + from git.repo import Repo # ----------------------------------------------------------------------------- @@ -589,7 +589,7 @@ class Submodule(IndexObject, TraversableIterableObj): if not dry_run: # see whether we have a valid branch to checkout try: - assert isinstance(mrepo, Repo) + mrepo = cast('Repo', mrepo) # find a remote which has our branch - we try to be flexible remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name) local_branch = mkhead(mrepo, self.branch_path) -- cgit v1.2.1 From f372187ade056a3069e68ba0a90bf53bd7d7e464 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:00:44 +0100 Subject: Make subodule a forward ref in Index.base3 --- git/index/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 5a564b8c..edb79edf 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -593,7 +593,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir)) return os.path.relpath(path, self.repo.working_tree_dir) - def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, Submodule]] + def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']] ) -> Tuple[List[PathLike], List[BaseIndexEntry]]: """ Split the items into two lists of path strings and BaseEntries. """ paths = [] @@ -664,7 +664,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # END path handling return entries_added - def add(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, Submodule]], force: bool = True, + def add(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], force: bool = True, fprogress: Callable = lambda *args: None, path_rewriter: Callable = None, write: bool = True, write_extension_data: bool = False) -> List[BaseIndexEntry]: """Add files from the working tree, specific blobs or BaseIndexEntries -- cgit v1.2.1 From de36cb6f0391fcf4d756909e0cec429599585700 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:04:48 +0100 Subject: UnMake subodule a forward ref in Index.base --- git/index/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index edb79edf..b54a19a7 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -22,6 +22,7 @@ from git.exc import ( ) from git.objects import ( Blob, + Submodule, Tree, Object, Commit, @@ -75,7 +76,6 @@ if TYPE_CHECKING: from git.repo import Repo from git.refs.reference import Reference from git.util import Actor - from git.objects import Submodule StageType = int -- cgit v1.2.1 From 3cc0edce2a0deb159cfb3dca10b6044086900ce9 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:07:40 +0100 Subject: UnMake subodule a forward ref in Index.base2 --- git/index/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index b54a19a7..50bcf504 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -842,7 +842,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): items = [items] for item in items: - if isinstance(item, (BaseIndexEntry, (Blob, 'Submodule'))): + if isinstance(item, (BaseIndexEntry, (Blob, Submodule))): paths.append(self._to_relative_path(item.path)) elif isinstance(item, str): paths.append(self._to_relative_path(item)) -- cgit v1.2.1 From 28bde3978b4ca18dc97488b88b4424a2d521ac68 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:14:43 +0100 Subject: Type index _items_to_rela_paths() --- git/index/base.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 50bcf504..c6d92526 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -833,12 +833,13 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return entries_added - def _items_to_rela_paths(self, items): + def _items_to_rela_paths(self, items: Union[PathLike, Sequence[Union[PathLike, BaseIndexEntry, Blob, Submodule]]] + ) -> List[PathLike]: """Returns a list of repo-relative paths from the given items which may be absolute or relative paths, entries or blobs""" paths = [] # if string put in list - if isinstance(items, str): + if isinstance(items, (str, os.PathLike)): items = [items] for item in items: @@ -851,8 +852,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # END for each item return paths - @post_clear_cache - @default_index + @ post_clear_cache + @ default_index def remove(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], working_tree: bool = False, **kwargs: Any) -> List[str]: """Remove the given items from the index and optionally from @@ -903,8 +904,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # rm 'path' return [p[4:-1] for p in removed_paths] - @post_clear_cache - @default_index + @ post_clear_cache + @ default_index def move(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], skip_errors: bool = False, **kwargs: Any) -> List[Tuple[str, str]]: """Rename/move the items, whereas the last item is considered the destination of @@ -1023,7 +1024,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): proc.wait() return stdout - @default_index + @ default_index def checkout(self, paths: Union[None, Iterable[PathLike]] = None, force: bool = False, fprogress: Callable = lambda *args: None, **kwargs: Any ) -> Union[None, Iterator[PathLike], Sequence[PathLike]]: @@ -1192,7 +1193,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # END paths handling assert "Should not reach this point" - @default_index + @ default_index def reset(self, commit: Union[Commit, 'Reference', str] = 'HEAD', working_tree: bool = False, paths: Union[None, Iterable[PathLike]] = None, head: bool = False, **kwargs: Any) -> 'IndexFile': @@ -1262,7 +1263,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return self - @default_index + @ default_index def diff(self, other: Union[diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = diff.Diffable.Index, paths: Union[str, List[PathLike], Tuple[PathLike, ...]] = None, create_patch: bool = False, **kwargs: Any ) -> diff.DiffIndex: -- cgit v1.2.1 From 1d0e666ebfdbe7eeb80b3d859f7e3823d36256e3 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:19:02 +0100 Subject: Check change_levels (should fail) --- git/diff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 7de4276a..14f823a1 100644 --- a/git/diff.py +++ b/git/diff.py @@ -28,8 +28,8 @@ Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T'] def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: - return True - # return inp in ['A', 'D', 'C', 'M', 'R', 'T'] + # return True + return inp in ['A', 'D', 'C', 'M', 'R', 'T'] # ------------------------------------------------------------------------ -- cgit v1.2.1 From e9858513addf8a4ee69890d46f58c5ef2528a6ab Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:22:37 +0100 Subject: Add 'U' to change_levels (should pass) --- git/diff.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 14f823a1..6c34a871 100644 --- a/git/diff.py +++ b/git/diff.py @@ -24,12 +24,12 @@ if TYPE_CHECKING: from subprocess import Popen -Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T'] +Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U'] def is_change_type(inp: str) -> TypeGuard[Lit_change_type]: # return True - return inp in ['A', 'D', 'C', 'M', 'R', 'T'] + return inp in ['A', 'D', 'C', 'M', 'R', 'T', 'U'] # ------------------------------------------------------------------------ -- cgit v1.2.1 From 873ebe61431c50bb39afd5cafff498b3e1879342 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 6 Jul 2021 17:56:24 +0100 Subject: Make diff.DiffIndex generic List['Diff'] --- git/diff.py | 28 ++++++++++++++++------------ git/index/util.py | 8 ++++---- 2 files changed, 20 insertions(+), 16 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 6c34a871..d3b18652 100644 --- a/git/diff.py +++ b/git/diff.py @@ -15,12 +15,13 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ -from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING, cast +from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING from git.types import PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree from git.repo.base import Repo + from git.objects.base import IndexObject from subprocess import Popen @@ -175,7 +176,10 @@ class Diffable(object): return index -class DiffIndex(list): +T_Diff = TypeVar('T_Diff', bound='Diff') + + +class DiffIndex(List[T_Diff]): """Implements an Index for diffs, allowing a list of Diffs to be queried by the diff properties. @@ -189,7 +193,7 @@ class DiffIndex(list): # T = Changed in the type change_type = ("A", "C", "D", "R", "M", "T") - def iter_change_type(self, change_type: Lit_change_type) -> Iterator['Diff']: + def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]: """ :return: iterator yielding Diff instances that match the given change_type @@ -207,8 +211,6 @@ class DiffIndex(list): raise ValueError("Invalid change type: %s" % change_type) for diff in self: - diff = cast('Diff', diff) - if diff.change_type == change_type: yield diff elif change_type == "A" and diff.new_file: @@ -289,7 +291,7 @@ class Diff(object): a_mode: Union[bytes, str, None], b_mode: Union[bytes, str, None], new_file: bool, deleted_file: bool, copied_file: bool, raw_rename_from: Optional[bytes], raw_rename_to: Optional[bytes], - diff: Union[str, bytes, None], change_type: Optional[str], score: Optional[int]) -> None: + diff: Union[str, bytes, None], change_type: Optional[Lit_change_type], score: Optional[int]) -> None: assert a_rawpath is None or isinstance(a_rawpath, bytes) assert b_rawpath is None or isinstance(b_rawpath, bytes) @@ -308,19 +310,21 @@ class Diff(object): repo = submodule.module() break + self.a_blob: Union['IndexObject', None] if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA: self.a_blob = None else: self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path) + self.b_blob: Union['IndexObject', None] if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA: self.b_blob = None else: self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path) - self.new_file = new_file - self.deleted_file = deleted_file - self.copied_file = copied_file + self.new_file: bool = new_file + self.deleted_file: bool = deleted_file + self.copied_file: bool = copied_file # be clear and use None instead of empty strings assert raw_rename_from is None or isinstance(raw_rename_from, bytes) @@ -329,7 +333,7 @@ class Diff(object): self.raw_rename_to = raw_rename_to or None self.diff = diff - self.change_type = change_type + self.change_type: Union[Lit_change_type, None] = change_type self.score = score def __eq__(self, other: object) -> bool: @@ -449,7 +453,7 @@ class Diff(object): # for now, we have to bake the stream text = b''.join(text_list) - index = DiffIndex() + index: 'DiffIndex' = DiffIndex() previous_header = None header = None a_path, b_path = None, None # for mypy @@ -560,7 +564,7 @@ class Diff(object): # handles # :100644 100644 687099101... 37c5e30c8... M .gitignore - index = DiffIndex() + index: 'DiffIndex' = DiffIndex() handle_process_output(proc, lambda byt: cls._handle_diff_line(byt, repo, index), None, finalize_process, decode_streams=False) diff --git a/git/index/util.py b/git/index/util.py index e0daef0c..3b3d6489 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -52,7 +52,7 @@ class TemporaryFileSwap(object): #{ Decorators -def post_clear_cache(func: Callable[..., Any]) -> Callable[..., Any]: +def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]: """Decorator for functions that alter the index using the git command. This would invalidate our possibly existing entries dictionary which is why it must be deleted to allow it to be lazily reread later. @@ -63,7 +63,7 @@ def post_clear_cache(func: Callable[..., Any]) -> Callable[..., Any]: """ @wraps(func) - def post_clear_cache_if_not_raised(self, *args: Any, **kwargs: Any) -> Any: + def post_clear_cache_if_not_raised(self, *args: Any, **kwargs: Any) -> _T: rval = func(self, *args, **kwargs) self._delete_entries_cache() return rval @@ -72,13 +72,13 @@ def post_clear_cache(func: Callable[..., Any]) -> Callable[..., Any]: return post_clear_cache_if_not_raised -def default_index(func: Callable[..., Any]) -> Callable[..., Any]: +def default_index(func: Callable[..., _T]) -> Callable[..., _T]: """Decorator assuring the wrapped method may only run if we are the default repository index. This is as we rely on git commands that operate on that index only. """ @wraps(func) - def check_default_index(self, *args: Any, **kwargs: Any) -> Any: + def check_default_index(self, *args: Any, **kwargs: Any) -> _T: if self._file_path != self._index_path(): raise AssertionError( "Cannot call %r on indices that do not represent the default git index" % func.__name__) -- cgit v1.2.1 From 2e2fe186d09272c3cb6c96467fff362deb90994f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 11:30:16 +0100 Subject: Increase mypy strictness (no_implicit_optional & warn_redundant_casts) and fix errors --- git/cmd.py | 2 +- git/config.py | 5 +++-- git/index/base.py | 10 ++++++---- git/objects/commit.py | 2 +- git/objects/submodule/base.py | 6 +++--- git/objects/tree.py | 1 - git/repo/base.py | 8 ++++---- git/types.py | 15 +++------------ 8 files changed, 21 insertions(+), 28 deletions(-) (limited to 'git') diff --git a/git/cmd.py b/git/cmd.py index 7df85581..dd887a18 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -831,7 +831,7 @@ class Git(LazyMixin): except cmd_not_found_exception as err: raise GitCommandNotFound(redacted_command, err) from err else: - proc = cast(Popen, proc) + # replace with a typeguard for Popen[bytes]? proc.stdout = cast(BinaryIO, proc.stdout) proc.stderr = cast(BinaryIO, proc.stderr) diff --git a/git/config.py b/git/config.py index 0ce3e831..19ce1f84 100644 --- a/git/config.py +++ b/git/config.py @@ -34,7 +34,7 @@ import configparser as cp from typing import (Any, Callable, IO, List, Dict, Sequence, TYPE_CHECKING, Tuple, Union, cast, overload) -from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never +from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never, is_config_level if TYPE_CHECKING: from git.repo.base import Repo @@ -54,6 +54,7 @@ log.addHandler(logging.NullHandler()) CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository") + # Section pattern to detect conditional includes. # https://git-scm.com/docs/git-config#_conditional_includes CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"") @@ -310,7 +311,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje if read_only: self._file_or_files = [get_config_path(f) for f in CONFIG_LEVELS - if f != 'repository'] + if is_config_level(f) and f != 'repository'] else: raise ValueError("No configuration level or configuration files specified") else: diff --git a/git/index/base.py b/git/index/base.py index c6d92526..d6670b2a 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -113,7 +113,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): _VERSION = 2 # latest version we support S_IFGITLINK = S_IFGITLINK # a submodule - def __init__(self, repo: 'Repo', file_path: PathLike = None) -> None: + def __init__(self, repo: 'Repo', file_path: Union[PathLike, None] = None) -> None: """Initialize this Index instance, optionally from the given ``file_path``. If no file_path is given, we will be created from the current index file. @@ -665,7 +665,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return entries_added def add(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], force: bool = True, - fprogress: Callable = lambda *args: None, path_rewriter: Callable = None, + fprogress: Callable = lambda *args: None, path_rewriter: Union[Callable[..., PathLike], None] = None, write: bool = True, write_extension_data: bool = False) -> List[BaseIndexEntry]: """Add files from the working tree, specific blobs or BaseIndexEntries to the index. @@ -970,7 +970,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return out def commit(self, message: str, parent_commits=None, head: bool = True, author: Union[None, 'Actor'] = None, - committer: Union[None, 'Actor'] = None, author_date: str = None, commit_date: str = None, + committer: Union[None, 'Actor'] = None, author_date: Union[str, None] = None, + commit_date: Union[str, None] = None, skip_hooks: bool = False) -> Commit: """Commit the current default index file, creating a commit object. For more information on the arguments, see tree.commit. @@ -1265,7 +1266,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): @ default_index def diff(self, other: Union[diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = diff.Diffable.Index, - paths: Union[str, List[PathLike], Tuple[PathLike, ...]] = None, create_patch: bool = False, **kwargs: Any + paths: Union[str, List[PathLike], Tuple[PathLike, ...], None] = None, + create_patch: bool = False, **kwargs: Any ) -> diff.DiffIndex: """Diff this index against the working copy or a Tree or Commit object diff --git a/git/objects/commit.py b/git/objects/commit.py index 81978ae8..65a87591 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -80,7 +80,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): "message", "parents", "encoding", "gpgsig") _id_attribute_ = "hexsha" - def __init__(self, repo: 'Repo', binsha: bytes, tree: 'Tree' = None, + def __init__(self, repo: 'Repo', binsha: bytes, tree: Union['Tree', None] = None, author: Union[Actor, None] = None, authored_date: Union[int, None] = None, author_tz_offset: Union[None, float] = None, diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 847b4325..5539069c 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -115,7 +115,7 @@ class Submodule(IndexObject, TraversableIterableObj): path: Union[PathLike, None] = None, name: Union[str, None] = None, parent_commit: Union[Commit_ish, None] = None, - url: str = None, + url: Union[str, None] = None, branch_path: Union[PathLike, None] = None ) -> None: """Initialize this instance with its attributes. We only document the ones @@ -339,7 +339,7 @@ class Submodule(IndexObject, TraversableIterableObj): @classmethod def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None, branch: Union[str, None] = None, no_checkout: bool = False, depth: Union[int, None] = None, - env: Mapping[str, str] = None, clone_multi_options: Union[Sequence[TBD], None] = None + env: Union[Mapping[str, str], None] = None, clone_multi_options: Union[Sequence[TBD], None] = None ) -> 'Submodule': """Add a new submodule to the given repository. This will alter the index as well as the .gitmodules file, but will not create a new commit. @@ -481,7 +481,7 @@ class Submodule(IndexObject, TraversableIterableObj): def update(self, recursive: bool = False, init: bool = True, to_latest_revision: bool = False, progress: Union['UpdateProgress', None] = None, dry_run: bool = False, - force: bool = False, keep_going: bool = False, env: Mapping[str, str] = None, + force: bool = False, keep_going: bool = False, env: Union[Mapping[str, str], None] = None, clone_multi_options: Union[Sequence[TBD], None] = None): """Update the repository of this submodule to point to the checkout we point at with the binsha of this instance. diff --git a/git/objects/tree.py b/git/objects/tree.py index 2e8d8a79..34fb93dc 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -216,7 +216,6 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): def _get_intermediate_items(cls, index_object: '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 () diff --git a/git/repo/base.py b/git/repo/base.py index a6f91aee..3214b528 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -36,7 +36,7 @@ import gitdb # typing ------------------------------------------------------ -from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish +from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, is_config_level from typing import (Any, BinaryIO, Callable, Dict, Iterator, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, Union, @@ -498,7 +498,7 @@ class Repo(object): unknown, instead the global path will be used.""" files = None if config_level is None: - files = [self._get_config_path(f) for f in self.config_level] + files = [self._get_config_path(f) for f in self.config_level if is_config_level(f)] else: files = [self._get_config_path(config_level)] return GitConfigParser(files, read_only=True, repo=self) @@ -623,7 +623,7 @@ class Repo(object): raise return True - def is_valid_object(self, sha: str, object_type: str = None) -> bool: + def is_valid_object(self, sha: str, object_type: Union[str, None] = None) -> bool: try: complete_sha = self.odb.partial_to_complete_sha_hex(sha) object_info = self.odb.info(complete_sha) @@ -976,7 +976,7 @@ class Repo(object): return blames @classmethod - def init(cls, path: PathLike = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB, + def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB, expand_vars: bool = True, **kwargs: Any) -> 'Repo': """Initialize a git repository at the given path if specified diff --git a/git/types.py b/git/types.py index 00f1ae57..f15db3b4 100644 --- a/git/types.py +++ b/git/types.py @@ -39,20 +39,11 @@ Lit_config_levels = Literal['system', 'global', 'user', 'repository'] def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: - return inp in ('system', 'global', 'user', 'repository') + # return inp in get_args(Lit_config_level) # only py >= 3.8 + return inp in ("system", "user", "global", "repository") -class ConfigLevels_NT(NamedTuple): - """NamedTuple of allowed CONFIG_LEVELS""" - # works for pylance, but not mypy - system: Literal['system'] - user: Literal['user'] - global_: Literal['global'] - repository: Literal['repository'] - - -ConfigLevels_Tup = Tuple[Lit_config_levels, Lit_config_levels, Lit_config_levels, Lit_config_levels] -# Typing this as specific literals breaks for mypy +ConfigLevels_Tup = Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']] def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: -- cgit v1.2.1 From 5d3818ed3d51d400517a352b5b62e966164af8cf Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 21:42:30 +0100 Subject: Finish initial typing of index folder --- git/index/base.py | 31 ++++++++++++++---------- git/index/fun.py | 62 +++++++++++++++++++++++++++--------------------- git/index/util.py | 13 ++++++---- git/objects/fun.py | 68 ++++++++++++++++++++++++++++++++++++----------------- git/objects/tree.py | 20 ++++++++-------- git/types.py | 10 ++++---- 6 files changed, 123 insertions(+), 81 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index d6670b2a..1812faee 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -18,6 +18,7 @@ from git.compat import ( from git.exc import ( GitCommandError, CheckoutError, + GitError, InvalidGitRepositoryError ) from git.objects import ( @@ -66,10 +67,10 @@ from .util import ( # typing ----------------------------------------------------------------------------- -from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, +from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, NoReturn, Sequence, TYPE_CHECKING, Tuple, Union) -from git.types import PathLike, TBD +from git.types import Commit_ish, PathLike, TBD if TYPE_CHECKING: from subprocess import Popen @@ -372,13 +373,13 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # UTILITIES @unbare_repo - def _iter_expand_paths(self, paths: Sequence[PathLike]) -> Iterator[PathLike]: + def _iter_expand_paths(self: 'IndexFile', paths: Sequence[PathLike]) -> Iterator[PathLike]: """Expand the directories in list of paths to the corresponding paths accordingly, Note: git will add items multiple times even if a glob overlapped with manually specified paths or if paths where specified multiple times - we respect that and do not prune""" - def raise_exc(e): + def raise_exc(e: Exception) -> NoReturn: raise e r = str(self.repo.working_tree_dir) rs = r + os.sep @@ -426,7 +427,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # END path exception handling # END for each path - def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item, fmakeexc, fprogress, + def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: TBD, fmakeexc: Callable[..., GitError], + fprogress: Callable[[PathLike, bool, TBD], None], read_from_stdout: bool = True) -> Union[None, str]: """Write path to proc.stdin and make sure it processes the item, including progress. @@ -498,7 +500,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): line.sort() return path_map - @classmethod + @ classmethod def entry_key(cls, *entry: Union[BaseIndexEntry, PathLike, StageType]) -> Tuple[PathLike, StageType]: return entry_key(*entry) @@ -631,8 +633,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return BaseIndexEntry((stat_mode_to_index_mode(st.st_mode), istream.binsha, 0, to_native_path_linux(filepath))) - @unbare_repo - @git_working_dir + @ unbare_repo + @ git_working_dir def _entries_for_paths(self, paths: List[str], path_rewriter: Callable, fprogress: Callable, entries: List[BaseIndexEntry]) -> List[BaseIndexEntry]: entries_added: List[BaseIndexEntry] = [] @@ -788,8 +790,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # create objects if required, otherwise go with the existing shas null_entries_indices = [i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA] if null_entries_indices: - @git_working_dir - def handle_null_entries(self): + @ git_working_dir + def handle_null_entries(self: 'IndexFile') -> None: for ei in null_entries_indices: null_entry = entries[ei] new_entry = self._store_path(null_entry.path, fprogress) @@ -969,8 +971,13 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return out - def commit(self, message: str, parent_commits=None, head: bool = True, author: Union[None, 'Actor'] = None, - committer: Union[None, 'Actor'] = None, author_date: Union[str, None] = None, + def commit(self, + message: str, + parent_commits: Union[Commit_ish, None] = None, + head: bool = True, + author: Union[None, 'Actor'] = None, + committer: Union[None, 'Actor'] = None, + author_date: Union[str, None] = None, commit_date: Union[str, None] = None, skip_hooks: bool = False) -> Commit: """Commit the current default index file, creating a commit object. diff --git a/git/index/fun.py b/git/index/fun.py index ffd109b1..74f6efbf 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -57,6 +57,7 @@ from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile + from git.objects.fun import EntryTup # ------------------------------------------------------------------------------------ @@ -188,7 +189,7 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i def is_entry_tuple(entry: Tuple) -> TypeGuard[Tuple[PathLike, int]]: return isinstance(entry, tuple) and len(entry) == 2 - + if len(entry) == 1: entry_first = entry[0] assert isinstance(entry_first, BaseIndexEntry) @@ -259,8 +260,8 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 :param sl: slice indicating the range we should process on the entries list :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name""" - tree_items = [] # type: List[Tuple[Union[bytes, str], int, str]] - tree_items_append = tree_items.append + tree_items: List[Tuple[bytes, int, str]] = [] + ci = sl.start end = sl.stop while ci < end: @@ -272,7 +273,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 rbound = entry.path.find('/', si) if rbound == -1: # its not a tree - tree_items_append((entry.binsha, entry.mode, entry.path[si:])) + tree_items.append((entry.binsha, entry.mode, entry.path[si:])) else: # find common base range base = entry.path[si:rbound] @@ -289,7 +290,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 # enter recursion # ci - 1 as we want to count our current item as well sha, _tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1) - tree_items_append((sha, S_IFDIR, base)) + tree_items.append((sha, S_IFDIR, base)) # skip ahead ci = xi @@ -306,7 +307,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 return (istream.binsha, tree_items_stringified) -def _tree_entry_to_baseindexentry(tree_entry: Tuple[str, int, str], stage: int) -> BaseIndexEntry: +def _tree_entry_to_baseindexentry(tree_entry: Tuple[bytes, int, str], stage: int) -> BaseIndexEntry: return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) @@ -319,14 +320,13 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr :param tree_shas: 1, 2 or 3 trees as identified by their binary 20 byte shas If 1 or two, the entries will effectively correspond to the last given tree If 3 are given, a 3 way merge is performed""" - out = [] # type: List[BaseIndexEntry] - out_append = out.append + out: List[BaseIndexEntry] = [] # one and two way is the same for us, as we don't have to handle an existing # index, instrea if len(tree_shas) in (1, 2): for entry in traverse_tree_recursive(odb, tree_shas[-1], ''): - out_append(_tree_entry_to_baseindexentry(entry, 0)) + out.append(_tree_entry_to_baseindexentry(entry, 0)) # END for each entry return out # END handle single tree @@ -334,8 +334,16 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr if len(tree_shas) > 3: raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) + EntryTupOrNone = Union[EntryTup, None] + + def is_three_entry_list(inp) -> TypeGuard[List[EntryTupOrNone]]: + return isinstance(inp, list) and len(inp) == 3 + # three trees - for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''): + for three_entries in traverse_trees_recursive(odb, tree_shas, ''): + + assert is_three_entry_list(three_entries) + base, ours, theirs = three_entries if base is not None: # base version exists if ours is not None: @@ -347,23 +355,23 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \ (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]): # changed by both - out_append(_tree_entry_to_baseindexentry(base, 1)) - out_append(_tree_entry_to_baseindexentry(ours, 2)) - out_append(_tree_entry_to_baseindexentry(theirs, 3)) + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) elif base[0] != ours[0] or base[1] != ours[1]: # only we changed it - out_append(_tree_entry_to_baseindexentry(ours, 0)) + out.append(_tree_entry_to_baseindexentry(ours, 0)) else: # either nobody changed it, or they did. In either # case, use theirs - out_append(_tree_entry_to_baseindexentry(theirs, 0)) + out.append(_tree_entry_to_baseindexentry(theirs, 0)) # END handle modification else: if ours[0] != base[0] or ours[1] != base[1]: # they deleted it, we changed it, conflict - out_append(_tree_entry_to_baseindexentry(base, 1)) - out_append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) # else: # we didn't change it, ignore # pass @@ -376,8 +384,8 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr else: if theirs[0] != base[0] or theirs[1] != base[1]: # deleted in ours, changed theirs, conflict - out_append(_tree_entry_to_baseindexentry(base, 1)) - out_append(_tree_entry_to_baseindexentry(theirs, 3)) + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) # END theirs changed # else: # theirs didn't change @@ -386,20 +394,20 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr # END handle ours else: # all three can't be None - if ours is None: + if ours is None and theirs is not None: # added in their branch - out_append(_tree_entry_to_baseindexentry(theirs, 0)) - elif theirs is None: + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + elif theirs is None and ours is not None: # added in our branch - out_append(_tree_entry_to_baseindexentry(ours, 0)) - else: + out.append(_tree_entry_to_baseindexentry(ours, 0)) + elif ours is not None and theirs is not None: # both have it, except for the base, see whether it changed if ours[0] != theirs[0] or ours[1] != theirs[1]: - out_append(_tree_entry_to_baseindexentry(ours, 2)) - out_append(_tree_entry_to_baseindexentry(theirs, 3)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) else: # it was added the same in both - out_append(_tree_entry_to_baseindexentry(ours, 0)) + out.append(_tree_entry_to_baseindexentry(ours, 0)) # END handle two items # END handle heads # END handle base exists diff --git a/git/index/util.py b/git/index/util.py index 3b3d6489..4f8af553 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -11,10 +11,13 @@ import os.path as osp # typing ---------------------------------------------------------------------- -from typing import (Any, Callable) +from typing import (Any, Callable, TYPE_CHECKING) from git.types import PathLike, _T +if TYPE_CHECKING: + from git.index import IndexFile + # --------------------------------------------------------------------------------- @@ -63,7 +66,7 @@ def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]: """ @wraps(func) - def post_clear_cache_if_not_raised(self, *args: Any, **kwargs: Any) -> _T: + def post_clear_cache_if_not_raised(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T: rval = func(self, *args, **kwargs) self._delete_entries_cache() return rval @@ -78,7 +81,7 @@ def default_index(func: Callable[..., _T]) -> Callable[..., _T]: on that index only. """ @wraps(func) - def check_default_index(self, *args: Any, **kwargs: Any) -> _T: + def check_default_index(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T: if self._file_path != self._index_path(): raise AssertionError( "Cannot call %r on indices that do not represent the default git index" % func.__name__) @@ -93,9 +96,9 @@ def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]: repository in order to assure relative paths are handled correctly""" @wraps(func) - def set_git_working_dir(self, *args: Any, **kwargs: Any) -> _T: + def set_git_working_dir(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T: cur_wd = os.getcwd() - os.chdir(self.repo.working_tree_dir) + os.chdir(str(self.repo.working_tree_dir)) try: return func(self, *args, **kwargs) finally: diff --git a/git/objects/fun.py b/git/objects/fun.py index 339a53b8..89b02ad4 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,6 +1,8 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR +from git import GitCmdObjectDB + from git.compat import ( safe_decode, defenc @@ -8,7 +10,12 @@ from git.compat import ( # typing ---------------------------------------------- -from typing import List, Tuple +from typing import Callable, List, Sequence, Tuple, TYPE_CHECKING, Union, overload + +if TYPE_CHECKING: + from _typeshed import ReadableBuffer + +EntryTup = Tuple[bytes, int, str] # same as TreeCacheTup in tree.py # --------------------------------------------------- @@ -18,7 +25,7 @@ __all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive 'traverse_tree_recursive') -def tree_to_stream(entries, write): +def tree_to_stream(entries: Sequence[EntryTup], write: Callable[['ReadableBuffer'], Union[int, None]]) -> None: """Write the give list of entries into a stream using its write method :param entries: **sorted** list of tuples with (binsha, mode, name) :param write: write method which takes a data string""" @@ -42,12 +49,14 @@ def tree_to_stream(entries, write): # According to my tests, this is exactly what git does, that is it just # takes the input literally, which appears to be utf8 on linux. if isinstance(name, str): - name = name.encode(defenc) - write(b''.join((mode_str, b' ', name, b'\0', binsha))) + name_bytes = name.encode(defenc) + else: + name_bytes = name + write(b''.join((mode_str, b' ', name_bytes, b'\0', binsha))) # END for each item -def tree_entries_from_data(data: bytes) -> List[Tuple[bytes, int, str]]: +def tree_entries_from_data(data: bytes) -> List[EntryTup]: """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), ...)""" @@ -93,36 +102,49 @@ def tree_entries_from_data(data: bytes) -> List[Tuple[bytes, int, str]]: return out -def _find_by_name(tree_data, name, is_dir, start_at): +def _find_by_name(tree_data: Sequence[Union[EntryTup, None]], name: str, is_dir: bool, start_at: int + ) -> Union[EntryTup, None]: """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""" + tree_data_list: List[Union[EntryTup, None]] = list(tree_data) try: - item = tree_data[start_at] + item = tree_data_list[start_at] if item and item[2] == name and S_ISDIR(item[1]) == is_dir: - tree_data[start_at] = None + tree_data_list[start_at] = None return item except IndexError: pass # END exception handling - for index, item in enumerate(tree_data): + for index, item in enumerate(tree_data_list): if item and item[2] == name and S_ISDIR(item[1]) == is_dir: - tree_data[index] = None + tree_data_list[index] = None return item # END if item matches # END for each item return None -def _to_full_path(item, path_prefix): +@ 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: Union[EntryTup, None], path_prefix: str) -> Union[EntryTup, None]: """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, tree_shas, path_prefix): +def traverse_trees_recursive(odb: GitCmdObjectDB, tree_shas: Sequence[Union[bytes, None]], + path_prefix: str) -> List[Union[EntryTup, None]]: """ :return: list with entries according to the given binary tree-shas. The result is encoded in a list @@ -137,28 +159,29 @@ def traverse_trees_recursive(odb, tree_shas, path_prefix): :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 = [] + trees_data: List[List[Union[EntryTup, None]]] = [] nt = len(tree_shas) for tree_sha in tree_shas: if tree_sha is None: - data = [] + data: List[Union[EntryTup, None]] = [] else: - data = tree_entries_from_data(odb.stream(tree_sha).read()) + data = list(tree_entries_from_data(odb.stream(tree_sha).read())) # make new list for typing as invariant # END handle muted trees trees_data.append(data) # END for each sha to get data for out = [] - out_append = out.append # find all matching entries and recursively process them together if the match # is a tree. If the match is a non-tree item, put it into the result. # Processed items will be set None for ti, tree_data in enumerate(trees_data): + for ii, item in enumerate(tree_data): if not item: continue # END skip already done items + entries: List[Union[EntryTup, None]] entries = [None for _ in range(nt)] entries[ti] = item _sha, mode, name = item @@ -169,17 +192,20 @@ def traverse_trees_recursive(odb, tree_shas, path_prefix): # ti+nt, not ti+1+nt for tio in range(ti + 1, ti + nt): tio = tio % nt - entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii) - # END for each other item data + td = trees_data[tio] + entries[tio] = _find_by_name(td, name, is_dir, ii) + # END for each other item data +#Revealed type is "builtins.list[Union[Tuple[builtins.bytes, builtins.int, builtins.str], None]]"## # +#Revealed type is "builtins.list[Union[Tuple[builtins.bytes, builtins.int, builtins.str], None]]" # if we are a directory, enter recursion 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)) - # END handle recursion + out.extend([_to_full_path(e, path_prefix) for e in entries]) + # END handle recursion # finally mark it done tree_data[ii] = None # END for each item @@ -190,7 +216,7 @@ def traverse_trees_recursive(odb, tree_shas, path_prefix): return out -def traverse_tree_recursive(odb, tree_sha, path_prefix): +def traverse_tree_recursive(odb: GitCmdObjectDB, tree_sha: bytes, path_prefix: str) -> List[Tuple[bytes, int, str]]: """ :return: list of entries of the tree pointed to by the binary tree_sha. An entry has the following format: diff --git a/git/objects/tree.py b/git/objects/tree.py index 34fb93dc..528cf5ca 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -21,8 +21,8 @@ from .fun import ( # typing ------------------------------------------------- -from typing import (Callable, Dict, Generic, Iterable, Iterator, List, - Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING) +from typing import (Callable, Dict, Iterable, Iterator, List, + Tuple, Type, Union, cast, TYPE_CHECKING) from git.types import PathLike, TypeGuard @@ -30,7 +30,7 @@ if TYPE_CHECKING: from git.repo import Repo from io import BytesIO -T_Tree_cache = TypeVar('T_Tree_cache', bound=Tuple[bytes, int, str]) +TreeCacheTup = Tuple[bytes, int, str] TraversedTreeTup = Union[Tuple[Union['Tree', None], IndexObjUnion, Tuple['Submodule', 'Submodule']]] @@ -42,7 +42,7 @@ cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b) __all__ = ("TreeModifier", "Tree") -def git_cmp(t1: T_Tree_cache, t2: T_Tree_cache) -> int: +def git_cmp(t1: TreeCacheTup, t2: TreeCacheTup) -> 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) @@ -55,8 +55,8 @@ def git_cmp(t1: T_Tree_cache, t2: T_Tree_cache) -> int: return len_a - len_b -def merge_sort(a: List[T_Tree_cache], - cmp: Callable[[T_Tree_cache, T_Tree_cache], int]) -> None: +def merge_sort(a: List[TreeCacheTup], + cmp: Callable[[TreeCacheTup, TreeCacheTup], int]) -> None: if len(a) < 2: return None @@ -91,7 +91,7 @@ def merge_sort(a: List[T_Tree_cache], k = k + 1 -class TreeModifier(Generic[T_Tree_cache], object): +class TreeModifier(object): """A utility class providing methods to alter the underlying cache in a list-like fashion. @@ -99,7 +99,7 @@ class TreeModifier(Generic[T_Tree_cache], object): the cache of a tree, will be sorted. Assuring it will be in a serializable state""" __slots__ = '_cache' - def __init__(self, cache: List[T_Tree_cache]) -> None: + def __init__(self, cache: List[TreeCacheTup]) -> None: self._cache = cache def _index_by_name(self, name: str) -> int: @@ -141,7 +141,7 @@ class TreeModifier(Generic[T_Tree_cache], object): sha = to_bin_sha(sha) index = self._index_by_name(name) - def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[T_Tree_cache]: + def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]: return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str) item = (sha, mode, name) @@ -167,7 +167,7 @@ class TreeModifier(Generic[T_Tree_cache], object): For more information on the parameters, see ``add`` :param binsha: 20 byte binary sha""" assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str) - tree_cache = cast(T_Tree_cache, (binsha, mode, name)) + tree_cache = (binsha, mode, name) self._cache.append(tree_cache) diff --git a/git/types.py b/git/types.py index f15db3b4..ac1bb2c8 100644 --- a/git/types.py +++ b/git/types.py @@ -7,6 +7,8 @@ import sys from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 +if TYPE_CHECKING: + from git.repo import Repo if sys.version_info[:2] >= (3, 8): from typing import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 @@ -71,9 +73,5 @@ class HSH_TD(TypedDict): files: Dict[PathLike, Files_TD] -# @runtime_checkable -class RepoLike(Protocol): - """Protocol class to allow structural type-checking of Repo - e.g. when cannot import due to circular imports""" - - def remotes(self): ... # NOQA: E704 +class Has_Repo(Protocol): + repo: 'Repo' -- cgit v1.2.1 From 9f88796704cc9f9826b1a25f322108f8dcc52ce6 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 21:45:37 +0100 Subject: Mak GitCmdObjectDB a froward ref --- git/objects/fun.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/objects/fun.py b/git/objects/fun.py index 89b02ad4..fc49e389 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,7 +1,6 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR -from git import GitCmdObjectDB from git.compat import ( safe_decode, @@ -14,6 +13,7 @@ from typing import Callable, List, Sequence, Tuple, TYPE_CHECKING, Union, overlo if TYPE_CHECKING: from _typeshed import ReadableBuffer + from git import GitCmdObjectDB EntryTup = Tuple[bytes, int, str] # same as TreeCacheTup in tree.py @@ -143,7 +143,7 @@ def _to_full_path(item: Union[EntryTup, None], path_prefix: str) -> Union[EntryT return (item[0], item[1], path_prefix + item[2]) -def traverse_trees_recursive(odb: GitCmdObjectDB, tree_shas: Sequence[Union[bytes, None]], +def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]], path_prefix: str) -> List[Union[EntryTup, None]]: """ :return: list with entries according to the given binary tree-shas. @@ -216,7 +216,7 @@ def traverse_trees_recursive(odb: GitCmdObjectDB, tree_shas: Sequence[Union[byte return out -def traverse_tree_recursive(odb: GitCmdObjectDB, tree_sha: bytes, path_prefix: str) -> List[Tuple[bytes, int, str]]: +def traverse_tree_recursive(odb: 'GitCmdObjectDB', tree_sha: bytes, path_prefix: str) -> List[Tuple[bytes, int, str]]: """ :return: list of entries of the tree pointed to by the binary tree_sha. An entry has the following format: -- cgit v1.2.1 From 1533596b03ef07b07311821d90de3ef72abba5d6 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 22:20:59 +0100 Subject: Mak EntryTup a froward ref --- git/index/fun.py | 11 +++++------ git/objects/fun.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 74f6efbf..382e3d44 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -57,7 +57,11 @@ from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile - from git.objects.fun import EntryTup + from git.objects.fun import EntryTupOrNone + + +def is_three_entry_list(inp) -> TypeGuard[List['EntryTupOrNone']]: + return isinstance(inp, list) and len(inp) == 3 # ------------------------------------------------------------------------------------ @@ -334,11 +338,6 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr if len(tree_shas) > 3: raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) - EntryTupOrNone = Union[EntryTup, None] - - def is_three_entry_list(inp) -> TypeGuard[List[EntryTupOrNone]]: - return isinstance(inp, list) and len(inp) == 3 - # three trees for three_entries in traverse_trees_recursive(odb, tree_shas, ''): diff --git a/git/objects/fun.py b/git/objects/fun.py index fc49e389..4ff56fdd 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: from git import GitCmdObjectDB EntryTup = Tuple[bytes, int, str] # same as TreeCacheTup in tree.py - +EntryTupOrNone = Union[EntryTup, None] # --------------------------------------------------- -- cgit v1.2.1 From 4333dcb182da3c9f9bd2c358bdf38db278cab557 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 22:49:34 +0100 Subject: Mmmmm --- git/index/fun.py | 9 ++++----- git/objects/fun.py | 18 ++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 382e3d44..f1b05f16 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -61,7 +61,7 @@ if TYPE_CHECKING: def is_three_entry_list(inp) -> TypeGuard[List['EntryTupOrNone']]: - return isinstance(inp, list) and len(inp) == 3 + return isinstance(inp, (tuple, list)) and len(inp) == 3 # ------------------------------------------------------------------------------------ @@ -339,10 +339,9 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) # three trees - for three_entries in traverse_trees_recursive(odb, tree_shas, ''): - - assert is_three_entry_list(three_entries) - base, ours, theirs = three_entries + for entryo in traverse_trees_recursive(odb, tree_shas, ''): + assert is_three_entry_list(entryo), f"{type(entryo)=} and {len(entryo)=}" # type:ignore + base, ours, theirs = entryo if base is not None: # base version exists if ours is not None: diff --git a/git/objects/fun.py b/git/objects/fun.py index 4ff56fdd..e6ad7892 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -102,13 +102,13 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: return out -def _find_by_name(tree_data: Sequence[Union[EntryTup, None]], name: str, is_dir: bool, start_at: int - ) -> Union[EntryTup, None]: +def _find_by_name(tree_data: Sequence[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""" - tree_data_list: List[Union[EntryTup, None]] = list(tree_data) + tree_data_list: List[EntryTupOrNone] = list(tree_data) try: item = tree_data_list[start_at] if item and item[2] == name and S_ISDIR(item[1]) == is_dir: @@ -136,7 +136,7 @@ def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup: ... -def _to_full_path(item: Union[EntryTup, None], path_prefix: str) -> Union[EntryTup, None]: +def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: """Rebuild entry with given path prefix""" if not item: return item @@ -144,7 +144,7 @@ def _to_full_path(item: Union[EntryTup, None], path_prefix: str) -> Union[EntryT def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]], - path_prefix: str) -> List[Union[EntryTup, None]]: + path_prefix: str) -> List[EntryTupOrNone]: """ :return: list with entries according to the given binary tree-shas. The result is encoded in a list @@ -159,11 +159,11 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by :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[Union[EntryTup, None]]] = [] + trees_data: List[List[EntryTupOrNone]] = [] nt = len(tree_shas) for tree_sha in tree_shas: if tree_sha is None: - data: List[Union[EntryTup, None]] = [] + data: List[EntryTupOrNone] = [] else: data = list(tree_entries_from_data(odb.stream(tree_sha).read())) # make new list for typing as invariant # END handle muted trees @@ -181,7 +181,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by if not item: continue # END skip already done items - entries: List[Union[EntryTup, None]] + entries: List[EntryTupOrNone] entries = [None for _ in range(nt)] entries[ti] = item _sha, mode, name = item @@ -196,8 +196,6 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by entries[tio] = _find_by_name(td, name, is_dir, ii) # END for each other item data -#Revealed type is "builtins.list[Union[Tuple[builtins.bytes, builtins.int, builtins.str], None]]"## # -#Revealed type is "builtins.list[Union[Tuple[builtins.bytes, builtins.int, builtins.str], None]]" # if we are a directory, enter recursion if is_dir: out.extend(traverse_trees_recursive( -- cgit v1.2.1 From fe5fef9ebd63ff79e57035cacbe647d096f110bc Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 22:57:16 +0100 Subject: Mmmmmm --- git/index/fun.py | 138 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 67 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index f1b05f16..dbe25276 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -13,6 +13,7 @@ from stat import ( S_IFREG, S_IXUSR, ) + import subprocess from git.cmd import PROC_CREATIONFLAGS, handle_process_output @@ -339,76 +340,79 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) # three trees - for entryo in traverse_trees_recursive(odb, tree_shas, ''): - assert is_three_entry_list(entryo), f"{type(entryo)=} and {len(entryo)=}" # type:ignore - base, ours, theirs = entryo - if base is not None: - # base version exists - if ours is not None: - # ours exists - if theirs is not None: - # it exists in all branches, if it was changed in both - # its a conflict, otherwise we take the changed version - # This should be the most common branch, so it comes first - if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \ - (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]): - # changed by both - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(ours, 2)) - out.append(_tree_entry_to_baseindexentry(theirs, 3)) - elif base[0] != ours[0] or base[1] != ours[1]: - # only we changed it - out.append(_tree_entry_to_baseindexentry(ours, 0)) - else: - # either nobody changed it, or they did. In either - # case, use theirs - out.append(_tree_entry_to_baseindexentry(theirs, 0)) - # END handle modification + entries = traverse_trees_recursive(odb, tree_shas, '') + base = entries[0] + ours = entries[1] + theirs = entries[2] + assert is_three_entry_list(entries), f"{type(entries)=} and {len(entries)=}" # type:ignore + + if base is not None: + # base version exists + if ours is not None: + # ours exists + if theirs is not None: + # it exists in all branches, if it was changed in both + # its a conflict, otherwise we take the changed version + # This should be the most common branch, so it comes first + if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \ + (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]): + # changed by both + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + elif base[0] != ours[0] or base[1] != ours[1]: + # only we changed it + out.append(_tree_entry_to_baseindexentry(ours, 0)) else: - - if ours[0] != base[0] or ours[1] != base[1]: - # they deleted it, we changed it, conflict - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(ours, 2)) - # else: - # we didn't change it, ignore - # pass - # END handle our change - # END handle theirs + # either nobody changed it, or they did. In either + # case, use theirs + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + # END handle modification else: - if theirs is None: - # deleted in both, its fine - its out - pass - else: - if theirs[0] != base[0] or theirs[1] != base[1]: - # deleted in ours, changed theirs, conflict - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(theirs, 3)) - # END theirs changed - # else: - # theirs didn't change - # pass - # END handle theirs - # END handle ours - else: - # all three can't be None - if ours is None and theirs is not None: - # added in their branch - out.append(_tree_entry_to_baseindexentry(theirs, 0)) - elif theirs is None and ours is not None: - # added in our branch - out.append(_tree_entry_to_baseindexentry(ours, 0)) - elif ours is not None and theirs is not None: - # both have it, except for the base, see whether it changed - if ours[0] != theirs[0] or ours[1] != theirs[1]: + + if ours[0] != base[0] or ours[1] != base[1]: + # they deleted it, we changed it, conflict + out.append(_tree_entry_to_baseindexentry(base, 1)) out.append(_tree_entry_to_baseindexentry(ours, 2)) + # else: + # we didn't change it, ignore + # pass + # END handle our change + # END handle theirs + else: + if theirs is None: + # deleted in both, its fine - its out + pass + else: + if theirs[0] != base[0] or theirs[1] != base[1]: + # deleted in ours, changed theirs, conflict + out.append(_tree_entry_to_baseindexentry(base, 1)) out.append(_tree_entry_to_baseindexentry(theirs, 3)) - else: - # it was added the same in both - out.append(_tree_entry_to_baseindexentry(ours, 0)) - # END handle two items - # END handle heads - # END handle base exists - # END for each entries tuple + # END theirs changed + # else: + # theirs didn't change + # pass + # END handle theirs + # END handle ours + else: + # all three can't be None + if ours is None and theirs is not None: + # added in their branch + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + elif theirs is None and ours is not None: + # added in our branch + out.append(_tree_entry_to_baseindexentry(ours, 0)) + elif ours is not None and theirs is not None: + # both have it, except for the base, see whether it changed + if ours[0] != theirs[0] or ours[1] != theirs[1]: + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + else: + # it was added the same in both + out.append(_tree_entry_to_baseindexentry(ours, 0)) + # END handle two items + # END handle heads + # END handle base exists +# END for each entries tuple return out -- cgit v1.2.1 From d344abf5594bebe0147feaba7e87c0079d28374f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 23:12:42 +0100 Subject: Fix traverse_trees_recursive() --- git/index/fun.py | 148 +++++++++++++++++++++++++---------------------------- git/objects/fun.py | 8 +-- 2 files changed, 73 insertions(+), 83 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index dbe25276..cd71237d 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -13,7 +13,6 @@ from stat import ( S_IFREG, S_IXUSR, ) - import subprocess from git.cmd import PROC_CREATIONFLAGS, handle_process_output @@ -58,11 +57,7 @@ from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile - from git.objects.fun import EntryTupOrNone - - -def is_three_entry_list(inp) -> TypeGuard[List['EntryTupOrNone']]: - return isinstance(inp, (tuple, list)) and len(inp) == 3 + # from git.objects.fun import EntryTupOrNone # ------------------------------------------------------------------------------------ @@ -192,8 +187,8 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i """:return: Key suitable to be used for the index.entries dictionary :param entry: One instance of type BaseIndexEntry or the path and the stage""" - def is_entry_tuple(entry: Tuple) -> TypeGuard[Tuple[PathLike, int]]: - return isinstance(entry, tuple) and len(entry) == 2 + def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]: + return isinstance(entry_key, tuple) and len(entry_key) == 2 if len(entry) == 1: entry_first = entry[0] @@ -201,7 +196,7 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i return (entry_first.path, entry_first.stage) else: # entry = tuple(entry) - assert is_entry_tuple(entry) + assert is_entry_key_tup(entry) return entry # END handle entry @@ -340,79 +335,74 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr raise ValueError("Cannot handle %i trees at once" % len(tree_shas)) # three trees - entries = traverse_trees_recursive(odb, tree_shas, '') - base = entries[0] - ours = entries[1] - theirs = entries[2] - assert is_three_entry_list(entries), f"{type(entries)=} and {len(entries)=}" # type:ignore - - if base is not None: - # base version exists - if ours is not None: - # ours exists - if theirs is not None: - # it exists in all branches, if it was changed in both - # its a conflict, otherwise we take the changed version - # This should be the most common branch, so it comes first - if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \ - (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]): - # changed by both - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(ours, 2)) - out.append(_tree_entry_to_baseindexentry(theirs, 3)) - elif base[0] != ours[0] or base[1] != ours[1]: - # only we changed it - out.append(_tree_entry_to_baseindexentry(ours, 0)) + for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''): + if base is not None: + # base version exists + if ours is not None: + # ours exists + if theirs is not None: + # it exists in all branches, if it was changed in both + # its a conflict, otherwise we take the changed version + # This should be the most common branch, so it comes first + if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \ + (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]): + # changed by both + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + elif base[0] != ours[0] or base[1] != ours[1]: + # only we changed it + out.append(_tree_entry_to_baseindexentry(ours, 0)) + else: + # either nobody changed it, or they did. In either + # case, use theirs + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + # END handle modification else: - # either nobody changed it, or they did. In either - # case, use theirs - out.append(_tree_entry_to_baseindexentry(theirs, 0)) - # END handle modification - else: - if ours[0] != base[0] or ours[1] != base[1]: - # they deleted it, we changed it, conflict - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(ours, 2)) - # else: - # we didn't change it, ignore - # pass - # END handle our change - # END handle theirs - else: - if theirs is None: - # deleted in both, its fine - its out - pass + if ours[0] != base[0] or ours[1] != base[1]: + # they deleted it, we changed it, conflict + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(ours, 2)) + # else: + # we didn't change it, ignore + # pass + # END handle our change + # END handle theirs else: - if theirs[0] != base[0] or theirs[1] != base[1]: - # deleted in ours, changed theirs, conflict - out.append(_tree_entry_to_baseindexentry(base, 1)) - out.append(_tree_entry_to_baseindexentry(theirs, 3)) - # END theirs changed - # else: - # theirs didn't change - # pass - # END handle theirs - # END handle ours - else: - # all three can't be None - if ours is None and theirs is not None: - # added in their branch - out.append(_tree_entry_to_baseindexentry(theirs, 0)) - elif theirs is None and ours is not None: - # added in our branch - out.append(_tree_entry_to_baseindexentry(ours, 0)) - elif ours is not None and theirs is not None: - # both have it, except for the base, see whether it changed - if ours[0] != theirs[0] or ours[1] != theirs[1]: - out.append(_tree_entry_to_baseindexentry(ours, 2)) - out.append(_tree_entry_to_baseindexentry(theirs, 3)) - else: - # it was added the same in both + if theirs is None: + # deleted in both, its fine - its out + pass + else: + if theirs[0] != base[0] or theirs[1] != base[1]: + # deleted in ours, changed theirs, conflict + out.append(_tree_entry_to_baseindexentry(base, 1)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + # END theirs changed + # else: + # theirs didn't change + # pass + # END handle theirs + # END handle ours + else: + # all three can't be None + if ours is None and theirs is not None: + # added in their branch + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + elif theirs is None and ours is not None: + # added in our branch out.append(_tree_entry_to_baseindexentry(ours, 0)) - # END handle two items - # END handle heads - # END handle base exists -# END for each entries tuple + elif ours is not None and theirs is not None: + # both have it, except for the base, see whether it changed + if ours[0] != theirs[0] or ours[1] != theirs[1]: + out.append(_tree_entry_to_baseindexentry(ours, 2)) + out.append(_tree_entry_to_baseindexentry(theirs, 3)) + else: + # it was added the same in both + out.append(_tree_entry_to_baseindexentry(ours, 0)) + # END handle two items + # END handle heads + # END handle base exists + # END for each entries tuple return out diff --git a/git/objects/fun.py b/git/objects/fun.py index e6ad7892..2abd7b09 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -144,9 +144,9 @@ def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]], - path_prefix: str) -> List[EntryTupOrNone]: + path_prefix: str) -> List[List[EntryTupOrNone]]: """ - :return: list with entries according to the given binary tree-shas. + :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 @@ -170,7 +170,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by trees_data.append(data) # END for each sha to get data for - out = [] + out: List[List[EntryTupOrNone]] = [] # find all matching entries and recursively process them together if the match # is a tree. If the match is a non-tree item, put it into the result. @@ -201,7 +201,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by out.extend(traverse_trees_recursive( odb, [((ei and ei[0]) or None) for ei in entries], path_prefix + name + '/')) else: - out.extend([_to_full_path(e, path_prefix) for e in entries]) + out.append([_to_full_path(e, path_prefix) for e in entries]) # END handle recursion # finally mark it done -- cgit v1.2.1 From dfbc0f42c7555b7145768774b861029c4283178c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 23:20:58 +0100 Subject: Fix traverse_trees_recursive() again --- git/objects/fun.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/objects/fun.py b/git/objects/fun.py index 2abd7b09..cb323afb 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -144,7 +144,7 @@ def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]], - path_prefix: str) -> List[List[EntryTupOrNone]]: + 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 @@ -170,7 +170,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by trees_data.append(data) # END for each sha to get data for - out: List[List[EntryTupOrNone]] = [] + out: List[Tuple[EntryTupOrNone, ...]] = [] # find all matching entries and recursively process them together if the match # is a tree. If the match is a non-tree item, put it into the result. @@ -201,7 +201,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by out.extend(traverse_trees_recursive( odb, [((ei and ei[0]) or None) for ei in entries], path_prefix + name + '/')) else: - out.append([_to_full_path(e, path_prefix) for e in entries]) + out.append(tuple(_to_full_path(e, path_prefix) for e in entries)) # END handle recursion # finally mark it done -- cgit v1.2.1 From c27d2b078b515a8321b3f7f7abdcea363d8049df Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 23:25:18 +0100 Subject: Use Tuple not tuple --- git/objects/fun.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/objects/fun.py b/git/objects/fun.py index cb323afb..42954fc2 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -144,7 +144,7 @@ def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone: def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]], - path_prefix: str) -> List[tuple[EntryTupOrNone, ...]]: + 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 @@ -165,7 +165,8 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by if tree_sha is None: data: List[EntryTupOrNone] = [] else: - data = list(tree_entries_from_data(odb.stream(tree_sha).read())) # make new list for typing as invariant + # make new list for typing as list invariant + data = [x for x in tree_entries_from_data(odb.stream(tree_sha).read())] # END handle muted trees trees_data.append(data) # END for each sha to get data for -- cgit v1.2.1 From 4f13b4e23526616f307370dc9a869b067e90b276 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 23:49:01 +0100 Subject: fix base,ours,theirs typing --- git/index/fun.py | 8 ++++---- git/objects/fun.py | 8 ++++---- git/objects/tree.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index cd71237d..774738db 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -386,13 +386,13 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr # END handle ours else: # all three can't be None - if ours is None and theirs is not None: + if ours is None: # added in their branch - out.append(_tree_entry_to_baseindexentry(theirs, 0)) - elif theirs is None and ours is not None: + out.append(_tree_entry_to_baseindexentry(theirs, 0)) # type: ignore + elif theirs is None: # ours is not None, theirs is None # added in our branch out.append(_tree_entry_to_baseindexentry(ours, 0)) - elif ours is not None and theirs is not None: + else: # both have it, except for the base, see whether it changed if ours[0] != theirs[0] or ours[1] != theirs[1]: out.append(_tree_entry_to_baseindexentry(ours, 2)) diff --git a/git/objects/fun.py b/git/objects/fun.py index 42954fc2..57cefcf2 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -102,13 +102,13 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: return out -def _find_by_name(tree_data: Sequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int +def _find_by_name(tree_data: List[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""" - tree_data_list: List[EntryTupOrNone] = list(tree_data) + tree_data_list: List[EntryTupOrNone] = tree_data try: item = tree_data_list[start_at] if item and item[2] == name and S_ISDIR(item[1]) == is_dir: @@ -160,6 +160,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by 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: @@ -193,8 +194,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by # ti+nt, not ti+1+nt for tio in range(ti + 1, ti + nt): tio = tio % nt - td = trees_data[tio] - entries[tio] = _find_by_name(td, name, is_dir, ii) + entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii) # END for each other item data # if we are a directory, enter recursion diff --git a/git/objects/tree.py b/git/objects/tree.py index 528cf5ca..d3681e23 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -44,7 +44,7 @@ __all__ = ("TreeModifier", "Tree") def git_cmp(t1: TreeCacheTup, t2: TreeCacheTup) -> int: a, b = t1[2], t2[2] - assert isinstance(a, str) and isinstance(b, str) # Need as mypy 9.0 cannot unpack TypeVar properly + # assert isinstance(a, str) and isinstance(b, str) len_a, len_b = len(a), len(b) min_len = min(len_a, len_b) min_cmp = cmp(a[:min_len], b[:min_len]) -- cgit v1.2.1 From 627defff96470464884ca81899fd0271e614b3e8 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 8 Jul 2021 23:55:09 +0100 Subject: Change List to MutableSequence in fun.py _find_by_name() --- git/index/fun.py | 5 +++-- git/objects/fun.py | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 774738db..5c09f2b9 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -388,8 +388,9 @@ def aggressive_tree_merge(odb, tree_shas: Sequence[bytes]) -> List[BaseIndexEntr # all three can't be None if ours is None: # added in their branch - out.append(_tree_entry_to_baseindexentry(theirs, 0)) # type: ignore - elif theirs is None: # ours is not None, theirs is None + assert theirs is not None + out.append(_tree_entry_to_baseindexentry(theirs, 0)) + elif theirs is None: # added in our branch out.append(_tree_entry_to_baseindexentry(ours, 0)) else: diff --git a/git/objects/fun.py b/git/objects/fun.py index 57cefcf2..be541eb8 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -9,7 +9,7 @@ from git.compat import ( # typing ---------------------------------------------- -from typing import Callable, List, Sequence, Tuple, TYPE_CHECKING, Union, overload +from typing import Callable, List, MutableSequence, Sequence, Tuple, TYPE_CHECKING, Union, overload if TYPE_CHECKING: from _typeshed import ReadableBuffer @@ -102,24 +102,24 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: return out -def _find_by_name(tree_data: List[EntryTupOrNone], name: str, is_dir: bool, start_at: int +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""" - tree_data_list: List[EntryTupOrNone] = tree_data + try: - item = tree_data_list[start_at] + item = tree_data[start_at] if item and item[2] == name and S_ISDIR(item[1]) == is_dir: - tree_data_list[start_at] = None + tree_data[start_at] = None return item except IndexError: pass # END exception handling - for index, item in enumerate(tree_data_list): + for index, item in enumerate(tree_data): if item and item[2] == name and S_ISDIR(item[1]) == is_dir: - tree_data_list[index] = None + tree_data[index] = None return item # END if item matches # END for each item -- cgit v1.2.1 From f271c58adb36550a02607811e97cc19feae4bafb Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 00:00:19 +0100 Subject: tests TraversableIterableObj typeguard --- git/index/fun.py | 1 - git/objects/util.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 5c09f2b9..45785687 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -195,7 +195,6 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i assert isinstance(entry_first, BaseIndexEntry) return (entry_first.path, entry_first.stage) else: - # entry = tuple(entry) assert is_entry_key_tup(entry) return entry # END handle entry diff --git a/git/objects/util.py b/git/objects/util.py index 0b449b7b..5de9c3e9 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from .tree import Tree, TraversedTreeTup from subprocess import Popen - + T_TIobj = TypeVar('T_TIobj', bound='TraversableIterableObj') # for TraversableIterableObj.traverse() TraversedTup = Union[Tuple[Union['Traversable', None], 'Traversable'], # for commit, submodule @@ -314,9 +314,9 @@ class Traversable(object): def is_TraversableIterableObj(inp: 'Traversable') -> TypeGuard['TraversableIterableObj']: # return isinstance(self, TraversableIterableObj) - # Can it be anythin else? - return isinstance(self, Traversable) - + # Can it be anything else? Check this + return isinstance(self, TraversableIterableObj) + assert is_TraversableIterableObj(self), f"{type(self)}" out: IterableList['TraversableIterableObj'] = IterableList(self._id_attribute_) out.extend(self.traverse(*args, **kwargs)) @@ -364,7 +364,7 @@ class Traversable(object): Submodule -> Iterator[Submodule, Tuple[Submodule, Submodule]] Tree -> Iterator[Union[Blob, Tree, Submodule, Tuple[Union[Submodule, Tree], Union[Blob, Tree, Submodule]]] - + ignore_self=True is_edge=True -> Iterator[item] ignore_self=True is_edge=False --> Iterator[item] ignore_self=False is_edge=True -> Iterator[item] | Iterator[Tuple[src, item]] -- cgit v1.2.1 From 4802a36bd0fec7e6ae03d6713ceae70de8e1783a Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 00:07:42 +0100 Subject: improve TraversableIterableObj typeguard --- git/objects/util.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 5de9c3e9..5fb4c58a 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -317,10 +317,12 @@ class Traversable(object): # Can it be anything else? Check this return isinstance(self, TraversableIterableObj) - assert is_TraversableIterableObj(self), f"{type(self)}" - out: IterableList['TraversableIterableObj'] = IterableList(self._id_attribute_) - out.extend(self.traverse(*args, **kwargs)) - return out + if is_TraversableIterableObj(self): + out: IterableList['TraversableIterableObj'] = IterableList(self._id_attribute_) + out.extend(self.traverse(*args, **kwargs)) + return out + else: + return IterableList("") # Its a Tree, which doesnt have _id_attribute_ def traverse(self, predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, -- cgit v1.2.1 From 1faa25fcf828062d2c4ad35a962b4d8d3b05e855 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 00:56:20 +0100 Subject: Rmv typeguard from list_traverse(), was wrong --- git/objects/util.py | 30 +++++++++++++----------------- git/util.py | 8 +++++--- 2 files changed, 18 insertions(+), 20 deletions(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 5fb4c58a..7173bc7a 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -23,7 +23,7 @@ from datetime import datetime, timedelta, tzinfo from typing import (Any, Callable, Deque, Iterator, NamedTuple, overload, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast) -from git.types import Literal, TypeGuard +from git.types import Literal if TYPE_CHECKING: from io import BytesIO, StringIO @@ -306,23 +306,19 @@ class Traversable(object): """ raise NotImplementedError("To be implemented in subclass") - def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList['TraversableIterableObj']: + def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList: """ :return: IterableList with the results of the traversal as produced by traverse() - List objects must be IterableObj and Traversable e.g. Commit, Submodule""" - - def is_TraversableIterableObj(inp: 'Traversable') -> TypeGuard['TraversableIterableObj']: - # return isinstance(self, TraversableIterableObj) - # Can it be anything else? Check this - return isinstance(self, TraversableIterableObj) + """ + if isinstance(self, TraversableIterableObj): + id = self._id_attribute_ + else: # Tree + id = "" - if is_TraversableIterableObj(self): - out: IterableList['TraversableIterableObj'] = IterableList(self._id_attribute_) - out.extend(self.traverse(*args, **kwargs)) - return out - else: - return IterableList("") # Its a Tree, which doesnt have _id_attribute_ + out: IterableList = IterableList(id) + out.extend(self.traverse(*args, **kwargs)) + return out def traverse(self, predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, @@ -449,7 +445,7 @@ class TraversableIterableObj(Traversable, IterableObj): TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj] - @overload # type: ignore + @ overload # type: ignore def traverse(self: T_TIobj, predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool], prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool], @@ -459,7 +455,7 @@ class TraversableIterableObj(Traversable, IterableObj): ) -> Iterator[T_TIobj]: ... - @overload + @ overload def traverse(self: T_TIobj, predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool], prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool], @@ -469,7 +465,7 @@ class TraversableIterableObj(Traversable, IterableObj): ) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]: ... - @overload + @ overload def traverse(self: T_TIobj, predicate: Callable[[Union[T_TIobj, TIobj_tuple], int], bool], prune: Callable[[Union[T_TIobj, TIobj_tuple], int], bool], diff --git a/git/util.py b/git/util.py index b13af358..63ac6134 100644 --- a/git/util.py +++ b/git/util.py @@ -36,11 +36,13 @@ if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo from git.config import GitConfigParser, SectionConstraint + from git.objects.base import IndexObject -from .types import (Literal, Protocol, SupportsIndex, # because behind py version guards + +from .types import (Literal, SupportsIndex, # because behind py version guards PathLike, HSH_TD, Total_TD, Files_TD) # aliases -T_IterableObj = TypeVar('T_IterableObj', bound='IterableObj', covariant=True) +T_IterableObj = TypeVar('T_IterableObj', bound=Union['IterableObj', 'IndexObject'], covariant=True) # So IterableList[Head] is subtype of IterableList[IterableObj] # --------------------------------------------------------------------- @@ -1068,7 +1070,7 @@ class Iterable(object): raise NotImplementedError("To be implemented by Subclass") -class IterableObj(Protocol): +class IterableObj(): """Defines an interface for iterable items which is to assure a uniform way to retrieve and iterate items within the git repository -- cgit v1.2.1 From f4cb7dbc707ea83f312aa669f3bea4dc10d3a24c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 10:08:58 +0100 Subject: Change type of list_traverse() again. --- git/objects/util.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 7173bc7a..982e7ac7 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -32,6 +32,7 @@ if TYPE_CHECKING: from .tag import TagObject from .tree import Tree, TraversedTreeTup from subprocess import Popen + from .submodule.base import Submodule T_TIobj = TypeVar('T_TIobj', bound='TraversableIterableObj') # for TraversableIterableObj.traverse() @@ -306,18 +307,28 @@ class Traversable(object): """ raise NotImplementedError("To be implemented in subclass") - def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList: + def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]: """ :return: IterableList with the results of the traversal as produced by traverse() + Commit -> IterableList['Commit'] + Submodule -> IterableList['Submodule'] + Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] """ - if isinstance(self, TraversableIterableObj): + # Commit and Submodule have id.__attribute__ as IterableObj + # Tree has id.__attribute__ inherited from IndexObject + if isinstance(self, (TraversableIterableObj, Tree)): id = self._id_attribute_ - else: # Tree - id = "" + else: + id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_ + # could add _id_attribute_ to Traversable, or make all Traversable also Iterable? + + out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id) + # overloads in subclasses (mypy does't allow typing self: subclass) + # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] - out: IterableList = IterableList(id) - out.extend(self.traverse(*args, **kwargs)) + # NOTE: if is_edge=True, self.traverse returns a Tuple, so should be prevented or flattened? + out.extend(self.traverse(*args, **kwargs)) # type: ignore return out def traverse(self, -- cgit v1.2.1 From 030b1fded8b8e1bcf3855beaf9035b4e3e755f5c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 10:23:14 +0100 Subject: Add list_traverse() to Tree and TraversableIterableObj. --- git/objects/tree.py | 7 +++++-- git/objects/util.py | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'git') diff --git a/git/objects/tree.py b/git/objects/tree.py index d3681e23..804554d8 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -4,7 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.util import join_path +from git.util import IterableList, join_path import git.diff as diff from git.util import to_bin_sha @@ -21,7 +21,7 @@ from .fun import ( # typing ------------------------------------------------- -from typing import (Callable, Dict, Iterable, Iterator, List, +from typing import (Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING) from git.types import PathLike, TypeGuard @@ -323,6 +323,9 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): super(Tree, self).traverse(predicate, prune, depth, # type: ignore branch_first, visit_once, ignore_self)) + def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[Union['Tree', 'Submodule', 'Blob']]: + return super(Tree, self).list_traverse(* args, **kwargs) + # List protocol def __getslice__(self, i: int, j: int) -> List[IndexObjUnion]: diff --git a/git/objects/util.py b/git/objects/util.py index 982e7ac7..4dce0aee 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,6 +19,8 @@ import time import calendar from datetime import datetime, timedelta, tzinfo +from git.objects.base import IndexObject # just for an isinstance check + # typing ------------------------------------------------------------ from typing import (Any, Callable, Deque, Iterator, NamedTuple, overload, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast) @@ -317,7 +319,7 @@ class Traversable(object): """ # Commit and Submodule have id.__attribute__ as IterableObj # Tree has id.__attribute__ inherited from IndexObject - if isinstance(self, (TraversableIterableObj, Tree)): + if isinstance(self, (TraversableIterableObj, IndexObject)): id = self._id_attribute_ else: id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_ @@ -456,6 +458,9 @@ class TraversableIterableObj(Traversable, IterableObj): TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj] + def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]: # type: ignore[override] + return super(TraversableIterableObj, self).list_traverse(* args, **kwargs) + @ overload # type: ignore def traverse(self: T_TIobj, predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool], -- cgit v1.2.1 From 3710e24d6b60213454af10b0dc0ff0c49717169f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 10:33:12 +0100 Subject: Rmv circular import, create Has_id_attribute Protocol instead --- git/objects/tree.py | 2 +- git/objects/util.py | 6 ++---- git/types.py | 10 ++++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'git') diff --git a/git/objects/tree.py b/git/objects/tree.py index 804554d8..e168c6c4 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -323,7 +323,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): super(Tree, self).traverse(predicate, prune, depth, # type: ignore branch_first, visit_once, ignore_self)) - def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[Union['Tree', 'Submodule', 'Blob']]: + def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: return super(Tree, self).list_traverse(* args, **kwargs) # List protocol diff --git a/git/objects/util.py b/git/objects/util.py index 4dce0aee..1c266563 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,13 +19,11 @@ import time import calendar from datetime import datetime, timedelta, tzinfo -from git.objects.base import IndexObject # just for an isinstance check - # typing ------------------------------------------------------------ from typing import (Any, Callable, Deque, Iterator, NamedTuple, overload, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast) -from git.types import Literal +from git.types import Has_id_attribute, Literal if TYPE_CHECKING: from io import BytesIO, StringIO @@ -319,7 +317,7 @@ class Traversable(object): """ # Commit and Submodule have id.__attribute__ as IterableObj # Tree has id.__attribute__ inherited from IndexObject - if isinstance(self, (TraversableIterableObj, IndexObject)): + if isinstance(self, (TraversableIterableObj, Has_id_attribute)): id = self._id_attribute_ else: id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_ diff --git a/git/types.py b/git/types.py index ac1bb2c8..b107c2e1 100644 --- a/git/types.py +++ b/git/types.py @@ -11,9 +11,9 @@ if TYPE_CHECKING: from git.repo import Repo if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 + from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401 else: - from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol # noqa: F401 + from typing_extensions import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401 if sys.version_info[:2] >= (3, 10): from typing import TypeGuard # noqa: F401 @@ -73,5 +73,11 @@ class HSH_TD(TypedDict): files: Dict[PathLike, Files_TD] +@runtime_checkable class Has_Repo(Protocol): repo: 'Repo' + + +@runtime_checkable +class Has_id_attribute(Protocol): + _id_attribute_: str -- cgit v1.2.1 From 5eea8910b2e07d424a2e33299149d13392a80a54 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 10:37:31 +0100 Subject: Fix list_traverse() docstring for Autodoc --- git/objects/tree.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'git') diff --git a/git/objects/tree.py b/git/objects/tree.py index e168c6c4..9d221652 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -324,6 +324,11 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): branch_first, visit_once, ignore_self)) def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: + """ + :return: IterableList with the results of the traversal as produced by + traverse() + Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] + """ return super(Tree, self).list_traverse(* args, **kwargs) # List protocol -- cgit v1.2.1 From 937746291cfdaa40938de03db305b1137c391907 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 11:40:32 +0100 Subject: Make has_repo protocol runtime checkable and use in Diffable --- git/config.py | 4 ++-- git/diff.py | 8 +++++--- git/types.py | 25 ++++++++++++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) (limited to 'git') diff --git a/git/config.py b/git/config.py index 19ce1f84..2c863f93 100644 --- a/git/config.py +++ b/git/config.py @@ -234,8 +234,8 @@ def get_config_path(config_level: Lit_config_levels) -> str: elif config_level == "repository": raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path") else: - # Should not reach here. Will raise ValueError if does. Static typing will warn about extra and missing elifs - assert_never(config_level, ValueError("Invalid configuration level: %r" % config_level)) + # Should not reach here. Will raise ValueError if does. Static typing will warn missing elifs + assert_never(config_level, ValueError(f"Invalid configuration level: {config_level!r}")) class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501 diff --git a/git/diff.py b/git/diff.py index d3b18652..cb216299 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,7 +16,7 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING -from git.types import PathLike, TBD, Literal, TypeGuard +from git.types import Has_Repo, PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree @@ -141,8 +141,10 @@ class Diffable(object): if paths is not None and not isinstance(paths, (tuple, list)): paths = [paths] - if hasattr(self, 'repo'): # else raise Error? - self.repo = self.repo # type: 'Repo' + if isinstance(self, Has_Repo): + self.repo: Repo = self.repo + else: + raise AttributeError("No repo member found, cannot create DiffIndex") diff_cmd = self.repo.git.diff if other is self.Index: diff --git a/git/types.py b/git/types.py index b107c2e1..9181e040 100644 --- a/git/types.py +++ b/git/types.py @@ -4,7 +4,7 @@ import os import sys -from typing import (Callable, Dict, NoReturn, Tuple, Union, Any, Iterator, # noqa: F401 +from typing import (Callable, Dict, NoReturn, Sequence, Tuple, Union, Any, Iterator, # noqa: F401 NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 if TYPE_CHECKING: @@ -37,6 +37,8 @@ _T = TypeVar('_T') Tree_ish = Union['Commit', 'Tree'] Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] +# Config_levels --------------------------------------------------------- + Lit_config_levels = Literal['system', 'global', 'user', 'repository'] @@ -47,12 +49,25 @@ def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]: ConfigLevels_Tup = Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']] +#----------------------------------------------------------------------------------- + + +def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None) -> None: + """For use in exhaustive checking of literal or Enum in if/else chain. + Should only be reached if all memebers not handled OR attempt to pass non-members through chain. + + If all members handled, type is Empty. Otherwise, will cause mypy error. + If non-members given, should cause mypy error at variable creation. -def assert_never(inp: NoReturn, exc: Union[Exception, None] = None) -> NoReturn: - if exc is None: - assert False, f"An unhandled Literal ({inp}) in an if/else chain was found" + If raise_error is True, will also raise AssertionError or the Exception passed to exc. + """ + if raise_error: + if exc is None: + raise ValueError(f"An unhandled Literal ({inp}) in an if/else chain was found") + else: + raise exc else: - raise exc + pass class Files_TD(TypedDict): -- cgit v1.2.1 From 3c6deb002c82c852bbd044fc9af2c1ecc9611efb Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 11:56:16 +0100 Subject: Flatten list_traverse() --- git/objects/util.py | 1 + 1 file changed, 1 insertion(+) (limited to 'git') diff --git a/git/objects/util.py b/git/objects/util.py index 1c266563..d3f3a622 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -328,6 +328,7 @@ class Traversable(object): # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] # NOTE: if is_edge=True, self.traverse returns a Tuple, so should be prevented or flattened? + kwargs['as_edge'] = False out.extend(self.traverse(*args, **kwargs)) # type: ignore return out -- cgit v1.2.1 From a024bddd2a36c67967eda4e9f931c648924f0b19 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 14:27:40 +0100 Subject: Move TraverseNT to global, cos mypy complained on testing --- git/objects/submodule/base.py | 1 + git/objects/util.py | 10 ++++++---- git/util.py | 33 +++++++++++++++++---------------- 3 files changed, 24 insertions(+), 20 deletions(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 5539069c..f366e44c 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -425,6 +425,7 @@ class Submodule(IndexObject, TraversableIterableObj): raise ValueError("A URL was not given and a repository did not exist at %s" % path) # END check url mrepo = sm.module() + assert isinstance(mrepo, Repo) urls = [r.url for r in mrepo.remotes] if not urls: raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath) diff --git a/git/objects/util.py b/git/objects/util.py index d3f3a622..fbe3d9de 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -35,6 +35,12 @@ if TYPE_CHECKING: from .submodule.base import Submodule +class TraverseNT(NamedTuple): + depth: int + item: Union['Traversable', 'Blob'] + src: Union['Traversable', None] + + T_TIobj = TypeVar('T_TIobj', bound='TraversableIterableObj') # for TraversableIterableObj.traverse() TraversedTup = Union[Tuple[Union['Traversable', None], 'Traversable'], # for commit, submodule @@ -379,10 +385,6 @@ class Traversable(object): ignore_self=True is_edge=False --> Iterator[item] ignore_self=False is_edge=True -> Iterator[item] | Iterator[Tuple[src, item]] ignore_self=False is_edge=False -> Iterator[Tuple[src, item]]""" - class TraverseNT(NamedTuple): - depth: int - item: Union['Traversable', 'Blob'] - src: Union['Traversable', None] visited = set() stack = deque() # type: Deque[TraverseNT] diff --git a/git/util.py b/git/util.py index 63ac6134..571e261e 100644 --- a/git/util.py +++ b/git/util.py @@ -36,13 +36,14 @@ if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo from git.config import GitConfigParser, SectionConstraint - from git.objects.base import IndexObject + # from git.objects.base import IndexObject from .types import (Literal, SupportsIndex, # because behind py version guards - PathLike, HSH_TD, Total_TD, Files_TD) # aliases + PathLike, HSH_TD, Total_TD, Files_TD, # aliases + Has_id_attribute) -T_IterableObj = TypeVar('T_IterableObj', bound=Union['IterableObj', 'IndexObject'], covariant=True) +T_IterableObj = TypeVar('T_IterableObj', bound=Union['IterableObj', 'Has_id_attribute'], covariant=True) # So IterableList[Head] is subtype of IterableList[IterableObj] # --------------------------------------------------------------------- @@ -82,7 +83,7 @@ log = logging.getLogger(__name__) HIDE_WINDOWS_KNOWN_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_KNOWN_ERRORS', True) HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRORS', True) -#{ Utility Methods +# { Utility Methods T = TypeVar('T') @@ -247,7 +248,7 @@ def py_where(program: str, path: Optional[PathLike] = None) -> List[str]: def _cygexpath(drive: Optional[str], path: str) -> str: if osp.isabs(path) and not drive: - ## Invoked from `cygpath()` directly with `D:Apps\123`? + # Invoked from `cygpath()` directly with `D:Apps\123`? # It's an error, leave it alone just slashes) p = path # convert to str if AnyPath given else: @@ -265,8 +266,8 @@ def _cygexpath(drive: Optional[str], path: str) -> str: _cygpath_parsers = ( - ## See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - ## and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths + # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + # and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths (re.compile(r"\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\(.*))?"), (lambda server, share, rest_path: '//%s/%s/%s' % (server, share, rest_path.replace('\\', '/'))), False @@ -297,7 +298,7 @@ _cygpath_parsers = ( def cygpath(path: str) -> str: """Use :meth:`git.cmd.Git.polish_url()` instead, that works on any environment.""" path = str(path) # ensure is str and not AnyPath. - #Fix to use Paths when 3.5 dropped. or to be just str if only for urls? + # Fix to use Paths when 3.5 dropped. or to be just str if only for urls? if not path.startswith(('/cygdrive', '//')): for regex, parser, recurse in _cygpath_parsers: match = regex.match(path) @@ -357,7 +358,7 @@ def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool: res = py_where(git_executable) git_dir = osp.dirname(res[0]) if res else "" - ## Just a name given, not a real path. + # Just a name given, not a real path. uname_cmd = osp.join(git_dir, 'uname') process = subprocess.Popen([uname_cmd], stdout=subprocess.PIPE, universal_newlines=True) @@ -378,7 +379,7 @@ def get_user_id() -> str: def finalize_process(proc: subprocess.Popen, **kwargs: Any) -> None: """Wait for the process (clone, fetch, pull or push) and handle its errors accordingly""" - ## TODO: No close proc-streams?? + # TODO: No close proc-streams?? proc.wait(**kwargs) @@ -432,9 +433,9 @@ def remove_password_if_present(cmdline): return new_cmdline -#} END utilities +# } END utilities -#{ Classes +# { Classes class RemoteProgress(object): @@ -984,7 +985,7 @@ class IterableList(List[T_IterableObj]): return False # END handle membership - def __getattr__(self, attr: str) -> Any: + def __getattr__(self, attr: str) -> T_IterableObj: attr = self._prefix + attr for item in self: if getattr(item, self._id_attr) == attr: @@ -992,7 +993,7 @@ class IterableList(List[T_IterableObj]): # END for each item return list.__getattribute__(self, attr) - def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> 'T_IterableObj': # type: ignore assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str" @@ -1007,7 +1008,7 @@ class IterableList(List[T_IterableObj]): raise IndexError("No item found with id %r" % (self._prefix + index)) from e # END handle getattr - def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None: assert isinstance(index, (int, str)), "Index of IterableList should be an int or str" @@ -1101,7 +1102,7 @@ class IterableObj(): :return: iterator yielding Items""" raise NotImplementedError("To be implemented by Subclass") -#} END classes +# } END classes class NullHandler(logging.Handler): -- cgit v1.2.1 From 627166094f9280a3e00b755b754a4bd6ed72bb66 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 14:33:15 +0100 Subject: Rmv submodule.base Repo assert --- git/objects/submodule/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'git') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index f366e44c..b485dbf6 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -425,7 +425,7 @@ class Submodule(IndexObject, TraversableIterableObj): raise ValueError("A URL was not given and a repository did not exist at %s" % path) # END check url mrepo = sm.module() - assert isinstance(mrepo, Repo) + # assert isinstance(mrepo, git.Repo) urls = [r.url for r in mrepo.remotes] if not urls: raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath) -- cgit v1.2.1 From 7c6ae2b94cfd1593c12366b6abc0cd5bbb6e07b2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 15:07:50 +0100 Subject: Try to distinguation git.diff module from diff.Diff.diff and diff.Daffable.diff() --- git/diff.py | 26 +++++++++++++------------- git/index/base.py | 15 ++++++++------- git/objects/tree.py | 4 ++-- git/remote.py | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index cb216299..71bbdf75 100644 --- a/git/diff.py +++ b/git/diff.py @@ -212,19 +212,19 @@ class DiffIndex(List[T_Diff]): if change_type not in self.change_type: raise ValueError("Invalid change type: %s" % change_type) - for diff in self: - if diff.change_type == change_type: - yield diff - elif change_type == "A" and diff.new_file: - yield diff - elif change_type == "D" and diff.deleted_file: - yield diff - elif change_type == "C" and diff.copied_file: - yield diff - elif change_type == "R" and diff.renamed: - yield diff - elif change_type == "M" and diff.a_blob and diff.b_blob and diff.a_blob != diff.b_blob: - yield diff + for diffidx in self: + if diffidx.change_type == change_type: + yield diffidx + elif change_type == "A" and diffidx.new_file: + yield diffidx + elif change_type == "D" and diffidx.deleted_file: + yield diffidx + elif change_type == "C" and diffidx.copied_file: + yield diffidx + elif change_type == "R" and diffidx.renamed: + yield diffidx + elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob: + yield diffidx # END for each diff diff --git a/git/index/base.py b/git/index/base.py index 1812faee..bd3dde99 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -41,7 +41,7 @@ from git.util import ( from gitdb.base import IStream from gitdb.db import MemoryDB -import git.diff as diff +import git.diff as git_diff import os.path as osp from .fun import ( @@ -88,7 +88,7 @@ Treeish = Union[Tree, Commit, str, bytes] __all__ = ('IndexFile', 'CheckoutError') -class IndexFile(LazyMixin, diff.Diffable, Serializable): +class IndexFile(LazyMixin, git_diff.Diffable, Serializable): """ Implements an Index that can be manipulated using a native implementation in @@ -575,8 +575,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): root_tree._cache = tree_items # type: ignore return root_tree - def _process_diff_args(self, args: List[Union[str, diff.Diffable, object]] - ) -> List[Union[str, diff.Diffable, object]]: + def _process_diff_args(self, args: List[Union[str, git_diff.Diffable, object]] + ) -> List[Union[str, git_diff.Diffable, object]]: try: args.pop(args.index(self)) except IndexError: @@ -1272,10 +1272,11 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): return self @ default_index - def diff(self, other: Union[diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = diff.Diffable.Index, + def diff(self, + other: Union[git_diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = git_diff.Diffable.Index, paths: Union[str, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any - ) -> diff.DiffIndex: + ) -> git_diff.DiffIndex: """Diff this index against the working copy or a Tree or Commit object For a documentation of the parameters and return values, see, @@ -1287,7 +1288,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): """ # index against index is always empty if other is self.Index: - return diff.DiffIndex() + return git_diff.DiffIndex() # index against anything but None is a reverse diff with the respective # item. Handle existing -R flags properly. Transform strings to the object diff --git a/git/objects/tree.py b/git/objects/tree.py index 9d221652..4c2a02bf 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -5,7 +5,7 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from git.util import IterableList, join_path -import git.diff as diff +import git.diff as git_diff from git.util import to_bin_sha from . import util @@ -180,7 +180,7 @@ class TreeModifier(object): #} END mutators -class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): +class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): """Tree objects represent an ordered list of Blobs and other Trees. diff --git a/git/remote.py b/git/remote.py index 739424ee..2998b987 100644 --- a/git/remote.py +++ b/git/remote.py @@ -469,7 +469,7 @@ class Remote(LazyMixin, IterableObj): def _config_section_name(self) -> str: return 'remote "%s"' % self.name - def _set_cache_(self, attr: str) -> Any: + def _set_cache_(self, attr: str) -> None: if attr == "_config_reader": # NOTE: This is cached as __getattr__ is overridden to return remote config values implicitly, such as # in print(r.pushurl) -- cgit v1.2.1 From f916c148ea956655837a98817778abe685bf7ee7 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 15:40:14 +0100 Subject: Improve Diffable method typing --- git/diff.py | 32 ++++++++++++++++---------------- git/index/base.py | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 71bbdf75..4024776d 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,13 +16,12 @@ from .objects.util import mode_str_to_int # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING -from git.types import Has_Repo, PathLike, TBD, Literal, TypeGuard +from git.types import PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree from git.repo.base import Repo from git.objects.base import IndexObject - from subprocess import Popen Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U'] @@ -82,7 +81,8 @@ class Diffable(object): class Index(object): pass - def _process_diff_args(self, args: List[Union[str, 'Diffable', object]]) -> List[Union[str, 'Diffable', object]]: + def _process_diff_args(self, args: List[Union[PathLike, 'Diffable', Type['Diffable.Index']]] + ) -> List[Union[PathLike, 'Diffable', Type['Diffable.Index']]]: """ :return: possibly altered version of the given args list. @@ -90,7 +90,7 @@ class Diffable(object): Subclasses can use it to alter the behaviour of the superclass""" return args - def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Index, + def diff(self, other: Union[Type['Index'], 'Tree', None, str] = Index, paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any) -> 'DiffIndex': """Creates diffs between two items being trees, trees and index or an @@ -123,7 +123,7 @@ class Diffable(object): :note: On a bare repository, 'other' needs to be provided as Index or as as Tree/Commit, or a git command error will occur""" - args = [] # type: List[Union[str, Diffable, object]] + args: List[Union[PathLike, Diffable, Type['Diffable.Index']]] = [] args.append("--abbrev=40") # we need full shas args.append("--full-index") # get full index paths, not only filenames @@ -141,7 +141,7 @@ class Diffable(object): if paths is not None and not isinstance(paths, (tuple, list)): paths = [paths] - if isinstance(self, Has_Repo): + if hasattr(self, 'Has_Repo'): self.repo: Repo = self.repo else: raise AttributeError("No repo member found, cannot create DiffIndex") @@ -400,36 +400,36 @@ class Diff(object): # end return res - @property + @ property def a_path(self) -> Optional[str]: return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None - @property + @ property def b_path(self) -> Optional[str]: return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None - @property + @ property def rename_from(self) -> Optional[str]: return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None - @property + @ property def rename_to(self) -> Optional[str]: return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None - @property + @ property def renamed(self) -> bool: """:returns: True if the blob of our diff has been renamed :note: This property is deprecated, please use ``renamed_file`` instead. """ return self.renamed_file - @property + @ property def renamed_file(self) -> bool: """:returns: True if the blob of our diff has been renamed """ return self.rename_from != self.rename_to - @classmethod + @ classmethod def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]: if path_match: return decode_path(path_match) @@ -442,7 +442,7 @@ class Diff(object): return None - @classmethod + @ classmethod def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex: """Create a new DiffIndex from the given text which must be in patch format :param repo: is the repository we are operating on - it is required @@ -505,7 +505,7 @@ class Diff(object): return index - @staticmethod + @ staticmethod def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> None: lines = lines_bytes.decode(defenc) @@ -559,7 +559,7 @@ class Diff(object): '', change_type, score) index.append(diff) - @classmethod + @ classmethod def _index_from_raw_format(cls, repo: 'Repo', proc: 'Popen') -> 'DiffIndex': """Create a new DiffIndex from the given stream which must be in raw format. :return: git.DiffIndex""" diff --git a/git/index/base.py b/git/index/base.py index bd3dde99..6738e223 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -68,7 +68,7 @@ from .util import ( # typing ----------------------------------------------------------------------------- from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, NoReturn, - Sequence, TYPE_CHECKING, Tuple, Union) + Sequence, TYPE_CHECKING, Tuple, Type, Union) from git.types import Commit_ish, PathLike, TBD @@ -575,8 +575,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): root_tree._cache = tree_items # type: ignore return root_tree - def _process_diff_args(self, args: List[Union[str, git_diff.Diffable, object]] - ) -> List[Union[str, git_diff.Diffable, object]]: + def _process_diff_args(self, args: List[Union[PathLike, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]] + ) -> List[Union[PathLike, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]]: try: args.pop(args.index(self)) except IndexError: -- cgit v1.2.1 From e7b685db1bf4d9d6aa3f95f4df3fda5992dab14c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 15:49:32 +0100 Subject: Rmv Diffable assert, add Remoote.url property --- git/diff.py | 2 -- git/remote.py | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 4024776d..1e2ee740 100644 --- a/git/diff.py +++ b/git/diff.py @@ -143,8 +143,6 @@ class Diffable(object): if hasattr(self, 'Has_Repo'): self.repo: Repo = self.repo - else: - raise AttributeError("No repo member found, cannot create DiffIndex") diff_cmd = self.repo.git.diff if other is self.Index: diff --git a/git/remote.py b/git/remote.py index 2998b987..3c3d3c48 100644 --- a/git/remote.py +++ b/git/remote.py @@ -558,6 +558,14 @@ class Remote(LazyMixin, IterableObj): """ return self.set_url(url, delete=True) + @property + def url(self) -> Union[str, List[str]]: + url_list = list(self.urls) + if len(url_list) == 1: + return url_list[0] + else: + return url_list + @property def urls(self) -> Iterator[str]: """:return: Iterator yielding all configured URL targets on a remote as strings""" -- cgit v1.2.1 From 9bb630f03a276a4f1ecc6d6909f82dc90f533026 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 15:53:45 +0100 Subject: Add remote.url type --- git/remote.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'git') diff --git a/git/remote.py b/git/remote.py index 3c3d3c48..f59b3245 100644 --- a/git/remote.py +++ b/git/remote.py @@ -449,8 +449,9 @@ class Remote(LazyMixin, IterableObj): :param repo: The repository we are a remote of :param name: the name of the remote, i.e. 'origin'""" - self.repo = repo # type: 'Repo' + self.repo = repo self.name = name + self.url: str def __getattr__(self, attr: str) -> Any: """Allows to call this instance like @@ -558,15 +559,7 @@ class Remote(LazyMixin, IterableObj): """ return self.set_url(url, delete=True) - @property - def url(self) -> Union[str, List[str]]: - url_list = list(self.urls) - if len(url_list) == 1: - return url_list[0] - else: - return url_list - - @property + @ property def urls(self) -> Iterator[str]: """:return: Iterator yielding all configured URL targets on a remote as strings""" try: @@ -599,7 +592,7 @@ class Remote(LazyMixin, IterableObj): else: raise ex - @property + @ property def refs(self) -> IterableList[RemoteReference]: """ :return: @@ -610,7 +603,7 @@ class Remote(LazyMixin, IterableObj): out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name)) return out_refs - @property + @ property def stale_refs(self) -> IterableList[Reference]: """ :return: @@ -644,7 +637,7 @@ class Remote(LazyMixin, IterableObj): # END for each line return out_refs - @classmethod + @ classmethod def create(cls, repo: 'Repo', name: str, url: str, **kwargs: Any) -> 'Remote': """Create a new remote to the given repository :param repo: Repository instance that is to receive the new remote @@ -661,7 +654,7 @@ class Remote(LazyMixin, IterableObj): # add is an alias add = create - @classmethod + @ classmethod def remove(cls, repo: 'Repo', name: str) -> str: """Remove the remote with the given name :return: the passed remote name to remove -- cgit v1.2.1 From b03af0547f5381cf4043a43acf533687d91f0ea1 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 16:12:40 +0100 Subject: Remove defsult_index decorator from diff() and do check within function. Breaks typechecking for some reason --- git/index/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 6738e223..149edf5a 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -1271,10 +1271,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): return self - @ default_index + # @ default_index, breaks typing for some reason, copied into function def diff(self, other: Union[git_diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = git_diff.Diffable.Index, - paths: Union[str, List[PathLike], Tuple[PathLike, ...], None] = None, + paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any ) -> git_diff.DiffIndex: """Diff this index against the working copy or a Tree or Commit object @@ -1286,6 +1286,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): Will only work with indices that represent the default git index as they have not been initialized with a stream. """ + + # only run if we are the default repository index + if self._file_path != self._index_path(): + raise AssertionError( + "Cannot call %r on indices that do not represent the default git index" % self.diff()) # index against index is always empty if other is self.Index: return git_diff.DiffIndex() -- cgit v1.2.1 From 797e962fc1811ddc5a5a34308bd243953eb77135 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 16:27:34 +0100 Subject: Make IndexFile and Diffable .diff() types agree --- git/diff.py | 3 ++- git/index/base.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 1e2ee740..7ca98ec3 100644 --- a/git/diff.py +++ b/git/diff.py @@ -20,6 +20,7 @@ from git.types import PathLike, TBD, Literal, TypeGuard if TYPE_CHECKING: from .objects.tree import Tree + from .objects import Commit from git.repo.base import Repo from git.objects.base import IndexObject from subprocess import Popen @@ -90,7 +91,7 @@ class Diffable(object): Subclasses can use it to alter the behaviour of the superclass""" return args - def diff(self, other: Union[Type['Index'], 'Tree', None, str] = Index, + def diff(self, other: Union[Type['Index'], 'Tree', 'Commit', None, str] = Index, # object for git.NULL_TREE paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any) -> 'DiffIndex': """Creates diffs between two items being trees, trees and index or an diff --git a/git/index/base.py b/git/index/base.py index 149edf5a..75df5184 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -1273,7 +1273,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): # @ default_index, breaks typing for some reason, copied into function def diff(self, - other: Union[git_diff.Diffable.Index, 'IndexFile.Index', Treeish, None, object] = git_diff.Diffable.Index, + other: Union[Type['git_diff.Diffable.Index'], 'IndexFile.Index', + 'Tree', 'Commit', str, None] = git_diff.Diffable.Index, paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any ) -> git_diff.DiffIndex: -- cgit v1.2.1 From 09053c565915d114384b1c20af8eecfed98c8069 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 22:58:02 +0100 Subject: Improve IndexFile_process_diff_args() to get checks to rerun --- git/diff.py | 8 ++++---- git/index/base.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'git') diff --git a/git/diff.py b/git/diff.py index 7ca98ec3..51dac390 100644 --- a/git/diff.py +++ b/git/diff.py @@ -82,8 +82,8 @@ class Diffable(object): class Index(object): pass - def _process_diff_args(self, args: List[Union[PathLike, 'Diffable', Type['Diffable.Index']]] - ) -> List[Union[PathLike, 'Diffable', Type['Diffable.Index']]]: + def _process_diff_args(self, args: List[Union[str, 'Diffable', Type['Diffable.Index'], object]] + ) -> List[Union[str, 'Diffable', Type['Diffable.Index'], object]]: """ :return: possibly altered version of the given args list. @@ -91,7 +91,7 @@ class Diffable(object): Subclasses can use it to alter the behaviour of the superclass""" return args - def diff(self, other: Union[Type['Index'], 'Tree', 'Commit', None, str] = Index, # object for git.NULL_TREE + def diff(self, other: Union[Type['Index'], 'Tree', 'Commit', None, str, object] = Index, paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any) -> 'DiffIndex': """Creates diffs between two items being trees, trees and index or an @@ -124,7 +124,7 @@ class Diffable(object): :note: On a bare repository, 'other' needs to be provided as Index or as as Tree/Commit, or a git command error will occur""" - args: List[Union[PathLike, Diffable, Type['Diffable.Index']]] = [] + args: List[Union[PathLike, Diffable, Type['Diffable.Index'], object]] = [] args.append("--abbrev=40") # we need full shas args.append("--full-index") # get full index paths, not only filenames diff --git a/git/index/base.py b/git/index/base.py index 75df5184..6f6ea5aa 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -575,8 +575,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): root_tree._cache = tree_items # type: ignore return root_tree - def _process_diff_args(self, args: List[Union[PathLike, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]] - ) -> List[Union[PathLike, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]]: + def _process_diff_args(self, # type: ignore[override] + args: List[Union[str, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]] + ) -> List[Union[str, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]]: try: args.pop(args.index(self)) except IndexError: @@ -1272,9 +1273,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): return self # @ default_index, breaks typing for some reason, copied into function - def diff(self, - other: Union[Type['git_diff.Diffable.Index'], 'IndexFile.Index', - 'Tree', 'Commit', str, None] = git_diff.Diffable.Index, + def diff(self, # type: ignore[override] + other: Union[Type['git_diff.Diffable.Index'], 'Tree', 'Commit', str, None] = git_diff.Diffable.Index, paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None, create_patch: bool = False, **kwargs: Any ) -> git_diff.DiffIndex: -- cgit v1.2.1 From 2ea528e9fbcac850d99ce527ad4a5e4afb3587a8 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 23:21:16 +0100 Subject: Fix typing of index.fun.write_tree_from_cache() --- git/index/base.py | 2 +- git/index/fun.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'git') diff --git a/git/index/base.py b/git/index/base.py index 6f6ea5aa..3aa06e38 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -572,7 +572,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): # note: additional deserialization could be saved if write_tree_from_cache # would return sorted tree entries root_tree = Tree(self.repo, binsha, path='') - root_tree._cache = tree_items # type: ignore + root_tree._cache = tree_items # type: ignore # should this be encoded to [bytes, int, str]? return root_tree def _process_diff_args(self, # type: ignore[override] diff --git a/git/index/fun.py b/git/index/fun.py index 45785687..96833a7a 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -249,7 +249,7 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 - ) -> Tuple[bytes, List[Tuple[str, int, str]]]: + ) -> Tuple[bytes, List[Tuple[bytes, int, str]]]: """Create a tree from the given sorted list of entries and put the respective trees into the given object database @@ -298,12 +298,11 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 # finally create the tree sio = BytesIO() - tree_to_stream(tree_items, sio.write) # converts bytes of each item[0] to str - tree_items_stringified = cast(List[Tuple[str, int, str]], tree_items) + tree_to_stream(tree_items, sio.write) # writes to stream as bytes, but doesnt change tree_items sio.seek(0) istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio)) - return (istream.binsha, tree_items_stringified) + return (istream.binsha, tree_items) def _tree_entry_to_baseindexentry(tree_entry: Tuple[bytes, int, str], stage: int) -> BaseIndexEntry: -- cgit v1.2.1 From e6a27adb71d21c81628acbdd65bf07037604ff90 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 23:33:53 +0100 Subject: Use TreeCacheTup type alias throughout --- git/index/fun.py | 7 ++++--- git/objects/fun.py | 2 +- git/objects/tree.py | 14 ++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index 96833a7a..df74c2c1 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -57,6 +57,7 @@ from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile + from objects.tree import TreeCacheTup # from git.objects.fun import EntryTupOrNone # ------------------------------------------------------------------------------------ @@ -249,7 +250,7 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 - ) -> Tuple[bytes, List[Tuple[bytes, int, str]]]: + ) -> Tuple[bytes, List[TreeCacheTup]]: """Create a tree from the given sorted list of entries and put the respective trees into the given object database @@ -259,7 +260,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 :param sl: slice indicating the range we should process on the entries list :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name""" - tree_items: List[Tuple[bytes, int, str]] = [] + tree_items: List[TreeCacheTup] = [] ci = sl.start end = sl.stop @@ -305,7 +306,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 return (istream.binsha, tree_items) -def _tree_entry_to_baseindexentry(tree_entry: Tuple[bytes, int, str], stage: int) -> BaseIndexEntry: +def _tree_entry_to_baseindexentry(tree_entry: TreeCacheTup, stage: int) -> BaseIndexEntry: return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) diff --git a/git/objects/fun.py b/git/objects/fun.py index be541eb8..fc2ea1e7 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -215,7 +215,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by return out -def traverse_tree_recursive(odb: 'GitCmdObjectDB', tree_sha: bytes, path_prefix: str) -> List[Tuple[bytes, int, str]]: +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: diff --git a/git/objects/tree.py b/git/objects/tree.py index 4c2a02bf..a9656c1d 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -31,9 +31,14 @@ if TYPE_CHECKING: from io import BytesIO TreeCacheTup = Tuple[bytes, int, str] + TraversedTreeTup = Union[Tuple[Union['Tree', None], IndexObjUnion, Tuple['Submodule', 'Submodule']]] + +def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]: + return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str) + #-------------------------------------------------------- @@ -141,11 +146,8 @@ class TreeModifier(object): sha = to_bin_sha(sha) index = self._index_by_name(name) - def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]: - return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str) - item = (sha, mode, name) - assert is_tree_cache(item) + # assert is_tree_cache(item) if index == -1: self._cache.append(item) @@ -223,12 +225,12 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): if attr == "_cache": # Set the data when we need it ostream = self.repo.odb.stream(self.binsha) - self._cache: List[Tuple[bytes, int, str]] = tree_entries_from_data(ostream.read()) + self._cache: List[TreeCacheTup] = tree_entries_from_data(ostream.read()) else: super(Tree, self)._set_cache_(attr) # END handle attribute - def _iter_convert_to_object(self, iterable: Iterable[Tuple[bytes, int, str]] + def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup] ) -> Iterator[IndexObjUnion]: """Iterable yields tuples of (binsha, mode, name), which will be converted to the respective object representation""" -- cgit v1.2.1 From 94c66525a6e7d5c74a9aee65d14630bb674439f7 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Fri, 9 Jul 2021 23:36:35 +0100 Subject: Make TreeCacheTup forward ref --- git/index/fun.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'git') diff --git a/git/index/fun.py b/git/index/fun.py index df74c2c1..e5e566a0 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -57,7 +57,7 @@ from git.types import PathLike, TypeGuard if TYPE_CHECKING: from .base import IndexFile - from objects.tree import TreeCacheTup + from git.objects.tree import TreeCacheTup # from git.objects.fun import EntryTupOrNone # ------------------------------------------------------------------------------------ @@ -250,7 +250,7 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 - ) -> Tuple[bytes, List[TreeCacheTup]]: + ) -> Tuple[bytes, List['TreeCacheTup']]: """Create a tree from the given sorted list of entries and put the respective trees into the given object database @@ -260,7 +260,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 :param sl: slice indicating the range we should process on the entries list :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name""" - tree_items: List[TreeCacheTup] = [] + tree_items: List['TreeCacheTup'] = [] ci = sl.start end = sl.stop @@ -306,7 +306,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb, sl: slice, si: int = 0 return (istream.binsha, tree_items) -def _tree_entry_to_baseindexentry(tree_entry: TreeCacheTup, stage: int) -> BaseIndexEntry: +def _tree_entry_to_baseindexentry(tree_entry: 'TreeCacheTup', stage: int) -> BaseIndexEntry: return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) -- cgit v1.2.1