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 :
2222from .objects .tree import Tree
23+ from .objects import Commit
2324from git .repo .base import Repo
24-
25+ from git . objects . base import IndexObject
2526from 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):
7582class Index (object ):
7683pass
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"""
8492return 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 ,
8795paths :Union [PathLike ,List [PathLike ],Tuple [PathLike , ...],None ]= None ,
8896create_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 ]] = [ ]
120128args .append ("--abbrev=40" )# we need full shas
121129args .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
134142if paths is not None and not isinstance (paths , (tuple ,list )):
135143paths = [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
140148diff_cmd = self .repo .git .diff
141149if other is self .Index :
@@ -169,7 +177,10 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
169177return 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
184195change_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']:
200211if change_type not in self .change_type :
201212raise 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',
281292a_mode :Union [bytes ,str ,None ],b_mode :Union [bytes ,str ,None ],
282293new_file :bool ,deleted_file :bool ,copied_file :bool ,
283294raw_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
286297assert a_rawpath is None or isinstance (a_rawpath ,bytes )
287298assert b_rawpath is None or isinstance (b_rawpath ,bytes )
@@ -300,19 +311,21 @@ def __init__(self, repo: 'Repo',
300311repo = submodule .module ()
301312break
302313
314+ self .a_blob :Union ['IndexObject' ,None ]
303315if a_blob_id is None or a_blob_id == self .NULL_HEX_SHA :
304316self .a_blob = None
305317else :
306318self .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 ]
308321if b_blob_id is None or b_blob_id == self .NULL_HEX_SHA :
309322self .b_blob = None
310323else :
311324self .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
318331assert raw_rename_from is None or isinstance (raw_rename_from ,bytes )
@@ -321,7 +334,7 @@ def __init__(self, repo: 'Repo',
321334self .raw_rename_to = raw_rename_to or None
322335
323336self .diff = diff
324- self .change_type = change_type
337+ self .change_type : Union [ Lit_change_type , None ] = change_type
325338self .score = score
326339
327340def __eq__ (self ,other :object )-> bool :
@@ -386,36 +399,36 @@ def __str__(self) -> str:
386399# end
387400return res
388401
389- @property
402+ @property
390403def a_path (self )-> Optional [str ]:
391404return self .a_rawpath .decode (defenc ,'replace' )if self .a_rawpath else None
392405
393- @property
406+ @property
394407def b_path (self )-> Optional [str ]:
395408return self .b_rawpath .decode (defenc ,'replace' )if self .b_rawpath else None
396409
397- @property
410+ @property
398411def rename_from (self )-> Optional [str ]:
399412return self .raw_rename_from .decode (defenc ,'replace' )if self .raw_rename_from else None
400413
401- @property
414+ @property
402415def rename_to (self )-> Optional [str ]:
403416return self .raw_rename_to .decode (defenc ,'replace' )if self .raw_rename_to else None
404417
405- @property
418+ @property
406419def 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 """
410423return self .renamed_file
411424
412- @property
425+ @property
413426def renamed_file (self )-> bool :
414427""":returns: True if the blob of our diff has been renamed
415428 """
416429return self .rename_from != self .rename_to
417430
418- @classmethod
431+ @classmethod
419432def _pick_best_path (cls ,path_match :bytes ,rename_match :bytes ,path_fallback_match :bytes )-> Optional [bytes ]:
420433if path_match :
421434return decode_path (path_match )
@@ -428,7 +441,7 @@ def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_m
428441
429442return None
430443
431- @classmethod
444+ @classmethod
432445def _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
443456text = b'' .join (text_list )
444- index = DiffIndex ()
457+ index : 'DiffIndex' = DiffIndex ()
445458previous_header = None
446459header = None
447460a_path ,b_path = None ,None # for mypy
@@ -491,19 +504,21 @@ def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
491504
492505return index
493506
494- @staticmethod
507+ @staticmethod
495508def _handle_diff_line (lines_bytes :bytes ,repo :'Repo' ,index :DiffIndex )-> None :
496509lines = lines_bytes .decode (defenc )
497510
498511for line in lines .split (':' )[1 :]:
499512meta ,_ ,path = line .partition ('\x00 ' )
500513path = 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 ]
502516old_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 ]
507522score_str = '' .join (_change_type [1 :])
508523score = int (score_str )if score_str .isdigit ()else None
509524path = path .strip ()
@@ -543,14 +558,14 @@ def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> Non
543558'' ,change_type ,score )
544559index .append (diff )
545560
546- @classmethod
561+ @classmethod
547562def _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 ()
554569handle_process_output (proc ,lambda byt :cls ._handle_diff_line (byt ,repo ,index ),
555570None ,finalize_process ,decode_streams = False )
556571