1515
1616# typing ------------------------------------------------------------------
1717
18- from typing import Any , Iterator , List , Match , Optional , Tuple , Type , Union , TYPE_CHECKING
19- from git .types import PathLike , TBD , Literal
18+ from typing import Any , Iterator , List , Match , Optional , Tuple , Type , TypeVar , Union , TYPE_CHECKING
19+ from git .types import PathLike , TBD , Literal , TypeGuard
2020
2121if TYPE_CHECKING :
2222 from .objects .tree import Tree
23+ from .objects import Commit
2324 from git .repo .base import Repo
24-
25+ from git . objects . base import IndexObject
2526 from subprocess import Popen
2627
27- Lit_change_type = Literal ['A' , 'D' , 'M' , 'R' , 'T' ]
28+ Lit_change_type = Literal ['A' , 'D' , 'C' , 'M' , 'R' , 'T' , 'U' ]
29+
30+
31+ def is_change_type (inp : str ) -> TypeGuard [Lit_change_type ]:
32+ # return True
33+ return inp in ['A' , 'D' , 'C' , 'M' , 'R' , 'T' , 'U' ]
2834
2935# ------------------------------------------------------------------------
3036
37+
3138__all__ = ('Diffable' , 'DiffIndex' , 'Diff' , 'NULL_TREE' )
3239
3340# Special object to compare against the empty tree in diffs
@@ -75,15 +82,16 @@ class Diffable(object):
7582 class Index (object ):
7683 pass
7784
78- def _process_diff_args (self , args : List [Union [str , 'Diffable' , object ]]) -> List [Union [str , 'Diffable' , object ]]:
85+ def _process_diff_args (self , args : List [Union [str , 'Diffable' , Type ['Diffable.Index' ], object ]]
86+ ) -> List [Union [str , 'Diffable' , Type ['Diffable.Index' ], object ]]:
7987 """
8088 :return:
8189 possibly altered version of the given args list.
8290 Method is called right before git command execution.
8391 Subclasses can use it to alter the behaviour of the superclass"""
8492 return args
8593
86- def diff (self , other : Union [Type [Index ], Type [ 'Tree' ], object , None , str ] = Index ,
94+ def diff (self , other : Union [Type [' Index' ], 'Tree' , 'Commit' , None , str , object ] = Index ,
8795 paths : Union [PathLike , List [PathLike ], Tuple [PathLike , ...], None ] = None ,
8896 create_patch : bool = False , ** kwargs : Any ) -> 'DiffIndex' :
8997 """Creates diffs between two items being trees, trees and index or an
@@ -116,7 +124,7 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
116124 :note:
117125 On a bare repository, 'other' needs to be provided as Index or as
118126 as Tree/Commit, or a git command error will occur"""
119- args = [] # type : List[Union[str , Diffable, object]]
127+ args : List [Union [PathLike , Diffable , Type [ 'Diffable.Index' ], object ]] = [ ]
120128 args .append ("--abbrev=40" ) # we need full shas
121129 args .append ("--full-index" ) # get full index paths, not only filenames
122130
@@ -134,8 +142,8 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
134142 if paths is not None and not isinstance (paths , (tuple , list )):
135143 paths = [paths ]
136144
137- if hasattr (self , 'repo ' ): # else raise Error?
138- self .repo = self .repo # type: 'Repo'
145+ if hasattr (self , 'Has_Repo ' ):
146+ self .repo : Repo = self .repo
139147
140148 diff_cmd = self .repo .git .diff
141149 if other is self .Index :
@@ -169,7 +177,10 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
169177 return index
170178
171179
172- class DiffIndex (list ):
180+ T_Diff = TypeVar ('T_Diff' , bound = 'Diff' )
181+
182+
183+ class DiffIndex (List [T_Diff ]):
173184
174185 """Implements an Index for diffs, allowing a list of Diffs to be queried by
175186 the diff properties.
@@ -183,7 +194,7 @@ class DiffIndex(list):
183194 # T = Changed in the type
184195 change_type = ("A" , "C" , "D" , "R" , "M" , "T" )
185196
186- def iter_change_type (self , change_type : Lit_change_type ) -> Iterator ['Diff' ]:
197+ def iter_change_type (self , change_type : Lit_change_type ) -> Iterator [T_Diff ]:
187198 """
188199 :return:
189200 iterator yielding Diff instances that match the given change_type
@@ -200,19 +211,19 @@ def iter_change_type(self, change_type: Lit_change_type) -> Iterator['Diff']:
200211 if change_type not in self .change_type :
201212 raise ValueError ("Invalid change type: %s" % change_type )
202213
203- for diff in self : # type: 'Diff'
204- if diff .change_type == change_type :
205- yield diff
206- elif change_type == "A" and diff .new_file :
207- yield diff
208- elif change_type == "D" and diff .deleted_file :
209- yield diff
210- elif change_type == "C" and diff .copied_file :
211- yield diff
212- elif change_type == "R" and diff .renamed :
213- yield diff
214- elif change_type == "M" and diff .a_blob and diff .b_blob and diff .a_blob != diff .b_blob :
215- yield diff
214+ for diffidx in self :
215+ if diffidx .change_type == change_type :
216+ yield diffidx
217+ elif change_type == "A" and diffidx .new_file :
218+ yield diffidx
219+ elif change_type == "D" and diffidx .deleted_file :
220+ yield diffidx
221+ elif change_type == "C" and diffidx .copied_file :
222+ yield diffidx
223+ elif change_type == "R" and diffidx .renamed :
224+ yield diffidx
225+ elif change_type == "M" and diffidx .a_blob and diffidx .b_blob and diffidx .a_blob != diffidx .b_blob :
226+ yield diffidx
216227 # END for each diff
217228
218229
@@ -281,7 +292,7 @@ def __init__(self, repo: 'Repo',
281292 a_mode : Union [bytes , str , None ], b_mode : Union [bytes , str , None ],
282293 new_file : bool , deleted_file : bool , copied_file : bool ,
283294 raw_rename_from : Optional [bytes ], raw_rename_to : Optional [bytes ],
284- diff : Union [str , bytes , None ], change_type : Optional [str ], score : Optional [int ]) -> None :
295+ diff : Union [str , bytes , None ], change_type : Optional [Lit_change_type ], score : Optional [int ]) -> None :
285296
286297 assert a_rawpath is None or isinstance (a_rawpath , bytes )
287298 assert b_rawpath is None or isinstance (b_rawpath , bytes )
@@ -300,19 +311,21 @@ def __init__(self, repo: 'Repo',
300311 repo = submodule .module ()
301312 break
302313
314+ self .a_blob : Union ['IndexObject' , None ]
303315 if a_blob_id is None or a_blob_id == self .NULL_HEX_SHA :
304316 self .a_blob = None
305317 else :
306318 self .a_blob = Blob (repo , hex_to_bin (a_blob_id ), mode = self .a_mode , path = self .a_path )
307319
320+ self .b_blob : Union ['IndexObject' , None ]
308321 if b_blob_id is None or b_blob_id == self .NULL_HEX_SHA :
309322 self .b_blob = None
310323 else :
311324 self .b_blob = Blob (repo , hex_to_bin (b_blob_id ), mode = self .b_mode , path = self .b_path )
312325
313- self .new_file = new_file
314- self .deleted_file = deleted_file
315- self .copied_file = copied_file
326+ self .new_file : bool = new_file
327+ self .deleted_file : bool = deleted_file
328+ self .copied_file : bool = copied_file
316329
317330 # be clear and use None instead of empty strings
318331 assert raw_rename_from is None or isinstance (raw_rename_from , bytes )
@@ -321,7 +334,7 @@ def __init__(self, repo: 'Repo',
321334 self .raw_rename_to = raw_rename_to or None
322335
323336 self .diff = diff
324- self .change_type = change_type
337+ self .change_type : Union [ Lit_change_type , None ] = change_type
325338 self .score = score
326339
327340 def __eq__ (self , other : object ) -> bool :
@@ -386,36 +399,36 @@ def __str__(self) -> str:
386399 # end
387400 return res
388401
389- @property
402+ @ property
390403 def a_path (self ) -> Optional [str ]:
391404 return self .a_rawpath .decode (defenc , 'replace' ) if self .a_rawpath else None
392405
393- @property
406+ @ property
394407 def b_path (self ) -> Optional [str ]:
395408 return self .b_rawpath .decode (defenc , 'replace' ) if self .b_rawpath else None
396409
397- @property
410+ @ property
398411 def rename_from (self ) -> Optional [str ]:
399412 return self .raw_rename_from .decode (defenc , 'replace' ) if self .raw_rename_from else None
400413
401- @property
414+ @ property
402415 def rename_to (self ) -> Optional [str ]:
403416 return self .raw_rename_to .decode (defenc , 'replace' ) if self .raw_rename_to else None
404417
405- @property
418+ @ property
406419 def renamed (self ) -> bool :
407420 """:returns: True if the blob of our diff has been renamed
408421 :note: This property is deprecated, please use ``renamed_file`` instead.
409422 """
410423 return self .renamed_file
411424
412- @property
425+ @ property
413426 def renamed_file (self ) -> bool :
414427 """:returns: True if the blob of our diff has been renamed
415428 """
416429 return self .rename_from != self .rename_to
417430
418- @classmethod
431+ @ classmethod
419432 def _pick_best_path (cls , path_match : bytes , rename_match : bytes , path_fallback_match : bytes ) -> Optional [bytes ]:
420433 if path_match :
421434 return decode_path (path_match )
@@ -428,7 +441,7 @@ def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_m
428441
429442 return None
430443
431- @classmethod
444+ @ classmethod
432445 def _index_from_patch_format (cls , repo : 'Repo' , proc : TBD ) -> DiffIndex :
433446 """Create a new DiffIndex from the given text which must be in patch format
434447 :param repo: is the repository we are operating on - it is required
@@ -441,7 +454,7 @@ def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
441454
442455 # for now, we have to bake the stream
443456 text = b'' .join (text_list )
444- index = DiffIndex ()
457+ index : 'DiffIndex' = DiffIndex ()
445458 previous_header = None
446459 header = None
447460 a_path , b_path = None , None # for mypy
@@ -491,19 +504,21 @@ def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
491504
492505 return index
493506
494- @staticmethod
507+ @ staticmethod
495508 def _handle_diff_line (lines_bytes : bytes , repo : 'Repo' , index : DiffIndex ) -> None :
496509 lines = lines_bytes .decode (defenc )
497510
498511 for line in lines .split (':' )[1 :]:
499512 meta , _ , path = line .partition ('\x00 ' )
500513 path = path .rstrip ('\x00 ' )
501- a_blob_id , b_blob_id = None , None # Type: Optional[str]
514+ a_blob_id : Optional [str ]
515+ b_blob_id : Optional [str ]
502516 old_mode , new_mode , a_blob_id , b_blob_id , _change_type = meta .split (None , 4 )
503517 # Change type can be R100
504518 # R: status letter
505519 # 100: score (in case of copy and rename)
506- change_type = _change_type [0 ]
520+ assert is_change_type (_change_type [0 ]), f"Unexpected value for change_type received: { _change_type [0 ]} "
521+ change_type : Lit_change_type = _change_type [0 ]
507522 score_str = '' .join (_change_type [1 :])
508523 score = int (score_str ) if score_str .isdigit () else None
509524 path = path .strip ()
@@ -543,14 +558,14 @@ def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> Non
543558 '' , change_type , score )
544559 index .append (diff )
545560
546- @classmethod
561+ @ classmethod
547562 def _index_from_raw_format (cls , repo : 'Repo' , proc : 'Popen' ) -> 'DiffIndex' :
548563 """Create a new DiffIndex from the given stream which must be in raw format.
549564 :return: git.DiffIndex"""
550565 # handles
551566 # :100644 100644 687099101... 37c5e30c8... M .gitignore
552567
553- index = DiffIndex ()
568+ index : 'DiffIndex' = DiffIndex ()
554569 handle_process_output (proc , lambda byt : cls ._handle_diff_line (byt , repo , index ),
555570 None , finalize_process , decode_streams = False )
556571
0 commit comments