33#
44# This module is part of GitPython and is released under
55# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6- import re
76
7+ import re
88from git .cmd import handle_process_output
99from git .compat import defenc
1010from git .util import finalize_process , hex_to_bin
1313from .objects .util import mode_str_to_int
1414
1515
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+
1627__all__ = ('Diffable' , 'DiffIndex' , 'Diff' , 'NULL_TREE' )
1728
1829# Special object to compare against the empty tree in diffs
19- NULL_TREE = object ()
30+ NULL_TREE : Final [ object ] = object ()
2031
2132_octal_byte_re = re .compile (b'\\ \\ ([0-9]{3})' )
2233
2334
24- def _octal_repl (matchobj ) :
35+ def _octal_repl (matchobj : Match ) -> bytes :
2536 value = matchobj .group (1 )
2637 value = int (value , 8 )
2738 value = bytes (bytearray ((value ,)))
2839 return value
2940
3041
31- def decode_path (path , has_ab_prefix = True ):
42+ def decode_path (path : bytes , has_ab_prefix : bool = True ) -> Optional [ bytes ] :
3243 if path == b'/dev/null' :
3344 return None
3445
@@ -60,15 +71,17 @@ class Diffable(object):
6071 class Index (object ):
6172 pass
6273
63- def _process_diff_args (self , args ) :
74+ def _process_diff_args (self , args : List [ Union [ str , 'Diffable' , object ]]) -> List [ Union [ str , 'Diffable' , object ]] :
6475 """
6576 :return:
6677 possibly altered version of the given args list.
6778 Method is called right before git command execution.
6879 Subclasses can use it to alter the behaviour of the superclass"""
6980 return args
7081
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' :
7285 """Creates diffs between two items being trees, trees and index or an
7386 index and the working tree. It will detect renames automatically.
7487
@@ -99,7 +112,7 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
99112 :note:
100113 On a bare repository, 'other' needs to be provided as Index or as
101114 as Tree/Commit, or a git command error will occur"""
102- args = []
115+ args = [] # type: List[Union[str, Diffable, object]]
103116 args .append ("--abbrev=40" ) # we need full shas
104117 args .append ("--full-index" ) # get full index paths, not only filenames
105118
@@ -117,6 +130,9 @@ def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
117130 if paths is not None and not isinstance (paths , (tuple , list )):
118131 paths = [paths ]
119132
133+ if hasattr (self , 'repo' ): # else raise Error?
134+ self .repo = self .repo # type: 'Repo'
135+
120136 diff_cmd = self .repo .git .diff
121137 if other is self .Index :
122138 args .insert (0 , '--cached' )
@@ -163,7 +179,7 @@ class DiffIndex(list):
163179 # T = Changed in the type
164180 change_type = ("A" , "C" , "D" , "R" , "M" , "T" )
165181
166- def iter_change_type (self , change_type ) :
182+ def iter_change_type (self , change_type : Lit_change_type ) -> Iterator [ 'Diff' ] :
167183 """
168184 :return:
169185 iterator yielding Diff instances that match the given change_type
@@ -180,7 +196,7 @@ def iter_change_type(self, change_type):
180196 if change_type not in self .change_type :
181197 raise ValueError ("Invalid change type: %s" % change_type )
182198
183- for diff in self :
199+ for diff in self : # type: 'Diff'
184200 if diff .change_type == change_type :
185201 yield diff
186202 elif change_type == "A" and diff .new_file :
@@ -255,22 +271,21 @@ class Diff(object):
255271 "new_file" , "deleted_file" , "copied_file" , "raw_rename_from" ,
256272 "raw_rename_to" , "diff" , "change_type" , "score" )
257273
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 :
264281
265282 assert a_rawpath is None or isinstance (a_rawpath , bytes )
266283 assert b_rawpath is None or isinstance (b_rawpath , bytes )
267284 self .a_rawpath = a_rawpath
268285 self .b_rawpath = b_rawpath
269286
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
274289
275290 # Determine whether this diff references a submodule, if it does then
276291 # 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,
305320 self .change_type = change_type
306321 self .score = score
307322
308- def __eq__ (self , other ) :
323+ def __eq__ (self , other : object ) -> bool :
309324 for name in self .__slots__ :
310325 if getattr (self , name ) != getattr (other , name ):
311326 return False
312327 # END for each name
313328 return True
314329
315- def __ne__ (self , other ) :
330+ def __ne__ (self , other : object ) -> bool :
316331 return not (self == other )
317332
318- def __hash__ (self ):
333+ def __hash__ (self ) -> int :
319334 return hash (tuple (getattr (self , n ) for n in self .__slots__ ))
320335
321- def __str__ (self ):
322- h = "%s"
336+ def __str__ (self ) -> str :
337+ h = "%s" # type: str
323338 if self .a_blob :
324339 h %= self .a_blob .path
325340 elif self .b_blob :
326341 h %= self .b_blob .path
327342
328- msg = ''
343+ msg = '' # type: str
329344 line = None # temp line
330345 line_length = 0 # line length
331346 for b , n in zip ((self .a_blob , self .b_blob ), ('lhs' , 'rhs' )):
@@ -354,7 +369,7 @@ def __str__(self):
354369 if self .diff :
355370 msg += '\n ---'
356371 try :
357- msg += self .diff .decode (defenc )
372+ msg += self .diff .decode (defenc ) if isinstance ( self . diff , bytes ) else self . diff
358373 except UnicodeDecodeError :
359374 msg += 'OMITTED BINARY DATA'
360375 # end handle encoding
@@ -368,36 +383,36 @@ def __str__(self):
368383 return res
369384
370385 @property
371- def a_path (self ):
386+ def a_path (self ) -> Optional [ str ] :
372387 return self .a_rawpath .decode (defenc , 'replace' ) if self .a_rawpath else None
373388
374389 @property
375- def b_path (self ):
390+ def b_path (self ) -> Optional [ str ] :
376391 return self .b_rawpath .decode (defenc , 'replace' ) if self .b_rawpath else None
377392
378393 @property
379- def rename_from (self ):
394+ def rename_from (self ) -> Optional [ str ] :
380395 return self .raw_rename_from .decode (defenc , 'replace' ) if self .raw_rename_from else None
381396
382397 @property
383- def rename_to (self ):
398+ def rename_to (self ) -> Optional [ str ] :
384399 return self .raw_rename_to .decode (defenc , 'replace' ) if self .raw_rename_to else None
385400
386401 @property
387- def renamed (self ):
402+ def renamed (self ) -> bool :
388403 """:returns: True if the blob of our diff has been renamed
389404 :note: This property is deprecated, please use ``renamed_file`` instead.
390405 """
391406 return self .renamed_file
392407
393408 @property
394- def renamed_file (self ):
409+ def renamed_file (self ) -> bool :
395410 """:returns: True if the blob of our diff has been renamed
396411 """
397412 return self .rename_from != self .rename_to
398413
399414 @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 ] :
401416 if path_match :
402417 return decode_path (path_match )
403418
@@ -410,21 +425,23 @@ def _pick_best_path(cls, path_match, rename_match, path_fallback_match):
410425 return None
411426
412427 @classmethod
413- def _index_from_patch_format (cls , repo , proc ) :
428+ def _index_from_patch_format (cls , repo : Repo , proc : TBD ) -> DiffIndex :
414429 """Create a new DiffIndex from the given text which must be in patch format
415430 :param repo: is the repository we are operating on - it is required
416431 :param stream: result of 'git diff' as a stream (supporting file protocol)
417432 :return: git.DiffIndex """
418433
419434 ## 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 )
422437
423438 # for now, we have to bake the stream
424- text = b'' .join (text )
439+ text = b'' .join (text_list )
425440 index = DiffIndex ()
426441 previous_header = None
427442 header = None
443+ a_path , b_path = None , None # for mypy
444+ a_mode , b_mode = None , None # for mypy
428445 for _header in cls .re_header .finditer (text ):
429446 a_path_fallback , b_path_fallback , \
430447 old_mode , new_mode , \
@@ -464,27 +481,28 @@ def _index_from_patch_format(cls, repo, proc):
464481 previous_header = _header
465482 header = _header
466483 # end for each header we parse
467- if index :
484+ if index and header :
468485 index [- 1 ].diff = text [header .end ():]
469486 # end assign last diff
470487
471488 return index
472489
473490 @classmethod
474- def _index_from_raw_format (cls , repo , proc ) :
491+ def _index_from_raw_format (cls , repo : 'Repo' , proc : TBD ) -> DiffIndex :
475492 """Create a new DiffIndex from the given stream which must be in raw format.
476493 :return: git.DiffIndex"""
477494 # handles
478495 # :100644 100644 687099101... 37c5e30c8... M .gitignore
479496
480497 index = DiffIndex ()
481498
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 )
484501
485502 for line in lines .split (':' )[1 :]:
486503 meta , _ , path = line .partition ('\x00 ' )
487504 path = path .rstrip ('\x00 ' )
505+ a_blob_id , b_blob_id = None , None # Type: Optional[str]
488506 old_mode , new_mode , a_blob_id , b_blob_id , _change_type = meta .split (None , 4 )
489507 # Change type can be R100
490508 # R: status letter
@@ -504,20 +522,20 @@ def handle_diff_line(lines):
504522 # NOTE: We cannot conclude from the existence of a blob to change type
505523 # as diffs with the working do not have blobs yet
506524 if change_type == 'D' :
507- b_blob_id = None
525+ b_blob_id = None # Optional[str]
508526 deleted_file = True
509527 elif change_type == 'A' :
510528 a_blob_id = None
511529 new_file = True
512530 elif change_type == 'C' :
513531 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 )
517535 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 )
521539 rename_from , rename_to = a_path , b_path
522540 elif change_type == 'T' :
523541 # Nothing to do
0 commit comments