Skip to content

Commit 2fd9f6e

Browse files
committed
add types to git.compat and git.diff
1 parent 71e28b8 commit 2fd9f6e

File tree

5 files changed

+91
-64
lines changed

5 files changed

+91
-64
lines changed

git/compat.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
import locale
1111
import os
1212
import sys
13-
from typing import AnyStr, Optional, Type
14-
1513

1614
from gitdb.utils.encoding import (
1715
force_bytes, # @UnusedImport
1816
force_text # @UnusedImport
1917
)
2018

19+
from typing import Any, AnyStr, Dict, Optional, Type
20+
from git.types import TBD
21+
2122

2223
is_win = (os.name == 'nt') # type: bool
2324
is_posix = (os.name == 'posix')
@@ -61,14 +62,17 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
6162
return None
6263

6364

64-
def with_metaclass(meta, *bases):
65+
def with_metaclass(meta: Type[Any], *bases: Any) -> 'metaclass': # type: ignore ## mypy cannot understand dynamic class creation
6566
"""copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15"""
66-
class metaclass(meta):
67+
68+
class metaclass(meta): # type: ignore
6769
__call__ = type.__call__
68-
__init__ = type.__init__
70+
__init__ = type.__init__ # type: ignore
6971

70-
def __new__(cls, name, nbases, d):
72+
def __new__(cls, name: str, nbases: Optional[int], d: Dict[str, Any]) -> TBD:
7173
if nbases is None:
7274
return type.__new__(cls, name, (), d)
7375
return meta(name, bases, d)
76+
7477
return metaclass(meta.__name__ + 'Helper', None, {})
78+

git/db.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Module with our own gitdb implementation - it uses the git command"""
2+
from typing import AnyStr
23
from git.util import bin_to_hex, hex_to_bin
34
from gitdb.base import (
45
OInfo,
@@ -13,7 +14,7 @@
1314
# typing-------------------------------------------------
1415

1516
from .cmd import Git
16-
from .types import PathLike, TBD
17+
from .types import PathLike
1718

1819
# --------------------------------------------------------
1920

@@ -48,7 +49,7 @@ def stream(self, sha: bytes) -> OStream:
4849

4950
# { Interface
5051

51-
def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes:
52+
def partial_to_complete_sha_hex(self, partial_hexsha: AnyStr) -> bytes:
5253
""":return: Full binary 20 byte sha from the given partial hexsha
5354
:raise AmbiguousObjectName:
5455
:raise BadObject:

git/diff.py

+66-48
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
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-
import re
76

7+
import re
88
from git.cmd import handle_process_output
99
from git.compat import defenc
1010
from git.util import finalize_process, hex_to_bin
@@ -13,22 +13,33 @@
1313
from .objects.util import mode_str_to_int
1414

1515

16+
# typing ------------------------------------------------------------------
17+
18+
from .objects.tree import Tree
19+
from git.repo.base import Repo
20+
from typing_extensions import Final, Literal
21+
from git.types import TBD
22+
from typing import Any, Iterator, List, Match, Optional, Tuple, Type, Union
23+
Lit_change_type = Literal['A', 'D', 'M', 'R', 'T']
24+
25+
# ------------------------------------------------------------------------
26+
1627
__all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
1728

1829
# Special object to compare against the empty tree in diffs
19-
NULL_TREE = object()
30+
NULL_TREE: Final[object] = object()
2031

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

2334

24-
def _octal_repl(matchobj):
35+
def _octal_repl(matchobj: Match) -> bytes:
2536
value = matchobj.group(1)
2637
value = int(value, 8)
2738
value = bytes(bytearray((value,)))
2839
return value
2940

3041

31-
def decode_path(path, has_ab_prefix=True):
42+
def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
3243
if path == b'/dev/null':
3344
return None
3445

@@ -60,15 +71,17 @@ class Diffable(object):
6071
class Index(object):
6172
pass
6273

63-
def _process_diff_args(self, args):
74+
def _process_diff_args(self, args: List[Union[str, 'Diffable', object]]) -> List[Union[str, 'Diffable', object]]:
6475
"""
6576
:return:
6677
possibly altered version of the given args list.
6778
Method is called right before git command execution.
6879
Subclasses can use it to alter the behaviour of the superclass"""
6980
return args
7081

71-
def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
82+
def diff(self, other: Union[Type[Index], Type[Tree], object, None, str] = Index,
83+
paths: Union[str, List[str], Tuple[str, ...], None] = None,
84+
create_patch: bool = False, **kwargs: Any) -> 'DiffIndex':
7285
"""Creates diffs between two items being trees, trees and index or an
7386
index and the working tree. It will detect renames automatically.
7487
@@ -99,7 +112,7 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
99112
:note:
100113
On a bare repository, 'other' needs to be provided as Index or as
101114
as Tree/Commit, or a git command error will occur"""
102-
args = []
115+
args = [] # type: List[Union[str, Diffable, object]]
103116
args.append("--abbrev=40") # we need full shas
104117
args.append("--full-index") # get full index paths, not only filenames
105118

@@ -117,6 +130,9 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
117130
if paths is not None and not isinstance(paths, (tuple, list)):
118131
paths = [paths]
119132

133+
if hasattr(self, 'repo'): # else raise Error?
134+
self.repo = self.repo # type: 'Repo'
135+
120136
diff_cmd = self.repo.git.diff
121137
if other is self.Index:
122138
args.insert(0, '--cached')
@@ -163,7 +179,7 @@ class DiffIndex(list):
163179
# T = Changed in the type
164180
change_type = ("A", "C", "D", "R", "M", "T")
165181

166-
def iter_change_type(self, change_type):
182+
def iter_change_type(self, change_type: Lit_change_type) -> Iterator['Diff']:
167183
"""
168184
:return:
169185
iterator yielding Diff instances that match the given change_type
@@ -180,7 +196,7 @@ def iter_change_type(self, change_type):
180196
if change_type not in self.change_type:
181197
raise ValueError("Invalid change type: %s" % change_type)
182198

183-
for diff in self:
199+
for diff in self: # type: 'Diff'
184200
if diff.change_type == change_type:
185201
yield diff
186202
elif change_type == "A" and diff.new_file:
@@ -255,22 +271,21 @@ class Diff(object):
255271
"new_file", "deleted_file", "copied_file", "raw_rename_from",
256272
"raw_rename_to", "diff", "change_type", "score")
257273

258-
def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,
259-
b_mode, new_file, deleted_file, copied_file, raw_rename_from,
260-
raw_rename_to, diff, change_type, score):
261-
262-
self.a_mode = a_mode
263-
self.b_mode = b_mode
274+
def __init__(self, repo: Repo,
275+
a_rawpath: Optional[bytes], b_rawpath: Optional[bytes],
276+
a_blob_id: Union[str, bytes, None], b_blob_id: Union[str, bytes, None],
277+
a_mode: Union[bytes, str, None], b_mode: Union[bytes, str, None],
278+
new_file: bool, deleted_file: bool, copied_file: bool,
279+
raw_rename_from: Optional[bytes], raw_rename_to: Optional[bytes],
280+
diff: Union[str, bytes, None], change_type: Optional[str], score: Optional[int]) -> None:
264281

265282
assert a_rawpath is None or isinstance(a_rawpath, bytes)
266283
assert b_rawpath is None or isinstance(b_rawpath, bytes)
267284
self.a_rawpath = a_rawpath
268285
self.b_rawpath = b_rawpath
269286

270-
if self.a_mode:
271-
self.a_mode = mode_str_to_int(self.a_mode)
272-
if self.b_mode:
273-
self.b_mode = mode_str_to_int(self.b_mode)
287+
self.a_mode = mode_str_to_int(a_mode) if a_mode else None
288+
self.b_mode = mode_str_to_int(b_mode) if b_mode else None
274289

275290
# Determine whether this diff references a submodule, if it does then
276291
# we need to overwrite "repo" to the corresponding submodule's repo instead
@@ -305,27 +320,27 @@ def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,
305320
self.change_type = change_type
306321
self.score = score
307322

308-
def __eq__(self, other):
323+
def __eq__(self, other: object) -> bool:
309324
for name in self.__slots__:
310325
if getattr(self, name) != getattr(other, name):
311326
return False
312327
# END for each name
313328
return True
314329

315-
def __ne__(self, other):
330+
def __ne__(self, other: object) -> bool:
316331
return not (self == other)
317332

318-
def __hash__(self):
333+
def __hash__(self) -> int:
319334
return hash(tuple(getattr(self, n) for n in self.__slots__))
320335

321-
def __str__(self):
322-
h = "%s"
336+
def __str__(self) -> str:
337+
h = "%s" # type: str
323338
if self.a_blob:
324339
h %= self.a_blob.path
325340
elif self.b_blob:
326341
h %= self.b_blob.path
327342

328-
msg = ''
343+
msg = '' # type: str
329344
line = None # temp line
330345
line_length = 0 # line length
331346
for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')):
@@ -354,7 +369,7 @@ def __str__(self):
354369
if self.diff:
355370
msg += '\n---'
356371
try:
357-
msg += self.diff.decode(defenc)
372+
msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
358373
except UnicodeDecodeError:
359374
msg += 'OMITTED BINARY DATA'
360375
# end handle encoding
@@ -368,36 +383,36 @@ def __str__(self):
368383
return res
369384

370385
@property
371-
def a_path(self):
386+
def a_path(self) -> Optional[str]:
372387
return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None
373388

374389
@property
375-
def b_path(self):
390+
def b_path(self) -> Optional[str]:
376391
return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None
377392

378393
@property
379-
def rename_from(self):
394+
def rename_from(self) -> Optional[str]:
380395
return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None
381396

382397
@property
383-
def rename_to(self):
398+
def rename_to(self) -> Optional[str]:
384399
return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None
385400

386401
@property
387-
def renamed(self):
402+
def renamed(self) -> bool:
388403
""":returns: True if the blob of our diff has been renamed
389404
:note: This property is deprecated, please use ``renamed_file`` instead.
390405
"""
391406
return self.renamed_file
392407

393408
@property
394-
def renamed_file(self):
409+
def renamed_file(self) -> bool:
395410
""":returns: True if the blob of our diff has been renamed
396411
"""
397412
return self.rename_from != self.rename_to
398413

399414
@classmethod
400-
def _pick_best_path(cls, path_match, rename_match, path_fallback_match):
415+
def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
401416
if path_match:
402417
return decode_path(path_match)
403418

@@ -410,21 +425,23 @@ def _pick_best_path(cls, path_match, rename_match, path_fallback_match):
410425
return None
411426

412427
@classmethod
413-
def _index_from_patch_format(cls, repo, proc):
428+
def _index_from_patch_format(cls, repo: Repo, proc: TBD) -> DiffIndex:
414429
"""Create a new DiffIndex from the given text which must be in patch format
415430
:param repo: is the repository we are operating on - it is required
416431
:param stream: result of 'git diff' as a stream (supporting file protocol)
417432
:return: git.DiffIndex """
418433

419434
## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
420-
text = []
421-
handle_process_output(proc, text.append, None, finalize_process, decode_streams=False)
435+
text_list = [] # type: List[bytes]
436+
handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
422437

423438
# for now, we have to bake the stream
424-
text = b''.join(text)
439+
text = b''.join(text_list)
425440
index = DiffIndex()
426441
previous_header = None
427442
header = None
443+
a_path, b_path = None, None # for mypy
444+
a_mode, b_mode = None, None # for mypy
428445
for _header in cls.re_header.finditer(text):
429446
a_path_fallback, b_path_fallback, \
430447
old_mode, new_mode, \
@@ -464,27 +481,28 @@ def _index_from_patch_format(cls, repo, proc):
464481
previous_header = _header
465482
header = _header
466483
# end for each header we parse
467-
if index:
484+
if index and header:
468485
index[-1].diff = text[header.end():]
469486
# end assign last diff
470487

471488
return index
472489

473490
@classmethod
474-
def _index_from_raw_format(cls, repo, proc):
491+
def _index_from_raw_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
475492
"""Create a new DiffIndex from the given stream which must be in raw format.
476493
:return: git.DiffIndex"""
477494
# handles
478495
# :100644 100644 687099101... 37c5e30c8... M .gitignore
479496

480497
index = DiffIndex()
481498

482-
def handle_diff_line(lines):
483-
lines = lines.decode(defenc)
499+
def handle_diff_line(lines_bytes: bytes) -> None:
500+
lines = lines_bytes.decode(defenc)
484501

485502
for line in lines.split(':')[1:]:
486503
meta, _, path = line.partition('\x00')
487504
path = path.rstrip('\x00')
505+
a_blob_id, b_blob_id = None, None # Type: Optional[str]
488506
old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
489507
# Change type can be R100
490508
# R: status letter
@@ -504,20 +522,20 @@ def handle_diff_line(lines):
504522
# NOTE: We cannot conclude from the existence of a blob to change type
505523
# as diffs with the working do not have blobs yet
506524
if change_type == 'D':
507-
b_blob_id = None
525+
b_blob_id = None # Optional[str]
508526
deleted_file = True
509527
elif change_type == 'A':
510528
a_blob_id = None
511529
new_file = True
512530
elif change_type == 'C':
513531
copied_file = True
514-
a_path, b_path = path.split('\x00', 1)
515-
a_path = a_path.encode(defenc)
516-
b_path = b_path.encode(defenc)
532+
a_path_str, b_path_str = path.split('\x00', 1)
533+
a_path = a_path_str.encode(defenc)
534+
b_path = b_path_str.encode(defenc)
517535
elif change_type == 'R':
518-
a_path, b_path = path.split('\x00', 1)
519-
a_path = a_path.encode(defenc)
520-
b_path = b_path.encode(defenc)
536+
a_path_str, b_path_str = path.split('\x00', 1)
537+
a_path = a_path_str.encode(defenc)
538+
b_path = b_path_str.encode(defenc)
521539
rename_from, rename_to = a_path, b_path
522540
elif change_type == 'T':
523541
# Nothing to do

0 commit comments

Comments
 (0)