From 11837f61aa4b5c286c6ee9870e23a7ee342858c5 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 18 May 2021 13:11:25 +0100 Subject: [PATCH 01/21] Add types to objects.base.py --- git/diff.py | 4 +-- git/index/fun.py | 2 +- git/objects/base.py | 63 +++++++++++++++++++++++++++++++-------------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/git/diff.py b/git/diff.py index a40fc244e..346a2ca7b 100644 --- a/git/diff.py +++ b/git/diff.py @@ -16,7 +16,7 @@ # typing ------------------------------------------------------------------ from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING -from git.types import PathLike, TBD, Final, Literal +from git.types import PathLike, TBD, Literal if TYPE_CHECKING: from .objects.tree import Tree @@ -31,7 +31,7 @@ __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') # Special object to compare against the empty tree in diffs -NULL_TREE = object() # type: Final[object] +NULL_TREE = object() _octal_byte_re = re.compile(b'\\\\([0-9]{3})') diff --git a/git/index/fun.py b/git/index/fun.py index f40928c33..96d9b4755 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -108,7 +108,7 @@ def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None: # end handle return code -def stat_mode_to_index_mode(mode): +def stat_mode_to_index_mode(mode: int) -> int: """Convert the given mode from a stat call to the corresponding index mode and return it""" if S_ISLNK(mode): # symlinks diff --git a/git/objects/base.py b/git/objects/base.py index 59f0e8368..e50387468 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -3,16 +3,34 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php + +from git.exc import WorkTreeRepositoryUnsupported from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex import gitdb.typ as dbtyp import os.path as osp -from typing import Optional # noqa: F401 unused import from .util import get_object_type_by_name -_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r" +# typing ------------------------------------------------------------------ + +from typing import Any, TYPE_CHECKING, Optional, Union + +from git.types import PathLike + +if TYPE_CHECKING: + from git.repo import Repo + from gitdb.base import OStream + from .tree import Tree + from .blob import Blob + from .tag import TagObject + from .commit import Commit + +# -------------------------------------------------------------------------- + + +_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutual git object type %r" __all__ = ("Object", "IndexObject") @@ -27,7 +45,7 @@ class Object(LazyMixin): __slots__ = ("repo", "binsha", "size") type = None # type: Optional[str] # to be set by subclass - def __init__(self, repo, binsha): + def __init__(self, repo: 'Repo', binsha: bytes): """Initialize an object by identifying it by its binary sha. All keyword arguments will be set on demand if None. @@ -40,7 +58,7 @@ def __init__(self, repo, binsha): assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha)) @classmethod - def new(cls, repo, id): # @ReservedAssignment + def new(cls, repo: 'Repo', id): # @ReservedAssignment """ :return: New Object instance of a type appropriate to the object type behind id. The id of the newly created object will be a binsha even though @@ -53,7 +71,7 @@ def new(cls, repo, id): # @ReservedAssignment return repo.rev_parse(str(id)) @classmethod - def new_from_sha(cls, repo, sha1): + def new_from_sha(cls, repo: 'Repo', sha1: bytes) -> Union['Commit', 'TagObject', 'Tree', 'Blob']: """ :return: new object instance of a type appropriate to represent the given binary sha1 @@ -67,7 +85,7 @@ def new_from_sha(cls, repo, sha1): inst.size = oinfo.size return inst - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: """Retrieve object information""" if attr == "size": oinfo = self.repo.odb.info(self.binsha) @@ -76,43 +94,43 @@ def _set_cache_(self, attr): else: super(Object, self)._set_cache_(attr) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """:return: True if the objects have the same SHA1""" if not hasattr(other, 'binsha'): return False return self.binsha == other.binsha - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: """:return: True if the objects do not have the same SHA1 """ if not hasattr(other, 'binsha'): return True return self.binsha != other.binsha - def __hash__(self): + def __hash__(self) -> int: """:return: Hash of our id allowing objects to be used in dicts and sets""" return hash(self.binsha) - def __str__(self): + def __str__(self) -> str: """:return: string of our SHA1 as understood by all git commands""" return self.hexsha - def __repr__(self): + def __repr__(self) -> str: """:return: string with pythonic representation of our object""" return '' % (self.__class__.__name__, self.hexsha) @property - def hexsha(self): + def hexsha(self) -> str: """:return: 40 byte hex version of our 20 byte binary sha""" # b2a_hex produces bytes return bin_to_hex(self.binsha).decode('ascii') @property - def data_stream(self): + def data_stream(self) -> 'OStream': """ :return: File Object compatible stream to the uncompressed raw data of the object :note: returned streams must be read in order""" return self.repo.odb.stream(self.binsha) - def stream_data(self, ostream): + def stream_data(self, ostream: 'OStream') -> 'Object': """Writes our data directly to the given output stream :param ostream: File object compatible stream object. :return: self""" @@ -130,7 +148,9 @@ class IndexObject(Object): # for compatibility with iterable lists _id_attribute_ = 'path' - def __init__(self, repo, binsha, mode=None, path=None): + def __init__(self, + repo: 'Repo', binsha: bytes, mode: Union[None, int] = None, path: Union[None, PathLike] = None + ) -> None: """Initialize a newly instanced IndexObject :param repo: is the Repo we are located in @@ -150,14 +170,14 @@ def __init__(self, repo, binsha, mode=None, path=None): if path is not None: self.path = path - def __hash__(self): + def __hash__(self) -> int: """ :return: Hash of our path as index items are uniquely identifiable by path, not by their data !""" return hash(self.path) - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: if attr in IndexObject.__slots__: # they cannot be retrieved lateron ( not without searching for them ) raise AttributeError( @@ -168,16 +188,19 @@ def _set_cache_(self, attr): # END handle slot attribute @property - def name(self): + def name(self) -> str: """:return: Name portion of the path, effectively being the basename""" return osp.basename(self.path) @property - def abspath(self): + def abspath(self) -> PathLike: """ :return: Absolute path to this index object in the file system ( as opposed to the .path field which is a path relative to the git repository ). The returned path will be native to the system and contains '\' on windows. """ - return join_path_native(self.repo.working_tree_dir, self.path) + if self.repo.working_tree_dir is not None: + return join_path_native(self.repo.working_tree_dir, self.path) + else: + raise WorkTreeRepositoryUnsupported From 434306e7d09300b62763b7ebd797d08e7b99ea77 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 18 May 2021 14:14:11 +0100 Subject: [PATCH 02/21] Add types to objects.blob.py --- git/objects/base.py | 2 +- git/objects/blob.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index e50387468..34c595eee 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -203,4 +203,4 @@ def abspath(self) -> PathLike: if self.repo.working_tree_dir is not None: return join_path_native(self.repo.working_tree_dir, self.path) else: - raise WorkTreeRepositoryUnsupported + raise WorkTreeRepositoryUnsupported("Working_tree_dir was None or empty") diff --git a/git/objects/blob.py b/git/objects/blob.py index 897f892bf..b027adabd 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -4,6 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php from mimetypes import guess_type +from typing import Tuple, Union from . import base __all__ = ('Blob', ) @@ -23,7 +24,7 @@ class Blob(base.IndexObject): __slots__ = () @property - def mime_type(self): + def mime_type(self) -> Union[None, Tuple[Union[None, str], Union[None, str]]]: """ :return: String describing the mime type of this file (based on the filename) :note: Defaults to 'text/plain' in case the actual file type is unknown. """ From ecb12c27b6dc56387594df26a205161a1e75c1b9 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 18 May 2021 14:21:47 +0100 Subject: [PATCH 03/21] Add types to objects.tag.py --- git/objects/tag.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index b9bc6c248..84d65d3fb 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -9,6 +9,12 @@ from ..util import hex_to_bin from ..compat import defenc +from typing import List, TYPE_CHECKING, Union + +if TYPE_CHECKING: + from git.repo import Repo + from git.util import Actor + __all__ = ("TagObject", ) @@ -18,8 +24,10 @@ class TagObject(base.Object): type = "tag" __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message") - def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment - tagger=None, tagged_date=None, tagger_tz_offset=None, message=None): + def __init__(self, repo: 'Repo', binsha: bytes, object: Union[None, base.Object] = None, + tag: Union[None, str] = None, tagger: Union[None, Actor] = None, tagged_date: Union[int, None] = None, + tagger_tz_offset: Union[int, None] = None, message: Union[str, None] = None + ) -> None: # @ReservedAssignment """Initialize a tag object with additional data :param repo: repository this object is located in @@ -46,11 +54,11 @@ def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment if message is not None: self.message = message - def _set_cache_(self, attr): + def _set_cache_(self, attr: str) -> None: """Cache all our attributes at once""" if attr in TagObject.__slots__: ostream = self.repo.odb.stream(self.binsha) - lines = ostream.read().decode(defenc, 'replace').splitlines() + lines = ostream.read().decode(defenc, 'replace').splitlines() # type: List[str] _obj, hexsha = lines[0].split(" ") _type_token, type_name = lines[1].split(" ") From 01c8d59e426ae097e486a0bffa5b21d2118a48c3 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Tue, 18 May 2021 14:39:47 +0100 Subject: [PATCH 04/21] Add initial types to objects.util.py --- git/objects/blob.py | 2 +- git/objects/util.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/git/objects/blob.py b/git/objects/blob.py index b027adabd..a6a5b2241 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -24,7 +24,7 @@ class Blob(base.IndexObject): __slots__ = () @property - def mime_type(self) -> Union[None, Tuple[Union[None, str], Union[None, str]]]: + def mime_type(self) -> Union[None, str, Tuple[Union[None, str], Union[None, str]]]: """ :return: String describing the mime type of this file (based on the filename) :note: Defaults to 'text/plain' in case the actual file type is unknown. """ diff --git a/git/objects/util.py b/git/objects/util.py index d15d83c35..e823d39aa 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -17,6 +17,15 @@ import calendar from datetime import datetime, timedelta, tzinfo + +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from .commit import Commit + from .blob import Blob + from .tag import TagObject + from .tree import Tree + __all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date', 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', 'verify_utctz', 'Actor', 'tzoffset', 'utc') @@ -26,7 +35,7 @@ #{ Functions -def mode_str_to_int(modestr): +def mode_str_to_int(modestr: str) -> int: """ :param modestr: string like 755 or 644 or 100644 - only the last 6 chars will be used :return: @@ -41,7 +50,7 @@ def mode_str_to_int(modestr): return mode -def get_object_type_by_name(object_type_name): +def get_object_type_by_name(object_type_name: str) -> Union['Commit', 'TagObject', 'Tree', 'Blob']: """ :return: type suitable to handle the given object type name. Use the type to create new instances. @@ -65,7 +74,7 @@ def get_object_type_by_name(object_type_name): raise ValueError("Cannot handle unknown object type: %s" % object_type_name) -def utctz_to_altz(utctz): +def utctz_to_altz(utctz: str) -> int: """we convert utctz to the timezone in seconds, it is the format time.altzone returns. Git stores it as UTC timezone which has the opposite sign as well, which explains the -1 * ( that was made explicit here ) @@ -73,7 +82,7 @@ def utctz_to_altz(utctz): return -1 * int(float(utctz) / 100 * 3600) -def altz_to_utctz_str(altz): +def altz_to_utctz_str(altz: int) -> str: """As above, but inverses the operation, returning a string that can be used in commit objects""" utci = -1 * int((float(altz) / 3600) * 100) @@ -83,7 +92,7 @@ def altz_to_utctz_str(altz): return prefix + utcs -def verify_utctz(offset): +def verify_utctz(offset: str) -> str: """:raise ValueError: if offset is incorrect :return: offset""" fmt_exc = ValueError("Invalid timezone offset format: %s" % offset) @@ -101,6 +110,7 @@ def verify_utctz(offset): class tzoffset(tzinfo): + def __init__(self, secs_west_of_utc, name=None): self._offset = timedelta(seconds=-secs_west_of_utc) self._name = name or 'fixed' From 9c3255387fe2ce9b156cc06714148436ad2490d9 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 13:47:11 +0100 Subject: [PATCH 05/21] Add types to objects.util.py tzoffset parse_actor_and_date() --- git/objects/util.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/git/objects/util.py b/git/objects/util.py index e823d39aa..ebfb37585 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -17,14 +17,15 @@ import calendar from datetime import datetime, timedelta, tzinfo - -from typing import TYPE_CHECKING, Union +# typing ------------------------------------------------------------ +from typing import Literal, TYPE_CHECKING, Tuple, Union if TYPE_CHECKING: from .commit import Commit from .blob import Blob from .tag import TagObject from .tree import Tree + from subprocess import Popen __all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date', 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', @@ -111,27 +112,27 @@ def verify_utctz(offset: str) -> str: class tzoffset(tzinfo): - def __init__(self, secs_west_of_utc, name=None): + def __init__(self, secs_west_of_utc: float, name: Union[None, str] = None) -> None: self._offset = timedelta(seconds=-secs_west_of_utc) self._name = name or 'fixed' - def __reduce__(self): + def __reduce__(self) -> Tuple['tzoffset', Tuple[float, str]]: return tzoffset, (-self._offset.total_seconds(), self._name) - def utcoffset(self, dt): + def utcoffset(self, dt) -> timedelta: return self._offset - def tzname(self, dt): + def tzname(self, dt) -> str: return self._name - def dst(self, dt): + def dst(self, dt) -> timedelta: return ZERO utc = tzoffset(0, 'UTC') -def from_timestamp(timestamp, tz_offset): +def from_timestamp(timestamp, tz_offset: float) -> datetime: """Converts a timestamp + tz_offset into an aware datetime instance.""" utc_dt = datetime.fromtimestamp(timestamp, utc) try: @@ -141,7 +142,7 @@ def from_timestamp(timestamp, tz_offset): return utc_dt -def parse_date(string_date): +def parse_date(string_date: str) -> Tuple[int, int]: """ Parse the given date as one of the following @@ -228,7 +229,7 @@ def parse_date(string_date): _re_only_actor = re.compile(r'^.+? (.*)$') -def parse_actor_and_date(line): +def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]: """Parse out the actor (author or committer) info from a line like:: author Tom Preston-Werner 1191999972 -0700 @@ -257,7 +258,7 @@ class ProcessStreamAdapter(object): it if the instance goes out of scope.""" __slots__ = ("_proc", "_stream") - def __init__(self, process, stream_name): + def __init__(self, process: Popen, stream_name: str): self._proc = process self._stream = getattr(process, stream_name) From f875ddea28b09f2b78496266c80502d5dc2b7411 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 14:27:15 +0100 Subject: [PATCH 06/21] Mypy fixes --- git/objects/base.py | 2 +- git/objects/tag.py | 8 ++++++-- git/objects/util.py | 26 ++++++++++++++------------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index 34c595eee..884f96515 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -89,7 +89,7 @@ def _set_cache_(self, attr: str) -> None: """Retrieve object information""" if attr == "size": oinfo = self.repo.odb.info(self.binsha) - self.size = oinfo.size + self.size = oinfo.size # type: int # assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type) else: super(Object, self)._set_cache_(attr) diff --git a/git/objects/tag.py b/git/objects/tag.py index 84d65d3fb..abcc75345 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -14,6 +14,9 @@ if TYPE_CHECKING: from git.repo import Repo from git.util import Actor + from .commit import Commit + from .blob import Blob + from .tree import Tree __all__ = ("TagObject", ) @@ -42,7 +45,7 @@ def __init__(self, repo: 'Repo', binsha: bytes, object: Union[None, base.Object] authored_date is in, in a format similar to time.altzone""" super(TagObject, self).__init__(repo, binsha) if object is not None: - self.object = object + self.object = object # type: Union['Commit', 'Blob', 'Tree', 'TagObject'] if tag is not None: self.tag = tag if tagger is not None: @@ -62,8 +65,9 @@ def _set_cache_(self, attr: str) -> None: _obj, hexsha = lines[0].split(" ") _type_token, type_name = lines[1].split(" ") + object_type = get_object_type_by_name(type_name.encode('ascii')) self.object = \ - get_object_type_by_name(type_name.encode('ascii'))(self.repo, hex_to_bin(hexsha)) + object_type(self.repo, hex_to_bin(hexsha)) self.tag = lines[2][4:] # tag diff --git a/git/objects/util.py b/git/objects/util.py index ebfb37585..6bc1b7093 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -18,7 +18,7 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import Literal, TYPE_CHECKING, Tuple, Union +from typing import Literal, TYPE_CHECKING, Tuple, Type, Union, cast if TYPE_CHECKING: from .commit import Commit @@ -36,7 +36,7 @@ #{ Functions -def mode_str_to_int(modestr: str) -> int: +def mode_str_to_int(modestr: Union[bytes, str]) -> int: """ :param modestr: string like 755 or 644 or 100644 - only the last 6 chars will be used :return: @@ -46,12 +46,14 @@ def mode_str_to_int(modestr: str) -> int: for example.""" mode = 0 for iteration, char in enumerate(reversed(modestr[-6:])): + char = cast(Union[str, int], char) mode += int(char) << iteration * 3 # END for each char return mode -def get_object_type_by_name(object_type_name: str) -> Union['Commit', 'TagObject', 'Tree', 'Blob']: +def get_object_type_by_name(object_type_name: bytes + ) -> Union[Type['Commit'], Type['TagObject'], Type['Tree'], Type['Blob']]: """ :return: type suitable to handle the given object type name. Use the type to create new instances. @@ -72,7 +74,7 @@ def get_object_type_by_name(object_type_name: str) -> Union['Commit', 'TagObject from . import tree return tree.Tree else: - raise ValueError("Cannot handle unknown object type: %s" % object_type_name) + raise ValueError("Cannot handle unknown object type: %s" % object_type_name.decode()) def utctz_to_altz(utctz: str) -> int: @@ -116,7 +118,7 @@ def __init__(self, secs_west_of_utc: float, name: Union[None, str] = None) -> No self._offset = timedelta(seconds=-secs_west_of_utc) self._name = name or 'fixed' - def __reduce__(self) -> Tuple['tzoffset', Tuple[float, str]]: + def __reduce__(self) -> Tuple[Type['tzoffset'], Tuple[float, str]]: return tzoffset, (-self._offset.total_seconds(), self._name) def utcoffset(self, dt) -> timedelta: @@ -163,18 +165,18 @@ def parse_date(string_date: str) -> Tuple[int, int]: # git time try: if string_date.count(' ') == 1 and string_date.rfind(':') == -1: - timestamp, offset = string_date.split() + timestamp, offset_str = string_date.split() if timestamp.startswith('@'): timestamp = timestamp[1:] - timestamp = int(timestamp) - return timestamp, utctz_to_altz(verify_utctz(offset)) + timestamp_int = int(timestamp) + return timestamp_int, utctz_to_altz(verify_utctz(offset_str)) else: - offset = "+0000" # local time by default + offset_str = "+0000" # local time by default if string_date[-5] in '-+': - offset = verify_utctz(string_date[-5:]) + offset_str = verify_utctz(string_date[-5:]) string_date = string_date[:-6] # skip space as well # END split timezone info - offset = utctz_to_altz(offset) + offset = utctz_to_altz(offset_str) # now figure out the date and time portion - split time date_formats = [] @@ -235,7 +237,7 @@ def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]: author Tom Preston-Werner 1191999972 -0700 :return: [Actor, int_seconds_since_epoch, int_timezone_offset]""" - actor, epoch, offset = '', 0, 0 + actor, epoch, offset = '', '0', '0' m = _re_actor_epoch.search(line) if m: actor, epoch, offset = m.groups() From 82b03c2eb07f08dd5d6174a04e4288d41f49920f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 14:29:11 +0100 Subject: [PATCH 07/21] flake8 fixes --- git/objects/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/objects/util.py b/git/objects/util.py index 6bc1b7093..b30733815 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -18,7 +18,7 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import Literal, TYPE_CHECKING, Tuple, Type, Union, cast +from typing import TYPE_CHECKING, Tuple, Type, Union, cast if TYPE_CHECKING: from .commit import Commit From c5c69071fd6c730d29c31759caddb0ba8b8e92c3 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 14:34:57 +0100 Subject: [PATCH 08/21] Mypy fixes --- git/objects/blob.py | 2 +- git/objects/util.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/git/objects/blob.py b/git/objects/blob.py index a6a5b2241..013eaf8c8 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -30,5 +30,5 @@ def mime_type(self) -> Union[None, str, Tuple[Union[None, str], Union[None, str] :note: Defaults to 'text/plain' in case the actual file type is unknown. """ guesses = None if self.path: - guesses = guess_type(self.path) + guesses = guess_type(str(self.path)) return guesses and guesses[0] or self.DEFAULT_MIME_TYPE diff --git a/git/objects/util.py b/git/objects/util.py index b30733815..c123b02a8 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -27,6 +27,8 @@ from .tree import Tree from subprocess import Popen +# -------------------------------------------------------------------- + __all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date', 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz', 'verify_utctz', 'Actor', 'tzoffset', 'utc') From 82b60ab31cfa2ca146069df8dbc21ebfc917db0f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 14:37:25 +0100 Subject: [PATCH 09/21] Change Popen to forwardref --- git/objects/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/objects/util.py b/git/objects/util.py index c123b02a8..012f9f235 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -262,7 +262,7 @@ class ProcessStreamAdapter(object): it if the instance goes out of scope.""" __slots__ = ("_proc", "_stream") - def __init__(self, process: Popen, stream_name: str): + def __init__(self, process: 'Popen', stream_name: str): self._proc = process self._stream = getattr(process, stream_name) From da88d360d040cfde4c2bdb6c2f38218481b9676b Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 17:29:43 +0100 Subject: [PATCH 10/21] Change Actor to forwardref --- git/objects/tag.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index abcc75345..cb6efbe9b 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -27,9 +27,13 @@ class TagObject(base.Object): type = "tag" __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message") - def __init__(self, repo: 'Repo', binsha: bytes, object: Union[None, base.Object] = None, - tag: Union[None, str] = None, tagger: Union[None, Actor] = None, tagged_date: Union[int, None] = None, - tagger_tz_offset: Union[int, None] = None, message: Union[str, None] = None + def __init__(self, repo: 'Repo', binsha: bytes, + object: Union[None, base.Object] = None, + tag: Union[None, str] = None, + tagger: Union[None, 'Actor'] = None, + tagged_date: Union[int, None] = None, + tagger_tz_offset: Union[int, None] = None, + message: Union[str, None] = None ) -> None: # @ReservedAssignment """Initialize a tag object with additional data From c242b55d7c64ee43405f8b335c762bcf92189d38 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 17:34:26 +0100 Subject: [PATCH 11/21] Add types to objects.util.py ProcessStreamAdapter --- git/objects/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git/objects/util.py b/git/objects/util.py index 012f9f235..fdc1406b7 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -18,7 +18,7 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import TYPE_CHECKING, Tuple, Type, Union, cast +from typing import Any, IO, TYPE_CHECKING, Tuple, Type, Union, cast if TYPE_CHECKING: from .commit import Commit @@ -262,11 +262,11 @@ class ProcessStreamAdapter(object): it if the instance goes out of scope.""" __slots__ = ("_proc", "_stream") - def __init__(self, process: 'Popen', stream_name: str): + def __init__(self, process: 'Popen', stream_name: str) -> None: self._proc = process - self._stream = getattr(process, stream_name) + self._stream = getattr(process, stream_name) # type: IO[str] ## guess - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: return getattr(self._stream, attr) From 5402a166a4971512f9d513bf36159dead9672ae9 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 20:44:53 +0100 Subject: [PATCH 12/21] Add types to objects _get_intermediate_items() --- git/objects/commit.py | 12 ++++--- git/objects/submodule/base.py | 6 ++-- git/objects/tree.py | 8 +++-- git/objects/util.py | 64 ++++++++++++++++++++++++++++------- 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 45e6d772c..6d3f0bac0 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -4,6 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +from typing import Tuple, Union from gitdb import IStream from git.util import ( hex_to_bin, @@ -70,7 +71,8 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None, committer=None, committed_date=None, committer_tz_offset=None, - message=None, parents=None, encoding=None, gpgsig=None): + message=None, parents: Union[Tuple['Commit', ...], None] = None, + encoding=None, gpgsig=None): """Instantiate a new Commit. All keyword arguments taking None as default will be implicitly set on first query. @@ -133,7 +135,7 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut self.gpgsig = gpgsig @classmethod - def _get_intermediate_items(cls, commit): + def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: # type: ignore return commit.parents @classmethod @@ -477,7 +479,7 @@ def _deserialize(self, stream): readline = stream.readline self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '') - self.parents = [] + self.parents_list = [] # List['Commit'] next_line = None while True: parent_line = readline() @@ -485,9 +487,9 @@ def _deserialize(self, stream): next_line = parent_line break # END abort reading parents - self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii')))) + self.parents_list.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii')))) # END for each parent line - self.parents = tuple(self.parents) + self.parents = tuple(self.parents_list) # type: Tuple['Commit', ...] # we don't know actual author encoding before we have parsed it, so keep the lines around author_line = next_line diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index e3be1a728..b03fa22a5 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -3,6 +3,7 @@ import logging import os import stat +from typing import List from unittest import SkipTest import uuid @@ -134,10 +135,11 @@ def _set_cache_(self, attr): super(Submodule, self)._set_cache_(attr) # END handle attribute name - def _get_intermediate_items(self, item): + @classmethod + def _get_intermediate_items(cls, item: 'Submodule') -> List['Submodule']: # type: ignore """:return: all the submodules of our module repository""" try: - return type(self).list_items(item.module()) + return cls.list_items(item.module()) except InvalidGitRepositoryError: return [] # END handle intermediate items diff --git a/git/objects/tree.py b/git/objects/tree.py index 68e98329b..65c9be4c7 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -3,6 +3,7 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +from typing import Iterable, Iterator, Tuple, Union, cast from git.util import join_path import git.diff as diff from git.util import to_bin_sha @@ -182,8 +183,10 @@ def __init__(self, repo, binsha, mode=tree_id << 12, path=None): super(Tree, self).__init__(repo, binsha, mode, path) @classmethod - def _get_intermediate_items(cls, index_object): + def _get_intermediate_items(cls, index_object: 'Tree', # type: ignore + ) -> Tuple['Tree', ...]: if index_object.type == "tree": + index_object = cast('Tree', index_object) return tuple(index_object._iter_convert_to_object(index_object._cache)) return () @@ -196,7 +199,8 @@ def _set_cache_(self, attr): super(Tree, self)._set_cache_(attr) # END handle attribute - def _iter_convert_to_object(self, iterable): + def _iter_convert_to_object(self, iterable: Iterable[Tuple[bytes, int, str]] + ) -> Iterator[Union[Blob, 'Tree', Submodule]]: """Iterable yields tuples of (binsha, mode, name), which will be converted to the respective object representation""" for binsha, mode, name in iterable: diff --git a/git/objects/util.py b/git/objects/util.py index fdc1406b7..88183567c 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -4,6 +4,8 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module for general utility functions""" + + from git.util import ( IterableList, Actor @@ -18,9 +20,10 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import Any, IO, TYPE_CHECKING, Tuple, Type, Union, cast +from typing import Any, Callable, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload if TYPE_CHECKING: + from .submodule.base import Submodule from .commit import Commit from .blob import Blob from .tag import TagObject @@ -115,7 +118,7 @@ def verify_utctz(offset: str) -> str: class tzoffset(tzinfo): - + def __init__(self, secs_west_of_utc: float, name: Union[None, str] = None) -> None: self._offset = timedelta(seconds=-secs_west_of_utc) self._name = name or 'fixed' @@ -275,29 +278,61 @@ class Traversable(object): """Simple interface to perform depth-first or breadth-first traversals into one direction. Subclasses only need to implement one function. - Instances of the Subclass must be hashable""" + Instances of the Subclass must be hashable + + Defined subclasses = [Commit, Tree, SubModule] + """ __slots__ = () + @overload + @classmethod + def _get_intermediate_items(cls, item: 'Commit') -> Tuple['Commit', ...]: + ... + + @overload @classmethod - def _get_intermediate_items(cls, item): + def _get_intermediate_items(cls, item: 'Submodule') -> Tuple['Submodule', ...]: + ... + + @overload + @classmethod + def _get_intermediate_items(cls, item: 'Tree') -> Tuple['Tree', ...]: + ... + + @overload + @classmethod + def _get_intermediate_items(cls, item: 'Traversable') -> Tuple['Traversable', ...]: + ... + + @classmethod + def _get_intermediate_items(cls, item: 'Traversable' + ) -> Sequence['Traversable']: """ Returns: - List of items connected to the given item. + Tuple of items connected to the given item. Must be implemented in subclass + + class Commit:: (cls, Commit) -> Tuple[Commit, ...] + class Submodule:: (cls, Submodule) -> Iterablelist[Submodule] + class Tree:: (cls, Tree) -> Tuple[Tree, ...] """ raise NotImplementedError("To be implemented in subclass") - def list_traverse(self, *args, **kwargs): + def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList: """ :return: IterableList with the results of the traversal as produced by traverse()""" - out = IterableList(self._id_attribute_) + out = IterableList(self._id_attribute_) # type: ignore[attr-defined] # defined in sublcasses out.extend(self.traverse(*args, **kwargs)) return out - def traverse(self, predicate=lambda i, d: True, - prune=lambda i, d: False, depth=-1, branch_first=True, - visit_once=True, ignore_self=1, as_edge=False): + def traverse(self, + predicate: Callable[[object, int], bool] = lambda i, d: True, + prune: Callable[[object, int], bool] = lambda i, d: False, + depth: int = -1, + branch_first: bool = True, + visit_once: bool = True, ignore_self: int = 1, as_edge: bool = False + ) -> Union[Iterator['Traversable'], Iterator[Tuple['Traversable', 'Traversable']]]: """:return: iterator yielding of items found when traversing self :param predicate: f(i,d) returns False if item i at depth d should not be included in the result @@ -329,13 +364,16 @@ def traverse(self, predicate=lambda i, d: True, destination, i.e. tuple(src, dest) with the edge spanning from source to destination""" visited = set() - stack = Deque() + stack = Deque() # type: Deque[Tuple[int, Traversable, Union[Traversable, None]]] stack.append((0, self, None)) # self is always depth level 0 - def addToStack(stack, item, branch_first, depth): + def addToStack(stack: Deque[Tuple[int, 'Traversable', Union['Traversable', None]]], + item: 'Traversable', + branch_first: bool, + depth) -> None: lst = self._get_intermediate_items(item) if not lst: - return + return None if branch_first: stack.extendleft((depth, i, item) for i in lst) else: From 6503ef72d90164840c06f168ab08f0426fb612bf Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 20:55:17 +0100 Subject: [PATCH 13/21] Add types to objects.util.py change deque to typing.Deque --- git/objects/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git/objects/util.py b/git/objects/util.py index 88183567c..106bab0e3 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -12,7 +12,7 @@ ) import re -from collections import deque as Deque +from collections import deque from string import digits import time @@ -20,7 +20,7 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import Any, Callable, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload +from typing import Any, Callable, Deque, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload if TYPE_CHECKING: from .submodule.base import Submodule @@ -364,7 +364,7 @@ def traverse(self, destination, i.e. tuple(src, dest) with the edge spanning from source to destination""" visited = set() - stack = Deque() # type: Deque[Tuple[int, Traversable, Union[Traversable, None]]] + stack = deque() # type: Deque[Tuple[int, Traversable, Union[Traversable, None]]] stack.append((0, self, None)) # self is always depth level 0 def addToStack(stack: Deque[Tuple[int, 'Traversable', Union['Traversable', None]]], From ce8cc4a6123a3ea11fc4e35416d93a8bd68cfd65 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 21:06:09 +0100 Subject: [PATCH 14/21] Add types to commit.py spit parents into list and tuple types --- git/objects/commit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/git/objects/commit.py b/git/objects/commit.py index 6d3f0bac0..b95f4c657 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -129,6 +129,7 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut self.message = message if parents is not None: self.parents = parents + self.parents_list = list(parents) if encoding is not None: self.encoding = encoding if gpgsig is not None: From 76bcd7081265f1d72fcc3101bfda62c67d8a7f32 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 21:10:33 +0100 Subject: [PATCH 15/21] Add types to commit.py undo --- git/objects/commit.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index b95f4c657..228e897ea 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -129,7 +129,6 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut self.message = message if parents is not None: self.parents = parents - self.parents_list = list(parents) if encoding is not None: self.encoding = encoding if gpgsig is not None: @@ -480,7 +479,7 @@ def _deserialize(self, stream): readline = stream.readline self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '') - self.parents_list = [] # List['Commit'] + self.parents = [] next_line = None while True: parent_line = readline() @@ -488,9 +487,9 @@ def _deserialize(self, stream): next_line = parent_line break # END abort reading parents - self.parents_list.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii')))) + self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii')))) # END for each parent line - self.parents = tuple(self.parents_list) # type: Tuple['Commit', ...] + self.parents = tuple(self.parents) # we don't know actual author encoding before we have parsed it, so keep the lines around author_line = next_line From c51f93823d46f0882b49822ce6f9e668228e5b8d Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 21:34:33 +0100 Subject: [PATCH 16/21] Add types to objects _serialize() and _deserialize() --- git/objects/commit.py | 20 ++++++++++++-------- git/objects/tree.py | 16 +++++++++++++--- git/objects/util.py | 8 ++++---- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 228e897ea..26db6e36d 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.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 typing import Tuple, Union from gitdb import IStream from git.util import ( hex_to_bin, @@ -37,6 +36,11 @@ from io import BytesIO import logging +from typing import List, Tuple, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from git.repo import Repo + log = logging.getLogger('git.objects.commit') log.addHandler(logging.NullHandler()) @@ -71,7 +75,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None, committer=None, committed_date=None, committer_tz_offset=None, - message=None, parents: Union[Tuple['Commit', ...], None] = None, + message=None, parents: Union[Tuple['Commit', ...], List['Commit'], None] = None, encoding=None, gpgsig=None): """Instantiate a new Commit. All keyword arguments taking None as default will be implicitly set on first query. @@ -135,11 +139,11 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut self.gpgsig = gpgsig @classmethod - def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: # type: ignore - return commit.parents + def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: # type: ignore ## cos overriding super + return tuple(commit.parents) @classmethod - def _calculate_sha_(cls, repo, commit): + def _calculate_sha_(cls, repo: 'Repo', commit: 'Commit') -> bytes: '''Calculate the sha of a commit. :param repo: Repo object the commit should be part of @@ -432,7 +436,7 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, #{ Serializable Implementation - def _serialize(self, stream): + def _serialize(self, stream: BytesIO) -> 'Commit': write = stream.write write(("tree %s\n" % self.tree).encode('ascii')) for p in self.parents: @@ -473,7 +477,7 @@ def _serialize(self, stream): # END handle encoding return self - def _deserialize(self, stream): + def _deserialize(self, stream: BytesIO) -> 'Commit': """:param from_rev_list: if true, the stream format is coming from the rev-list command Otherwise it is assumed to be a plain data stream from our object""" readline = stream.readline @@ -513,7 +517,7 @@ def _deserialize(self, stream): buf = enc.strip() while buf: if buf[0:10] == b"encoding ": - self.encoding = buf[buf.find(' ') + 1:].decode( + self.encoding = buf[buf.find(b' ') + 1:].decode( self.encoding, 'ignore') elif buf[0:7] == b"gpgsig ": sig = buf[buf.find(b' ') + 1:] + b"\n" diff --git a/git/objects/tree.py b/git/objects/tree.py index 65c9be4c7..29b2a6846 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -3,7 +3,6 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from typing import Iterable, Iterator, Tuple, Union, cast from git.util import join_path import git.diff as diff from git.util import to_bin_sha @@ -18,6 +17,17 @@ tree_to_stream ) + +# typing ------------------------------------------------- + +from typing import Iterable, Iterator, Tuple, Union, cast, TYPE_CHECKING + +if TYPE_CHECKING: + from io import BytesIO + +#-------------------------------------------------------- + + cmp = lambda a, b: (a > b) - (a < b) __all__ = ("TreeModifier", "Tree") @@ -321,7 +331,7 @@ def __contains__(self, item): def __reversed__(self): return reversed(self._iter_convert_to_object(self._cache)) - def _serialize(self, stream): + def _serialize(self, stream: 'BytesIO') -> 'Tree': """Serialize this tree into the stream. Please note that we will assume our tree data to be in a sorted state. If this is not the case, serialization will not generate a correct tree representation as these are assumed to be sorted @@ -329,7 +339,7 @@ def _serialize(self, stream): tree_to_stream(self._cache, stream.write) return self - def _deserialize(self, stream): + def _deserialize(self, stream: 'BytesIO') -> 'Tree': self._cache = tree_entries_from_data(stream.read()) return self diff --git a/git/objects/util.py b/git/objects/util.py index 106bab0e3..b94e9f122 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -5,7 +5,6 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module for general utility functions""" - from git.util import ( IterableList, Actor @@ -20,9 +19,10 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import Any, Callable, Deque, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload +from typing import (Any, Callable, Deque, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload) if TYPE_CHECKING: + from io import BytesIO from .submodule.base import Submodule from .commit import Commit from .blob import Blob @@ -412,14 +412,14 @@ class Serializable(object): """Defines methods to serialize and deserialize objects from and into a data stream""" __slots__ = () - def _serialize(self, stream): + def _serialize(self, stream: 'BytesIO') -> 'Serializable': """Serialize the data of this object into the given data stream :note: a serialized object would ``_deserialize`` into the same object :param stream: a file-like object :return: self""" raise NotImplementedError("To be implemented in subclass") - def _deserialize(self, stream): + def _deserialize(self, stream: 'BytesIO') -> 'Serializable': """Deserialize all information regarding this object from the stream :param stream: a file-like object :return: self""" From 90f0fb8f449b6d3e4f12c28d8699ee79a6763b80 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 20 May 2021 21:51:56 +0100 Subject: [PATCH 17/21] change IO[str] to stringIO --- git/objects/blob.py | 3 +-- git/objects/util.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/git/objects/blob.py b/git/objects/blob.py index 013eaf8c8..017178f05 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.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 mimetypes import guess_type -from typing import Tuple, Union from . import base __all__ = ('Blob', ) @@ -24,7 +23,7 @@ class Blob(base.IndexObject): __slots__ = () @property - def mime_type(self) -> Union[None, str, Tuple[Union[None, str], Union[None, str]]]: + def mime_type(self) -> str: """ :return: String describing the mime type of this file (based on the filename) :note: Defaults to 'text/plain' in case the actual file type is unknown. """ diff --git a/git/objects/util.py b/git/objects/util.py index b94e9f122..087f0166b 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -19,10 +19,10 @@ from datetime import datetime, timedelta, tzinfo # typing ------------------------------------------------------------ -from typing import (Any, Callable, Deque, IO, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload) +from typing import (Any, Callable, Deque, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload) if TYPE_CHECKING: - from io import BytesIO + from io import BytesIO, StringIO from .submodule.base import Submodule from .commit import Commit from .blob import Blob @@ -267,7 +267,7 @@ class ProcessStreamAdapter(object): def __init__(self, process: 'Popen', stream_name: str) -> None: self._proc = process - self._stream = getattr(process, stream_name) # type: IO[str] ## guess + self._stream = getattr(process, stream_name) # type: StringIO ## guess def __getattr__(self, attr: str) -> Any: return getattr(self._stream, attr) From 567c892322776756e8d0095e89f39b25b9b01bc2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 17 Jun 2021 17:22:31 +0100 Subject: [PATCH 18/21] rebase with dropped 3.5 --- .appveyor.yml | 29 ++--------------------------- .github/workflows/pythonpackage.yml | 2 +- .travis.yml | 3 +-- README.md | 2 +- doc/source/intro.rst | 2 +- git/cmd.py | 3 +-- git/repo/base.py | 2 +- setup.py | 3 +-- 8 files changed, 9 insertions(+), 37 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0a86c1a75..833f5c7b9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,29 +6,12 @@ environment: CYGWIN64_GIT_PATH: "C:\\cygwin64\\bin;%GIT_DAEMON_PATH%" matrix: - - PYTHON: "C:\\Python34-x64" - PYTHON_VERSION: "3.4" - GIT_PATH: "%GIT_DAEMON_PATH%" - - PYTHON: "C:\\Python35-x64" - PYTHON_VERSION: "3.5" - GIT_PATH: "%GIT_DAEMON_PATH%" - PYTHON: "C:\\Python36-x64" PYTHON_VERSION: "3.6" GIT_PATH: "%GIT_DAEMON_PATH%" - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7" GIT_PATH: "%GIT_DAEMON_PATH%" - - PYTHON: "C:\\Miniconda35-x64" - PYTHON_VERSION: "3.5" - IS_CONDA: "yes" - MAYFAIL: "yes" - GIT_PATH: "%GIT_DAEMON_PATH%" - ## Cygwin - - PYTHON: "C:\\Python35-x64" - PYTHON_VERSION: "3.5" - IS_CYGWIN: "yes" - MAYFAIL: "yes" - GIT_PATH: "%CYGWIN64_GIT_PATH%" matrix: allow_failures: @@ -76,18 +59,10 @@ install: build: false test_script: - - IF "%IS_CYGWIN%" == "yes" ( - nosetests -v - ) ELSE ( - IF "%PYTHON_VERSION%" == "3.5" ( - nosetests -v --with-coverage - ) ELSE ( - nosetests -v - ) - ) + - nosetests -v on_success: - - IF "%PYTHON_VERSION%" == "3.5" IF NOT "%IS_CYGWIN%" == "yes" (codecov) + - IF "%PYTHON_VERSION%" == "3.6" IF NOT "%IS_CYGWIN%" == "yes" (codecov) # Enable this to be able to login to the build worker. You can use the # `remmina` program in Ubuntu, use the login information that the line below diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3c7215cbe..53da76149 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 diff --git a/.travis.yml b/.travis.yml index 1fbb1ddb8..8a171b4fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - "3.4" - - "3.5" - "3.6" - "3.7" - "3.8" @@ -38,7 +37,7 @@ script: - ulimit -n - coverage run --omit="test/*" -m unittest --buffer - coverage report - - if [ "$TRAVIS_PYTHON_VERSION" == '3.5' ]; then cd doc && make html; fi + - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then cd doc && make html; fi - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then flake8 --ignore=W293,E265,E266,W503,W504,E731; fi after_success: - codecov diff --git a/README.md b/README.md index 0d0edeb43..4725d3aeb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ If it is not in your `PATH`, you can help GitPython find it by setting the `GIT_PYTHON_GIT_EXECUTABLE=` environment variable. * Git (1.7.x or newer) -* Python >= 3.5 +* Python >= 3.6 The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`. The installer takes care of installing them for you. diff --git a/doc/source/intro.rst b/doc/source/intro.rst index 7168c91b1..956a36073 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -13,7 +13,7 @@ The object database implementation is optimized for handling large quantities of Requirements ============ -* `Python`_ >= 3.5 +* `Python`_ >= 3.6 * `Git`_ 1.7.0 or newer It should also work with older versions, but it may be that some operations involving remotes will not work as expected. diff --git a/git/cmd.py b/git/cmd.py index d8b82352d..d15b97ca5 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -150,7 +150,6 @@ def dashify(string: str) -> str: def slots_to_dict(self, exclude: Sequence[str] = ()) -> Dict[str, Any]: - # annotate self.__slots__ as Tuple[str, ...] once 3.5 dropped return {s: getattr(self, s) for s in self.__slots__ if s not in exclude} @@ -462,7 +461,7 @@ class CatFileContentStream(object): If not all data is read to the end of the objects's lifetime, we read the rest to assure the underlying stream continues to work""" - __slots__ = ('_stream', '_nbr', '_size') + __slots__: Tuple[str, ...] = ('_stream', '_nbr', '_size') def __init__(self, size: int, stream: IO[bytes]) -> None: self._stream = stream diff --git a/git/repo/base.py b/git/repo/base.py index e23ebb1ac..2d2e915cf 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -519,7 +519,7 @@ def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator['Tree']: :note: Takes all arguments known to iter_commits method""" return (c.tree for c in self.iter_commits(*args, **kwargs)) - def tree(self, rev: Union['Commit', 'Tree', None] = None) -> 'Tree': + def tree(self, rev: Union['Commit', 'Tree', str, None] = None) -> 'Tree': """The Tree object for the given treeish revision Examples:: diff --git a/setup.py b/setup.py index f8829c386..3fbcbbad1 100755 --- a/setup.py +++ b/setup.py @@ -99,7 +99,7 @@ def build_py_modules(basedir, excludes=[]): include_package_data=True, py_modules=build_py_modules("./git", excludes=["git.ext.*"]), package_dir={'git': 'git'}, - python_requires='>=3.5', + python_requires='>=3.6', install_requires=requirements, tests_require=requirements + test_requirements, zip_safe=False, @@ -123,7 +123,6 @@ def build_py_modules(basedir, excludes=[]): "Operating System :: MacOS :: MacOS X", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", From df39446bb7b90ab9436fa3a76f6d4182c2a47da2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 17 Jun 2021 17:35:46 +0100 Subject: [PATCH 19/21] del travis --- .travis.yml | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8a171b4fd..000000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -# UNUSED, only for reference. If adjustments are needed, please see github actions -language: python -python: - - "3.4" - - "3.6" - - "3.7" - - "3.8" - - "nightly" - # - "pypy" - won't work as smmap doesn't work (see gitdb/.travis.yml for details) -matrix: - allow_failures: - - python: "nightly" -git: - # a higher depth is needed for most of the tests - must be high enough to not actually be shallow - # as we clone our own repository in the process - depth: 99999 -install: - - python --version; git --version - - git submodule update --init --recursive - - git fetch --tags - - pip install -r test-requirements.txt - - pip install -r doc/requirements.txt - - pip install codecov - - # generate some reflog as git-python tests need it (in master) - - ./init-tests-after-clone.sh - - # as commits are performed with the default user, it needs to be set for travis too - - git config --global user.email "travis@ci.com" - - git config --global user.name "Travis Runner" - # If we rewrite the user's config by accident, we will mess it up - # and cause subsequent tests to fail - - cat git/test/fixtures/.gitconfig >> ~/.gitconfig -script: - # Make sure we limit open handles to see if we are leaking them - - ulimit -n 128 - - ulimit -n - - coverage run --omit="test/*" -m unittest --buffer - - coverage report - - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then cd doc && make html; fi - - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then flake8 --ignore=W293,E265,E266,W503,W504,E731; fi -after_success: - - codecov From f8ec952343583324c4f5dbefa4fb846f395ea6e4 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 17 Jun 2021 17:49:48 +0100 Subject: [PATCH 20/21] fix issue with mypy update to 0.9 --- git/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/util.py b/git/util.py index edbd5f1e7..05eeb4ad3 100644 --- a/git/util.py +++ b/git/util.py @@ -971,7 +971,7 @@ def __getattr__(self, attr: str) -> Any: # END for each item return list.__getattribute__(self, attr) - def __getitem__(self, index: Union[int, slice, str]) -> Any: + def __getitem__(self, index: Union[int, slice, str]) -> Any: # type: ignore if isinstance(index, int): return list.__getitem__(self, index) elif isinstance(index, slice): @@ -983,7 +983,7 @@ def __getitem__(self, index: Union[int, slice, str]) -> Any: raise IndexError("No item found with id %r" % (self._prefix + index)) from e # END handle getattr - def __delitem__(self, index: Union[int, str, slice]) -> None: + def __delitem__(self, index: Union[int, str, slice]) -> None: # type: ignore delindex = cast(int, index) if not isinstance(index, int): From 18b6aa55309adfa8aa99bdaf9e8f80337befe74e Mon Sep 17 00:00:00 2001 From: Yobmod Date: Thu, 17 Jun 2021 18:10:46 +0100 Subject: [PATCH 21/21] add SupportsIndex to IterableList, with version import guards --- git/types.py | 9 +++------ git/util.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/git/types.py b/git/types.py index 91d35b567..a410cb366 100644 --- a/git/types.py +++ b/git/types.py @@ -7,15 +7,12 @@ from typing import Union, Any if sys.version_info[:2] >= (3, 8): - from typing import Final, Literal # noqa: F401 + from typing import Final, Literal, SupportsIndex # noqa: F401 else: - from typing_extensions import Final, Literal # noqa: F401 + from typing_extensions import Final, Literal, SupportsIndex # noqa: F401 -if sys.version_info[:2] < (3, 6): - # os.PathLike (PEP-519) only got introduced with Python 3.6 - PathLike = str -elif sys.version_info[:2] < (3, 9): +if sys.version_info[:2] < (3, 9): # Python >= 3.6, < 3.9 PathLike = Union[str, os.PathLike] elif sys.version_info[:2] >= (3, 9): diff --git a/git/util.py b/git/util.py index 05eeb4ad3..516c315c1 100644 --- a/git/util.py +++ b/git/util.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: from git.remote import Remote from git.repo.base import Repo -from .types import PathLike, TBD, Literal +from .types import PathLike, TBD, Literal, SupportsIndex # --------------------------------------------------------------------- @@ -971,7 +971,10 @@ def __getattr__(self, attr: str) -> Any: # END for each item return list.__getattribute__(self, attr) - def __getitem__(self, index: Union[int, slice, str]) -> Any: # type: ignore + def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + + assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str" + if isinstance(index, int): return list.__getitem__(self, index) elif isinstance(index, slice): @@ -983,12 +986,13 @@ def __getitem__(self, index: Union[int, slice, str]) -> Any: # type: ignore raise IndexError("No item found with id %r" % (self._prefix + index)) from e # END handle getattr - def __delitem__(self, index: Union[int, str, slice]) -> None: # type: ignore + def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any: + + assert isinstance(index, (int, str)), "Index of IterableList should be an int or str" delindex = cast(int, index) if not isinstance(index, int): delindex = -1 - assert not isinstance(index, slice) name = self._prefix + index for i, item in enumerate(self): if getattr(item, self._id_attr) == name: