77import logging
88import os
99import re
10+ from dataclasses import dataclass
1011import shlex
1112import warnings
1213from gitdb .db .loose import LooseObjectDB
4142from git .types import TBD , PathLike , Lit_config_levels , Commit_ish , Tree_ish , assert_never
4243from typing import (Any , BinaryIO , Callable , Dict ,
4344 Iterator , List , Mapping , Optional , Sequence ,
44- TextIO , Tuple , Type , Union ,
45+ TextIO , Tuple , Type , TypedDict , Union ,
4546 NamedTuple , cast , TYPE_CHECKING )
4647
4748from git .types import ConfigLevels_Tup
5354 from git .objects .submodule .base import UpdateProgress
5455 from git .remote import RemoteProgress
5556
56-
5757# -----------------------------------------------------------
5858
5959log = logging .getLogger (__name__ )
@@ -874,7 +874,7 @@ def blame_incremental(self, rev: str | HEAD, file: str, **kwargs: Any) -> Iterat
874874 range (orig_lineno , orig_lineno + num_lines ))
875875
876876 def blame (self , rev : Union [str , HEAD ], file : str , incremental : bool = False , ** kwargs : Any
877- ) -> Union [ List [List [Union [ Optional [ ' Commit' ], List [str ]]]], Optional [ Iterator [BlameEntry ]]] :
877+ ) -> List [List [Commit | List [str | bytes ] | None ]] | Iterator [BlameEntry ] | None :
878878 """The blame information for the given file at the given revision.
879879
880880 :param rev: revision specifier, see git-rev-parse for viable options.
@@ -886,25 +886,52 @@ def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **k
886886 if incremental :
887887 return self .blame_incremental (rev , file , ** kwargs )
888888
889- data = self .git .blame (rev , '--' , file , p = True , stdout_as_string = False , ** kwargs )
890- commits : Dict [str , TBD ] = {}
891- blames : List [List [Union [Optional ['Commit' ], List [str ]]]] = []
892-
893- info : Dict [str , TBD ] = {} # use Any until TypedDict available
889+ data : bytes = self .git .blame (rev , '--' , file , p = True , stdout_as_string = False , ** kwargs )
890+ commits : Dict [str , Commit ] = {}
891+ blames : List [List [Commit | List [str | bytes ] | None ]] = []
892+
893+ class InfoTC (TypedDict , total = False ):
894+ sha : str
895+ id : str
896+ filename : str
897+ summary : str
898+ author : str
899+ author_email : str
900+ author_date : int
901+ committer : str
902+ committer_email : str
903+ committer_date : int
904+
905+ @dataclass
906+ class InfoDC (Dict [str , Union [str , int ]]):
907+ sha : str = ''
908+ id : str = ''
909+ filename : str = ''
910+ summary : str = ''
911+ author : str = ''
912+ author_email : str = ''
913+ author_date : int = 0
914+ committer : str = ''
915+ committer_email : str = ''
916+ committer_date : int = 0
917+
918+ # info: InfoTD = {}
919+ info = InfoDC ()
894920
895921 keepends = True
896- for line in data .splitlines (keepends ):
922+ for line_bytes in data .splitlines (keepends ):
897923 try :
898- line = line .rstrip ().decode (defenc )
924+ line_str = line_bytes .rstrip ().decode (defenc )
899925 except UnicodeDecodeError :
900926 firstpart = ''
927+ parts = ['' ]
901928 is_binary = True
902929 else :
903930 # As we don't have an idea when the binary data ends, as it could contain multiple newlines
904931 # in the process. So we rely on being able to decode to tell us what is is.
905932 # This can absolutely fail even on text files, but even if it does, we should be fine treating it
906933 # as binary instead
907- parts = self .re_whitespace .split (line , 1 )
934+ parts = self .re_whitespace .split (line_str , 1 )
908935 firstpart = parts [0 ]
909936 is_binary = False
910937 # end handle decode of line
@@ -916,10 +943,10 @@ def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **k
916943 # another line of blame with the same data
917944 digits = parts [- 1 ].split (" " )
918945 if len (digits ) == 3 :
919- info = { 'id' : firstpart }
946+ info . id = firstpart
920947 blames .append ([None , []])
921- elif info [ 'id' ] != firstpart :
922- info = { 'id' : firstpart }
948+ elif info . id != firstpart :
949+ info . id = firstpart
923950 blames .append ([commits .get (firstpart ), []])
924951 # END blame data initialization
925952 else :
@@ -936,9 +963,9 @@ def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **k
936963 # committer-tz -0700 - IGNORED BY US
937964 role = m .group (0 )
938965 if firstpart .endswith ('-mail' ):
939- info ["%s_email" % role ] = parts [- 1 ]
966+ info [f" { role } _email" ] = parts [- 1 ]
940967 elif firstpart .endswith ('-time' ):
941- info ["%s_date" % role ] = int (parts [- 1 ])
968+ info [f" { role } _date" ] = int (parts [- 1 ])
942969 elif role == firstpart :
943970 info [role ] = parts [- 1 ]
944971 # END distinguish mail,time,name
@@ -953,38 +980,40 @@ def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **k
953980 info ['summary' ] = parts [- 1 ]
954981 elif firstpart == '' :
955982 if info :
956- sha = info [ 'id' ]
983+ sha = info . id
957984 c = commits .get (sha )
958985 if c is None :
959986 c = Commit (self , hex_to_bin (sha ),
960- author = Actor ._from_string (info [ ' author' ] + ' ' + info [ ' author_email' ] ),
961- authored_date = info [ ' author_date' ] ,
987+ author = Actor ._from_string (info . author + ' ' + info . author_email ),
988+ authored_date = info . author_date ,
962989 committer = Actor ._from_string (
963- info [ ' committer' ] + ' ' + info [ ' committer_email' ] ),
964- committed_date = info [ ' committer_date' ] )
990+ info . committer + ' ' + info . committer_email ),
991+ committed_date = info . committer_date )
965992 commits [sha ] = c
993+ blames [- 1 ][0 ] = c
966994 # END if commit objects needs initial creation
967- if not is_binary :
968- if line and line [0 ] == '\t ' :
969- line = line [1 :]
970- else :
971- # NOTE: We are actually parsing lines out of binary data, which can lead to the
972- # binary being split up along the newline separator. We will append this to the blame
973- # we are currently looking at, even though it should be concatenated with the last line
974- # we have seen.
975- pass
976- # end handle line contents
977- blames [- 1 ][0 ] = c
978995 if blames [- 1 ][1 ] is not None :
979- blames [- 1 ][1 ].append (line )
980- info = {'id' : sha }
996+ if not is_binary :
997+ if line_str and line_str [0 ] == '\t ' :
998+ line_str = line_str [1 :]
999+
1000+ blames [- 1 ][1 ].append (line_str )
1001+ else :
1002+ # NOTE: We are actually parsing lines out of binary data, which can lead to the
1003+ # binary being split up along the newline separator. We will append this to the
1004+ # blame we are currently looking at, even though it should be concatenated with
1005+ # the last line we have seen.
1006+ blames [- 1 ][1 ].append (line_bytes )
1007+ # end handle line contents
1008+
1009+ info .id = sha
9811010 # END if we collected commit info
9821011 # END distinguish filename,summary,rest
9831012 # END distinguish author|committer vs filename,summary,rest
9841013 # END distinguish hexsha vs other information
9851014 return blames
9861015
987- @classmethod
1016+ @ classmethod
9881017 def init (cls , path : Union [PathLike , None ] = None , mkdir : bool = True , odbt : Type [GitCmdObjectDB ] = GitCmdObjectDB ,
9891018 expand_vars : bool = True , ** kwargs : Any ) -> 'Repo' :
9901019 """Initialize a git repository at the given path if specified
@@ -1023,7 +1052,7 @@ def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type
10231052 git .init (** kwargs )
10241053 return cls (path , odbt = odbt )
10251054
1026- @classmethod
1055+ @ classmethod
10271056 def _clone (cls , git : 'Git' , url : PathLike , path : PathLike , odb_default_type : Type [GitCmdObjectDB ],
10281057 progress : Union ['RemoteProgress' , 'UpdateProgress' , Callable [..., 'RemoteProgress' ], None ] = None ,
10291058 multi_options : Optional [List [str ]] = None , ** kwargs : Any
@@ -1101,7 +1130,7 @@ def clone(self, path: PathLike, progress: Optional[Callable] = None,
11011130 :return: ``git.Repo`` (the newly cloned repo)"""
11021131 return self ._clone (self .git , self .common_dir , path , type (self .odb ), progress , multi_options , ** kwargs )
11031132
1104- @classmethod
1133+ @ classmethod
11051134 def clone_from (cls , url : PathLike , to_path : PathLike , progress : Optional [Callable ] = None ,
11061135 env : Optional [Mapping [str , Any ]] = None ,
11071136 multi_options : Optional [List [str ]] = None , ** kwargs : Any ) -> 'Repo' :
0 commit comments