diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 8581c0bfc..dd94ab9d5 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9, "3.10.0-beta.4"]
+        python-version: [3.7, 3.8, 3.9, "3.10.0-beta.4"]
 
     steps:
     - uses: actions/checkout@v2
diff --git a/README.md b/README.md
index 5087dbccb..dd449d32f 100644
--- a/README.md
+++ b/README.md
@@ -24,23 +24,21 @@ or low-level like git-plumbing.
 
 It provides abstractions of git objects for easy access of repository data, and additionally
 allows you to access the git repository more directly using either a pure python implementation,
-or the faster, but more resource intensive *git command* implementation.
+or the faster, but more resource intensive _git command_ implementation.
 
 The object database implementation is optimized for handling large quantities of objects and large datasets,
 which is achieved by using low-level structures and data streaming.
 
-
 ### DEVELOPMENT STATUS
 
 This project is in **maintenance mode**, which means that
 
-* …there will be no feature development, unless these are contributed
-* …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
-* …issues will be responded to with waiting times of up to a month
+- …there will be no feature development, unless these are contributed
+- …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
+- …issues will be responded to with waiting times of up to a month
 
 The project is open to contributions of all kinds, as well as new maintainers.
 
-
 ### REQUIREMENTS
 
 GitPython needs the `git` executable to be installed on the system and available
@@ -48,8 +46,8 @@ in your `PATH` for most operations.
 If it is not in your `PATH`, you can help GitPython find it by setting
 the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
 
-* Git (1.7.x or newer)
-* Python >= 3.6
+- Git (1.7.x or newer)
+- Python >= 3.7
 
 The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
 The installer takes care of installing them for you.
@@ -98,20 +96,20 @@ See [Issue #525](https://github.com/gitpython-developers/GitPython/issues/525).
 
 ### RUNNING TESTS
 
-*Important*: Right after cloning this repository, please be sure to have executed
+_Important_: Right after cloning this repository, please be sure to have executed
 the `./init-tests-after-clone.sh` script in the repository root. Otherwise
 you will encounter test failures.
 
-On *Windows*, make sure you have `git-daemon` in your PATH.  For MINGW-git, the `git-daemon.exe`
+On _Windows_, make sure you have `git-daemon` in your PATH. For MINGW-git, the `git-daemon.exe`
 exists in `Git\mingw64\libexec\git-core\`; CYGWIN has no daemon, but should get along fine
 with MINGW's.
 
-Ensure testing libraries are installed. 
-In the root directory, run: `pip install -r test-requirements.txt` 
+Ensure testing libraries are installed.
+In the root directory, run: `pip install -r test-requirements.txt`
 
 To lint, run: `flake8`
 
-To typecheck, run: `mypy -p git` 
+To typecheck, run: `mypy -p git`
 
 To test, run: `pytest`
 
@@ -119,36 +117,35 @@ Configuration for flake8 is in the ./.flake8 file.
 
 Configurations for mypy, pytest and coverage.py are in ./pyproject.toml.
 
-The same linting and testing will also be performed against different supported python versions 
+The same linting and testing will also be performed against different supported python versions
 upon submitting a pull request (or on each push if you have a fork with a "main" branch and actions enabled).
 
-
 ### Contributions
 
 Please have a look at the [contributions file][contributing].
 
 ### INFRASTRUCTURE
 
-* [User Documentation](http://gitpython.readthedocs.org)
-* [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
- * Please post on stackoverflow and use the `gitpython` tag
-* [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
-  * Post reproducible bugs and feature requests as a new issue.
+- [User Documentation](http://gitpython.readthedocs.org)
+- [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
+- Please post on stackoverflow and use the `gitpython` tag
+- [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
+  - Post reproducible bugs and feature requests as a new issue.
     Please be sure to provide the following information if posting bugs:
-    * GitPython version (e.g. `import git; git.__version__`)
-    * Python version (e.g. `python --version`)
-    * The encountered stack-trace, if applicable
-    * Enough information to allow reproducing the issue
+    - GitPython version (e.g. `import git; git.__version__`)
+    - Python version (e.g. `python --version`)
+    - The encountered stack-trace, if applicable
+    - Enough information to allow reproducing the issue
 
 ### How to make a new release
 
-* Update/verify the **version** in the `VERSION` file
-* Update/verify that the `doc/source/changes.rst` changelog file was updated
-* Commit everything
-* Run `git tag -s <version>` to tag the version in Git
-* Run `make release`
-* Close the milestone mentioned in the _changelog_ and create a new one. _Do not reuse milestones by renaming them_.
-* set the upcoming version in the `VERSION` file, usually be
+- Update/verify the **version** in the `VERSION` file
+- Update/verify that the `doc/source/changes.rst` changelog file was updated
+- Commit everything
+- Run `git tag -s <version>` to tag the version in Git
+- Run `make release`
+- Close the milestone mentioned in the _changelog_ and create a new one. _Do not reuse milestones by renaming them_.
+- set the upcoming version in the `VERSION` file, usually be
   incrementing the patch level, and possibly by appending `-dev`. Probably you
   want to `git push` once more.
 
@@ -200,22 +197,22 @@ gpg --edit-key 4C08421980C9
 
 ### Projects using GitPython
 
-* [PyDriller](https://github.com/ishepard/pydriller)
-* [Kivy Designer](https://github.com/kivy/kivy-designer)
-* [Prowl](https://github.com/nettitude/Prowl)
-* [Python Taint](https://github.com/python-security/pyt)
-* [Buster](https://github.com/axitkhurana/buster)
-* [git-ftp](https://github.com/ezyang/git-ftp)
-* [Git-Pandas](https://github.com/wdm0006/git-pandas)
-* [PyGitUp](https://github.com/msiemens/PyGitUp)
-* [PyJFuzz](https://github.com/mseclab/PyJFuzz)
-* [Loki](https://github.com/Neo23x0/Loki)
-* [Omniwallet](https://github.com/OmniLayer/omniwallet)
-* [GitViper](https://github.com/BeayemX/GitViper)
-* [Git Gud](https://github.com/bthayer2365/git-gud)
+- [PyDriller](https://github.com/ishepard/pydriller)
+- [Kivy Designer](https://github.com/kivy/kivy-designer)
+- [Prowl](https://github.com/nettitude/Prowl)
+- [Python Taint](https://github.com/python-security/pyt)
+- [Buster](https://github.com/axitkhurana/buster)
+- [git-ftp](https://github.com/ezyang/git-ftp)
+- [Git-Pandas](https://github.com/wdm0006/git-pandas)
+- [PyGitUp](https://github.com/msiemens/PyGitUp)
+- [PyJFuzz](https://github.com/mseclab/PyJFuzz)
+- [Loki](https://github.com/Neo23x0/Loki)
+- [Omniwallet](https://github.com/OmniLayer/omniwallet)
+- [GitViper](https://github.com/BeayemX/GitViper)
+- [Git Gud](https://github.com/bthayer2365/git-gud)
 
 ### LICENSE
 
-New BSD License.  See the LICENSE file.
+New BSD License. See the LICENSE file.
 
 [contributing]: https://github.com/gitpython-developers/GitPython/blob/master/CONTRIBUTING.md
diff --git a/doc/source/intro.rst b/doc/source/intro.rst
index 956a36073..4f22a0942 100644
--- a/doc/source/intro.rst
+++ b/doc/source/intro.rst
@@ -13,15 +13,17 @@ The object database implementation is optimized for handling large quantities of
 Requirements
 ============
 
-* `Python`_ >= 3.6
+* `Python`_ >= 3.7
 * `Git`_ 1.7.0 or newer
     It should also work with older versions, but it may be that some operations
     involving remotes will not work as expected.
 * `GitDB`_ - a pure python git database implementation
+* `typing_extensions`_ >= 3.7.3.4 (if python < 3.10)
 
 .. _Python: https://www.python.org
 .. _Git: https://git-scm.com/
 .. _GitDB: https://pypi.python.org/pypi/gitdb
+.. _typing_extensions: https://pypi.org/project/typing-extensions/
 
 Installing GitPython
 ====================
@@ -60,7 +62,7 @@ Leakage of System Resources
 ---------------------------
 
 GitPython is not suited for long-running processes (like daemons) as it tends to
-leak system resources. It was written in a time where destructors (as implemented 
+leak system resources. It was written in a time where destructors (as implemented
 in the `__del__` method) still ran deterministically.
 
 In case you still want to use it in such a context, you will want to search the
diff --git a/git/cmd.py b/git/cmd.py
index f82127453..b84c43df3 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -42,7 +42,7 @@
 from typing import (Any, AnyStr, BinaryIO, Callable, Dict, IO, Iterator, List, Mapping,
                     Sequence, TYPE_CHECKING, TextIO, Tuple, Union, cast, overload)
 
-from git.types import PathLike, Literal
+from git.types import PathLike, Literal, TBD
 
 if TYPE_CHECKING:
     from git.repo.base import Repo
@@ -68,7 +68,7 @@
 # Documentation
 ## @{
 
-def handle_process_output(process: subprocess.Popen,
+def handle_process_output(process: Union[subprocess.Popen, 'Git.AutoInterrupt'],
                           stdout_handler: Union[None,
                                                 Callable[[AnyStr], None],
                                                 Callable[[List[AnyStr]], None],
@@ -77,7 +77,7 @@ def handle_process_output(process: subprocess.Popen,
                                                 Callable[[AnyStr], None],
                                                 Callable[[List[AnyStr]], None]],
                           finalizer: Union[None,
-                                           Callable[[subprocess.Popen], None]] = None,
+                                           Callable[[Union[subprocess.Popen, 'Git.AutoInterrupt']], None]] = None,
                           decode_streams: bool = True) -> None:
     """Registers for notifications to learn that process output is ready to read, and dispatches lines to
     the respective line handlers.
@@ -165,7 +165,7 @@ def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], exc
 ## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
 # see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
 PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP  # type: ignore[attr-defined]
-                      if is_win else 0)
+                      if is_win else 0)                                       # mypy error if not windows
 
 
 class Git(LazyMixin):
@@ -421,15 +421,15 @@ def __getattr__(self, attr: str) -> Any:
             return getattr(self.proc, attr)
 
         # TODO: Bad choice to mimic `proc.wait()` but with different args.
-        def wait(self, stderr: Union[None, bytes] = b'') -> int:
+        def wait(self, stderr: Union[None, str, bytes] = b'') -> int:
             """Wait for the process and return its status code.
 
             :param stderr: Previously read value of stderr, in case stderr is already closed.
             :warn: may deadlock if output or error pipes are used and not handled separately.
             :raise GitCommandError: if the return status is not 0"""
             if stderr is None:
-                stderr = b''
-            stderr = force_bytes(data=stderr, encoding='utf-8')
+                stderr_b = b''
+            stderr_b = force_bytes(data=stderr, encoding='utf-8')
 
             if self.proc is not None:
                 status = self.proc.wait()
@@ -437,11 +437,11 @@ def wait(self, stderr: Union[None, bytes] = b'') -> int:
                 def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
                     if stream:
                         try:
-                            return stderr + force_bytes(stream.read())
+                            return stderr_b + force_bytes(stream.read())
                         except ValueError:
-                            return stderr or b''
+                            return stderr_b or b''
                     else:
-                        return stderr or b''
+                        return stderr_b or b''
 
                 if status != 0:
                     errstr = read_all_from_possibly_closed_stream(self.proc.stderr)
@@ -575,8 +575,8 @@ def __init__(self, working_dir: Union[None, PathLike] = None):
         self._environment: Dict[str, str] = {}
 
         # cached command slots
-        self.cat_file_header = None
-        self.cat_file_all = None
+        self.cat_file_header: Union[None, TBD] = None
+        self.cat_file_all: Union[None, TBD] = None
 
     def __getattr__(self, name: str) -> Any:
         """A convenience method as it allows to call the command as if it was
@@ -1012,17 +1012,14 @@ def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any
 
     @classmethod
     def __unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
-        if not isinstance(arg_list, (list, tuple)):
-            return [str(arg_list)]
 
         outlist = []
-        for arg in arg_list:
-            if isinstance(arg_list, (list, tuple)):
+        if isinstance(arg_list, (list, tuple)):
+            for arg in arg_list:
                 outlist.extend(cls.__unpack_args(arg))
-            # END recursion
-            else:
-                outlist.append(str(arg))
-        # END for each arg
+        else:
+            outlist.append(str(arg_list))
+
         return outlist
 
     def __call__(self, **kwargs: Any) -> 'Git':
diff --git a/git/compat.py b/git/compat.py
index 7a0a15d23..988c04eff 100644
--- a/git/compat.py
+++ b/git/compat.py
@@ -29,8 +29,6 @@
     Union,
     overload,
 )
-from git.types import TBD
-
 # ---------------------------------------------------------------------------
 
 
@@ -97,19 +95,3 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
     elif s is not None:
         raise TypeError('Expected bytes or text, but got %r' % (s,))
     return None
-
-
-# type: ignore ## mypy cannot understand dynamic class creation
-def with_metaclass(meta: Type[Any], *bases: Any) -> TBD:
-    """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15"""
-
-    class metaclass(meta):  # type: ignore
-        __call__ = type.__call__
-        __init__ = type.__init__    # type: ignore
-
-        def __new__(cls, name: str, nbases: Optional[Tuple[int, ...]], d: Dict[str, Any]) -> TBD:
-            if nbases is None:
-                return type.__new__(cls, name, (), d)
-            return meta(name, bases, d)
-
-    return metaclass(meta.__name__ + 'Helper', None, {})  # type: ignore
diff --git a/git/config.py b/git/config.py
index 011d0e0b1..293281d21 100644
--- a/git/config.py
+++ b/git/config.py
@@ -33,7 +33,7 @@
 from typing import (Any, Callable, Generic, IO, List, Dict, Sequence,
                     TYPE_CHECKING, Tuple, TypeVar, Union, cast, overload)
 
-from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never, _T
+from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T
 
 if TYPE_CHECKING:
     from git.repo.base import Repo
@@ -44,10 +44,10 @@
 
 if sys.version_info[:3] < (3, 7, 2):
     # typing.Ordereddict not added until py 3.7.2
-    from collections import OrderedDict     # type: ignore  # until 3.6 dropped
-    OrderedDict_OMD = OrderedDict           # type: ignore  # until 3.6 dropped
+    from collections import OrderedDict
+    OrderedDict_OMD = OrderedDict
 else:
-    from typing import OrderedDict                   # type: ignore  # until 3.6 dropped
+    from typing import OrderedDict
     OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]]  # type: ignore[assignment, misc]
 
 # -------------------------------------------------------------
@@ -72,7 +72,7 @@
 
 class MetaParserBuilder(abc.ABCMeta):
     """Utlity class wrapping base-class methods into decorators that assure read-only properties"""
-    def __new__(cls, name: str, bases: TBD, clsdict: Dict[str, Any]) -> TBD:
+    def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> 'MetaParserBuilder':
         """
         Equip all base-class methods with a needs_values decorator, and all non-const methods
         with a set_dirty_and_flush_changes decorator in addition to that."""
@@ -177,7 +177,7 @@ def __exit__(self, exception_type: str, exception_value: str, traceback: str) ->
 class _OMD(OrderedDict_OMD):
     """Ordered multi-dict."""
 
-    def __setitem__(self, key: str, value: _T) -> None:  # type: ignore[override]
+    def __setitem__(self, key: str, value: _T) -> None:
         super(_OMD, self).__setitem__(key, [value])
 
     def add(self, key: str, value: Any) -> None:
@@ -203,8 +203,8 @@ def setlast(self, key: str, value: Any) -> None:
         prior = super(_OMD, self).__getitem__(key)
         prior[-1] = value
 
-    def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]:  # type: ignore
-        return super(_OMD, self).get(key, [default])[-1]          # type: ignore
+    def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]:
+        return super(_OMD, self).get(key, [default])[-1]
 
     def getall(self, key: str) -> List[_T]:
         return super(_OMD, self).__getitem__(key)
@@ -236,7 +236,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, ValueError(f"Invalid configuration level: {config_level!r}"))
+        assert_never(config_level,                    # type: ignore[unreachable]
+                     ValueError(f"Invalid configuration level: {config_level!r}"))
 
 
 class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
@@ -299,10 +300,10 @@ def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Unio
         :param repo: Reference to repository to use if [includeIf] sections are found in configuration files.
 
         """
-        cp.RawConfigParser.__init__(self, dict_type=_OMD)  # type: ignore[arg-type]
-        self._dict: Callable[..., _OMD]  # type: ignore[assignment]   # mypy/typeshed bug
-        self._defaults: _OMD              # type: ignore[assignment]  # mypy/typeshed bug
-        self._sections: _OMD              # type: ignore[assignment]  # mypy/typeshed bug
+        cp.RawConfigParser.__init__(self, dict_type=_OMD)
+        self._dict: Callable[..., _OMD]  # type: ignore   # mypy/typeshed bug?
+        self._defaults: _OMD
+        self._sections: _OMD              # type: ignore  # mypy/typeshed bug?
 
         # Used in python 3, needs to stay in sync with sections for underlying implementation to work
         if not hasattr(self, '_proxies'):
@@ -617,12 +618,12 @@ def _write(self, fp: IO) -> None:
         def write_section(name: str, section_dict: _OMD) -> None:
             fp.write(("[%s]\n" % name).encode(defenc))
 
-            values: Sequence[Union[str, bytes, int, float, bool]]
+            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():
                 if key == "__name__":
                     continue
 
-                v: Union[str, bytes, int, float, bool]
                 for v in values:
                     fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc))
                 # END if key is not __name__
@@ -630,7 +631,8 @@ def write_section(name: str, section_dict: _OMD) -> None:
 
         if self._defaults:
             write_section(cp.DEFAULTSECT, self._defaults)
-        value: TBD
+        value: _OMD
+
         for name, value in self._sections.items():
             write_section(name, value)
 
diff --git a/git/diff.py b/git/diff.py
index 74ca0b64d..cea66d7ee 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -16,7 +16,7 @@
 # typing ------------------------------------------------------------------
 
 from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast
-from git.types import PathLike, TBD, Literal
+from git.types import PathLike, Literal
 
 if TYPE_CHECKING:
     from .objects.tree import Tree
@@ -24,6 +24,7 @@
     from git.repo.base import Repo
     from git.objects.base import IndexObject
     from subprocess import Popen
+    from git import Git
 
 Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U']
 
@@ -442,7 +443,7 @@ def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_m
         return None
 
     @ classmethod
-    def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
+    def _index_from_patch_format(cls, repo: 'Repo', proc: Union['Popen', 'Git.AutoInterrupt']) -> DiffIndex:
         """Create a new DiffIndex from the given text which must be in patch format
         :param repo: is the repository we are operating on - it is required
         :param stream: result of 'git diff' as a stream (supporting file protocol)
@@ -455,8 +456,8 @@ def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
         # for now, we have to bake the stream
         text = b''.join(text_list)
         index: 'DiffIndex' = DiffIndex()
-        previous_header = None
-        header = None
+        previous_header: Union[Match[bytes], None] = None
+        header: Union[Match[bytes], None] = None
         a_path, b_path = None, None  # for mypy
         a_mode, b_mode = None, None  # for mypy
         for _header in cls.re_header.finditer(text):
diff --git a/git/index/base.py b/git/index/base.py
index 6452419c5..102703e6d 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -70,7 +70,7 @@
 from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, NoReturn,
                     Sequence, TYPE_CHECKING, Tuple, Type, Union)
 
-from git.types import Commit_ish, PathLike, TBD
+from git.types import Commit_ish, PathLike
 
 if TYPE_CHECKING:
     from subprocess import Popen
@@ -181,7 +181,7 @@ def _deserialize(self, stream: IO) -> 'IndexFile':
         self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream)
         return self
 
-    def _entries_sorted(self) -> List[TBD]:
+    def _entries_sorted(self) -> List[IndexEntry]:
         """:return: list of entries, in a sorted fashion, first by path, then by stage"""
         return sorted(self.entries.values(), key=lambda e: (e.path, e.stage))
 
@@ -427,8 +427,8 @@ def raise_exc(e: Exception) -> NoReturn:
             # END path exception handling
         # END for each path
 
-    def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: TBD, fmakeexc: Callable[..., GitError],
-                             fprogress: Callable[[PathLike, bool, TBD], None],
+    def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: PathLike, fmakeexc: Callable[..., GitError],
+                             fprogress: Callable[[PathLike, bool, PathLike], None],
                              read_from_stdout: bool = True) -> Union[None, str]:
         """Write path to proc.stdin and make sure it processes the item, including progress.
 
@@ -492,12 +492,13 @@ def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]:
             are at stage 3 will not have a stage 3 entry.
         """
         is_unmerged_blob = lambda t: t[0] != 0
-        path_map: Dict[PathLike, List[Tuple[TBD, Blob]]] = {}
+        path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {}
         for stage, blob in self.iter_blobs(is_unmerged_blob):
             path_map.setdefault(blob.path, []).append((stage, blob))
         # END for each unmerged blob
         for line in path_map.values():
             line.sort()
+
         return path_map
 
     @ classmethod
@@ -1201,7 +1202,6 @@ def handle_stderr(proc: 'Popen[bytes]', iter_checked_out_files: Iterable[PathLik
             handle_stderr(proc, checked_out_files)
             return checked_out_files
         # END paths handling
-        assert "Should not reach this point"
 
     @ default_index
     def reset(self, commit: Union[Commit, 'Reference', str] = 'HEAD', working_tree: bool = False,
diff --git a/git/objects/fun.py b/git/objects/fun.py
index d6cdafe1e..19b4e525a 100644
--- a/git/objects/fun.py
+++ b/git/objects/fun.py
@@ -51,7 +51,7 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[['ReadableBuffer
         if isinstance(name, str):
             name_bytes = name.encode(defenc)
         else:
-            name_bytes = name
+            name_bytes = name  # type: ignore[unreachable]  # check runtime types - is always str?
         write(b''.join((mode_str, b' ', name_bytes, b'\0', binsha)))
     # END for each item
 
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index 559d2585e..d306c91d4 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -379,6 +379,7 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
         :return: The newly created submodule instance
         :note: works atomically, such that no change will be done if the repository
             update fails for instance"""
+
         if repo.bare:
             raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
         # END handle bare repos
@@ -434,7 +435,7 @@ def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = No
             url = urls[0]
         else:
             # clone new repo
-            kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout}
+            kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {'n': no_checkout}
             if not branch_is_default:
                 kwargs['b'] = br.name
             # END setup checkout-branch
diff --git a/git/objects/tree.py b/git/objects/tree.py
index 0cceb59ac..22531895e 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -215,7 +215,7 @@ def __init__(self, repo: 'Repo', binsha: bytes, mode: int = tree_id << 12, path:
         super(Tree, self).__init__(repo, binsha, mode, path)
 
     @ classmethod
-    def _get_intermediate_items(cls, index_object: 'Tree',
+    def _get_intermediate_items(cls, index_object: IndexObjUnion,
                                 ) -> Union[Tuple['Tree', ...], Tuple[()]]:
         if index_object.type == "tree":
             return tuple(index_object._iter_convert_to_object(index_object._cache))
diff --git a/git/objects/util.py b/git/objects/util.py
index f627211ec..16d4c0ac8 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -167,7 +167,7 @@ def from_timestamp(timestamp: float, tz_offset: float) -> datetime:
         return utc_dt
 
 
-def parse_date(string_date: str) -> Tuple[int, int]:
+def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
     """
     Parse the given date as one of the following
 
@@ -181,9 +181,13 @@ def parse_date(string_date: str) -> Tuple[int, int]:
     :raise ValueError: If the format could not be understood
     :note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY.
     """
-    if isinstance(string_date, datetime) and string_date.tzinfo:
-        offset = -int(string_date.utcoffset().total_seconds())
-        return int(string_date.astimezone(utc).timestamp()), offset
+    if isinstance(string_date, datetime):
+        if string_date.tzinfo:
+            utcoffset = cast(timedelta, string_date.utcoffset())  # typeguard, if tzinfoand is not None
+            offset = -int(utcoffset.total_seconds())
+            return int(string_date.astimezone(utc).timestamp()), offset
+        else:
+            raise ValueError(f"string_date datetime object without tzinfo, {string_date}")
 
     # git time
     try:
@@ -245,7 +249,7 @@ def parse_date(string_date: str) -> Tuple[int, int]:
             raise ValueError("no format matched")
         # END handle format
     except Exception as e:
-        raise ValueError("Unsupported date format: %s" % string_date) from e
+        raise ValueError(f"Unsupported date format or type: {string_date}, type={type(string_date)}") from e
     # END handle exceptions
 
 
@@ -338,7 +342,7 @@ def _list_traverse(self, as_edge: bool = False, *args: Any, **kwargs: Any
         """
         # Commit and Submodule have id.__attribute__ as IterableObj
         # Tree has id.__attribute__ inherited from IndexObject
-        if isinstance(self, (TraversableIterableObj, Has_id_attribute)):
+        if isinstance(self, Has_id_attribute):
             id = self._id_attribute_
         else:
             id = ""     # shouldn't reach here, unless Traversable subclass created with no _id_attribute_
@@ -346,7 +350,7 @@ def _list_traverse(self, as_edge: bool = False, *args: Any, **kwargs: Any
 
         if not as_edge:
             out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id)
-            out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))  # type: ignore
+            out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))
             return out
             # overloads in subclasses (mypy does't allow typing self: subclass)
             # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]]
diff --git a/git/refs/reference.py b/git/refs/reference.py
index a3647fb3b..2a33fbff0 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -8,7 +8,7 @@
 # typing ------------------------------------------------------------------
 
 from typing import Any, Callable, Iterator, Type, Union, TYPE_CHECKING  # NOQA
-from git.types import Commit_ish, PathLike, TBD, Literal, _T                                  # NOQA
+from git.types import Commit_ish, PathLike, _T                                  # NOQA
 
 if TYPE_CHECKING:
     from git.repo import Repo
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index b4a933aa7..0c0fa4045 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -21,8 +21,8 @@
 
 # typing ------------------------------------------------------------------
 
-from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast  # NOQA
-from git.types import Commit_ish, PathLike, TBD, Literal                                               # NOQA
+from typing import Any, Iterator, List, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast  # NOQA
+from git.types import Commit_ish, PathLike                                             # NOQA
 
 if TYPE_CHECKING:
     from git.repo import Repo
@@ -72,12 +72,13 @@ def __str__(self) -> str:
     def __repr__(self) -> str:
         return '<git.%s "%s">' % (self.__class__.__name__, self.path)
 
-    def __eq__(self, other: Any) -> bool:
+    def __eq__(self, other: object) -> bool:
         if hasattr(other, 'path'):
+            other = cast(SymbolicReference, other)
             return self.path == other.path
         return False
 
-    def __ne__(self, other: Any) -> bool:
+    def __ne__(self, other: object) -> bool:
         return not (self == other)
 
     def __hash__(self) -> int:
@@ -284,7 +285,7 @@ def set_object(self, object: Union[Commit_ish, 'SymbolicReference', str], logmsg
     commit = property(_get_commit, set_commit, doc="Query or set commits directly")       # type: ignore
     object = property(_get_object, set_object, doc="Return the object our ref currently refers to")  # type: ignore
 
-    def _get_reference(self) -> 'Reference':
+    def _get_reference(self) -> 'SymbolicReference':
         """:return: Reference Object we point to
         :raise TypeError: If this symbolic reference is detached, hence it doesn't point
             to a reference, but to a commit"""
@@ -364,8 +365,9 @@ def set_reference(self, ref: Union[Commit_ish, 'SymbolicReference', str],
         return self
 
     # aliased reference
+    reference: Union['Head', 'TagReference', 'RemoteReference', 'Reference']
     reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")  # type: ignore
-    ref: Union['Head', 'TagReference', 'RemoteReference', 'Reference'] = reference             # type: ignore
+    ref = reference
 
     def is_valid(self) -> bool:
         """
@@ -681,7 +683,7 @@ def iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLik
         return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached)
 
     @classmethod
-    def from_path(cls, repo: 'Repo', path: PathLike) -> Union['Head', 'TagReference', 'Reference']:
+    def from_path(cls: Type[T_References], repo: 'Repo', path: PathLike) -> T_References:
         """
         :param path: full .git-directory-relative path name to the Reference to instantiate
         :note: use to_full_path() if you only have a partial path of a known Reference Type
@@ -696,10 +698,13 @@ def from_path(cls, repo: 'Repo', path: PathLike) -> Union['Head', 'TagReference'
         from . import HEAD, Head, RemoteReference, TagReference, Reference
         for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference):
             try:
+                instance: T_References
                 instance = ref_type(repo, path)
                 if instance.__class__ == SymbolicReference and instance.is_detached:
                     raise ValueError("SymbolRef was detached, we drop it")
-                return instance
+                else:
+                    return instance
+
             except ValueError:
                 pass
             # END exception handling
diff --git a/git/remote.py b/git/remote.py
index 11007cb68..3888506fd 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -37,10 +37,10 @@
 
 # typing-------------------------------------------------------
 
-from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence,  # NOQA[TC002]
+from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence,
                     TYPE_CHECKING, Type, Union, cast, overload)
 
-from git.types import PathLike, Literal, TBD, Commit_ish     # NOQA[TC002]
+from git.types import PathLike, Literal, Commit_ish
 
 if TYPE_CHECKING:
     from git.repo.base import Repo
@@ -50,7 +50,6 @@
 
 flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?']
 
-
 # def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]:
 #     return inp in [' ', '!', '+', '-', '=', '*', 't', '?']
 
@@ -633,7 +632,7 @@ def stale_refs(self) -> IterableList[Reference]:
             as well. This is a fix for the issue described here:
             https://github.com/gitpython-developers/GitPython/issues/260
             """
-        out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
+        out_refs: IterableList[Reference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
         for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
             # expecting
             # * [would prune] origin/new_branch
@@ -643,7 +642,7 @@ def stale_refs(self) -> IterableList[Reference]:
             ref_name = line.replace(token, "")
             # sometimes, paths start with a full ref name, like refs/tags/foo, see #260
             if ref_name.startswith(Reference._common_path_default + '/'):
-                out_refs.append(SymbolicReference.from_path(self.repo, ref_name))
+                out_refs.append(Reference.from_path(self.repo, ref_name))
             else:
                 fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name)
                 out_refs.append(RemoteReference(self.repo, fqhn))
@@ -707,9 +706,10 @@ def update(self, **kwargs: Any) -> 'Remote':
         self.repo.git.remote(scmd, self.name, **kwargs)
         return self
 
-    def _get_fetch_info_from_stderr(self, proc: TBD,
+    def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
                                     progress: Union[Callable[..., Any], RemoteProgress, None]
                                     ) -> IterableList['FetchInfo']:
+
         progress = to_progress_instance(progress)
 
         # skip first line as it is some remote info we are not interested in
@@ -768,7 +768,7 @@ def _get_fetch_info_from_stderr(self, proc: TBD,
                 log.warning("Git informed while fetching: %s", err_line.strip())
         return output
 
-    def _get_push_info(self, proc: TBD,
+    def _get_push_info(self, proc: 'Git.AutoInterrupt',
                        progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList[PushInfo]:
         progress = to_progress_instance(progress)
 
diff --git a/git/repo/__init__.py b/git/repo/__init__.py
index 712df60de..23c18db85 100644
--- a/git/repo/__init__.py
+++ b/git/repo/__init__.py
@@ -1,3 +1,3 @@
 """Initialize the Repo package"""
 # flake8: noqa
-from .base import *
+from .base import Repo as Repo
diff --git a/git/repo/base.py b/git/repo/base.py
index 5581233ba..c0229a844 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -3,6 +3,7 @@
 #
 # This module is part of GitPython and is released under
 # the BSD License: http://www.opensource.org/licenses/bsd-license.php
+from __future__ import annotations
 import logging
 import os
 import re
@@ -37,13 +38,13 @@
 
 # typing ------------------------------------------------------
 
-from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish
+from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, assert_never
 from typing import (Any, BinaryIO, Callable, Dict,
                     Iterator, List, Mapping, Optional, Sequence,
                     TextIO, Tuple, Type, Union,
                     NamedTuple, cast, TYPE_CHECKING)
 
-from git.types import ConfigLevels_Tup
+from git.types import ConfigLevels_Tup, TypedDict
 
 if TYPE_CHECKING:
     from git.util import IterableList
@@ -52,7 +53,6 @@
     from git.objects.submodule.base import UpdateProgress
     from git.remote import RemoteProgress
 
-
 # -----------------------------------------------------------
 
 log = logging.getLogger(__name__)
@@ -200,7 +200,6 @@ def __init__(self, path: Optional[PathLike] = None, odbt: Type[LooseObjectDB] =
         # END while curpath
 
         if self.git_dir is None:
-            self.git_dir = cast(PathLike, self.git_dir)
             raise InvalidGitRepositoryError(epath)
 
         self._bare = False
@@ -235,7 +234,7 @@ def __init__(self, path: Optional[PathLike] = None, odbt: Type[LooseObjectDB] =
     def __enter__(self) -> 'Repo':
         return self
 
-    def __exit__(self, exc_type: TBD, exc_value: TBD, traceback: TBD) -> None:
+    def __exit__(self, *args: Any) -> None:
         self.close()
 
     def __del__(self) -> None:
@@ -385,13 +384,13 @@ def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule:
         :return: created submodules"""
         return Submodule.add(self, *args, **kwargs)
 
-    def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator:
+    def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
         """An iterator yielding Submodule instances, see Traversable interface
         for a description of args and kwargs
         :return: Iterator"""
         return RootModule(self).traverse(*args, **kwargs)
 
-    def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator:
+    def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
         """Update the submodules, keeping the repository consistent as it will
         take the previous state into consideration. For more information, please
         see the documentation of RootModule.update"""
@@ -445,7 +444,7 @@ def create_tag(self, path: PathLike, ref: str = 'HEAD',
         :return: TagReference object """
         return TagReference.create(self, path, ref, message, force, **kwargs)
 
-    def delete_tag(self, *tags: TBD) -> None:
+    def delete_tag(self, *tags: TagReference) -> None:
         """Delete the given tag references"""
         return TagReference.delete(self, *tags)
 
@@ -481,10 +480,12 @@ def _get_config_path(self, config_level: Lit_config_levels) -> str:
                 raise NotADirectoryError
             else:
                 return osp.normpath(osp.join(repo_dir, "config"))
+        else:
 
-        raise ValueError("Invalid configuration level: %r" % config_level)
+            assert_never(config_level,                                                  # type:ignore[unreachable]
+                         ValueError(f"Invalid configuration level: {config_level!r}"))
 
-    def config_reader(self, config_level: Optional[Lit_config_levels] = None
+    def config_reader(self, config_level: Optional[Lit_config_levels] = None,
                       ) -> GitConfigParser:
         """
         :return:
@@ -775,7 +776,7 @@ def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]:
         finalize_process(proc)
         return untracked_files
 
-    def ignored(self, *paths: PathLike) -> List[PathLike]:
+    def ignored(self, *paths: PathLike) -> List[str]:
         """Checks if paths are ignored via .gitignore
         Doing so using the "git check-ignore" method.
 
@@ -783,7 +784,7 @@ def ignored(self, *paths: PathLike) -> List[PathLike]:
         :return: subset of those paths which are ignored
         """
         try:
-            proc = self.git.check_ignore(*paths)
+            proc: str = self.git.check_ignore(*paths)
         except GitCommandError:
             return []
         return proc.replace("\\\\", "\\").replace('"', "").split("\n")
@@ -795,7 +796,7 @@ def active_branch(self) -> Head:
         # reveal_type(self.head.reference)  # => Reference
         return self.head.reference
 
-    def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iterator['BlameEntry']]:
+    def blame_incremental(self, rev: str | HEAD, file: str, **kwargs: Any) -> Iterator['BlameEntry']:
         """Iterator for blame information for the given file at the given revision.
 
         Unlike .blame(), this does not return the actual file's contents, only
@@ -809,8 +810,9 @@ def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iter
         If you combine all line number ranges outputted by this command, you
         should get a continuous range spanning all line numbers in the file.
         """
-        data = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
-        commits: Dict[str, Commit] = {}
+
+        data: bytes = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
+        commits: Dict[bytes, Commit] = {}
 
         stream = (line for line in data.split(b'\n') if line)
         while True:
@@ -818,15 +820,15 @@ def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iter
                 line = next(stream)  # when exhausted, causes a StopIteration, terminating this function
             except StopIteration:
                 return
-            split_line: Tuple[str, str, str, str] = line.split()
-            hexsha, orig_lineno_str, lineno_str, num_lines_str = split_line
-            lineno = int(lineno_str)
-            num_lines = int(num_lines_str)
-            orig_lineno = int(orig_lineno_str)
+            split_line = line.split()
+            hexsha, orig_lineno_b, lineno_b, num_lines_b = split_line
+            lineno = int(lineno_b)
+            num_lines = int(num_lines_b)
+            orig_lineno = int(orig_lineno_b)
             if hexsha not in commits:
                 # Now read the next few lines and build up a dict of properties
                 # for this commit
-                props = {}
+                props: Dict[bytes, bytes] = {}
                 while True:
                     try:
                         line = next(stream)
@@ -870,8 +872,8 @@ def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iter
                              safe_decode(orig_filename),
                              range(orig_lineno, orig_lineno + num_lines))
 
-    def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
-              ) -> Union[List[List[Union[Optional['Commit'], List[str]]]], Optional[Iterator[BlameEntry]]]:
+    def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **kwargs: Any
+              ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
         """The blame information for the given file at the given revision.
 
         :param rev: revision specifier, see git-rev-parse for viable options.
@@ -883,25 +885,38 @@ def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
         if incremental:
             return self.blame_incremental(rev, file, **kwargs)
 
-        data = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs)
-        commits: Dict[str, TBD] = {}
-        blames: List[List[Union[Optional['Commit'], List[str]]]] = []
-
-        info: Dict[str, TBD] = {}  # use Any until TypedDict available
+        data: bytes = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs)
+        commits: Dict[str, Commit] = {}
+        blames: List[List[Commit | List[str | bytes] | None]] = []
+
+        class InfoTD(TypedDict, total=False):
+            sha: str
+            id: str
+            filename: str
+            summary: str
+            author: str
+            author_email: str
+            author_date: int
+            committer: str
+            committer_email: str
+            committer_date: int
+
+        info: InfoTD = {}
 
         keepends = True
-        for line in data.splitlines(keepends):
+        for line_bytes in data.splitlines(keepends):
             try:
-                line = line.rstrip().decode(defenc)
+                line_str = line_bytes.rstrip().decode(defenc)
             except UnicodeDecodeError:
                 firstpart = ''
+                parts = []
                 is_binary = True
             else:
                 # As we don't have an idea when the binary data ends, as it could contain multiple newlines
                 # in the process. So we rely on being able to decode to tell us what is is.
                 # This can absolutely fail even on text files, but even if it does, we should be fine treating it
                 # as binary instead
-                parts = self.re_whitespace.split(line, 1)
+                parts = self.re_whitespace.split(line_str, 1)
                 firstpart = parts[0]
                 is_binary = False
             # end handle decode of line
@@ -932,12 +947,20 @@ def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
                     # committer-time 1192271832
                     # committer-tz -0700  - IGNORED BY US
                     role = m.group(0)
-                    if firstpart.endswith('-mail'):
-                        info["%s_email" % role] = parts[-1]
-                    elif firstpart.endswith('-time'):
-                        info["%s_date" % role] = int(parts[-1])
-                    elif role == firstpart:
-                        info[role] = parts[-1]
+                    if role == 'author':
+                        if firstpart.endswith('-mail'):
+                            info["author_email"] = parts[-1]
+                        elif firstpart.endswith('-time'):
+                            info["author_date"] = int(parts[-1])
+                        elif role == firstpart:
+                            info["author"] = parts[-1]
+                    elif role == 'committer':
+                        if firstpart.endswith('-mail'):
+                            info["committer_email"] = parts[-1]
+                        elif firstpart.endswith('-time'):
+                            info["committer_date"] = int(parts[-1])
+                        elif role == firstpart:
+                            info["committer"] = parts[-1]
                     # END distinguish mail,time,name
                 else:
                     # handle
@@ -954,26 +977,29 @@ def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
                             c = commits.get(sha)
                             if c is None:
                                 c = Commit(self, hex_to_bin(sha),
-                                           author=Actor._from_string(info['author'] + ' ' + info['author_email']),
+                                           author=Actor._from_string(f"{info['author']} {info['author_email']}"),
                                            authored_date=info['author_date'],
                                            committer=Actor._from_string(
-                                               info['committer'] + ' ' + info['committer_email']),
+                                               f"{info['committer']} {info['committer_email']}"),
                                            committed_date=info['committer_date'])
                                 commits[sha] = c
-                            # END if commit objects needs initial creation
-                            if not is_binary:
-                                if line and line[0] == '\t':
-                                    line = line[1:]
-                            else:
-                                # NOTE: We are actually parsing lines out of binary data, which can lead to the
-                                # binary being split up along the newline separator. We will append this to the blame
-                                # we are currently looking at, even though it should be concatenated with the last line
-                                # we have seen.
-                                pass
-                            # end handle line contents
                             blames[-1][0] = c
+                            # END if commit objects needs initial creation
+
                             if blames[-1][1] is not None:
+                                line: str | bytes
+                                if not is_binary:
+                                    if line_str and line_str[0] == '\t':
+                                        line_str = line_str[1:]
+                                    line = line_str
+                                else:
+                                    line = line_bytes
+                                    # NOTE: We are actually parsing lines out of binary data, which can lead to the
+                                    # binary being split up along the newline separator. We will append this to the
+                                    # blame we are currently looking at, even though it should be concatenated with
+                                    # the last line we have seen.
                                 blames[-1][1].append(line)
+
                             info = {'id': sha}
                         # END if we collected commit info
                     # END distinguish filename,summary,rest
@@ -981,7 +1007,7 @@ def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
             # END distinguish hexsha vs other information
         return blames
 
-    @classmethod
+    @ classmethod
     def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
              expand_vars: bool = True, **kwargs: Any) -> 'Repo':
         """Initialize a git repository at the given path if specified
@@ -1020,7 +1046,7 @@ def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type
         git.init(**kwargs)
         return cls(path, odbt=odbt)
 
-    @classmethod
+    @ classmethod
     def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB],
                progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None] = None,
                multi_options: Optional[List[str]] = None, **kwargs: Any
@@ -1098,9 +1124,9 @@ def clone(self, path: PathLike, progress: Optional[Callable] = None,
         :return: ``git.Repo`` (the newly cloned repo)"""
         return self._clone(self.git, self.common_dir, path, type(self.odb), progress, multi_options, **kwargs)
 
-    @classmethod
+    @ classmethod
     def clone_from(cls, url: PathLike, to_path: PathLike, progress: Optional[Callable] = None,
-                   env: Optional[Mapping[str, Any]] = None,
+                   env: Optional[Mapping[str, str]] = None,
                    multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo':
         """Create a clone from the given URL
 
@@ -1122,7 +1148,7 @@ def clone_from(cls, url: PathLike, to_path: PathLike, progress: Optional[Callabl
         return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
 
     def archive(self, ostream: Union[TextIO, BinaryIO], treeish: Optional[str] = None,
-                prefix: Optional[str] = None, **kwargs: Any) -> 'Repo':
+                prefix: Optional[str] = None, **kwargs: Any) -> Repo:
         """Archive the tree at the given revision.
 
         :param ostream: file compatible stream object to which the archive will be written as bytes
@@ -1169,7 +1195,7 @@ def __repr__(self) -> str:
         clazz = self.__class__
         return '<%s.%s %r>' % (clazz.__module__, clazz.__name__, self.git_dir)
 
-    def currently_rebasing_on(self) -> Union['SymbolicReference', Commit_ish, None]:
+    def currently_rebasing_on(self) -> Commit | None:
         """
         :return: The commit which is currently being replayed while rebasing.
 
diff --git a/git/repo/fun.py b/git/repo/fun.py
index 7d5c78237..1a83dd3dc 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -1,4 +1,5 @@
 """Package with general repository related functions"""
+from __future__ import annotations
 import os
 import stat
 from string import digits
@@ -18,12 +19,13 @@
 # Typing ----------------------------------------------------------------------
 
 from typing import Union, Optional, cast, TYPE_CHECKING
-
+from git.types import Commit_ish
 
 if TYPE_CHECKING:
     from git.types import PathLike
     from .base import Repo
     from git.db import GitCmdObjectDB
+    from git.refs.reference import Reference
     from git.objects import Commit, TagObject, Blob, Tree
     from git.refs.tag import Tag
 
@@ -202,7 +204,7 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
         raise NotImplementedError("commit by message search ( regex )")
     # END handle search
 
-    obj = cast(Object, None)   # not ideal. Should use guards
+    obj: Union[Commit_ish, 'Reference', None] = None
     ref = None
     output_type = "commit"
     start = 0
@@ -222,14 +224,16 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
                 ref = repo.head.ref
             else:
                 if token == '@':
-                    ref = name_to_object(repo, rev[:start], return_ref=True)
+                    ref = cast('Reference', name_to_object(repo, rev[:start], return_ref=True))
                 else:
-                    obj = name_to_object(repo, rev[:start])
+                    obj = cast(Commit_ish, name_to_object(repo, rev[:start]))
                 # END handle token
             # END handle refname
+        else:
+            assert obj is not None
 
             if ref is not None:
-                obj = ref.commit
+                obj = cast('Commit', ref.commit)
             # END handle ref
         # END initialize obj on first token
 
@@ -247,11 +251,13 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
                 pass  # default
             elif output_type == 'tree':
                 try:
+                    obj = cast(Commit_ish, obj)
                     obj = to_commit(obj).tree
                 except (AttributeError, ValueError):
                     pass    # error raised later
                 # END exception handling
             elif output_type in ('', 'blob'):
+                obj = cast('TagObject', obj)
                 if obj and obj.type == 'tag':
                     obj = deref_tag(obj)
                 else:
@@ -280,13 +286,13 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
                 obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
 
                 # make it pass the following checks
-                output_type = None
+                output_type = ''
             else:
                 raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
             # END handle output type
 
             # empty output types don't require any specific type, its just about dereferencing tags
-            if output_type and obj.type != output_type:
+            if output_type and obj and obj.type != output_type:
                 raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
             # END verify output type
 
@@ -319,6 +325,7 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
         parsed_to = start
         # handle hierarchy walk
         try:
+            obj = cast(Commit_ish, obj)
             if token == "~":
                 obj = to_commit(obj)
                 for _ in range(num):
@@ -340,14 +347,14 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
             # END end handle tag
         except (IndexError, AttributeError) as e:
             raise BadName(
-                "Invalid revision spec '%s' - not enough "
-                "parent commits to reach '%s%i'" % (rev, token, num)) from e
+                f"Invalid revision spec '{rev}' - not enough "
+                f"parent commits to reach '{token}{int(num)}'") from e
         # END exception handling
     # END parse loop
 
     # still no obj ? Its probably a simple name
     if obj is None:
-        obj = name_to_object(repo, rev)
+        obj = cast(Commit_ish, name_to_object(repo, rev))
         parsed_to = lr
     # END handle simple name
 
diff --git a/git/types.py b/git/types.py
index ccaffef3e..64bf3d96d 100644
--- a/git/types.py
+++ b/git/types.py
@@ -23,7 +23,7 @@
     PathLike = Union[str, os.PathLike]
 elif sys.version_info[:2] >= (3, 9):
     # os.PathLike only becomes subscriptable from Python 3.9 onwards
-    PathLike = Union[str, 'os.PathLike[str]']  # forward ref as pylance complains unless editing with py3.9+
+    PathLike = Union[str, os.PathLike]
 
 if TYPE_CHECKING:
     from git.repo import Repo
diff --git a/git/util.py b/git/util.py
index 92d95379e..4f82219e6 100644
--- a/git/util.py
+++ b/git/util.py
@@ -38,6 +38,7 @@
     from git.remote import Remote
     from git.repo.base import Repo
     from git.config import GitConfigParser, SectionConstraint
+    from git import Git
     # from git.objects.base import IndexObject
 
 
@@ -69,7 +70,7 @@
 # Most of these are unused here, but are for use by git-python modules so these
 # don't see gitdb all the time. Flake of course doesn't like it.
 __all__ = ["stream_copy", "join_path", "to_native_path_linux",
-           "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList",
+           "join_path_native", "Stats", "IndexFileSHA1Writer", "IterableObj", "IterableList",
            "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists',
            'RemoteProgress', 'CallableRemoteProgress', 'rmtree', 'unbare_repo',
            'HIDE_WINDOWS_KNOWN_ERRORS']
@@ -379,7 +380,7 @@ def get_user_id() -> str:
     return "%s@%s" % (getpass.getuser(), platform.node())
 
 
-def finalize_process(proc: subprocess.Popen, **kwargs: Any) -> None:
+def finalize_process(proc: Union[subprocess.Popen, 'Git.AutoInterrupt'], **kwargs: Any) -> None:
     """Wait for the process (clone, fetch, pull or push) and handle its errors accordingly"""
     # TODO: No close proc-streams??
     proc.wait(**kwargs)
@@ -403,7 +404,7 @@ def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[
         p = osp.expanduser(p)  # type: ignore
         if expand_vars:
             p = osp.expandvars(p)    # type: ignore
-        return osp.normpath(osp.abspath(p))    # type: ignore
+        return osp.normpath(osp.abspath(p))  # type: ignore
     except Exception:
         return None
 
@@ -1033,7 +1034,7 @@ def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None:
 
 class IterableClassWatcher(type):
     """ Metaclass that watches """
-    def __init__(cls, name: str, bases: List, clsdict: Dict) -> None:
+    def __init__(cls, name: str, bases: Tuple, clsdict: Dict) -> None:
         for base in bases:
             if type(base) == IterableClassWatcher:
                 warnings.warn(f"GitPython Iterable subclassed by {name}. "
diff --git a/pyproject.toml b/pyproject.toml
index 4751ffcb9..102b6fdc4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,9 +22,11 @@ filterwarnings = 'ignore::DeprecationWarning'
 disallow_untyped_defs = true
 no_implicit_optional = true
 warn_redundant_casts = true
-# warn_unused_ignores = True
-# warn_unreachable = True
+# warn_unused_ignores = true
+warn_unreachable = true
 show_error_codes = true
+implicit_reexport = true
+# strict = true
 
 # TODO: remove when 'gitdb' is fully annotated
 [[tool.mypy.overrides]]
diff --git a/setup.py b/setup.py
index 215590710..ae6319f9e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,4 @@
+from typing import Sequence
 from setuptools import setup, find_packages
 from setuptools.command.build_py import build_py as _build_py
 from setuptools.command.sdist import sdist as _sdist
@@ -18,7 +19,7 @@
 
 class build_py(_build_py):
 
-    def run(self):
+    def run(self) -> None:
         init = path.join(self.build_lib, 'git', '__init__.py')
         if path.exists(init):
             os.unlink(init)
@@ -29,7 +30,7 @@ def run(self):
 
 class sdist(_sdist):
 
-    def make_release_tree(self, base_dir, files):
+    def make_release_tree(self, base_dir: str, files: Sequence) -> None:
         _sdist.make_release_tree(self, base_dir, files)
         orig = path.join('git', '__init__.py')
         assert path.exists(orig), orig
@@ -40,7 +41,7 @@ def make_release_tree(self, base_dir, files):
         _stamp_version(dest)
 
 
-def _stamp_version(filename):
+def _stamp_version(filename: str) -> None:
     found, out = False, []
     try:
         with open(filename, 'r') as f:
@@ -59,7 +60,7 @@ def _stamp_version(filename):
         print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr)
 
 
-def build_py_modules(basedir, excludes=()):
+def build_py_modules(basedir: str, excludes: Sequence = ()) -> Sequence:
     # create list of py_modules from tree
     res = set()
     _prefix = os.path.basename(basedir)
@@ -90,7 +91,7 @@ def build_py_modules(basedir, excludes=()):
     include_package_data=True,
     py_modules=build_py_modules("./git", excludes=["git.ext.*"]),
     package_dir={'git': 'git'},
-    python_requires='>=3.6',
+    python_requires='>=3.7',
     install_requires=requirements,
     tests_require=requirements + test_requirements,
     zip_safe=False,
@@ -112,11 +113,12 @@ def build_py_modules(basedir, excludes=()):
         "Operating System :: POSIX",
         "Operating System :: Microsoft :: Windows",
         "Operating System :: MacOS :: MacOS X",
+        "Typing:: Typed",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9"
+        "Programming Language :: Python :: 3.10"
     ]
 )