From 31ac93b2f38f2410e41bf90ad28dff31e79b114e Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:25:10 +0200
Subject: [PATCH 1/7] Do not typecheck submodule

It has too many errors. Fixing them should be done in the separate
project.
---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index 0d5ebf012..4d2014afb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ implicit_reexport = true
 # strict = true
 
 # TODO: remove when 'gitdb' is fully annotated
+exclude = ["^git/ext/gitdb"]
 [[tool.mypy.overrides]]
 module = "gitdb.*"
 ignore_missing_imports = true

From b55cf65cc96740c6128987ab0c07b43112bdfe31 Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:34:39 +0200
Subject: [PATCH 2/7] Define supported version for mypy

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index 4d2014afb..57988372a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,6 +19,7 @@ filterwarnings = 'ignore::DeprecationWarning'
 # filterwarnings ignore::WarningType  # ignores those warnings
 
 [tool.mypy]
+python_version = "3.7"
 disallow_untyped_defs = true
 no_implicit_optional = true
 warn_redundant_casts = true

From 76394d42ce2a33b4db71fd64763c1e9dae136747 Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:39:32 +0200
Subject: [PATCH 3/7] Ignore remaining [unreachable] type errors

---
 git/__init__.py  | 2 +-
 git/config.py    | 4 ++--
 git/repo/base.py | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/git/__init__.py b/git/__init__.py
index cd6602bf0..6196a42d7 100644
--- a/git/__init__.py
+++ b/git/__init__.py
@@ -76,7 +76,7 @@ def refresh(path: Optional[PathLike] = None) -> None:
     if not Git.refresh(path=path):
         return
     if not FetchInfo.refresh():
-        return
+        return  # type: ignore [unreachable]
 
     GIT_OK = True
 
diff --git a/git/config.py b/git/config.py
index e05a297af..caf1f6241 100644
--- a/git/config.py
+++ b/git/config.py
@@ -265,8 +265,8 @@ def get_config_path(config_level: Lit_config_levels) -> str:
         raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path")
     else:
         # Should not reach here. Will raise ValueError if does. Static typing will warn missing elifs
-        assert_never(
-            config_level,  # type: ignore[unreachable]
+        assert_never(  # type: ignore[unreachable]
+            config_level,
             ValueError(f"Invalid configuration level: {config_level!r}"),
         )
 
diff --git a/git/repo/base.py b/git/repo/base.py
index 1fa98d8c7..4bfead46f 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -549,8 +549,8 @@ def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[Pa
                 return osp.normpath(osp.join(repo_dir, "config"))
         else:
 
-            assert_never(
-                config_level,  # type:ignore[unreachable]
+            assert_never(  # type:ignore[unreachable]
+                config_level,
                 ValueError(f"Invalid configuration level: {config_level!r}"),
             )
 

From c6dab191d1f96373aaae5c6c117f13c1006631de Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:49:02 +0200
Subject: [PATCH 4/7] Allow explicit casting even when slightly redundant

---
 git/cmd.py            | 2 +-
 git/objects/commit.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git/cmd.py b/git/cmd.py
index dfce9024d..5b0b6b816 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -154,7 +154,7 @@ def pump_stream(
         p_stdout = process.proc.stdout if process.proc else None
         p_stderr = process.proc.stderr if process.proc else None
     else:
-        process = cast(Popen, process)
+        process = cast(Popen, process)  # type: ignore [redundant-cast]
         cmdline = getattr(process, "args", "")
         p_stdout = process.stdout
         p_stderr = process.stderr
diff --git a/git/objects/commit.py b/git/objects/commit.py
index 138db0afe..6cca65c1f 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -460,7 +460,7 @@ def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen,
             if proc_or_stream.stdout is not None:
                 stream = proc_or_stream.stdout
         elif hasattr(proc_or_stream, "readline"):
-            proc_or_stream = cast(IO, proc_or_stream)
+            proc_or_stream = cast(IO, proc_or_stream)  # type: ignore [redundant-cast]
             stream = proc_or_stream
 
         readline = stream.readline

From 6035db092decf72e6a01e175d044f0343818b51c Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:51:50 +0200
Subject: [PATCH 5/7] Run black and exclude submodule

---
 README.md             | 2 ++
 git/cmd.py            | 7 ++-----
 git/config.py         | 3 +--
 git/diff.py           | 3 +--
 git/exc.py            | 2 --
 git/index/fun.py      | 1 -
 git/objects/commit.py | 4 +---
 git/objects/fun.py    | 1 -
 git/objects/util.py   | 2 +-
 git/remote.py         | 1 -
 git/repo/base.py      | 3 +--
 git/util.py           | 2 --
 pyproject.toml        | 1 +
 13 files changed, 10 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index 676d2c6d6..30c54b57f 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,8 @@ To typecheck, run: `mypy -p git`
 
 To test, run: `pytest`
 
+For automatic code formatting run: `black git`
+
 Configuration for flake8 is in the ./.flake8 file.
 
 Configurations for mypy, pytest and coverage.py are in ./pyproject.toml.
diff --git a/git/cmd.py b/git/cmd.py
index 5b0b6b816..84d888494 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -122,6 +122,7 @@ def handle_process_output(
         To specify a timeout in seconds for the git command, after which the process
         should be killed.
     """
+
     # Use 2 "pump" threads and wait for both to finish.
     def pump_stream(
         cmdline: List[str],
@@ -488,10 +489,7 @@ def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) ->
         """
         # Options can be of the form `foo` or `--foo bar` `--foo=bar`,
         # so we need to check if they start with "--foo" or if they are equal to "foo".
-        bare_unsafe_options = [
-            option.lstrip("-")
-            for option in unsafe_options
-        ]
+        bare_unsafe_options = [option.lstrip("-") for option in unsafe_options]
         for option in options:
             for unsafe_option, bare_option in zip(unsafe_options, bare_unsafe_options):
                 if option.startswith(unsafe_option) or option == bare_option:
@@ -1194,7 +1192,6 @@ def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any
 
     @classmethod
     def _unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
-
         outlist = []
         if isinstance(arg_list, (list, tuple)):
             for arg in arg_list:
diff --git a/git/config.py b/git/config.py
index caf1f6241..1973111eb 100644
--- a/git/config.py
+++ b/git/config.py
@@ -248,7 +248,6 @@ def items_all(self) -> List[Tuple[str, List[_T]]]:
 
 
 def get_config_path(config_level: Lit_config_levels) -> str:
-
     # we do not support an absolute path of the gitconfig on windows ,
     # use the global config instead
     if is_win and config_level == "system":
@@ -655,7 +654,7 @@ def write_section(name: str, section_dict: _OMD) -> None:
 
             values: Sequence[str]  # runtime only gets str in tests, but should be whatever _OMD stores
             v: str
-            for (key, values) in section_dict.items_all():
+            for key, values in section_dict.items_all():
                 if key == "__name__":
                     continue
 
diff --git a/git/diff.py b/git/diff.py
index c1a5bd26f..1424ff3ad 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -145,7 +145,7 @@ def diff(
         args.append("--full-index")  # get full index paths, not only filenames
 
         # remove default '-M' arg (check for renames) if user is overriding it
-        if not any(x in kwargs for x in ('find_renames', 'no_renames', 'M')):
+        if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
             args.append("-M")
 
         if create_patch:
@@ -338,7 +338,6 @@ def __init__(
         change_type: Optional[Lit_change_type],
         score: Optional[int],
     ) -> None:
-
         assert a_rawpath is None or isinstance(a_rawpath, bytes)
         assert b_rawpath is None or isinstance(b_rawpath, bytes)
         self.a_rawpath = a_rawpath
diff --git a/git/exc.py b/git/exc.py
index 9b69a5889..775528bf6 100644
--- a/git/exc.py
+++ b/git/exc.py
@@ -139,7 +139,6 @@ def __init__(
         valid_files: Sequence[PathLike],
         failed_reasons: List[str],
     ) -> None:
-
         Exception.__init__(self, message)
         self.failed_files = failed_files
         self.failed_reasons = failed_reasons
@@ -170,7 +169,6 @@ def __init__(
         stderr: Union[bytes, str, None] = None,
         stdout: Union[bytes, str, None] = None,
     ) -> None:
-
         super(HookExecutionError, self).__init__(command, status, stderr, stdout)
         self._msg = "Hook('%s') failed%s"
 
diff --git a/git/index/fun.py b/git/index/fun.py
index d0925ed51..3dc5e96d2 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -394,7 +394,6 @@ def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) ->
                         out.append(_tree_entry_to_baseindexentry(theirs, 0))
                     # END handle modification
                 else:
-
                     if ours[0] != base[0] or ours[1] != base[1]:
                         # they deleted it, we changed it, conflict
                         out.append(_tree_entry_to_baseindexentry(base, 1))
diff --git a/git/objects/commit.py b/git/objects/commit.py
index 6cca65c1f..6db3ea0f3 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -345,9 +345,7 @@ def trailers(self) -> Dict[str, str]:
             Dictionary containing whitespace stripped trailer information.
             Only contains the latest instance of each trailer key.
         """
-        return {
-            k: v[0] for k, v in self.trailers_dict.items()
-        }
+        return {k: v[0] for k, v in self.trailers_dict.items()}
 
     @property
     def trailers_list(self) -> List[Tuple[str, str]]:
diff --git a/git/objects/fun.py b/git/objects/fun.py
index e91403a8b..043eec721 100644
--- a/git/objects/fun.py
+++ b/git/objects/fun.py
@@ -190,7 +190,6 @@ def traverse_trees_recursive(
     # is a tree. If the match is a non-tree item, put it into the result.
     # Processed items will be set None
     for ti, tree_data in enumerate(trees_data):
-
         for ii, item in enumerate(tree_data):
             if not item:
                 continue
diff --git a/git/objects/util.py b/git/objects/util.py
index af279154c..d72c04d17 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -143,7 +143,7 @@ def utctz_to_altz(utctz: str) -> int:
     :param utctz: git utc timezone string, i.e. +0200
     """
     int_utctz = int(utctz)
-    seconds = ((abs(int_utctz) // 100) * 3600 + (abs(int_utctz) % 100) * 60)
+    seconds = (abs(int_utctz) // 100) * 3600 + (abs(int_utctz) % 100) * 60
     return seconds if int_utctz < 0 else -seconds
 
 
diff --git a/git/remote.py b/git/remote.py
index 5886a69f0..95a2b8ac6 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -826,7 +826,6 @@ def _get_fetch_info_from_stderr(
         progress: Union[Callable[..., Any], RemoteProgress, None],
         kill_after_timeout: Union[None, float] = None,
     ) -> IterableList["FetchInfo"]:
-
         progress = to_progress_instance(progress)
 
         # skip first line as it is some remote info we are not interested in
diff --git a/git/repo/base.py b/git/repo/base.py
index 4bfead46f..723613c6f 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -498,7 +498,7 @@ def delete_head(self, *heads: "Union[str, Head]", **kwargs: Any) -> None:
     def create_tag(
         self,
         path: PathLike,
-        ref: Union[str, 'SymbolicReference'] = "HEAD",
+        ref: Union[str, "SymbolicReference"] = "HEAD",
         message: Optional[str] = None,
         force: bool = False,
         **kwargs: Any,
@@ -548,7 +548,6 @@ def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[Pa
             else:
                 return osp.normpath(osp.join(repo_dir, "config"))
         else:
-
             assert_never(  # type:ignore[unreachable]
                 config_level,
                 ValueError(f"Invalid configuration level: {config_level!r}"),
diff --git a/git/util.py b/git/util.py
index 30028b1c2..5bfe11cd8 100644
--- a/git/util.py
+++ b/git/util.py
@@ -1083,7 +1083,6 @@ def __getattr__(self, attr: str) -> T_IterableObj:
         return list.__getattribute__(self, attr)
 
     def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj:  # type: ignore
-
         assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str"
 
         if isinstance(index, int):
@@ -1098,7 +1097,6 @@ def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_Iterabl
         # END handle getattr
 
     def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None:
-
         assert isinstance(index, (int, str)), "Index of IterableList should be an int or str"
 
         delindex = cast(int, index)
diff --git a/pyproject.toml b/pyproject.toml
index 57988372a..32c9d4a26 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,3 +45,4 @@ omit = ["*/git/ext/*"]
 [tool.black]
 line-length = 120
 target-version = ['py37']
+exclude = "git/ext/gitdb"

From 3908e79baf27b3d65265ca75db216f9368748351 Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 16:54:10 +0200
Subject: [PATCH 6/7] Add missing type annotation

---
 git/index/fun.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git/index/fun.py b/git/index/fun.py
index 3dc5e96d2..4a2f3cb6d 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -76,7 +76,7 @@ def hook_path(name: str, git_dir: PathLike) -> str:
     return osp.join(git_dir, "hooks", name)
 
 
-def _has_file_extension(path):
+def _has_file_extension(path: str) -> str:
     return osp.splitext(path)[1]
 
 

From f01ee4f8d0b83f06fc7ba5458ac896ac3b81184a Mon Sep 17 00:00:00 2001
From: Bodo Graumann <mail@bodograumann.de>
Date: Thu, 20 Jul 2023 17:21:41 +0200
Subject: [PATCH 7/7] Apply straight-forward typing fixes

---
 git/cmd.py          | 2 +-
 git/index/base.py   | 2 +-
 git/index/fun.py    | 8 ++++----
 git/objects/util.py | 2 +-
 git/util.py         | 2 +-
 5 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/git/cmd.py b/git/cmd.py
index 84d888494..3d170facd 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -211,7 +211,7 @@ def dashify(string: str) -> str:
     return string.replace("_", "-")
 
 
-def slots_to_dict(self: object, exclude: Sequence[str] = ()) -> Dict[str, Any]:
+def slots_to_dict(self: "Git", exclude: Sequence[str] = ()) -> Dict[str, Any]:
     return {s: getattr(self, s) for s in self.__slots__ if s not in exclude}
 
 
diff --git a/git/index/base.py b/git/index/base.py
index dd8f9aa2e..193baf3ad 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -656,7 +656,7 @@ def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry
     def _entries_for_paths(
         self,
         paths: List[str],
-        path_rewriter: Callable,
+        path_rewriter: Union[Callable, None],
         fprogress: Callable,
         entries: List[BaseIndexEntry],
     ) -> List[BaseIndexEntry]:
diff --git a/git/index/fun.py b/git/index/fun.py
index 4a2f3cb6d..b50f1f465 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -102,7 +102,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
             relative_hp = Path(hp).relative_to(index.repo.working_dir).as_posix()
             cmd = ["bash.exe", relative_hp]
 
-        cmd = subprocess.Popen(
+        process = subprocess.Popen(
             cmd + list(args),
             env=env,
             stdout=subprocess.PIPE,
@@ -116,13 +116,13 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
     else:
         stdout_list: List[str] = []
         stderr_list: List[str] = []
-        handle_process_output(cmd, stdout_list.append, stderr_list.append, finalize_process)
+        handle_process_output(process, stdout_list.append, stderr_list.append, finalize_process)
         stdout = "".join(stdout_list)
         stderr = "".join(stderr_list)
-        if cmd.returncode != 0:
+        if process.returncode != 0:
             stdout = force_text(stdout, defenc)
             stderr = force_text(stderr, defenc)
-            raise HookExecutionError(hp, cmd.returncode, stderr, stdout)
+            raise HookExecutionError(hp, process.returncode, stderr, stdout)
     # end handle return code
 
 
diff --git a/git/objects/util.py b/git/objects/util.py
index d72c04d17..56938507e 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -147,7 +147,7 @@ def utctz_to_altz(utctz: str) -> int:
     return seconds if int_utctz < 0 else -seconds
 
 
-def altz_to_utctz_str(altz: int) -> str:
+def altz_to_utctz_str(altz: float) -> str:
     """Convert a timezone offset west of UTC in seconds into a git timezone offset string
 
     :param altz: timezone offset in seconds west of UTC
diff --git a/git/util.py b/git/util.py
index 5bfe11cd8..0ef8bdeb7 100644
--- a/git/util.py
+++ b/git/util.py
@@ -1049,7 +1049,7 @@ class IterableList(List[T_IterableObj]):
 
     __slots__ = ("_id_attr", "_prefix")
 
-    def __new__(cls, id_attr: str, prefix: str = "") -> "IterableList[IterableObj]":
+    def __new__(cls, id_attr: str, prefix: str = "") -> "IterableList[T_IterableObj]":
         return super(IterableList, cls).__new__(cls)
 
     def __init__(self, id_attr: str, prefix: str = "") -> None: