Skip to content

Commit 94ae0c5

Browse files
committed
Test Dataclass in repo.base.blame()
1 parent 4dd06c3 commit 94ae0c5

File tree

2 files changed

+67
-38
lines changed

2 files changed

+67
-38
lines changed

‎git/repo/base.py

+66-37
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88
import os
99
import re
10+
from dataclasses import dataclass
1011
import shlex
1112
import warnings
1213
from gitdb.db.loose import LooseObjectDB
@@ -41,7 +42,7 @@
4142
from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, assert_never
4243
from 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

4748
from git.types import ConfigLevels_Tup
@@ -53,7 +54,6 @@
5354
from git.objects.submodule.base import UpdateProgress
5455
from git.remote import RemoteProgress
5556

56-
5757
# -----------------------------------------------------------
5858

5959
log = 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':

‎git/types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
PathLike = Union[str, os.PathLike]
2424
elif sys.version_info[:2] >= (3, 9):
2525
# os.PathLike only becomes subscriptable from Python 3.9 onwards
26-
PathLike = Union[str, 'os.PathLike[str]'] # forward ref as pylance complains unless editing with py3.9+
26+
PathLike = Union[str, os.PathLike]
2727

2828
if TYPE_CHECKING:
2929
from git.repo import Repo

0 commit comments

Comments
 (0)