3
3
#
4
4
# This module is part of GitPython and is released under
5
5
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6
- import re
7
6
7
+ import re
8
8
from git .cmd import handle_process_output
9
9
from git .compat import defenc
10
10
from git .util import finalize_process , hex_to_bin
13
13
from .objects .util import mode_str_to_int
14
14
15
15
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
+
16
27
__all__ = ('Diffable' , 'DiffIndex' , 'Diff' , 'NULL_TREE' )
17
28
18
29
# Special object to compare against the empty tree in diffs
19
- NULL_TREE = object ()
30
+ NULL_TREE : Final [ object ] = object ()
20
31
21
32
_octal_byte_re = re .compile (b'\\ \\ ([0-9]{3})' )
22
33
23
34
24
- def _octal_repl (matchobj ) :
35
+ def _octal_repl (matchobj : Match ) -> bytes :
25
36
value = matchobj .group (1 )
26
37
value = int (value , 8 )
27
38
value = bytes (bytearray ((value ,)))
28
39
return value
29
40
30
41
31
- def decode_path (path , has_ab_prefix = True ):
42
+ def decode_path (path : bytes , has_ab_prefix : bool = True ) -> Optional [ bytes ] :
32
43
if path == b'/dev/null' :
33
44
return None
34
45
@@ -60,15 +71,17 @@ class Diffable(object):
60
71
class Index (object ):
61
72
pass
62
73
63
- def _process_diff_args (self , args ) :
74
+ def _process_diff_args (self , args : List [ Union [ str , 'Diffable' , object ]]) -> List [ Union [ str , 'Diffable' , object ]] :
64
75
"""
65
76
:return:
66
77
possibly altered version of the given args list.
67
78
Method is called right before git command execution.
68
79
Subclasses can use it to alter the behaviour of the superclass"""
69
80
return args
70
81
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' :
72
85
"""Creates diffs between two items being trees, trees and index or an
73
86
index and the working tree. It will detect renames automatically.
74
87
@@ -99,7 +112,7 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
99
112
:note:
100
113
On a bare repository, 'other' needs to be provided as Index or as
101
114
as Tree/Commit, or a git command error will occur"""
102
- args = []
115
+ args = [] # type: List[Union[str, Diffable, object]]
103
116
args .append ("--abbrev=40" ) # we need full shas
104
117
args .append ("--full-index" ) # get full index paths, not only filenames
105
118
@@ -117,6 +130,9 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
117
130
if paths is not None and not isinstance (paths , (tuple , list )):
118
131
paths = [paths ]
119
132
133
+ if hasattr (self , 'repo' ): # else raise Error?
134
+ self .repo = self .repo # type: 'Repo'
135
+
120
136
diff_cmd = self .repo .git .diff
121
137
if other is self .Index :
122
138
args .insert (0 , '--cached' )
@@ -163,7 +179,7 @@ class DiffIndex(list):
163
179
# T = Changed in the type
164
180
change_type = ("A" , "C" , "D" , "R" , "M" , "T" )
165
181
166
- def iter_change_type (self , change_type ) :
182
+ def iter_change_type (self , change_type : Lit_change_type ) -> Iterator [ 'Diff' ] :
167
183
"""
168
184
:return:
169
185
iterator yielding Diff instances that match the given change_type
@@ -180,7 +196,7 @@ def iter_change_type(self, change_type):
180
196
if change_type not in self .change_type :
181
197
raise ValueError ("Invalid change type: %s" % change_type )
182
198
183
- for diff in self :
199
+ for diff in self : # type: 'Diff'
184
200
if diff .change_type == change_type :
185
201
yield diff
186
202
elif change_type == "A" and diff .new_file :
@@ -255,22 +271,21 @@ class Diff(object):
255
271
"new_file" , "deleted_file" , "copied_file" , "raw_rename_from" ,
256
272
"raw_rename_to" , "diff" , "change_type" , "score" )
257
273
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 :
264
281
265
282
assert a_rawpath is None or isinstance (a_rawpath , bytes )
266
283
assert b_rawpath is None or isinstance (b_rawpath , bytes )
267
284
self .a_rawpath = a_rawpath
268
285
self .b_rawpath = b_rawpath
269
286
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
274
289
275
290
# Determine whether this diff references a submodule, if it does then
276
291
# 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,
305
320
self .change_type = change_type
306
321
self .score = score
307
322
308
- def __eq__ (self , other ) :
323
+ def __eq__ (self , other : object ) -> bool :
309
324
for name in self .__slots__ :
310
325
if getattr (self , name ) != getattr (other , name ):
311
326
return False
312
327
# END for each name
313
328
return True
314
329
315
- def __ne__ (self , other ) :
330
+ def __ne__ (self , other : object ) -> bool :
316
331
return not (self == other )
317
332
318
- def __hash__ (self ):
333
+ def __hash__ (self ) -> int :
319
334
return hash (tuple (getattr (self , n ) for n in self .__slots__ ))
320
335
321
- def __str__ (self ):
322
- h = "%s"
336
+ def __str__ (self ) -> str :
337
+ h = "%s" # type: str
323
338
if self .a_blob :
324
339
h %= self .a_blob .path
325
340
elif self .b_blob :
326
341
h %= self .b_blob .path
327
342
328
- msg = ''
343
+ msg = '' # type: str
329
344
line = None # temp line
330
345
line_length = 0 # line length
331
346
for b , n in zip ((self .a_blob , self .b_blob ), ('lhs' , 'rhs' )):
@@ -354,7 +369,7 @@ def __str__(self):
354
369
if self .diff :
355
370
msg += '\n ---'
356
371
try :
357
- msg += self .diff .decode (defenc )
372
+ msg += self .diff .decode (defenc ) if isinstance ( self . diff , bytes ) else self . diff
358
373
except UnicodeDecodeError :
359
374
msg += 'OMITTED BINARY DATA'
360
375
# end handle encoding
@@ -368,36 +383,36 @@ def __str__(self):
368
383
return res
369
384
370
385
@property
371
- def a_path (self ):
386
+ def a_path (self ) -> Optional [ str ] :
372
387
return self .a_rawpath .decode (defenc , 'replace' ) if self .a_rawpath else None
373
388
374
389
@property
375
- def b_path (self ):
390
+ def b_path (self ) -> Optional [ str ] :
376
391
return self .b_rawpath .decode (defenc , 'replace' ) if self .b_rawpath else None
377
392
378
393
@property
379
- def rename_from (self ):
394
+ def rename_from (self ) -> Optional [ str ] :
380
395
return self .raw_rename_from .decode (defenc , 'replace' ) if self .raw_rename_from else None
381
396
382
397
@property
383
- def rename_to (self ):
398
+ def rename_to (self ) -> Optional [ str ] :
384
399
return self .raw_rename_to .decode (defenc , 'replace' ) if self .raw_rename_to else None
385
400
386
401
@property
387
- def renamed (self ):
402
+ def renamed (self ) -> bool :
388
403
""":returns: True if the blob of our diff has been renamed
389
404
:note: This property is deprecated, please use ``renamed_file`` instead.
390
405
"""
391
406
return self .renamed_file
392
407
393
408
@property
394
- def renamed_file (self ):
409
+ def renamed_file (self ) -> bool :
395
410
""":returns: True if the blob of our diff has been renamed
396
411
"""
397
412
return self .rename_from != self .rename_to
398
413
399
414
@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 ] :
401
416
if path_match :
402
417
return decode_path (path_match )
403
418
@@ -410,21 +425,23 @@ def _pick_best_path(cls, path_match, rename_match, path_fallback_match):
410
425
return None
411
426
412
427
@classmethod
413
- def _index_from_patch_format (cls , repo , proc ) :
428
+ def _index_from_patch_format (cls , repo : Repo , proc : TBD ) -> DiffIndex :
414
429
"""Create a new DiffIndex from the given text which must be in patch format
415
430
:param repo: is the repository we are operating on - it is required
416
431
:param stream: result of 'git diff' as a stream (supporting file protocol)
417
432
:return: git.DiffIndex """
418
433
419
434
## 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 )
422
437
423
438
# for now, we have to bake the stream
424
- text = b'' .join (text )
439
+ text = b'' .join (text_list )
425
440
index = DiffIndex ()
426
441
previous_header = None
427
442
header = None
443
+ a_path , b_path = None , None # for mypy
444
+ a_mode , b_mode = None , None # for mypy
428
445
for _header in cls .re_header .finditer (text ):
429
446
a_path_fallback , b_path_fallback , \
430
447
old_mode , new_mode , \
@@ -464,27 +481,28 @@ def _index_from_patch_format(cls, repo, proc):
464
481
previous_header = _header
465
482
header = _header
466
483
# end for each header we parse
467
- if index :
484
+ if index and header :
468
485
index [- 1 ].diff = text [header .end ():]
469
486
# end assign last diff
470
487
471
488
return index
472
489
473
490
@classmethod
474
- def _index_from_raw_format (cls , repo , proc ) :
491
+ def _index_from_raw_format (cls , repo : 'Repo' , proc : TBD ) -> DiffIndex :
475
492
"""Create a new DiffIndex from the given stream which must be in raw format.
476
493
:return: git.DiffIndex"""
477
494
# handles
478
495
# :100644 100644 687099101... 37c5e30c8... M .gitignore
479
496
480
497
index = DiffIndex ()
481
498
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 )
484
501
485
502
for line in lines .split (':' )[1 :]:
486
503
meta , _ , path = line .partition ('\x00 ' )
487
504
path = path .rstrip ('\x00 ' )
505
+ a_blob_id , b_blob_id = None , None # Type: Optional[str]
488
506
old_mode , new_mode , a_blob_id , b_blob_id , _change_type = meta .split (None , 4 )
489
507
# Change type can be R100
490
508
# R: status letter
@@ -504,20 +522,20 @@ def handle_diff_line(lines):
504
522
# NOTE: We cannot conclude from the existence of a blob to change type
505
523
# as diffs with the working do not have blobs yet
506
524
if change_type == 'D' :
507
- b_blob_id = None
525
+ b_blob_id = None # Optional[str]
508
526
deleted_file = True
509
527
elif change_type == 'A' :
510
528
a_blob_id = None
511
529
new_file = True
512
530
elif change_type == 'C' :
513
531
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 )
517
535
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 )
521
539
rename_from , rename_to = a_path , b_path
522
540
elif change_type == 'T' :
523
541
# Nothing to do
0 commit comments