Skip to content

Commit 6a0d131

Browse files
authored
Merge pull request #1271 from Yobmod/main
Add initial types to object, and fix CI
2 parents b0f79c5 + 18b6aa5 commit 6a0d131

File tree

15 files changed

+214
-97
lines changed

15 files changed

+214
-97
lines changed

.travis.yml

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# UNUSED, only for reference. If adjustments are needed, please see github actions
22
language: python
33
python:
4+
<<<<<<< HEAD
5+
- "3.4"
6+
=======
7+
>>>>>>> b0f79c58ad919e90261d1e332df79a4ad0bc40de
48
- "3.6"
59
- "3.7"
610
- "3.8"

git/cmd.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ def dashify(string: str) -> str:
148148

149149

150150
def slots_to_dict(self, exclude: Sequence[str] = ()) -> Dict[str, Any]:
151-
# annotate self.__slots__ as Tuple[str, ...] once 3.5 dropped
152151
return {s: getattr(self, s) for s in self.__slots__ if s not in exclude}
153152

154153

@@ -460,7 +459,7 @@ class CatFileContentStream(object):
460459
If not all data is read to the end of the objects's lifetime, we read the
461460
rest to assure the underlying stream continues to work"""
462461

463-
__slots__ = ('_stream', '_nbr', '_size')
462+
__slots__: Tuple[str, ...] = ('_stream', '_nbr', '_size')
464463

465464
def __init__(self, size: int, stream: IO[bytes]) -> None:
466465
self._stream = stream

git/diff.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# typing ------------------------------------------------------------------
1717

1818
from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union, TYPE_CHECKING
19-
from git.types import PathLike, TBD, Final, Literal
19+
from git.types import PathLike, TBD, Literal
2020

2121
if TYPE_CHECKING:
2222
from .objects.tree import Tree
@@ -31,7 +31,7 @@
3131
__all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
3232

3333
# Special object to compare against the empty tree in diffs
34-
NULL_TREE = object() # type: Final[object]
34+
NULL_TREE = object()
3535

3636
_octal_byte_re = re.compile(b'\\\\([0-9]{3})')
3737

git/index/fun.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None:
109109
# end handle return code
110110

111111

112-
def stat_mode_to_index_mode(mode):
112+
def stat_mode_to_index_mode(mode: int) -> int:
113113
"""Convert the given mode from a stat call to the corresponding index mode
114114
and return it"""
115115
if S_ISLNK(mode): # symlinks

git/objects/base.py

+44-21
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,34 @@
33
#
44
# This module is part of GitPython and is released under
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6+
7+
from git.exc import WorkTreeRepositoryUnsupported
68
from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex
79

810
import gitdb.typ as dbtyp
911
import os.path as osp
10-
from typing import Optional # noqa: F401 unused import
1112

1213
from .util import get_object_type_by_name
1314

1415

15-
_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r"
16+
# typing ------------------------------------------------------------------
17+
18+
from typing import Any, TYPE_CHECKING, Optional, Union
19+
20+
from git.types import PathLike
21+
22+
if TYPE_CHECKING:
23+
from git.repo import Repo
24+
from gitdb.base import OStream
25+
from .tree import Tree
26+
from .blob import Blob
27+
from .tag import TagObject
28+
from .commit import Commit
29+
30+
# --------------------------------------------------------------------------
31+
32+
33+
_assertion_msg_format = "Created object %r whose python type %r disagrees with the acutual git object type %r"
1634

1735
__all__ = ("Object", "IndexObject")
1836

@@ -27,7 +45,7 @@ class Object(LazyMixin):
2745
__slots__ = ("repo", "binsha", "size")
2846
type = None # type: Optional[str] # to be set by subclass
2947

30-
def __init__(self, repo, binsha):
48+
def __init__(self, repo: 'Repo', binsha: bytes):
3149
"""Initialize an object by identifying it by its binary sha.
3250
All keyword arguments will be set on demand if None.
3351
@@ -40,7 +58,7 @@ def __init__(self, repo, binsha):
4058
assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha))
4159

4260
@classmethod
43-
def new(cls, repo, id): # @ReservedAssignment
61+
def new(cls, repo: 'Repo', id): # @ReservedAssignment
4462
"""
4563
:return: New Object instance of a type appropriate to the object type behind
4664
id. The id of the newly created object will be a binsha even though
@@ -53,7 +71,7 @@ def new(cls, repo, id): # @ReservedAssignment
5371
return repo.rev_parse(str(id))
5472

5573
@classmethod
56-
def new_from_sha(cls, repo, sha1):
74+
def new_from_sha(cls, repo: 'Repo', sha1: bytes) -> Union['Commit', 'TagObject', 'Tree', 'Blob']:
5775
"""
5876
:return: new object instance of a type appropriate to represent the given
5977
binary sha1
@@ -67,52 +85,52 @@ def new_from_sha(cls, repo, sha1):
6785
inst.size = oinfo.size
6886
return inst
6987

70-
def _set_cache_(self, attr):
88+
def _set_cache_(self, attr: str) -> None:
7189
"""Retrieve object information"""
7290
if attr == "size":
7391
oinfo = self.repo.odb.info(self.binsha)
74-
self.size = oinfo.size
92+
self.size = oinfo.size # type: int
7593
# assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type)
7694
else:
7795
super(Object, self)._set_cache_(attr)
7896

79-
def __eq__(self, other):
97+
def __eq__(self, other: Any) -> bool:
8098
""":return: True if the objects have the same SHA1"""
8199
if not hasattr(other, 'binsha'):
82100
return False
83101
return self.binsha == other.binsha
84102

85-
def __ne__(self, other):
103+
def __ne__(self, other: Any) -> bool:
86104
""":return: True if the objects do not have the same SHA1 """
87105
if not hasattr(other, 'binsha'):
88106
return True
89107
return self.binsha != other.binsha
90108

91-
def __hash__(self):
109+
def __hash__(self) -> int:
92110
""":return: Hash of our id allowing objects to be used in dicts and sets"""
93111
return hash(self.binsha)
94112

95-
def __str__(self):
113+
def __str__(self) -> str:
96114
""":return: string of our SHA1 as understood by all git commands"""
97115
return self.hexsha
98116

99-
def __repr__(self):
117+
def __repr__(self) -> str:
100118
""":return: string with pythonic representation of our object"""
101119
return '<git.%s "%s">' % (self.__class__.__name__, self.hexsha)
102120

103121
@property
104-
def hexsha(self):
122+
def hexsha(self) -> str:
105123
""":return: 40 byte hex version of our 20 byte binary sha"""
106124
# b2a_hex produces bytes
107125
return bin_to_hex(self.binsha).decode('ascii')
108126

109127
@property
110-
def data_stream(self):
128+
def data_stream(self) -> 'OStream':
111129
""" :return: File Object compatible stream to the uncompressed raw data of the object
112130
:note: returned streams must be read in order"""
113131
return self.repo.odb.stream(self.binsha)
114132

115-
def stream_data(self, ostream):
133+
def stream_data(self, ostream: 'OStream') -> 'Object':
116134
"""Writes our data directly to the given output stream
117135
:param ostream: File object compatible stream object.
118136
:return: self"""
@@ -130,7 +148,9 @@ class IndexObject(Object):
130148
# for compatibility with iterable lists
131149
_id_attribute_ = 'path'
132150

133-
def __init__(self, repo, binsha, mode=None, path=None):
151+
def __init__(self,
152+
repo: 'Repo', binsha: bytes, mode: Union[None, int] = None, path: Union[None, PathLike] = None
153+
) -> None:
134154
"""Initialize a newly instanced IndexObject
135155
136156
:param repo: is the Repo we are located in
@@ -150,14 +170,14 @@ def __init__(self, repo, binsha, mode=None, path=None):
150170
if path is not None:
151171
self.path = path
152172

153-
def __hash__(self):
173+
def __hash__(self) -> int:
154174
"""
155175
:return:
156176
Hash of our path as index items are uniquely identifiable by path, not
157177
by their data !"""
158178
return hash(self.path)
159179

160-
def _set_cache_(self, attr):
180+
def _set_cache_(self, attr: str) -> None:
161181
if attr in IndexObject.__slots__:
162182
# they cannot be retrieved lateron ( not without searching for them )
163183
raise AttributeError(
@@ -168,16 +188,19 @@ def _set_cache_(self, attr):
168188
# END handle slot attribute
169189

170190
@property
171-
def name(self):
191+
def name(self) -> str:
172192
""":return: Name portion of the path, effectively being the basename"""
173193
return osp.basename(self.path)
174194

175195
@property
176-
def abspath(self):
196+
def abspath(self) -> PathLike:
177197
"""
178198
:return:
179199
Absolute path to this index object in the file system ( as opposed to the
180200
.path field which is a path relative to the git repository ).
181201
182202
The returned path will be native to the system and contains '\' on windows. """
183-
return join_path_native(self.repo.working_tree_dir, self.path)
203+
if self.repo.working_tree_dir is not None:
204+
return join_path_native(self.repo.working_tree_dir, self.path)
205+
else:
206+
raise WorkTreeRepositoryUnsupported("Working_tree_dir was None or empty")

git/objects/blob.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ class Blob(base.IndexObject):
2323
__slots__ = ()
2424

2525
@property
26-
def mime_type(self):
26+
def mime_type(self) -> str:
2727
"""
2828
:return: String describing the mime type of this file (based on the filename)
2929
:note: Defaults to 'text/plain' in case the actual file type is unknown. """
3030
guesses = None
3131
if self.path:
32-
guesses = guess_type(self.path)
32+
guesses = guess_type(str(self.path))
3333
return guesses and guesses[0] or self.DEFAULT_MIME_TYPE

git/objects/commit.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
from io import BytesIO
3737
import logging
3838

39+
from typing import List, Tuple, Union, TYPE_CHECKING
40+
41+
if TYPE_CHECKING:
42+
from git.repo import Repo
43+
3944
log = logging.getLogger('git.objects.commit')
4045
log.addHandler(logging.NullHandler())
4146

@@ -70,7 +75,8 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
7075

7176
def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None,
7277
committer=None, committed_date=None, committer_tz_offset=None,
73-
message=None, parents=None, encoding=None, gpgsig=None):
78+
message=None, parents: Union[Tuple['Commit', ...], List['Commit'], None] = None,
79+
encoding=None, gpgsig=None):
7480
"""Instantiate a new Commit. All keyword arguments taking None as default will
7581
be implicitly set on first query.
7682
@@ -133,11 +139,11 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut
133139
self.gpgsig = gpgsig
134140

135141
@classmethod
136-
def _get_intermediate_items(cls, commit):
137-
return commit.parents
142+
def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]: # type: ignore ## cos overriding super
143+
return tuple(commit.parents)
138144

139145
@classmethod
140-
def _calculate_sha_(cls, repo, commit):
146+
def _calculate_sha_(cls, repo: 'Repo', commit: 'Commit') -> bytes:
141147
'''Calculate the sha of a commit.
142148
143149
:param repo: Repo object the commit should be part of
@@ -430,7 +436,7 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False,
430436

431437
#{ Serializable Implementation
432438

433-
def _serialize(self, stream):
439+
def _serialize(self, stream: BytesIO) -> 'Commit':
434440
write = stream.write
435441
write(("tree %s\n" % self.tree).encode('ascii'))
436442
for p in self.parents:
@@ -471,7 +477,7 @@ def _serialize(self, stream):
471477
# END handle encoding
472478
return self
473479

474-
def _deserialize(self, stream):
480+
def _deserialize(self, stream: BytesIO) -> 'Commit':
475481
""":param from_rev_list: if true, the stream format is coming from the rev-list command
476482
Otherwise it is assumed to be a plain data stream from our object"""
477483
readline = stream.readline
@@ -511,7 +517,7 @@ def _deserialize(self, stream):
511517
buf = enc.strip()
512518
while buf:
513519
if buf[0:10] == b"encoding ":
514-
self.encoding = buf[buf.find(' ') + 1:].decode(
520+
self.encoding = buf[buf.find(b' ') + 1:].decode(
515521
self.encoding, 'ignore')
516522
elif buf[0:7] == b"gpgsig ":
517523
sig = buf[buf.find(b' ') + 1:] + b"\n"

git/objects/submodule/base.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import os
55
import stat
6+
from typing import List
67
from unittest import SkipTest
78
import uuid
89

@@ -134,10 +135,11 @@ def _set_cache_(self, attr):
134135
super(Submodule, self)._set_cache_(attr)
135136
# END handle attribute name
136137

137-
def _get_intermediate_items(self, item):
138+
@classmethod
139+
def _get_intermediate_items(cls, item: 'Submodule') -> List['Submodule']: # type: ignore
138140
""":return: all the submodules of our module repository"""
139141
try:
140-
return type(self).list_items(item.module())
142+
return cls.list_items(item.module())
141143
except InvalidGitRepositoryError:
142144
return []
143145
# END handle intermediate items

git/objects/tag.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
from ..util import hex_to_bin
1010
from ..compat import defenc
1111

12+
from typing import List, TYPE_CHECKING, Union
13+
14+
if TYPE_CHECKING:
15+
from git.repo import Repo
16+
from git.util import Actor
17+
from .commit import Commit
18+
from .blob import Blob
19+
from .tree import Tree
20+
1221
__all__ = ("TagObject", )
1322

1423

@@ -18,8 +27,14 @@ class TagObject(base.Object):
1827
type = "tag"
1928
__slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message")
2029

21-
def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment
22-
tagger=None, tagged_date=None, tagger_tz_offset=None, message=None):
30+
def __init__(self, repo: 'Repo', binsha: bytes,
31+
object: Union[None, base.Object] = None,
32+
tag: Union[None, str] = None,
33+
tagger: Union[None, 'Actor'] = None,
34+
tagged_date: Union[int, None] = None,
35+
tagger_tz_offset: Union[int, None] = None,
36+
message: Union[str, None] = None
37+
) -> None: # @ReservedAssignment
2338
"""Initialize a tag object with additional data
2439
2540
:param repo: repository this object is located in
@@ -34,7 +49,7 @@ def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment
3449
authored_date is in, in a format similar to time.altzone"""
3550
super(TagObject, self).__init__(repo, binsha)
3651
if object is not None:
37-
self.object = object
52+
self.object = object # type: Union['Commit', 'Blob', 'Tree', 'TagObject']
3853
if tag is not None:
3954
self.tag = tag
4055
if tagger is not None:
@@ -46,16 +61,17 @@ def __init__(self, repo, binsha, object=None, tag=None, # @ReservedAssignment
4661
if message is not None:
4762
self.message = message
4863

49-
def _set_cache_(self, attr):
64+
def _set_cache_(self, attr: str) -> None:
5065
"""Cache all our attributes at once"""
5166
if attr in TagObject.__slots__:
5267
ostream = self.repo.odb.stream(self.binsha)
53-
lines = ostream.read().decode(defenc, 'replace').splitlines()
68+
lines = ostream.read().decode(defenc, 'replace').splitlines() # type: List[str]
5469

5570
_obj, hexsha = lines[0].split(" ")
5671
_type_token, type_name = lines[1].split(" ")
72+
object_type = get_object_type_by_name(type_name.encode('ascii'))
5773
self.object = \
58-
get_object_type_by_name(type_name.encode('ascii'))(self.repo, hex_to_bin(hexsha))
74+
object_type(self.repo, hex_to_bin(hexsha))
5975

6076
self.tag = lines[2][4:] # tag <tag name>
6177

0 commit comments

Comments
 (0)