summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--git/cmd.py3
-rw-r--r--git/diff.py4
-rw-r--r--git/index/fun.py2
-rw-r--r--git/objects/base.py65
-rw-r--r--git/objects/blob.py4
-rw-r--r--git/objects/commit.py20
-rw-r--r--git/objects/submodule/base.py6
-rw-r--r--git/objects/tag.py28
-rw-r--r--git/objects/tree.py22
-rw-r--r--git/objects/util.py129
-rw-r--r--git/repo/base.py2
-rw-r--r--git/types.py9
-rw-r--r--git/util.py12
-rwxr-xr-xsetup.py1
15 files changed, 214 insertions, 97 deletions
diff --git a/.travis.yml b/.travis.yml
index 570beaad..1bed8368 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,10 @@
# UNUSED, only for reference. If adjustments are needed, please see github actions
language: python
python:
+<<<<<<< HEAD
+ - "3.4"
+=======
+>>>>>>> b0f79c58ad919e90261d1e332df79a4ad0bc40de
- "3.6"
- "3.7"
- "3.8"
diff --git a/git/cmd.py b/git/cmd.py
index 4f58b314..e078e4a1 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -148,7 +148,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}
@@ -460,7 +459,7 @@ class Git(LazyMixin):
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/diff.py b/git/diff.py
index a40fc244..346a2ca7 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, Final, Literal
+from git.types import PathLike, TBD, Literal
if TYPE_CHECKING:
from .objects.tree import Tree
@@ -31,7 +31,7 @@ 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() # 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 1012f480..3fded347 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -109,7 +109,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 59f0e836..884f9651 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 @@ class Object(LazyMixin):
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 @@ class Object(LazyMixin):
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,52 +85,52 @@ class Object(LazyMixin):
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)
- 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)
- 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 '<git.%s "%s">' % (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 @@ class IndexObject(Object):
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 @@ class IndexObject(Object):
# 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("Working_tree_dir was None or empty")
diff --git a/git/objects/blob.py b/git/objects/blob.py
index 897f892b..017178f0 100644
--- a/git/objects/blob.py
+++ b/git/objects/blob.py
@@ -23,11 +23,11 @@ class Blob(base.IndexObject):
__slots__ = ()
@property
- def mime_type(self):
+ 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. """
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/commit.py b/git/objects/commit.py
index 45e6d772..26db6e36 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -36,6 +36,11 @@ import os
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())
@@ -70,7 +75,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', ...], 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.
@@ -133,11 +139,11 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
self.gpgsig = gpgsig
@classmethod
- def _get_intermediate_items(cls, commit):
- 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
@@ -430,7 +436,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
#{ 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:
@@ -471,7 +477,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
# 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
@@ -511,7 +517,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
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/submodule/base.py b/git/objects/submodule/base.py
index e3be1a72..b03fa22a 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -3,6 +3,7 @@ from io import BytesIO
import logging
import os
import stat
+from typing import List
from unittest import SkipTest
import uuid
@@ -134,10 +135,11 @@ class Submodule(IndexObject, Iterable, Traversable):
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/tag.py b/git/objects/tag.py
index b9bc6c24..cb6efbe9 100644
--- a/git/objects/tag.py
+++ b/git/objects/tag.py
@@ -9,6 +9,15 @@ from .util import get_object_type_by_name, parse_actor_and_date
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
+ from .commit import Commit
+ from .blob import Blob
+ from .tree import Tree
+
__all__ = ("TagObject", )
@@ -18,8 +27,14 @@ 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
@@ -34,7 +49,7 @@ class TagObject(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:
@@ -46,16 +61,17 @@ class TagObject(base.Object):
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(" ")
+ 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 <tag name>
diff --git a/git/objects/tree.py b/git/objects/tree.py
index 68e98329..29b2a684 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -17,6 +17,17 @@ from .fun import (
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")
@@ -182,8 +193,10 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
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 +209,8 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
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:
@@ -317,7 +331,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
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
@@ -325,7 +339,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable):
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 d15d83c3..087f0166 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -4,19 +4,34 @@
# 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
)
import re
-from collections import deque as Deque
+from collections import deque
from string import digits
import time
import calendar
from datetime import datetime, timedelta, tzinfo
+# typing ------------------------------------------------------------
+from typing import (Any, Callable, Deque, Iterator, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast, overload)
+
+if TYPE_CHECKING:
+ from io import BytesIO, StringIO
+ from .submodule.base import Submodule
+ 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',
'verify_utctz', 'Actor', 'tzoffset', 'utc')
@@ -26,7 +41,7 @@ ZERO = timedelta(0)
#{ Functions
-def mode_str_to_int(modestr):
+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:
@@ -36,12 +51,14 @@ def mode_str_to_int(modestr):
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):
+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.
@@ -62,10 +79,10 @@ def get_object_type_by_name(object_type_name):
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):
+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 +90,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 +100,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,27 +118,28 @@ def verify_utctz(offset):
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[Type['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:
@@ -131,7 +149,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
@@ -152,18 +170,18 @@ def parse_date(string_date):
# 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 = []
@@ -218,13 +236,13 @@ _re_actor_epoch = re.compile(r'^.+? (.*) (\d+) ([+-]\d+).*$')
_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 <tom@mojombo.com> 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()
@@ -247,11 +265,11 @@ 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) -> None:
self._proc = process
- self._stream = getattr(process, stream_name)
+ self._stream = getattr(process, stream_name) # type: StringIO ## guess
- def __getattr__(self, attr):
+ def __getattr__(self, attr: str) -> Any:
return getattr(self._stream, attr)
@@ -260,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: '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):
+ 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
@@ -314,13 +364,16 @@ class Traversable(object):
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:
@@ -359,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"""
diff --git a/git/repo/base.py b/git/repo/base.py
index 6cc56031..5abd4961 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -530,7 +530,7 @@ class Repo(object):
: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/git/types.py b/git/types.py
index 91d35b56..a410cb36 100644
--- a/git/types.py
+++ b/git/types.py
@@ -7,15 +7,12 @@ import sys
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 edbd5f1e..516c315c 100644
--- a/git/util.py
+++ b/git/util.py
@@ -29,7 +29,7 @@ import pathlib
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 @@ class IterableList(list):
# END for each item
return list.__getattribute__(self, attr)
- def __getitem__(self, index: Union[int, slice, str]) -> Any:
+ 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 @@ class IterableList(list):
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[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:
diff --git a/setup.py b/setup.py
index 850d680d..2845bbec 100755
--- a/setup.py
+++ b/setup.py
@@ -123,7 +123,6 @@ setup(
"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",