15
15
16
16
# typing ------------------------------------------------------------------
17
17
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
20
20
21
21
if TYPE_CHECKING :
22
22
from .objects .tree import Tree
23
+ from .objects import Commit
23
24
from git .repo .base import Repo
24
-
25
+ from git . objects . base import IndexObject
25
26
from subprocess import Popen
26
27
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' ]
28
34
29
35
# ------------------------------------------------------------------------
30
36
37
+
31
38
__all__ = ('Diffable' , 'DiffIndex' , 'Diff' , 'NULL_TREE' )
32
39
33
40
# Special object to compare against the empty tree in diffs
@@ -75,15 +82,16 @@ class Diffable(object):
75
82
class Index (object ):
76
83
pass
77
84
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 ]]:
79
87
"""
80
88
:return:
81
89
possibly altered version of the given args list.
82
90
Method is called right before git command execution.
83
91
Subclasses can use it to alter the behaviour of the superclass"""
84
92
return args
85
93
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 ,
87
95
paths : Union [PathLike , List [PathLike ], Tuple [PathLike , ...], None ] = None ,
88
96
create_patch : bool = False , ** kwargs : Any ) -> 'DiffIndex' :
89
97
"""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
116
124
:note:
117
125
On a bare repository, 'other' needs to be provided as Index or as
118
126
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 ]] = [ ]
120
128
args .append ("--abbrev=40" ) # we need full shas
121
129
args .append ("--full-index" ) # get full index paths, not only filenames
122
130
@@ -134,8 +142,8 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
134
142
if paths is not None and not isinstance (paths , (tuple , list )):
135
143
paths = [paths ]
136
144
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
139
147
140
148
diff_cmd = self .repo .git .diff
141
149
if other is self .Index :
@@ -169,7 +177,10 @@ def diff(self, other: Union[Type[Index], Type['Tree'], object, None, str] = Inde
169
177
return index
170
178
171
179
172
- class DiffIndex (list ):
180
+ T_Diff = TypeVar ('T_Diff' , bound = 'Diff' )
181
+
182
+
183
+ class DiffIndex (List [T_Diff ]):
173
184
174
185
"""Implements an Index for diffs, allowing a list of Diffs to be queried by
175
186
the diff properties.
@@ -183,7 +194,7 @@ class DiffIndex(list):
183
194
# T = Changed in the type
184
195
change_type = ("A" , "C" , "D" , "R" , "M" , "T" )
185
196
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 ]:
187
198
"""
188
199
:return:
189
200
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']:
200
211
if change_type not in self .change_type :
201
212
raise ValueError ("Invalid change type: %s" % change_type )
202
213
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
216
227
# END for each diff
217
228
218
229
@@ -281,7 +292,7 @@ def __init__(self, repo: 'Repo',
281
292
a_mode : Union [bytes , str , None ], b_mode : Union [bytes , str , None ],
282
293
new_file : bool , deleted_file : bool , copied_file : bool ,
283
294
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 :
285
296
286
297
assert a_rawpath is None or isinstance (a_rawpath , bytes )
287
298
assert b_rawpath is None or isinstance (b_rawpath , bytes )
@@ -300,19 +311,21 @@ def __init__(self, repo: 'Repo',
300
311
repo = submodule .module ()
301
312
break
302
313
314
+ self .a_blob : Union ['IndexObject' , None ]
303
315
if a_blob_id is None or a_blob_id == self .NULL_HEX_SHA :
304
316
self .a_blob = None
305
317
else :
306
318
self .a_blob = Blob (repo , hex_to_bin (a_blob_id ), mode = self .a_mode , path = self .a_path )
307
319
320
+ self .b_blob : Union ['IndexObject' , None ]
308
321
if b_blob_id is None or b_blob_id == self .NULL_HEX_SHA :
309
322
self .b_blob = None
310
323
else :
311
324
self .b_blob = Blob (repo , hex_to_bin (b_blob_id ), mode = self .b_mode , path = self .b_path )
312
325
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
316
329
317
330
# be clear and use None instead of empty strings
318
331
assert raw_rename_from is None or isinstance (raw_rename_from , bytes )
@@ -321,7 +334,7 @@ def __init__(self, repo: 'Repo',
321
334
self .raw_rename_to = raw_rename_to or None
322
335
323
336
self .diff = diff
324
- self .change_type = change_type
337
+ self .change_type : Union [ Lit_change_type , None ] = change_type
325
338
self .score = score
326
339
327
340
def __eq__ (self , other : object ) -> bool :
@@ -386,36 +399,36 @@ def __str__(self) -> str:
386
399
# end
387
400
return res
388
401
389
- @property
402
+ @ property
390
403
def a_path (self ) -> Optional [str ]:
391
404
return self .a_rawpath .decode (defenc , 'replace' ) if self .a_rawpath else None
392
405
393
- @property
406
+ @ property
394
407
def b_path (self ) -> Optional [str ]:
395
408
return self .b_rawpath .decode (defenc , 'replace' ) if self .b_rawpath else None
396
409
397
- @property
410
+ @ property
398
411
def rename_from (self ) -> Optional [str ]:
399
412
return self .raw_rename_from .decode (defenc , 'replace' ) if self .raw_rename_from else None
400
413
401
- @property
414
+ @ property
402
415
def rename_to (self ) -> Optional [str ]:
403
416
return self .raw_rename_to .decode (defenc , 'replace' ) if self .raw_rename_to else None
404
417
405
- @property
418
+ @ property
406
419
def renamed (self ) -> bool :
407
420
""":returns: True if the blob of our diff has been renamed
408
421
:note: This property is deprecated, please use ``renamed_file`` instead.
409
422
"""
410
423
return self .renamed_file
411
424
412
- @property
425
+ @ property
413
426
def renamed_file (self ) -> bool :
414
427
""":returns: True if the blob of our diff has been renamed
415
428
"""
416
429
return self .rename_from != self .rename_to
417
430
418
- @classmethod
431
+ @ classmethod
419
432
def _pick_best_path (cls , path_match : bytes , rename_match : bytes , path_fallback_match : bytes ) -> Optional [bytes ]:
420
433
if path_match :
421
434
return decode_path (path_match )
@@ -428,7 +441,7 @@ def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_m
428
441
429
442
return None
430
443
431
- @classmethod
444
+ @ classmethod
432
445
def _index_from_patch_format (cls , repo : 'Repo' , proc : TBD ) -> DiffIndex :
433
446
"""Create a new DiffIndex from the given text which must be in patch format
434
447
: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:
441
454
442
455
# for now, we have to bake the stream
443
456
text = b'' .join (text_list )
444
- index = DiffIndex ()
457
+ index : 'DiffIndex' = DiffIndex ()
445
458
previous_header = None
446
459
header = None
447
460
a_path , b_path = None , None # for mypy
@@ -491,19 +504,21 @@ def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
491
504
492
505
return index
493
506
494
- @staticmethod
507
+ @ staticmethod
495
508
def _handle_diff_line (lines_bytes : bytes , repo : 'Repo' , index : DiffIndex ) -> None :
496
509
lines = lines_bytes .decode (defenc )
497
510
498
511
for line in lines .split (':' )[1 :]:
499
512
meta , _ , path = line .partition ('\x00 ' )
500
513
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 ]
502
516
old_mode , new_mode , a_blob_id , b_blob_id , _change_type = meta .split (None , 4 )
503
517
# Change type can be R100
504
518
# R: status letter
505
519
# 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 ]
507
522
score_str = '' .join (_change_type [1 :])
508
523
score = int (score_str ) if score_str .isdigit () else None
509
524
path = path .strip ()
@@ -543,14 +558,14 @@ def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> Non
543
558
'' , change_type , score )
544
559
index .append (diff )
545
560
546
- @classmethod
561
+ @ classmethod
547
562
def _index_from_raw_format (cls , repo : 'Repo' , proc : 'Popen' ) -> 'DiffIndex' :
548
563
"""Create a new DiffIndex from the given stream which must be in raw format.
549
564
:return: git.DiffIndex"""
550
565
# handles
551
566
# :100644 100644 687099101... 37c5e30c8... M .gitignore
552
567
553
- index = DiffIndex ()
568
+ index : 'DiffIndex' = DiffIndex ()
554
569
handle_process_output (proc , lambda byt : cls ._handle_diff_line (byt , repo , index ),
555
570
None , finalize_process , decode_streams = False )
556
571
0 commit comments