18
18
import time
19
19
from unittest import SkipTest
20
20
from urllib .parse import urlsplit , urlunsplit
21
+ import warnings
21
22
22
23
# typing ---------------------------------------------------------
23
24
24
25
from typing import (Any , AnyStr , BinaryIO , Callable , Dict , Generator , IO , Iterator , List ,
25
- Optional , Pattern , Sequence , Tuple , Union , cast , TYPE_CHECKING , overload )
26
+ Optional , Pattern , Sequence , Tuple , TypeVar , Union , cast , TYPE_CHECKING , overload )
26
27
27
28
import pathlib
28
29
29
30
if TYPE_CHECKING :
30
31
from git .remote import Remote
31
32
from git .repo .base import Repo
32
- from .types import PathLike , TBD , Literal , SupportsIndex
33
+ from git .config import GitConfigParser , SectionConstraint
34
+
35
+ from .types import PathLike , Literal , SupportsIndex , HSH_TD , Files_TD
33
36
34
37
# ---------------------------------------------------------------------
35
38
@@ -81,7 +84,7 @@ def unbare_repo(func: Callable) -> Callable:
81
84
encounter a bare repository"""
82
85
83
86
@wraps (func )
84
- def wrapper (self : 'Remote' , * args : Any , ** kwargs : Any ) -> TBD :
87
+ def wrapper (self : 'Remote' , * args : Any , ** kwargs : Any ) -> Callable :
85
88
if self .repo .bare :
86
89
raise InvalidGitRepositoryError ("Method '%s' cannot operate on bare repositories" % func .__name__ )
87
90
# END bare method
@@ -107,7 +110,7 @@ def rmtree(path: PathLike) -> None:
107
110
:note: we use shutil rmtree but adjust its behaviour to see whether files that
108
111
couldn't be deleted are read-only. Windows will not remove them in that case"""
109
112
110
- def onerror (func : Callable , path : PathLike , exc_info : TBD ) -> None :
113
+ def onerror (func : Callable , path : PathLike , exc_info : str ) -> None :
111
114
# Is the error an access error ?
112
115
os .chmod (path , stat .S_IWUSR )
113
116
@@ -447,7 +450,7 @@ class RemoteProgress(object):
447
450
re_op_relative = re .compile (r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)" )
448
451
449
452
def __init__ (self ) -> None :
450
- self ._seen_ops = [] # type: List[TBD ]
453
+ self ._seen_ops = [] # type: List[int ]
451
454
self ._cur_line = None # type: Optional[str]
452
455
self .error_lines = [] # type: List[str]
453
456
self .other_lines = [] # type: List[str]
@@ -668,7 +671,8 @@ def _from_string(cls, string: str) -> 'Actor':
668
671
# END handle name/email matching
669
672
670
673
@classmethod
671
- def _main_actor (cls , env_name : str , env_email : str , config_reader : Optional [TBD ] = None ) -> 'Actor' :
674
+ def _main_actor (cls , env_name : str , env_email : str ,
675
+ config_reader : Union [None , 'GitConfigParser' , 'SectionConstraint' ] = None ) -> 'Actor' :
672
676
actor = Actor ('' , '' )
673
677
user_id = None # We use this to avoid multiple calls to getpass.getuser()
674
678
@@ -697,7 +701,7 @@ def default_name() -> str:
697
701
return actor
698
702
699
703
@classmethod
700
- def committer (cls , config_reader : Optional [ TBD ] = None ) -> 'Actor' :
704
+ def committer (cls , config_reader : Union [ None , 'GitConfigParser' , 'SectionConstraint' ] = None ) -> 'Actor' :
701
705
"""
702
706
:return: Actor instance corresponding to the configured committer. It behaves
703
707
similar to the git implementation, such that the environment will override
@@ -708,7 +712,7 @@ def committer(cls, config_reader: Optional[TBD] = None) -> 'Actor':
708
712
return cls ._main_actor (cls .env_committer_name , cls .env_committer_email , config_reader )
709
713
710
714
@classmethod
711
- def author (cls , config_reader : Optional [ TBD ] = None ) -> 'Actor' :
715
+ def author (cls , config_reader : Union [ None , 'GitConfigParser' , 'SectionConstraint' ] = None ) -> 'Actor' :
712
716
"""Same as committer(), but defines the main author. It may be specified in the environment,
713
717
but defaults to the committer"""
714
718
return cls ._main_actor (cls .env_author_name , cls .env_author_email , config_reader )
@@ -742,7 +746,9 @@ class Stats(object):
742
746
files = number of changed files as int"""
743
747
__slots__ = ("total" , "files" )
744
748
745
- def __init__ (self , total : Dict [str , Dict [str , int ]], files : Dict [str , Dict [str , int ]]):
749
+ from git .types import Total_TD , Files_TD
750
+
751
+ def __init__ (self , total : Total_TD , files : Dict [PathLike , Files_TD ]):
746
752
self .total = total
747
753
self .files = files
748
754
@@ -751,9 +757,13 @@ def _list_from_string(cls, repo: 'Repo', text: str) -> 'Stats':
751
757
"""Create a Stat object from output retrieved by git-diff.
752
758
753
759
:return: git.Stat"""
754
- hsh = {'total' : {'insertions' : 0 , 'deletions' : 0 , 'lines' : 0 , 'files' : 0 },
755
- 'files' : {}
756
- } # type: Dict[str, Dict[str, TBD]] ## need typeddict or refactor for mypy
760
+
761
+ hsh : HSH_TD = {'total' : {'insertions' : 0 ,
762
+ 'deletions' : 0 ,
763
+ 'lines' : 0 ,
764
+ 'files' : 0 },
765
+ 'files' : {}
766
+ }
757
767
for line in text .splitlines ():
758
768
(raw_insertions , raw_deletions , filename ) = line .split ("\t " )
759
769
insertions = raw_insertions != '-' and int (raw_insertions ) or 0
@@ -762,9 +772,10 @@ def _list_from_string(cls, repo: 'Repo', text: str) -> 'Stats':
762
772
hsh ['total' ]['deletions' ] += deletions
763
773
hsh ['total' ]['lines' ] += insertions + deletions
764
774
hsh ['total' ]['files' ] += 1
765
- hsh ['files' ][filename .strip ()] = {'insertions' : insertions ,
766
- 'deletions' : deletions ,
767
- 'lines' : insertions + deletions }
775
+ files_dict : Files_TD = {'insertions' : insertions ,
776
+ 'deletions' : deletions ,
777
+ 'lines' : insertions + deletions }
778
+ hsh ['files' ][filename .strip ()] = files_dict
768
779
return Stats (hsh ['total' ], hsh ['files' ])
769
780
770
781
@@ -920,7 +931,10 @@ def _obtain_lock(self) -> None:
920
931
# END endless loop
921
932
922
933
923
- class IterableList (list ):
934
+ T = TypeVar ('T' , bound = 'IterableObj' )
935
+
936
+
937
+ class IterableList (List [T ]):
924
938
925
939
"""
926
940
List of iterable objects allowing to query an object by id or by named index::
@@ -930,6 +944,9 @@ class IterableList(list):
930
944
heads['master']
931
945
heads[0]
932
946
947
+ Iterable parent objects = [Commit, SubModule, Reference, FetchInfo, PushInfo]
948
+ Iterable via inheritance = [Head, TagReference, RemoteReference]
949
+ ]
933
950
It requires an id_attribute name to be set which will be queried from its
934
951
contained items to have a means for comparison.
935
952
@@ -938,7 +955,7 @@ class IterableList(list):
938
955
can be left out."""
939
956
__slots__ = ('_id_attr' , '_prefix' )
940
957
941
- def __new__ (cls , id_attr : str , prefix : str = '' ) -> 'IterableList' :
958
+ def __new__ (cls , id_attr : str , prefix : str = '' ) -> 'IterableList[IterableObj] ' :
942
959
return super (IterableList , cls ).__new__ (cls )
943
960
944
961
def __init__ (self , id_attr : str , prefix : str = '' ) -> None :
@@ -1007,16 +1024,29 @@ def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> Any:
1007
1024
list .__delitem__ (self , delindex )
1008
1025
1009
1026
1027
+ class IterableClassWatcher (type ):
1028
+ def __init__ (cls , name , bases , clsdict ):
1029
+ for base in bases :
1030
+ if type (base ) == IterableClassWatcher :
1031
+ warnings .warn (f"GitPython Iterable subclassed by { name } . "
1032
+ "Iterable is deprecated due to naming clash, "
1033
+ "Use IterableObj instead \n " ,
1034
+ DeprecationWarning ,
1035
+ stacklevel = 2 )
1036
+
1037
+
1010
1038
class Iterable (object ):
1011
1039
1012
1040
"""Defines an interface for iterable items which is to assure a uniform
1013
1041
way to retrieve and iterate items within the git repository"""
1014
1042
__slots__ = ()
1015
1043
_id_attribute_ = "attribute that most suitably identifies your instance"
1044
+ __metaclass__ = IterableClassWatcher
1016
1045
1017
1046
@classmethod
1018
- def list_items (cls , repo : 'Repo' , * args : Any , ** kwargs : Any ) -> 'IterableList' :
1047
+ def list_items (cls , repo , * args , ** kwargs ) :
1019
1048
"""
1049
+ Deprecaated, use IterableObj instead.
1020
1050
Find all items of this type - subclasses can specify args and kwargs differently.
1021
1051
If no args are given, subclasses are obliged to return all items if no additional
1022
1052
arguments arg given.
@@ -1029,7 +1059,35 @@ def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> 'IterableList':
1029
1059
return out_list
1030
1060
1031
1061
@classmethod
1032
- def iter_items (cls , repo : 'Repo' , * args : Any , ** kwargs : Any ) -> Iterator [TBD ]:
1062
+ def iter_items (cls , repo : 'Repo' , * args : Any , ** kwargs : Any ):
1063
+ # return typed to be compatible with subtypes e.g. Remote
1064
+ """For more information about the arguments, see list_items
1065
+ :return: iterator yielding Items"""
1066
+ raise NotImplementedError ("To be implemented by Subclass" )
1067
+
1068
+
1069
+ class IterableObj ():
1070
+ """Defines an interface for iterable items which is to assure a uniform
1071
+ way to retrieve and iterate items within the git repository"""
1072
+ __slots__ = ()
1073
+ _id_attribute_ = "attribute that most suitably identifies your instance"
1074
+
1075
+ @classmethod
1076
+ def list_items (cls , repo : 'Repo' , * args : Any , ** kwargs : Any ) -> IterableList [T ]:
1077
+ """
1078
+ Find all items of this type - subclasses can specify args and kwargs differently.
1079
+ If no args are given, subclasses are obliged to return all items if no additional
1080
+ arguments arg given.
1081
+
1082
+ :note: Favor the iter_items method as it will
1083
+
1084
+ :return:list(Item,...) list of item instances"""
1085
+ out_list : IterableList = IterableList (cls ._id_attribute_ )
1086
+ out_list .extend (cls .iter_items (repo , * args , ** kwargs ))
1087
+ return out_list
1088
+
1089
+ @classmethod
1090
+ def iter_items (cls , repo : 'Repo' , * args : Any , ** kwargs : Any ) -> Iterator [T ]:
1033
1091
# return typed to be compatible with subtypes e.g. Remote
1034
1092
"""For more information about the arguments, see list_items
1035
1093
:return: iterator yielding Items"""
0 commit comments