diff options
author | yobmod <yobmod@gmail.com> | 2021-03-02 21:46:17 +0000 |
---|---|---|
committer | yobmod <yobmod@gmail.com> | 2021-03-02 21:46:17 +0000 |
commit | 2fd9f6ee5c8b4ae4e01a40dc398e2768d838210d (patch) | |
tree | 93a63a1bee90204f68cf41dc08484ad4adab7ad4 | |
parent | 71e28b8e2ac1b8bc8990454721740b2073829110 (diff) | |
download | gitpython-2fd9f6ee5c8b4ae4e01a40dc398e2768d838210d.tar.gz |
add types to git.compat and git.diff
-rw-r--r-- | git/compat.py | 16 | ||||
-rw-r--r-- | git/db.py | 5 | ||||
-rw-r--r-- | git/diff.py | 114 | ||||
-rw-r--r-- | git/exc.py | 11 | ||||
-rw-r--r-- | git/util.py | 9 |
5 files changed, 91 insertions, 64 deletions
diff --git a/git/compat.py b/git/compat.py index 8d9e551d..4fe394ae 100644 --- a/git/compat.py +++ b/git/compat.py @@ -10,14 +10,15 @@ import locale import os import sys -from typing import AnyStr, Optional, Type - from gitdb.utils.encoding import ( force_bytes, # @UnusedImport force_text # @UnusedImport ) +from typing import Any, AnyStr, Dict, Optional, Type +from git.types import TBD + is_win = (os.name == 'nt') # type: bool is_posix = (os.name == 'posix') @@ -61,14 +62,17 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]: return None -def with_metaclass(meta, *bases): +def with_metaclass(meta: Type[Any], *bases: Any) -> 'metaclass': # type: ignore ## mypy cannot understand dynamic class creation """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15""" - class metaclass(meta): + + class metaclass(meta): # type: ignore __call__ = type.__call__ - __init__ = type.__init__ + __init__ = type.__init__ # type: ignore - def __new__(cls, name, nbases, d): + def __new__(cls, name: str, nbases: Optional[int], d: Dict[str, Any]) -> TBD: if nbases is None: return type.__new__(cls, name, (), d) return meta(name, bases, d) + return metaclass(meta.__name__ + 'Helper', None, {}) + @@ -1,4 +1,5 @@ """Module with our own gitdb implementation - it uses the git command""" +from typing import AnyStr from git.util import bin_to_hex, hex_to_bin from gitdb.base import ( OInfo, @@ -13,7 +14,7 @@ from .exc import GitCommandError # typing------------------------------------------------- from .cmd import Git -from .types import PathLike, TBD +from .types import PathLike # -------------------------------------------------------- @@ -48,7 +49,7 @@ class GitCmdObjectDB(LooseObjectDB): # { Interface - def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes: + def partial_to_complete_sha_hex(self, partial_hexsha: AnyStr) -> bytes: """:return: Full binary 20 byte sha from the given partial hexsha :raise AmbiguousObjectName: :raise BadObject: diff --git a/git/diff.py b/git/diff.py index a9dc4b57..b25aadc7 100644 --- a/git/diff.py +++ b/git/diff.py @@ -3,8 +3,8 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -import re +import re from git.cmd import handle_process_output from git.compat import defenc from git.util import finalize_process, hex_to_bin @@ -13,22 +13,33 @@ from .objects.blob import Blob from .objects.util import mode_str_to_int +# typing ------------------------------------------------------------------ + +from .objects.tree import Tree +from git.repo.base import Repo +from typing_extensions import Final, Literal +from git.types import TBD +from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union +Lit_change_type = Literal['A', 'D', 'M', 'R', 'T'] + +# ------------------------------------------------------------------------ + __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') # Special object to compare against the empty tree in diffs -NULL_TREE = object() +NULL_TREE: Final[object] = object() _octal_byte_re = re.compile(b'\\\\([0-9]{3})') -def _octal_repl(matchobj): +def _octal_repl(matchobj: Match) -> bytes: value = matchobj.group(1) value = int(value, 8) value = bytes(bytearray((value,))) return value -def decode_path(path, has_ab_prefix=True): +def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]: if path == b'/dev/null': return None @@ -60,7 +71,7 @@ class Diffable(object): class Index(object): pass - def _process_diff_args(self, args): + def _process_diff_args(self, args: List[Union[str, 'Diffable', object]]) -> List[Union[str, 'Diffable', object]]: """ :return: possibly altered version of the given args list. @@ -68,7 +79,9 @@ class Diffable(object): Subclasses can use it to alter the behaviour of the superclass""" return args - def diff(self, other=Index, paths=None, create_patch=False, **kwargs): + def diff(self, other: Union[Type[Index], Type[Tree], object, None, str] = Index, + paths: Union[str, List[str], Tuple[str, ...], None] = None, + create_patch: bool = False, **kwargs: Any) -> 'DiffIndex': """Creates diffs between two items being trees, trees and index or an index and the working tree. It will detect renames automatically. @@ -99,7 +112,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 = [] + args = [] # type: List[Union[str, Diffable, object]] args.append("--abbrev=40") # we need full shas args.append("--full-index") # get full index paths, not only filenames @@ -117,6 +130,9 @@ 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' + diff_cmd = self.repo.git.diff if other is self.Index: args.insert(0, '--cached') @@ -163,7 +179,7 @@ class DiffIndex(list): # T = Changed in the type change_type = ("A", "C", "D", "R", "M", "T") - def iter_change_type(self, change_type): + def iter_change_type(self, change_type: Lit_change_type) -> Iterator['Diff']: """ :return: iterator yielding Diff instances that match the given change_type @@ -180,7 +196,7 @@ class DiffIndex(list): if change_type not in self.change_type: raise ValueError("Invalid change type: %s" % change_type) - for diff in self: + for diff in self: # type: 'Diff' if diff.change_type == change_type: yield diff elif change_type == "A" and diff.new_file: @@ -255,22 +271,21 @@ class Diff(object): "new_file", "deleted_file", "copied_file", "raw_rename_from", "raw_rename_to", "diff", "change_type", "score") - def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode, - b_mode, new_file, deleted_file, copied_file, raw_rename_from, - raw_rename_to, diff, change_type, score): - - self.a_mode = a_mode - self.b_mode = b_mode + def __init__(self, repo: Repo, + a_rawpath: Optional[bytes], b_rawpath: Optional[bytes], + a_blob_id: Union[str, bytes, None], b_blob_id: Union[str, bytes, None], + 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: assert a_rawpath is None or isinstance(a_rawpath, bytes) assert b_rawpath is None or isinstance(b_rawpath, bytes) self.a_rawpath = a_rawpath self.b_rawpath = b_rawpath - if self.a_mode: - self.a_mode = mode_str_to_int(self.a_mode) - if self.b_mode: - self.b_mode = mode_str_to_int(self.b_mode) + self.a_mode = mode_str_to_int(a_mode) if a_mode else None + self.b_mode = mode_str_to_int(b_mode) if b_mode else None # Determine whether this diff references a submodule, if it does then # we need to overwrite "repo" to the corresponding submodule's repo instead @@ -305,27 +320,27 @@ class Diff(object): self.change_type = change_type self.score = score - def __eq__(self, other): + def __eq__(self, other: object) -> bool: for name in self.__slots__: if getattr(self, name) != getattr(other, name): return False # END for each name return True - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash(tuple(getattr(self, n) for n in self.__slots__)) - def __str__(self): - h = "%s" + def __str__(self) -> str: + h = "%s" # type: str if self.a_blob: h %= self.a_blob.path elif self.b_blob: h %= self.b_blob.path - msg = '' + msg = '' # type: str line = None # temp line line_length = 0 # line length for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')): @@ -354,7 +369,7 @@ class Diff(object): if self.diff: msg += '\n---' try: - msg += self.diff.decode(defenc) + msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff except UnicodeDecodeError: msg += 'OMITTED BINARY DATA' # end handle encoding @@ -368,36 +383,36 @@ class Diff(object): return res @property - def a_path(self): + def a_path(self) -> Optional[str]: return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None @property - def b_path(self): + def b_path(self) -> Optional[str]: return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None @property - def rename_from(self): + def rename_from(self) -> Optional[str]: return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None @property - def rename_to(self): + def rename_to(self) -> Optional[str]: return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None @property - def renamed(self): + 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 - def renamed_file(self): + def renamed_file(self) -> bool: """:returns: True if the blob of our diff has been renamed """ return self.rename_from != self.rename_to @classmethod - def _pick_best_path(cls, path_match, rename_match, path_fallback_match): + 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) @@ -410,21 +425,23 @@ class Diff(object): return None @classmethod - def _index_from_patch_format(cls, repo, proc): + 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 :param stream: result of 'git diff' as a stream (supporting file protocol) :return: git.DiffIndex """ ## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise. - text = [] - handle_process_output(proc, text.append, None, finalize_process, decode_streams=False) + text_list = [] # type: List[bytes] + handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False) # for now, we have to bake the stream - text = b''.join(text) + text = b''.join(text_list) index = DiffIndex() previous_header = None header = None + a_path, b_path = None, None # for mypy + a_mode, b_mode = None, None # for mypy for _header in cls.re_header.finditer(text): a_path_fallback, b_path_fallback, \ old_mode, new_mode, \ @@ -464,14 +481,14 @@ class Diff(object): previous_header = _header header = _header # end for each header we parse - if index: + if index and header: index[-1].diff = text[header.end():] # end assign last diff return index @classmethod - def _index_from_raw_format(cls, repo, proc): + def _index_from_raw_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex: """Create a new DiffIndex from the given stream which must be in raw format. :return: git.DiffIndex""" # handles @@ -479,12 +496,13 @@ class Diff(object): index = DiffIndex() - def handle_diff_line(lines): - lines = lines.decode(defenc) + def handle_diff_line(lines_bytes: bytes) -> None: + lines = lines_bytes.decode(defenc) 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] old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4) # Change type can be R100 # R: status letter @@ -504,20 +522,20 @@ 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 new_file = True elif change_type == 'C': copied_file = True - a_path, b_path = path.split('\x00', 1) - a_path = a_path.encode(defenc) - b_path = b_path.encode(defenc) + a_path_str, b_path_str = path.split('\x00', 1) + a_path = a_path_str.encode(defenc) + b_path = b_path_str.encode(defenc) elif change_type == 'R': - a_path, b_path = path.split('\x00', 1) - a_path = a_path.encode(defenc) - b_path = b_path.encode(defenc) + a_path_str, b_path_str = path.split('\x00', 1) + a_path = a_path_str.encode(defenc) + b_path = b_path_str.encode(defenc) rename_from, rename_to = a_path, b_path elif change_type == 'T': # Nothing to do @@ -12,10 +12,11 @@ from git.compat import safe_decode from git.repo.base import Repo from git.types import PathLike -from typing import IO, List, Optional, Sequence, Tuple, Union +from typing import IO, List, Optional, Tuple, Union # ------------------------------------------------------------------ + class GitError(Exception): """ Base class for all package exceptions """ @@ -44,7 +45,7 @@ class CommandError(GitError): #: "'%s' failed%s" _msg = "Cmd('%s') failed%s" - def __init__(self, command: Union[List[str], Tuple[str, ...], str], + def __init__(self, command: Union[List[str], Tuple[str, ...], str], status: Union[str, None, Exception] = None, stderr: Optional[IO[str]] = None, stdout: Optional[IO[str]] = None) -> None: if not isinstance(command, (tuple, list)): @@ -84,7 +85,7 @@ class GitCommandNotFound(CommandError): class GitCommandError(CommandError): """ Thrown if execution of the git command fails with non-zero status code. """ - def __init__(self, command: Union[List[str], Tuple[str, ...], str], + def __init__(self, command: Union[List[str], Tuple[str, ...], str], status: Union[str, None, Exception] = None, stderr: Optional[IO[str]] = None, stdout: Optional[IO[str]] = None, @@ -106,7 +107,9 @@ class CheckoutError(GitError): were checked out successfully and hence match the version stored in the index""" - def __init__(self, message: str, failed_files: List[PathLike], valid_files: List[PathLike], failed_reasons: List[str]) -> None: + def __init__(self, message: str, failed_files: List[PathLike], valid_files: List[PathLike], + failed_reasons: List[str]) -> None: + Exception.__init__(self, message) self.failed_files = failed_files self.failed_reasons = failed_reasons diff --git a/git/util.py b/git/util.py index b5cce59d..2b0c8171 100644 --- a/git/util.py +++ b/git/util.py @@ -4,7 +4,6 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php from git.remote import Remote -from _typeshed import ReadableBuffer import contextlib from functools import wraps import getpass @@ -19,12 +18,14 @@ from sys import maxsize import time from unittest import SkipTest -from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, List, NoReturn, Optional, Pattern, - Sequence, TextIO, Tuple, Union, cast) -from typing_extensions import Literal +# typing --------------------------------------------------------- +from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, List, + NoReturn, Optional, Pattern, Sequence, Tuple, Union, cast) from git.repo.base import Repo from .types import PathLike, TBD +# --------------------------------------------------------------------- + from gitdb.util import ( # NOQA @IgnorePep8 make_sha, |