From 3514369631c68c58854b8d9d7e674b0cd64eeaed Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:10:50 +0100
Subject: [PATCH 01/12] Update head types in repo/base.py

---
 git/repo/base.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/git/repo/base.py b/git/repo/base.py
index bb8ddf135..355f93999 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -422,14 +422,14 @@ def _to_full_tag_path(path):
 
     def create_head(self, path: PathLike, commit: str = 'HEAD',
                     force: bool = False, logmsg: Optional[str] = None
-                    ) -> Head:
+                    ) -> 'Head':
         """Create a new head within the repository.
         For more documentation, please see the Head.create method.
 
         :return: newly created Head Reference"""
         return Head.create(self, path, commit, logmsg, force)
 
-    def delete_head(self, *heads: 'SymbolicReference', **kwargs: Any) -> None:
+    def delete_head(self, *heads: 'Head', **kwargs: Any) -> None:
         """Delete the given heads
 
         :param kwargs: Additional keyword arguments to be passed to git-branch"""
@@ -788,10 +788,10 @@ def ignored(self, *paths: PathLike) -> List[PathLike]:
         return proc.replace("\\\\", "\\").replace('"', "").split("\n")
 
     @property
-    def active_branch(self) -> 'SymbolicReference':
+    def active_branch(self) -> Head:
         """The name of the currently active branch.
-
         :return: Head to the active branch"""
+        # reveal_type(self.head.reference)  # => Reference
         return self.head.reference
 
     def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iterator['BlameEntry']]:

From bca7f4f752010ff42dd4b7b60e3b28566885f8bd Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:14:59 +0100
Subject: [PATCH 02/12] Update types in symbolic.py

---
 git/refs/symbolic.py | 172 ++++++++++++++++++++++++-------------------
 1 file changed, 98 insertions(+), 74 deletions(-)

diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index 0e9dad5cc..4171fe234 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -1,4 +1,3 @@
-from git.types import PathLike
 import os
 
 from git.compat import defenc
@@ -17,17 +16,18 @@
     BadName
 )
 
-import os.path as osp
-
-from .log import RefLog
+from .log import RefLog, RefLogEntry
 
 # typing ------------------------------------------------------------------
 
-from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING  # NOQA
+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
 
 if TYPE_CHECKING:
     from git.repo import Repo
+    from git.refs import Reference, Head, TagReference, RemoteReference
+    from git.config import GitConfigParser
+    from git.objects.commit import Actor
 
 T_References = TypeVar('T_References', bound='SymbolicReference')
 
@@ -37,9 +37,9 @@
 __all__ = ["SymbolicReference"]
 
 
-def _git_dir(repo, path):
+def _git_dir(repo: 'Repo', path: PathLike) -> PathLike:
     """ Find the git dir that's appropriate for the path"""
-    name = "%s" % (path,)
+    name = f"{path}"
     if name in ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'index', 'logs']:
         return repo.git_dir
     return repo.common_dir
@@ -59,46 +59,47 @@ class SymbolicReference(object):
     _remote_common_path_default = "refs/remotes"
     _id_attribute_ = "name"
 
-    def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = False):
+    def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = False) -> None:
         self.repo = repo
         self.path = str(path)
+        self.ref = self._get_reference()
 
     def __str__(self) -> str:
         return self.path
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<git.%s "%s">' % (self.__class__.__name__, self.path)
 
-    def __eq__(self, other):
+    def __eq__(self, other) -> bool:
         if hasattr(other, 'path'):
             return self.path == other.path
         return False
 
-    def __ne__(self, other):
+    def __ne__(self, other) -> bool:
         return not (self == other)
 
     def __hash__(self):
         return hash(self.path)
 
     @property
-    def name(self):
+    def name(self) -> str:
         """
         :return:
             In case of symbolic references, the shortest assumable name
             is the path itself."""
-        return self.path
+        return str(self.path)
 
     @property
-    def abspath(self):
+    def abspath(self) -> PathLike:
         return join_path_native(_git_dir(self.repo, self.path), self.path)
 
     @classmethod
-    def _get_packed_refs_path(cls, repo):
-        return osp.join(repo.common_dir, 'packed-refs')
+    def _get_packed_refs_path(cls, repo: 'Repo') -> str:
+        return os.path.join(repo.common_dir, 'packed-refs')
 
     @classmethod
-    def _iter_packed_refs(cls, repo):
-        """Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs.
+    def _iter_packed_refs(cls, repo: 'Repo') -> Iterator[Tuple[str, str]]:
+        """Returns an iterator yielding pairs of sha1/path pairs for the corresponding refs.
         :note: The packed refs file will be kept open as long as we iterate"""
         try:
             with open(cls._get_packed_refs_path(repo), 'rt', encoding='UTF-8') as fp:
@@ -126,7 +127,7 @@ def _iter_packed_refs(cls, repo):
                     if line[0] == '^':
                         continue
 
-                    yield tuple(line.split(' ', 1))
+                    yield cast(Tuple[str, str], tuple(line.split(' ', 1)))
                 # END for each line
         except OSError:
             return None
@@ -137,26 +138,26 @@ def _iter_packed_refs(cls, repo):
         # alright.
 
     @classmethod
-    def dereference_recursive(cls, repo, ref_path):
+    def dereference_recursive(cls, repo: 'Repo', ref_path: PathLike) -> str:
         """
         :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
             intermediate references as required
         :param repo: the repository containing the reference at ref_path"""
         while True:
-            hexsha, ref_path = cls._get_ref_info(repo, ref_path)
+            hexsha, _ref_path_out = cls._get_ref_info(repo, ref_path)
             if hexsha is not None:
                 return hexsha
         # END recursive dereferencing
 
     @classmethod
-    def _get_ref_info_helper(cls, repo, ref_path):
+    def _get_ref_info_helper(cls, repo: 'Repo', ref_path: PathLike) -> Union[Tuple[str, None], Tuple[None, PathLike]]:
         """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
         rela_path points to, or None. target_ref_path is the reference we
         point to, or None"""
-        tokens = None
+        tokens: Union[List[str], Tuple[str, str], None] = None
         repodir = _git_dir(repo, ref_path)
         try:
-            with open(osp.join(repodir, ref_path), 'rt', encoding='UTF-8') as fp:
+            with open(os.path.join(repodir, ref_path), 'rt', encoding='UTF-8') as fp:
                 value = fp.read().rstrip()
             # Don't only split on spaces, but on whitespace, which allows to parse lines like
             # 60b64ef992065e2600bfef6187a97f92398a9144                branch 'master' of git-server:/path/to/repo
@@ -188,13 +189,14 @@ def _get_ref_info_helper(cls, repo, ref_path):
         raise ValueError("Failed to parse reference information from %r" % ref_path)
 
     @classmethod
-    def _get_ref_info(cls, repo, ref_path):
+    def _get_ref_info(cls, repo: 'Repo', ref_path: PathLike
+                      ) -> Union[Tuple[str, None], Tuple[None, PathLike]]:
         """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
         rela_path points to, or None. target_ref_path is the reference we
         point to, or None"""
         return cls._get_ref_info_helper(repo, ref_path)
 
-    def _get_object(self):
+    def _get_object(self) -> Commit_ish:
         """
         :return:
             The object our ref currently refers to. Refs can be cached, they will
@@ -203,7 +205,7 @@ def _get_object(self):
         # Our path will be resolved to the hexsha which will be used accordingly
         return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path)))
 
-    def _get_commit(self):
+    def _get_commit(self) -> 'Commit':
         """
         :return:
             Commit object we point to, works for detached and non-detached
@@ -218,7 +220,8 @@ def _get_commit(self):
         # END handle type
         return obj
 
-    def set_commit(self, commit: Union[Commit, 'SymbolicReference', str], logmsg=None):
+    def set_commit(self, commit: Union[Commit, 'SymbolicReference', str],
+                   logmsg: Union[str, None] = None) -> None:
         """As set_object, but restricts the type of object to be a Commit
 
         :raise ValueError: If commit is not a Commit object or doesn't point to
@@ -228,11 +231,13 @@ def set_commit(self, commit: Union[Commit, 'SymbolicReference', str], logmsg=Non
         invalid_type = False
         if isinstance(commit, Object):
             invalid_type = commit.type != Commit.type
+            commit = cast('Commit', commit)
         elif isinstance(commit, SymbolicReference):
             invalid_type = commit.object.type != Commit.type
         else:
             try:
-                invalid_type = self.repo.rev_parse(commit).type != Commit.type
+                commit = self.repo.rev_parse(commit)
+                invalid_type = commit.type != Commit.type
             except (BadObject, BadName) as e:
                 raise ValueError("Invalid object: %s" % commit) from e
             # END handle exception
@@ -245,9 +250,12 @@ def set_commit(self, commit: Union[Commit, 'SymbolicReference', str], logmsg=Non
         # we leave strings to the rev-parse method below
         self.set_object(commit, logmsg)
 
-        return self
+        # return self
+        return None
 
-    def set_object(self, object, logmsg=None):  # @ReservedAssignment
+    def set_object(self, object: Union[Commit_ish, 'SymbolicReference'],
+                   logmsg: Union[str, None] = None
+                   ) -> 'SymbolicReference':  # @ReservedAssignment
         """Set the object we point to, possibly dereference our symbolic reference first.
         If the reference does not exist, it will be created
 
@@ -274,10 +282,11 @@ def set_object(self, object, logmsg=None):  # @ReservedAssignment
         # set the commit on our reference
         return self._get_reference().set_object(object, logmsg)
 
-    commit = property(_get_commit, set_commit, doc="Query or set commits directly")
-    object = property(_get_object, set_object, doc="Return the object our ref currently refers to")
+    commit = cast('Commit', property(_get_commit, set_commit, doc="Query or set commits directly"))
+    object = property(_get_object, set_object, doc="Return the object our ref currently refers to")  # type: ignore
 
-    def _get_reference(self):
+    def _get_reference(self
+                       ) -> Union['Head', 'RemoteReference', 'TagReference', 'Reference']:
         """: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"""
@@ -286,7 +295,8 @@ def _get_reference(self):
             raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha))
         return self.from_path(self.repo, target_ref_path)
 
-    def set_reference(self, ref, logmsg=None):
+    def set_reference(self, ref: Union[str, Commit_ish, 'SymbolicReference'], logmsg: Union[str, None] = None
+                      ) -> 'SymbolicReference':
         """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference.
         Otherwise an Object, given as Object instance or refspec, is assumed and if valid,
         will be set which effectively detaches the refererence if it was a purely
@@ -327,7 +337,7 @@ def set_reference(self, ref, logmsg=None):
             raise TypeError("Require commit, got %r" % obj)
         # END verify type
 
-        oldbinsha = None
+        oldbinsha: bytes = b''
         if logmsg is not None:
             try:
                 oldbinsha = self.commit.binsha
@@ -355,11 +365,16 @@ def set_reference(self, ref, logmsg=None):
 
         return self
 
-    # aliased reference
-    reference = property(_get_reference, set_reference, doc="Returns the Reference we point to")
-    ref: Union[Commit_ish] = reference     # type: ignore  # Union[str, Commit_ish, SymbolicReference]
+    @ property
+    def reference(self) -> Union['Head', 'RemoteReference', 'TagReference', 'Reference']:
+        return self._get_reference()
 
-    def is_valid(self):
+    @ reference.setter
+    def reference(self, ref: Union[str, Commit_ish, 'SymbolicReference'], logmsg: Union[str, None] = None
+                  ) -> 'SymbolicReference':
+        return self.set_reference(ref=ref, logmsg=logmsg)
+
+    def is_valid(self) -> bool:
         """
         :return:
             True if the reference is valid, hence it can be read and points to
@@ -371,7 +386,7 @@ def is_valid(self):
         else:
             return True
 
-    @property
+    @ property
     def is_detached(self):
         """
         :return:
@@ -383,7 +398,7 @@ def is_detached(self):
         except TypeError:
             return True
 
-    def log(self):
+    def log(self) -> 'RefLog':
         """
         :return: RefLog for this reference. Its last entry reflects the latest change
             applied to this reference
@@ -392,7 +407,8 @@ def log(self):
             instead of calling this method repeatedly. It should be considered read-only."""
         return RefLog.from_file(RefLog.path(self))
 
-    def log_append(self, oldbinsha, message, newbinsha=None):
+    def log_append(self, oldbinsha: bytes, message: Union[str, None],
+                   newbinsha: Union[bytes, None] = None) -> 'RefLogEntry':
         """Append a logentry to the logfile of this ref
 
         :param oldbinsha: binary sha this ref used to point to
@@ -404,15 +420,19 @@ def log_append(self, oldbinsha, message, newbinsha=None):
         # correct to allow overriding the committer on a per-commit level.
         # See https://github.com/gitpython-developers/GitPython/pull/146
         try:
-            committer_or_reader = self.commit.committer
+            committer_or_reader: Union['Actor', 'GitConfigParser'] = self.commit.committer
         except ValueError:
             committer_or_reader = self.repo.config_reader()
         # end handle newly cloned repositories
-        return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha,
-                                   (newbinsha is None and self.commit.binsha) or newbinsha,
-                                   message)
+        if newbinsha is None:
+            newbinsha = self.commit.binsha
+
+        if message is None:
+            message = ''
 
-    def log_entry(self, index):
+        return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)
+
+    def log_entry(self, index: int) -> RefLogEntry:
         """:return: RefLogEntry at the given index
         :param index: python list compatible positive or negative index
 
@@ -421,22 +441,23 @@ def log_entry(self, index):
             In that case, it will be faster than the ``log()`` method"""
         return RefLog.entry_at(RefLog.path(self), index)
 
-    @classmethod
-    def to_full_path(cls, path) -> PathLike:
+    @ classmethod
+    def to_full_path(cls, path: Union[PathLike, 'SymbolicReference']) -> str:
         """
         :return: string with a full repository-relative path which can be used to initialize
             a Reference instance, for instance by using ``Reference.from_path``"""
         if isinstance(path, SymbolicReference):
             path = path.path
-        full_ref_path = path
+        full_ref_path = str(path)
         if not cls._common_path_default:
             return full_ref_path
-        if not path.startswith(cls._common_path_default + "/"):
+
+        if not str(path).startswith(cls._common_path_default + "/"):
             full_ref_path = '%s/%s' % (cls._common_path_default, path)
         return full_ref_path
 
-    @classmethod
-    def delete(cls, repo, path):
+    @ classmethod
+    def delete(cls, repo: 'Repo', path: PathLike) -> None:
         """Delete the reference at the given path
 
         :param repo:
@@ -447,8 +468,8 @@ def delete(cls, repo, path):
             or just "myreference", hence 'refs/' is implied.
             Alternatively the symbolic reference to be deleted"""
         full_ref_path = cls.to_full_path(path)
-        abs_path = osp.join(repo.common_dir, full_ref_path)
-        if osp.exists(abs_path):
+        abs_path = os.path.join(repo.common_dir, full_ref_path)
+        if os.path.exists(abs_path):
             os.remove(abs_path)
         else:
             # check packed refs
@@ -458,8 +479,8 @@ def delete(cls, repo, path):
                     new_lines = []
                     made_change = False
                     dropped_last_line = False
-                    for line in reader:
-                        line = line.decode(defenc)
+                    for line_bytes in reader:
+                        line = line_bytes.decode(defenc)
                         _, _, line_ref = line.partition(' ')
                         line_ref = line_ref.strip()
                         # keep line if it is a comment or if the ref to delete is not
@@ -489,12 +510,14 @@ def delete(cls, repo, path):
 
         # delete the reflog
         reflog_path = RefLog.path(cls(repo, full_ref_path))
-        if osp.isfile(reflog_path):
+        if os.path.isfile(reflog_path):
             os.remove(reflog_path)
         # END remove reflog
 
-    @classmethod
-    def _create(cls, repo, path, resolve, reference, force, logmsg=None):
+    @ classmethod
+    def _create(cls: Type[T_References], repo: 'Repo', path: PathLike, resolve: bool,
+                reference: Union[str, 'SymbolicReference'],
+                force: bool, logmsg: Union[str, None] = None) -> T_References:
         """internal method used to create a new symbolic reference.
         If resolve is False, the reference will be taken as is, creating
         a proper symbolic reference. Otherwise it will be resolved to the
@@ -502,14 +525,14 @@ def _create(cls, repo, path, resolve, reference, force, logmsg=None):
         instead"""
         git_dir = _git_dir(repo, path)
         full_ref_path = cls.to_full_path(path)
-        abs_ref_path = osp.join(git_dir, full_ref_path)
+        abs_ref_path = os.path.join(git_dir, full_ref_path)
 
         # figure out target data
         target = reference
         if resolve:
             target = repo.rev_parse(str(reference))
 
-        if not force and osp.isfile(abs_ref_path):
+        if not force and os.path.isfile(abs_ref_path):
             target_data = str(target)
             if isinstance(target, SymbolicReference):
                 target_data = target.path
@@ -527,8 +550,9 @@ def _create(cls, repo, path, resolve, reference, force, logmsg=None):
         return ref
 
     @classmethod
-    def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str] = 'HEAD',
-               logmsg: Union[str, None] = None, force: bool = False, **kwargs: Any):
+    def create(cls: Type[T_References], repo: 'Repo', path: PathLike,
+               reference: Union[str, 'SymbolicReference'] = 'SymbolicReference',
+               logmsg: Union[str, None] = None, force: bool = False, **kwargs: Any) -> T_References:
         """Create a new symbolic reference, hence a reference pointing , to another reference.
 
         :param repo:
@@ -540,7 +564,7 @@ def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str]
 
         :param reference:
             The reference to which the new symbolic reference should point to.
-            If it is a commit'ish, the symbolic ref will be detached.
+            If it is a ref to a commit'ish, the symbolic ref will be detached.
 
         :param force:
             if True, force creation even if a symbolic reference with that name already exists.
@@ -559,7 +583,7 @@ def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str]
         :note: This does not alter the current HEAD, index or Working Tree"""
         return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
 
-    def rename(self, new_path, force=False):
+    def rename(self, new_path: str, force: bool = False) -> 'SymbolicReference':
         """Rename self to a new path
 
         :param new_path:
@@ -577,9 +601,9 @@ def rename(self, new_path, force=False):
         if self.path == new_path:
             return self
 
-        new_abs_path = osp.join(_git_dir(self.repo, new_path), new_path)
-        cur_abs_path = osp.join(_git_dir(self.repo, self.path), self.path)
-        if osp.isfile(new_abs_path):
+        new_abs_path = os.path.join(_git_dir(self.repo, new_path), new_path)
+        cur_abs_path = os.path.join(_git_dir(self.repo, self.path), self.path)
+        if os.path.isfile(new_abs_path):
             if not force:
                 # if they point to the same file, its not an error
                 with open(new_abs_path, 'rb') as fd1:
@@ -594,8 +618,8 @@ def rename(self, new_path, force=False):
             os.remove(new_abs_path)
         # END handle existing target file
 
-        dname = osp.dirname(new_abs_path)
-        if not osp.isdir(dname):
+        dname = os.path.dirname(new_abs_path)
+        if not os.path.isdir(dname):
             os.makedirs(dname)
         # END create directory
 
@@ -630,7 +654,7 @@ def _iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLi
 
         # read packed refs
         for _sha, rela_path in cls._iter_packed_refs(repo):
-            if rela_path.startswith(common_path):
+            if rela_path.startswith(str(common_path)):
                 rela_paths.add(rela_path)
             # END relative path matches common path
         # END packed refs reading
@@ -665,7 +689,7 @@ def iter_items(cls, repo: 'Repo', common_path: Union[PathLike, None] = None, *ar
         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, path):
+    def from_path(cls, repo: 'Repo', path: PathLike) -> Union['Head', 'RemoteReference', 'TagReference', 'Reference']:
         """
         :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

From 8d89e43382c3ccef21f5786f6061936024cc4138 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:15:55 +0100
Subject: [PATCH 03/12] Update types in head.py

---
 git/refs/head.py | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/git/refs/head.py b/git/refs/head.py
index 338efce9f..260bf5e7e 100644
--- a/git/refs/head.py
+++ b/git/refs/head.py
@@ -1,4 +1,4 @@
-from git.config import SectionConstraint
+from git.config import GitConfigParser, SectionConstraint
 from git.util import join_path
 from git.exc import GitCommandError
 
@@ -142,7 +142,7 @@ def delete(cls, repo: 'Repo', *heads: 'Head', **kwargs: Any):
             flag = "-D"
         repo.git.branch(flag, *heads)
 
-    def set_tracking_branch(self, remote_reference: 'RemoteReference') -> 'Head':
+    def set_tracking_branch(self, remote_reference: Union['RemoteReference', None]) -> 'Head':
         """
         Configure this branch to track the given remote reference. This will alter
             this branch's configuration accordingly.
@@ -203,7 +203,7 @@ def rename(self, new_path: PathLike, force: bool = False) -> 'Head':
         self.path = "%s/%s" % (self._common_path_default, new_path)
         return self
 
-    def checkout(self, force: bool = False, **kwargs: Any):
+    def checkout(self, force: bool = False, **kwargs: Any) -> Union['HEAD', 'Head']:
         """Checkout this head by setting the HEAD to this reference, by updating the index
         to reflect the tree we point to and by updating the working tree to reflect
         the latest index.
@@ -235,10 +235,11 @@ def checkout(self, force: bool = False, **kwargs: Any):
         self.repo.git.checkout(self, **kwargs)
         if self.repo.head.is_detached:
             return self.repo.head
-        return self.repo.active_branch
+        else:
+            return self.repo.active_branch
 
     #{ Configuration
-    def _config_parser(self, read_only: bool) -> SectionConstraint:
+    def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]:
         if read_only:
             parser = self.repo.config_reader()
         else:
@@ -247,13 +248,13 @@ def _config_parser(self, read_only: bool) -> SectionConstraint:
 
         return SectionConstraint(parser, 'branch "%s"' % self.name)
 
-    def config_reader(self) -> SectionConstraint:
+    def config_reader(self) -> SectionConstraint[GitConfigParser]:
         """
         :return: A configuration parser instance constrained to only read
             this instance's values"""
         return self._config_parser(read_only=True)
 
-    def config_writer(self) -> SectionConstraint:
+    def config_writer(self) -> SectionConstraint[GitConfigParser]:
         """
         :return: A configuration writer instance with read-and write access
             to options of this head"""

From b1b7a256c2097e6efbef5c103da18af391422159 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:17:22 +0100
Subject: [PATCH 04/12] Update types in tag.py

---
 git/refs/tag.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/git/refs/tag.py b/git/refs/tag.py
index 281ce09ad..edfab33d8 100644
--- a/git/refs/tag.py
+++ b/git/refs/tag.py
@@ -4,13 +4,14 @@
 
 # typing ------------------------------------------------------------------
 
-from typing import Any, Union, TYPE_CHECKING
+from typing import Any, Type, Union, TYPE_CHECKING
 from git.types import Commit_ish, PathLike
 
 if TYPE_CHECKING:
     from git.repo import Repo
     from git.objects import Commit
     from git.objects import TagObject
+    from git.refs import SymbolicReference
 
 
 # ------------------------------------------------------------------------------
@@ -68,7 +69,8 @@ def object(self) -> Commit_ish:  # type: ignore[override]
         return Reference._get_object(self)
 
     @classmethod
-    def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str] = 'HEAD',
+    def create(cls: Type['TagReference'], repo: 'Repo', path: PathLike,
+               reference: Union[str, 'SymbolicReference'] = 'HEAD',
                logmsg: Union[str, None] = None,
                force: bool = False, **kwargs: Any) -> 'TagReference':
         """Create a new tag reference.
@@ -78,7 +80,7 @@ def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str]
             The prefix refs/tags is implied
 
         :param ref:
-            A reference to the object you want to tag. It can be a commit, tree or
+            A reference to the Object you want to tag. The Object can be a commit, tree or
             blob.
 
         :param logmsg:
@@ -98,7 +100,9 @@ def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str]
             Additional keyword arguments to be passed to git-tag
 
         :return: A new TagReference"""
-        args = (path, reference)
+        if 'ref' in kwargs and kwargs['ref']:
+            reference = kwargs['ref']
+
         if logmsg:
             kwargs['m'] = logmsg
         elif 'message' in kwargs and kwargs['message']:
@@ -107,11 +111,13 @@ def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str]
         if force:
             kwargs['f'] = True
 
+        args = (path, reference)
+
         repo.git.tag(*args, **kwargs)
         return TagReference(repo, "%s/%s" % (cls._common_path_default, path))
 
     @classmethod
-    def delete(cls, repo: 'Repo', *tags: 'TagReference') -> None:
+    def delete(cls, repo: 'Repo', *tags: 'TagReference') -> None:  # type: ignore[override]
         """Delete the given existing tag or tags"""
         repo.git.tag("-d", *tags)
 

From 08fdad19043d13dce90a6f3e8bb5cb3363c69e54 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:18:48 +0100
Subject: [PATCH 05/12] Update types in reference.py

---
 git/refs/reference.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/git/refs/reference.py b/git/refs/reference.py
index 646622816..bc2c6e807 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -62,7 +62,9 @@ def __str__(self) -> str:
 
     #{ Interface
 
-    def set_object(self, object: Commit_ish, logmsg: Union[str, None] = None) -> 'Reference':  # @ReservedAssignment
+    # @ReservedAssignment
+    def set_object(self, object: Union[Commit_ish, 'SymbolicReference'], logmsg: Union[str, None] = None
+                   ) -> 'SymbolicReference':
         """Special version which checks if the head-log needs an update as well
         :return: self"""
         oldbinsha = None

From b88b2ff9e4b80010691eaf9bd8bed97cfc040e99 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:20:31 +0100
Subject: [PATCH 06/12] Update types in commit.py

---
 git/objects/commit.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/git/objects/commit.py b/git/objects/commit.py
index 884f65228..9d7096563 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -128,6 +128,7 @@ def __init__(self, repo: 'Repo', binsha: bytes, tree: Union[Tree, None] = None,
             as what time.altzone returns. The sign is inverted compared to git's
             UTC timezone."""
         super(Commit, self).__init__(repo, binsha)
+        self.binsha = binsha
         if tree is not None:
             assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
         if tree is not None:

From 806d7e18cdbf3731ff96de11803f3ab1fd877a70 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:21:30 +0100
Subject: [PATCH 07/12] update types in submodule/base.py

---
 git/objects/submodule/base.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index 29212167c..143511909 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -563,6 +563,7 @@ def update(self, recursive: bool = False, init: bool = True, to_latest_revision:
                     progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
                 # END fetch new data
             except InvalidGitRepositoryError:
+                mrepo = None
                 if not init:
                     return self
                 # END early abort if init is not allowed
@@ -603,7 +604,7 @@ def update(self, recursive: bool = False, init: bool = True, to_latest_revision:
 
                         # make sure HEAD is not detached
                         mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
-                        mrepo.head.ref.set_tracking_branch(remote_branch)
+                        mrepo.head.reference.set_tracking_branch(remote_branch)
                     except (IndexError, InvalidGitRepositoryError):
                         log.warning("Failed to checkout tracking branch %s", self.branch_path)
                     # END handle tracking branch
@@ -629,13 +630,14 @@ def update(self, recursive: bool = False, init: bool = True, to_latest_revision:
             if mrepo is not None and to_latest_revision:
                 msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
                 if not is_detached:
-                    rref = mrepo.head.ref.tracking_branch()
+                    rref = mrepo.head.reference.tracking_branch()
                     if rref is not None:
                         rcommit = rref.commit
                         binsha = rcommit.binsha
                         hexsha = rcommit.hexsha
                     else:
-                        log.error("%s a tracking branch was not set for local branch '%s'", msg_base, mrepo.head.ref)
+                        log.error("%s a tracking branch was not set for local branch '%s'",
+                                  msg_base, mrepo.head.reference)
                     # END handle remote ref
                 else:
                     log.error("%s there was no local tracking branch", msg_base)

From a79d006c5158979da597503168e5f6512246ba00 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:22:33 +0100
Subject: [PATCH 08/12] Update submodule/root.py

---
 git/objects/submodule/root.py | 363 ++++++++++++++++++++++++++++++++++
 1 file changed, 363 insertions(+)

diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index bcac5419a..6ed2a8f63 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -357,8 +357,371 @@ def update(self, previous_commit: Union[Commit_ish, None] = None,
 
         return self
 
+from .base import (
+    Submodule,
+    UpdateProgress
+)
+from .util import (
+    find_first_remote_branch
+)
+from git.exc import InvalidGitRepositoryError
+import git
+
+import logging
+
+# typing -------------------------------------------------------------------
+
+from typing import TYPE_CHECKING, Union
+
+from git.types import Commit_ish
+
+if TYPE_CHECKING:
+    from git.repo import Repo
+    from git.util import IterableList
+
+# ----------------------------------------------------------------------------
+
+__all__ = ["RootModule", "RootUpdateProgress"]
+
+log = logging.getLogger('git.objects.submodule.root')
+log.addHandler(logging.NullHandler())
+
+
+class RootUpdateProgress(UpdateProgress):
+    """Utility class which adds more opcodes to the UpdateProgress"""
+    REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
+        1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)]
+    _num_op_codes = UpdateProgress._num_op_codes + 4
+
+    __slots__ = ()
+
+
+BEGIN = RootUpdateProgress.BEGIN
+END = RootUpdateProgress.END
+REMOVE = RootUpdateProgress.REMOVE
+BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE
+URLCHANGE = RootUpdateProgress.URLCHANGE
+PATHCHANGE = RootUpdateProgress.PATHCHANGE
+
+
+class RootModule(Submodule):
+
+    """A (virtual) Root of all submodules in the given repository. It can be used
+    to more easily traverse all submodules of the master repository"""
+
+    __slots__ = ()
+
+    k_root_name = '__ROOT__'
+
+    def __init__(self, repo: 'Repo'):
+        # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
+        super(RootModule, self).__init__(
+            repo,
+            binsha=self.NULL_BIN_SHA,
+            mode=self.k_default_mode,
+            path='',
+            name=self.k_root_name,
+            parent_commit=repo.head.commit,
+            url='',
+            branch_path=git.Head.to_full_path(self.k_head_default)
+        )
+
+    def _clear_cache(self) -> None:
+        """May not do anything"""
+        pass
+
+    #{ Interface
+
+    def update(self, previous_commit: Union[Commit_ish, None] = None,                    # type: ignore[override]
+               recursive: bool = True, force_remove: bool = False, init: bool = True,
+               to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None,
+               dry_run: bool = False, force_reset: bool = False, keep_going: bool = False
+               ) -> 'RootModule':
+        """Update the submodules of this repository to the current HEAD commit.
+        This method behaves smartly by determining changes of the path of a submodules
+        repository, next to changes to the to-be-checked-out commit or the branch to be
+        checked out. This works if the submodules ID does not change.
+        Additionally it will detect addition and removal of submodules, which will be handled
+        gracefully.
+
+        :param previous_commit: If set to a commit'ish, the commit we should use
+            as the previous commit the HEAD pointed to before it was set to the commit it points to now.
+            If None, it defaults to HEAD@{1} otherwise
+        :param recursive: if True, the children of submodules will be updated as well
+            using the same technique
+        :param force_remove: If submodules have been deleted, they will be forcibly removed.
+            Otherwise the update may fail if a submodule's repository cannot be deleted as
+            changes have been made to it (see Submodule.update() for more information)
+        :param init: If we encounter a new module which would need to be initialized, then do it.
+        :param to_latest_revision: If True, instead of checking out the revision pointed to
+            by this submodule's sha, the checked out tracking branch will be merged with the
+            latest remote branch fetched from the repository's origin.
+            Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore
+            the remote branch must be in the future for this to have an effect.
+        :param force_reset: if True, submodules may checkout or reset their branch even if the repository has
+            pending changes that would be overwritten, or if the local tracking branch is in the future of the
+            remote tracking branch and would be reset into its past.
+        :param progress: RootUpdateProgress instance or None if no progress should be sent
+        :param dry_run: if True, operations will not actually be performed. Progress messages
+            will change accordingly to indicate the WOULD DO state of the operation.
+        :param keep_going: if True, we will ignore but log all errors, and keep going recursively.
+            Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
+            otherwise.
+            In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
+        :return: self"""
+        if self.repo.bare:
+            raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
+        # END handle bare
+
+        if progress is None:
+            progress = RootUpdateProgress()
+        # END assure progress is set
+
+        prefix = ''
+        if dry_run:
+            prefix = 'DRY-RUN: '
+
+        repo = self.repo
+
+        try:
+            # SETUP BASE COMMIT
+            ###################
+            cur_commit = repo.head.commit
+            if previous_commit is None:
+                try:
+                    previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
+                    if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
+                        raise IndexError
+                    # END handle initial commit
+                except IndexError:
+                    # in new repositories, there is no previous commit
+                    previous_commit = cur_commit
+                # END exception handling
+            else:
+                previous_commit = repo.commit(previous_commit)   # obtain commit object
+            # END handle previous commit
+
+            psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit)
+            sms: 'IterableList[Submodule]' = self.list_items(repo)
+            spsms = set(psms)
+            ssms = set(sms)
+
+            # HANDLE REMOVALS
+            ###################
+            rrsm = (spsms - ssms)
+            len_rrsm = len(rrsm)
+
+            for i, rsm in enumerate(rrsm):
+                op = REMOVE
+                if i == 0:
+                    op |= BEGIN
+                # END handle begin
+
+                # fake it into thinking its at the current commit to allow deletion
+                # of previous module. Trigger the cache to be updated before that
+                progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
+                rsm._parent_commit = repo.head.commit
+                rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
+
+                if i == len_rrsm - 1:
+                    op |= END
+                # END handle end
+                progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
+            # END for each removed submodule
+
+            # HANDLE PATH RENAMES
+            #####################
+            # url changes + branch changes
+            csms = (spsms & ssms)
+            len_csms = len(csms)
+            for i, csm in enumerate(csms):
+                psm: 'Submodule' = psms[csm.name]
+                sm: 'Submodule' = sms[csm.name]
+
+                # PATH CHANGES
+                ##############
+                if sm.path != psm.path and psm.module_exists():
+                    progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
+                                    "Moving repository of submodule %r from %s to %s"
+                                    % (sm.name, psm.abspath, sm.abspath))
+                    # move the module to the new path
+                    if not dry_run:
+                        psm.move(sm.path, module=True, configuration=False)
+                    # END handle dry_run
+                    progress.update(
+                        END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
+                # END handle path changes
+
+                if sm.module_exists():
+                    # HANDLE URL CHANGE
+                    ###################
+                    if sm.url != psm.url:
+                        # Add the new remote, remove the old one
+                        # This way, if the url just changes, the commits will not
+                        # have to be re-retrieved
+                        nn = '__new_origin__'
+                        smm = sm.module()
+                        rmts = smm.remotes
+
+                        # don't do anything if we already have the url we search in place
+                        if len([r for r in rmts if r.url == sm.url]) == 0:
+                            progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
+                                            "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
+
+                            if not dry_run:
+                                assert nn not in [r.name for r in rmts]
+                                smr = smm.create_remote(nn, sm.url)
+                                smr.fetch(progress=progress)
+
+                                # If we have a tracking branch, it should be available
+                                # in the new remote as well.
+                                if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
+                                    raise ValueError(
+                                        "Submodule branch named %r was not available in new submodule remote at %r"
+                                        % (sm.branch_name, sm.url)
+                                    )
+                                # END head is not detached
+
+                                # now delete the changed one
+                                rmt_for_deletion = None
+                                for remote in rmts:
+                                    if remote.url == psm.url:
+                                        rmt_for_deletion = remote
+                                        break
+                                    # END if urls match
+                                # END for each remote
+
+                                # if we didn't find a matching remote, but have exactly one,
+                                # we can safely use this one
+                                if rmt_for_deletion is None:
+                                    if len(rmts) == 1:
+                                        rmt_for_deletion = rmts[0]
+                                    else:
+                                        # if we have not found any remote with the original url
+                                        # we may not have a name. This is a special case,
+                                        # and its okay to fail here
+                                        # Alternatively we could just generate a unique name and leave all
+                                        # existing ones in place
+                                        raise InvalidGitRepositoryError(
+                                            "Couldn't find original remote-repo at url %r" % psm.url)
+                                    # END handle one single remote
+                                # END handle check we found a remote
+
+                                orig_name = rmt_for_deletion.name
+                                smm.delete_remote(rmt_for_deletion)
+                                # NOTE: Currently we leave tags from the deleted remotes
+                                # as well as separate tracking branches in the possibly totally
+                                # changed repository ( someone could have changed the url to
+                                # another project ). At some point, one might want to clean
+                                # it up, but the danger is high to remove stuff the user
+                                # has added explicitly
+
+                                # rename the new remote back to what it was
+                                smr.rename(orig_name)
+
+                                # early on, we verified that the our current tracking branch
+                                # exists in the remote. Now we have to assure that the
+                                # sha we point to is still contained in the new remote
+                                # tracking branch.
+                                smsha = sm.binsha
+                                found = False
+                                rref = smr.refs[self.branch_name]
+                                for c in rref.commit.traverse():
+                                    if c.binsha == smsha:
+                                        found = True
+                                        break
+                                    # END traverse all commits in search for sha
+                                # END for each commit
+
+                                if not found:
+                                    # adjust our internal binsha to use the one of the remote
+                                    # this way, it will be checked out in the next step
+                                    # This will change the submodule relative to us, so
+                                    # the user will be able to commit the change easily
+                                    log.warning("Current sha %s was not contained in the tracking\
+             branch at the new remote, setting it the the remote's tracking branch", sm.hexsha)
+                                    sm.binsha = rref.commit.binsha
+                                # END reset binsha
+
+                                # NOTE: All checkout is performed by the base implementation of update
+                            # END handle dry_run
+                            progress.update(
+                                END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
+                        # END skip remote handling if new url already exists in module
+                    # END handle url
+
+                    # HANDLE PATH CHANGES
+                    #####################
+                    if sm.branch_path != psm.branch_path:
+                        # finally, create a new tracking branch which tracks the
+                        # new remote branch
+                        progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
+                                        "Changing branch of submodule %r from %s to %s"
+                                        % (sm.name, psm.branch_path, sm.branch_path))
+                        if not dry_run:
+                            smm = sm.module()
+                            smmr = smm.remotes
+                            # As the branch might not exist yet, we will have to fetch all remotes to be sure ... .
+                            for remote in smmr:
+                                remote.fetch(progress=progress)
+                            # end for each remote
+
+                            try:
+                                tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
+                            except OSError:
+                                # ... or reuse the existing one
+                                tbr = git.Head(smm, sm.branch_path)
+                            # END assure tracking branch exists
+
+                            tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
+                            # NOTE: All head-resetting is done in the base implementation of update
+                            # but we will have to checkout the new branch here. As it still points to the currently
+                            # checkout out commit, we don't do any harm.
+                            # As we don't want to update working-tree or index, changing the ref is all there is to do
+                            smm.head.reference = tbr
+                        # END handle dry_run
+
+                        progress.update(
+                            END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
+                    # END handle branch
+                # END handle
+            # END for each common submodule
+        except Exception as err:
+            if not keep_going:
+                raise
+            log.error(str(err))
+        # end handle keep_going
+
+        # FINALLY UPDATE ALL ACTUAL SUBMODULES
+        ######################################
+        for sm in sms:
+            # update the submodule using the default method
+            sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
+                      progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going)
+
+            # update recursively depth first - question is which inconsitent
+            # state will be better in case it fails somewhere. Defective branch
+            # or defective depth. The RootSubmodule type will never process itself,
+            # which was done in the previous expression
+            if recursive:
+                # the module would exist by now if we are not in dry_run mode
+                if sm.module_exists():
+                    type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
+                                                   init=init, to_latest_revision=to_latest_revision,
+                                                   progress=progress, dry_run=dry_run, force_reset=force_reset,
+                                                   keep_going=keep_going)
+                # END handle dry_run
+            # END handle recursive
+        # END for each submodule to update
+
+        return self
+
     def module(self) -> 'Repo':
         """:return: the actual repository containing the submodules"""
         return self.repo
     #} END interface
+#} END classes
+        """:return: the actual repository containing the submodules"""
+        return self.repo
+    #} END interface
 #} END classes

From 63771e1822601ce84e4129f2b53c997483faa9d1 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:27:09 +0100
Subject: [PATCH 09/12] Update root.py

---
 git/objects/submodule/root.py | 367 +---------------------------------
 1 file changed, 1 insertion(+), 366 deletions(-)

diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index 6ed2a8f63..5e84d1616 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -2,368 +2,7 @@
     Submodule,
     UpdateProgress
 )
-from .util import (
-    find_first_remote_branch
-)
-from git.exc import InvalidGitRepositoryError
-import git
-
-import logging
-
-# typing -------------------------------------------------------------------
-
-from typing import TYPE_CHECKING, Union
-
-from git.types import Commit_ish
-
-if TYPE_CHECKING:
-    from git.repo import Repo
-    from git.util import IterableList
-
-# ----------------------------------------------------------------------------
-
-__all__ = ["RootModule", "RootUpdateProgress"]
-
-log = logging.getLogger('git.objects.submodule.root')
-log.addHandler(logging.NullHandler())
-
-
-class RootUpdateProgress(UpdateProgress):
-    """Utility class which adds more opcodes to the UpdateProgress"""
-    REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
-        1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)]
-    _num_op_codes = UpdateProgress._num_op_codes + 4
-
-    __slots__ = ()
-
-
-BEGIN = RootUpdateProgress.BEGIN
-END = RootUpdateProgress.END
-REMOVE = RootUpdateProgress.REMOVE
-BRANCHCHANGE = RootUpdateProgress.BRANCHCHANGE
-URLCHANGE = RootUpdateProgress.URLCHANGE
-PATHCHANGE = RootUpdateProgress.PATHCHANGE
-
-
-class RootModule(Submodule):
-
-    """A (virtual) Root of all submodules in the given repository. It can be used
-    to more easily traverse all submodules of the master repository"""
-
-    __slots__ = ()
-
-    k_root_name = '__ROOT__'
-
-    def __init__(self, repo: 'Repo'):
-        # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
-        super(RootModule, self).__init__(
-            repo,
-            binsha=self.NULL_BIN_SHA,
-            mode=self.k_default_mode,
-            path='',
-            name=self.k_root_name,
-            parent_commit=repo.head.commit,
-            url='',
-            branch_path=git.Head.to_full_path(self.k_head_default)
-        )
-
-    def _clear_cache(self) -> None:
-        """May not do anything"""
-        pass
-
-    #{ Interface
-
-    def update(self, previous_commit: Union[Commit_ish, None] = None,                    # type: ignore[override]
-               recursive: bool = True, force_remove: bool = False, init: bool = True,
-               to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None,
-               dry_run: bool = False, force_reset: bool = False, keep_going: bool = False
-               ) -> 'RootModule':
-        """Update the submodules of this repository to the current HEAD commit.
-        This method behaves smartly by determining changes of the path of a submodules
-        repository, next to changes to the to-be-checked-out commit or the branch to be
-        checked out. This works if the submodules ID does not change.
-        Additionally it will detect addition and removal of submodules, which will be handled
-        gracefully.
-
-        :param previous_commit: If set to a commit'ish, the commit we should use
-            as the previous commit the HEAD pointed to before it was set to the commit it points to now.
-            If None, it defaults to HEAD@{1} otherwise
-        :param recursive: if True, the children of submodules will be updated as well
-            using the same technique
-        :param force_remove: If submodules have been deleted, they will be forcibly removed.
-            Otherwise the update may fail if a submodule's repository cannot be deleted as
-            changes have been made to it (see Submodule.update() for more information)
-        :param init: If we encounter a new module which would need to be initialized, then do it.
-        :param to_latest_revision: If True, instead of checking out the revision pointed to
-            by this submodule's sha, the checked out tracking branch will be merged with the
-            latest remote branch fetched from the repository's origin.
-            Unless force_reset is specified, a local tracking branch will never be reset into its past, therefore
-            the remote branch must be in the future for this to have an effect.
-        :param force_reset: if True, submodules may checkout or reset their branch even if the repository has
-            pending changes that would be overwritten, or if the local tracking branch is in the future of the
-            remote tracking branch and would be reset into its past.
-        :param progress: RootUpdateProgress instance or None if no progress should be sent
-        :param dry_run: if True, operations will not actually be performed. Progress messages
-            will change accordingly to indicate the WOULD DO state of the operation.
-        :param keep_going: if True, we will ignore but log all errors, and keep going recursively.
-            Unless dry_run is set as well, keep_going could cause subsequent/inherited errors you wouldn't see
-            otherwise.
-            In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
-        :return: self"""
-        if self.repo.bare:
-            raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
-        # END handle bare
-
-        if progress is None:
-            progress = RootUpdateProgress()
-        # END assure progress is set
-
-        prefix = ''
-        if dry_run:
-            prefix = 'DRY-RUN: '
-
-        repo = self.repo
-
-        try:
-            # SETUP BASE COMMIT
-            ###################
-            cur_commit = repo.head.commit
-            if previous_commit is None:
-                try:
-                    previous_commit = repo.commit(repo.head.log_entry(-1).oldhexsha)
-                    if previous_commit.binsha == previous_commit.NULL_BIN_SHA:
-                        raise IndexError
-                    # END handle initial commit
-                except IndexError:
-                    # in new repositories, there is no previous commit
-                    previous_commit = cur_commit
-                # END exception handling
-            else:
-                previous_commit = repo.commit(previous_commit)   # obtain commit object
-            # END handle previous commit
-
-            psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit)
-            sms: 'IterableList[Submodule]' = self.list_items(repo)
-            spsms = set(psms)
-            ssms = set(sms)
-
-            # HANDLE REMOVALS
-            ###################
-            rrsm = (spsms - ssms)
-            len_rrsm = len(rrsm)
-
-            for i, rsm in enumerate(rrsm):
-                op = REMOVE
-                if i == 0:
-                    op |= BEGIN
-                # END handle begin
-
-                # fake it into thinking its at the current commit to allow deletion
-                # of previous module. Trigger the cache to be updated before that
-                progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
-                rsm._parent_commit = repo.head.commit
-                rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
-
-                if i == len_rrsm - 1:
-                    op |= END
-                # END handle end
-                progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
-            # END for each removed submodule
-
-            # HANDLE PATH RENAMES
-            #####################
-            # url changes + branch changes
-            csms = (spsms & ssms)
-            len_csms = len(csms)
-            for i, csm in enumerate(csms):
-                psm: 'Submodule' = psms[csm.name]
-                sm: 'Submodule' = sms[csm.name]
-
-                # PATH CHANGES
-                ##############
-                if sm.path != psm.path and psm.module_exists():
-                    progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
-                                    "Moving repository of submodule %r from %s to %s"
-                                    % (sm.name, psm.abspath, sm.abspath))
-                    # move the module to the new path
-                    if not dry_run:
-                        psm.move(sm.path, module=True, configuration=False)
-                    # END handle dry_run
-                    progress.update(
-                        END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
-                # END handle path changes
-
-                if sm.module_exists():
-                    # HANDLE URL CHANGE
-                    ###################
-                    if sm.url != psm.url:
-                        # Add the new remote, remove the old one
-                        # This way, if the url just changes, the commits will not
-                        # have to be re-retrieved
-                        nn = '__new_origin__'
-                        smm = sm.module()
-                        rmts = smm.remotes
-
-                        # don't do anything if we already have the url we search in place
-                        if len([r for r in rmts if r.url == sm.url]) == 0:
-                            progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
-                                            "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
-
-                            if not dry_run:
-                                assert nn not in [r.name for r in rmts]
-                                smr = smm.create_remote(nn, sm.url)
-                                smr.fetch(progress=progress)
-
-                                # If we have a tracking branch, it should be available
-                                # in the new remote as well.
-                                if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
-                                    raise ValueError(
-                                        "Submodule branch named %r was not available in new submodule remote at %r"
-                                        % (sm.branch_name, sm.url)
-                                    )
-                                # END head is not detached
-
-                                # now delete the changed one
-                                rmt_for_deletion = None
-                                for remote in rmts:
-                                    if remote.url == psm.url:
-                                        rmt_for_deletion = remote
-                                        break
-                                    # END if urls match
-                                # END for each remote
-
-                                # if we didn't find a matching remote, but have exactly one,
-                                # we can safely use this one
-                                if rmt_for_deletion is None:
-                                    if len(rmts) == 1:
-                                        rmt_for_deletion = rmts[0]
-                                    else:
-                                        # if we have not found any remote with the original url
-                                        # we may not have a name. This is a special case,
-                                        # and its okay to fail here
-                                        # Alternatively we could just generate a unique name and leave all
-                                        # existing ones in place
-                                        raise InvalidGitRepositoryError(
-                                            "Couldn't find original remote-repo at url %r" % psm.url)
-                                    # END handle one single remote
-                                # END handle check we found a remote
-
-                                orig_name = rmt_for_deletion.name
-                                smm.delete_remote(rmt_for_deletion)
-                                # NOTE: Currently we leave tags from the deleted remotes
-                                # as well as separate tracking branches in the possibly totally
-                                # changed repository ( someone could have changed the url to
-                                # another project ). At some point, one might want to clean
-                                # it up, but the danger is high to remove stuff the user
-                                # has added explicitly
-
-                                # rename the new remote back to what it was
-                                smr.rename(orig_name)
-
-                                # early on, we verified that the our current tracking branch
-                                # exists in the remote. Now we have to assure that the
-                                # sha we point to is still contained in the new remote
-                                # tracking branch.
-                                smsha = sm.binsha
-                                found = False
-                                rref = smr.refs[self.branch_name]
-                                for c in rref.commit.traverse():
-                                    if c.binsha == smsha:
-                                        found = True
-                                        break
-                                    # END traverse all commits in search for sha
-                                # END for each commit
-
-                                if not found:
-                                    # adjust our internal binsha to use the one of the remote
-                                    # this way, it will be checked out in the next step
-                                    # This will change the submodule relative to us, so
-                                    # the user will be able to commit the change easily
-                                    log.warning("Current sha %s was not contained in the tracking\
-             branch at the new remote, setting it the the remote's tracking branch", sm.hexsha)
-                                    sm.binsha = rref.commit.binsha
-                                # END reset binsha
-
-                                # NOTE: All checkout is performed by the base implementation of update
-                            # END handle dry_run
-                            progress.update(
-                                END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
-                        # END skip remote handling if new url already exists in module
-                    # END handle url
-
-                    # HANDLE PATH CHANGES
-                    #####################
-                    if sm.branch_path != psm.branch_path:
-                        # finally, create a new tracking branch which tracks the
-                        # new remote branch
-                        progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
-                                        "Changing branch of submodule %r from %s to %s"
-                                        % (sm.name, psm.branch_path, sm.branch_path))
-                        if not dry_run:
-                            smm = sm.module()
-                            smmr = smm.remotes
-                            # As the branch might not exist yet, we will have to fetch all remotes to be sure ... .
-                            for remote in smmr:
-                                remote.fetch(progress=progress)
-                            # end for each remote
-
-                            try:
-                                tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
-                            except OSError:
-                                # ... or reuse the existing one
-                                tbr = git.Head(smm, sm.branch_path)
-                            # END assure tracking branch exists
-
-                            tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
-                            # NOTE: All head-resetting is done in the base implementation of update
-                            # but we will have to checkout the new branch here. As it still points to the currently
-                            # checkout out commit, we don't do any harm.
-                            # As we don't want to update working-tree or index, changing the ref is all there is to do
-                            smm.head.reference = tbr
-                        # END handle dry_run
-
-                        progress.update(
-                            END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
-                    # END handle branch
-                # END handle
-            # END for each common submodule
-        except Exception as err:
-            if not keep_going:
-                raise
-            log.error(str(err))
-        # end handle keep_going
-
-        # FINALLY UPDATE ALL ACTUAL SUBMODULES
-        ######################################
-        for sm in sms:
-            # update the submodule using the default method
-            sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
-                      progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going)
-
-            # update recursively depth first - question is which inconsitent
-            # state will be better in case it fails somewhere. Defective branch
-            # or defective depth. The RootSubmodule type will never process itself,
-            # which was done in the previous expression
-            if recursive:
-                # the module would exist by now if we are not in dry_run mode
-                if sm.module_exists():
-                    type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
-                                                   init=init, to_latest_revision=to_latest_revision,
-                                                   progress=progress, dry_run=dry_run, force_reset=force_reset,
-                                                   keep_going=keep_going)
-                # END handle dry_run
-            # END handle recursive
-        # END for each submodule to update
-
-        return self
-
-from .base import (
-    Submodule,
-    UpdateProgress
-)
-from .util import (
-    find_first_remote_branch
-)
+from .util import find_first_remote_branch
 from git.exc import InvalidGitRepositoryError
 import git
 
@@ -720,8 +359,4 @@ def module(self) -> 'Repo':
         """:return: the actual repository containing the submodules"""
         return self.repo
     #} END interface
-#} END classes
-        """:return: the actual repository containing the submodules"""
-        return self.repo
-    #} END interface
 #} END classes

From 8c26562fd71426ca500778c998baa96b6e508e9c Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:30:40 +0100
Subject: [PATCH 10/12] Update util.py

---
 git/objects/util.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/git/objects/util.py b/git/objects/util.py
index ef1ae77ba..db7807c26 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -493,6 +493,11 @@ def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TI
         return super(TraversableIterableObj, self)._list_traverse(* args, **kwargs)
 
     @ overload                     # type: ignore
+    def traverse(self: T_TIobj
+                 ) -> Iterator[T_TIobj]:
+        ...
+
+    @ overload
     def traverse(self: T_TIobj,
                  predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
                  prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],

From 9ac93a71349374f0406fec69166e2c51cfca172a Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:31:45 +0100
Subject: [PATCH 11/12] Add None check to test_refs.py

---
 test/test_refs.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test/test_refs.py b/test/test_refs.py
index 1315f885f..ab760a6f5 100644
--- a/test/test_refs.py
+++ b/test/test_refs.py
@@ -125,11 +125,15 @@ def test_heads(self, rwrepo):
             gp_tracking_branch = rwrepo.create_head('gp_tracking#123')
             special_name_remote_ref = rwrepo.remotes[0].refs[special_name]  # get correct type
             gp_tracking_branch.set_tracking_branch(special_name_remote_ref)
-            assert gp_tracking_branch.tracking_branch().path == special_name_remote_ref.path
+            TBranch = gp_tracking_branch.tracking_branch()
+            if TBranch is not None:
+                assert TBranch.path == special_name_remote_ref.path
 
             git_tracking_branch = rwrepo.create_head('git_tracking#123')
             rwrepo.git.branch('-u', special_name_remote_ref.name, git_tracking_branch.name)
-            assert git_tracking_branch.tracking_branch().name == special_name_remote_ref.name
+            TBranch = gp_tracking_branch.tracking_branch()
+            if TBranch is not None:
+                assert TBranch.name == special_name_remote_ref.name
         # END for each head
 
         # verify REFLOG gets altered
@@ -453,7 +457,7 @@ def test_head_reset(self, rw_repo):
 
         self.assertRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
         # it works if the new ref points to the same reference
-        SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
+        assert SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
         SymbolicReference.delete(rw_repo, symref)
         # would raise if the symref wouldn't have been deletedpbl
         symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)

From 77ac3b3ca4d0375a0814fe5ed5f5882013868824 Mon Sep 17 00:00:00 2001
From: Dominic <yobmod@gmail.com>
Date: Sat, 31 Jul 2021 11:43:02 +0100
Subject: [PATCH 12/12] rvert test_ref.py

---
 test/test_refs.py | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/test/test_refs.py b/test/test_refs.py
index ab760a6f5..1315f885f 100644
--- a/test/test_refs.py
+++ b/test/test_refs.py
@@ -125,15 +125,11 @@ def test_heads(self, rwrepo):
             gp_tracking_branch = rwrepo.create_head('gp_tracking#123')
             special_name_remote_ref = rwrepo.remotes[0].refs[special_name]  # get correct type
             gp_tracking_branch.set_tracking_branch(special_name_remote_ref)
-            TBranch = gp_tracking_branch.tracking_branch()
-            if TBranch is not None:
-                assert TBranch.path == special_name_remote_ref.path
+            assert gp_tracking_branch.tracking_branch().path == special_name_remote_ref.path
 
             git_tracking_branch = rwrepo.create_head('git_tracking#123')
             rwrepo.git.branch('-u', special_name_remote_ref.name, git_tracking_branch.name)
-            TBranch = gp_tracking_branch.tracking_branch()
-            if TBranch is not None:
-                assert TBranch.name == special_name_remote_ref.name
+            assert git_tracking_branch.tracking_branch().name == special_name_remote_ref.name
         # END for each head
 
         # verify REFLOG gets altered
@@ -457,7 +453,7 @@ def test_head_reset(self, rw_repo):
 
         self.assertRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit)
         # it works if the new ref points to the same reference
-        assert SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
+        SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path  # @NoEffect
         SymbolicReference.delete(rw_repo, symref)
         # would raise if the symref wouldn't have been deletedpbl
         symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference)