diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml
index 2c1eed391..bd09a939b 100644
--- a/.github/workflows/alpine-test.yml
+++ b/.github/workflows/alpine-test.yml
@@ -3,7 +3,7 @@ name: test-alpine
 on: [push, pull_request, workflow_dispatch]
 
 jobs:
-  build:
+  test:
     runs-on: ubuntu-latest
 
     container:
@@ -16,10 +16,10 @@ jobs:
     steps:
     - name: Prepare Alpine Linux
       run: |
-        apk add sudo git git-daemon python3 py3-pip
+        apk add sudo git git-daemon python3 py3-pip py3-virtualenv
         echo 'Defaults env_keep += "CI GITHUB_* RUNNER_*"' >/etc/sudoers.d/ci_env
         addgroup -g 127 docker
-        adduser -D -u 1001 runner
+        adduser -D -u 1001 runner  # TODO: Check if this still works on GHA as intended.
         adduser runner docker
       shell: sh -exo pipefail {0}  # Run this as root, not the "runner" user.
 
@@ -47,20 +47,21 @@ jobs:
     - name: Set up virtualenv
       run: |
         python -m venv .venv
-        . .venv/bin/activate
-        printf '%s=%s\n' 'PATH' "$PATH" 'VIRTUAL_ENV' "$VIRTUAL_ENV" >>"$GITHUB_ENV"
 
     - name: Update PyPA packages
       run: |
         # Get the latest pip, wheel, and prior to Python 3.12, setuptools.
+        . .venv/bin/activate
         python -m pip install -U pip $(pip freeze --all | grep -ow ^setuptools) wheel
 
     - name: Install project and test dependencies
       run: |
+        . .venv/bin/activate
         pip install ".[test]"
 
     - name: Show version and platform information
       run: |
+        . .venv/bin/activate
         uname -a
         command -v git python
         git version
@@ -69,4 +70,5 @@ jobs:
 
     - name: Test with pytest
       run: |
+        . .venv/bin/activate
         pytest --color=yes -p no:sugar --instafail -vv
diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml
index bde4ea659..278777907 100644
--- a/.github/workflows/cygwin-test.yml
+++ b/.github/workflows/cygwin-test.yml
@@ -3,7 +3,7 @@ name: test-cygwin
 on: [push, pull_request, workflow_dispatch]
 
 jobs:
-  build:
+  test:
     runs-on: windows-latest
 
     strategy:
@@ -15,7 +15,7 @@ jobs:
 
     defaults:
       run:
-        shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr "{0}"
+        shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr "{0}"
 
     steps:
     - name: Force LF line endings
@@ -27,10 +27,11 @@ jobs:
       with:
         fetch-depth: 0
 
-    - name: Set up Cygwin
-      uses: egor-tensin/setup-cygwin@v4
+    - name: Install Cygwin
+      uses: cygwin/cygwin-install-action@v5
       with:
-        packages: python39=3.9.16-1 python39-pip python39-virtualenv git
+        packages: python39 python39-pip python39-virtualenv git wget
+        add-to-path: false  # No need to change $PATH outside the Cygwin environment.
 
     - name: Arrange for verbose output
       run: |
@@ -55,10 +56,14 @@ jobs:
         # and cause subsequent tests to fail
         cat test/fixtures/.gitconfig >> ~/.gitconfig
 
-    - name: Ensure the "pip" command is available
+    - name: Set up virtualenv
       run: |
-        # This is used unless, and before, an updated pip is installed.
-        ln -s pip3 /usr/bin/pip
+        python3.9 -m venv --without-pip .venv
+        echo 'BASH_ENV=.venv/bin/activate' >>"$GITHUB_ENV"
+
+    - name: Bootstrap pip in virtualenv
+      run: |
+        wget -qO- https://bootstrap.pypa.io/get-pip.py | python
 
     - name: Update PyPA packages
       run: |
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 747db62f0..9fd660c6b 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -9,19 +9,30 @@ permissions:
   contents: read
 
 jobs:
-  build:
+  test:
     strategy:
-      fail-fast: false
       matrix:
-        os: ["ubuntu-22.04", "macos-latest", "windows-latest"]
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+        os-type: [ubuntu, macos, windows]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"]
         exclude:
-        - os: "macos-latest"
-          python-version: "3.7"
+        - os-type: macos
+          python-version: "3.7"  # Not available for the ARM-based macOS runners.
+        - os-type: macos
+          python-version: "3.13t"
+        - os-type: windows
+          python-version: "3.13"  # FIXME: Fix and enable Python 3.13 on Windows (#1955).
+        - os-type: windows
+          python-version: "3.13t"
         include:
+        - os-ver: latest
+        - os-type: ubuntu
+          python-version: "3.7"
+          os-ver: "22.04"
         - experimental: false
 
-    runs-on: ${{ matrix.os }}
+      fail-fast: false
+
+    runs-on: ${{ matrix.os-type }}-${{ matrix.os-ver }}
 
     defaults:
       run:
@@ -39,9 +50,10 @@ jobs:
         allow-prereleases: ${{ matrix.experimental }}
 
     - name: Set up WSL (Windows)
-      if: startsWith(matrix.os, 'windows')
-      uses: Vampire/setup-wsl@v3.1.1
+      if: matrix.os-type == 'windows'
+      uses: Vampire/setup-wsl@v5.0.1
       with:
+        wsl-version: 1
         distribution: Alpine
         additional-packages: bash
 
@@ -76,7 +88,7 @@ jobs:
 
     # For debugging hook tests on native Windows systems that may have WSL.
     - name: Show bash.exe candidates (Windows)
-      if: startsWith(matrix.os, 'windows')
+      if: matrix.os-type == 'windows'
       run: |
         set +e
         bash.exe -c 'printenv WSL_DISTRO_NAME; uname -a'
diff --git a/SECURITY.md b/SECURITY.md
index d39425b70..0aea34845 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -11,4 +11,6 @@ Only the latest version of GitPython can receive security updates. If a vulnerab
 
 ## Reporting a Vulnerability
 
-Please report private portions of a vulnerability to <https://github.com/gitpython-developers/GitPython/security/advisories/new>. Doing so helps to receive updates and collaborate on the matter, without disclosing it publicliy right away.
+Please report private portions of a vulnerability to <https://github.com/gitpython-developers/GitPython/security/advisories/new>. Doing so helps to receive updates and collaborate on the matter, without disclosing it publicly right away.
+
+Vulnerabilities in GitPython's dependencies [gitdb](https://github.com/gitpython-developers/gitdb/blob/master/SECURITY.md) or [smmap](https://github.com/gitpython-developers/smmap/blob/master/SECURITY.md), which primarily exist to support GitPython, can be reported here as well, at that same link. The affected package (`GitPython`, `gitdb`, or `smmap`) can be included in the report, if known.
diff --git a/VERSION b/VERSION
index d1bf6638d..e6af1c454 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.1.43
+3.1.44
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index 3c903423c..00a3c660e 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -2,6 +2,12 @@
 Changelog
 =========
 
+3.1.44
+======
+
+See the following for all changes.
+https://github.com/gitpython-developers/GitPython/releases/tag/3.1.44
+
 3.1.43
 ======
 
diff --git a/fuzzing/fuzz-targets/fuzz_submodule.py b/fuzzing/fuzz-targets/fuzz_submodule.py
index d22b0aa5b..afa653d0d 100644
--- a/fuzzing/fuzz-targets/fuzz_submodule.py
+++ b/fuzzing/fuzz-targets/fuzz_submodule.py
@@ -9,11 +9,17 @@
     get_max_filename_length,
 )
 
-# Setup the git environment
+# Setup the Git environment
 setup_git_environment()
 from git import Repo, GitCommandError, InvalidGitRepositoryError
 
 
+def sanitize_input(input_str, max_length=255):
+    """Sanitize and truncate inputs to avoid invalid Git operations."""
+    sanitized = "".join(ch for ch in input_str if ch.isalnum() or ch in ("-", "_", "."))
+    return sanitized[:max_length]
+
+
 def TestOneInput(data):
     fdp = atheris.FuzzedDataProvider(data)
 
@@ -24,12 +30,23 @@ def TestOneInput(data):
         try:
             with tempfile.TemporaryDirectory() as submodule_temp_dir:
                 sub_repo = Repo.init(submodule_temp_dir, bare=fdp.ConsumeBool())
-                sub_repo.index.commit(fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(1, 512)))
+                commit_message = sanitize_input(fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(1, 512)))
+                sub_repo.index.commit(commit_message)
 
-                submodule_name = fdp.ConsumeUnicodeNoSurrogates(
-                    fdp.ConsumeIntInRange(1, max(1, get_max_filename_length(repo.working_tree_dir)))
+                submodule_name = sanitize_input(
+                    fdp.ConsumeUnicodeNoSurrogates(
+                        fdp.ConsumeIntInRange(1, get_max_filename_length(repo.working_tree_dir))
+                    )
                 )
-                submodule_path = os.path.join(repo.working_tree_dir, submodule_name)
+
+                submodule_path = os.path.relpath(
+                    os.path.join(repo.working_tree_dir, submodule_name),
+                    start=repo.working_tree_dir,
+                )
+
+                # Ensure submodule_path is valid
+                if not submodule_name or submodule_name.startswith("/") or ".." in submodule_name:
+                    return -1  # Reject invalid input so they are not added to the corpus
 
                 submodule = repo.create_submodule(submodule_name, submodule_path, url=sub_repo.git_dir)
                 repo.index.commit("Added submodule")
@@ -39,25 +56,38 @@ def TestOneInput(data):
                     value_length = fdp.ConsumeIntInRange(1, max(1, fdp.remaining_bytes()))
 
                     writer.set_value(
-                        fdp.ConsumeUnicodeNoSurrogates(key_length), fdp.ConsumeUnicodeNoSurrogates(value_length)
+                        sanitize_input(fdp.ConsumeUnicodeNoSurrogates(key_length)),
+                        sanitize_input(fdp.ConsumeUnicodeNoSurrogates(value_length)),
                     )
                     writer.release()
 
-                submodule.update(init=fdp.ConsumeBool(), dry_run=fdp.ConsumeBool(), force=fdp.ConsumeBool())
+                submodule.update(
+                    init=fdp.ConsumeBool(),
+                    dry_run=fdp.ConsumeBool(),
+                    force=fdp.ConsumeBool(),
+                )
+
                 submodule_repo = submodule.module()
 
-                new_file_name = fdp.ConsumeUnicodeNoSurrogates(
-                    fdp.ConsumeIntInRange(1, max(1, get_max_filename_length(submodule_repo.working_tree_dir)))
+                new_file_name = sanitize_input(
+                    fdp.ConsumeUnicodeNoSurrogates(
+                        fdp.ConsumeIntInRange(1, get_max_filename_length(submodule_repo.working_tree_dir))
+                    )
                 )
                 new_file_path = os.path.join(submodule_repo.working_tree_dir, new_file_name)
                 with open(new_file_path, "wb") as new_file:
                     new_file.write(fdp.ConsumeBytes(fdp.ConsumeIntInRange(1, 512)))
+
                 submodule_repo.index.add([new_file_path])
                 submodule_repo.index.commit("Added new file to submodule")
 
                 repo.submodule_update(recursive=fdp.ConsumeBool())
-                submodule_repo.head.reset(commit="HEAD~1", working_tree=fdp.ConsumeBool(), head=fdp.ConsumeBool())
-                # Use fdp.PickValueInList to ensure at least one of 'module' or 'configuration' is True
+                submodule_repo.head.reset(
+                    commit="HEAD~1",
+                    working_tree=fdp.ConsumeBool(),
+                    head=fdp.ConsumeBool(),
+                )
+
                 module_option_value, configuration_option_value = fdp.PickValueInList(
                     [(True, False), (False, True), (True, True)]
                 )
@@ -82,12 +112,7 @@ def TestOneInput(data):
         ):
             return -1
         except Exception as e:
-            if isinstance(e, ValueError) and "embedded null byte" in str(e):
-                return -1
-            elif isinstance(e, OSError) and "File name too long" in str(e):
-                return -1
-            else:
-                return handle_exception(e)
+            return handle_exception(e)
 
 
 def main():
diff --git a/git/ext/gitdb b/git/ext/gitdb
index 3d3e9572d..f36c0cc42 160000
--- a/git/ext/gitdb
+++ b/git/ext/gitdb
@@ -1 +1 @@
-Subproject commit 3d3e9572dc452fea53d328c101b3d1440bbefe40
+Subproject commit f36c0cc42ea2f529291e441073f74e920988d4d2
diff --git a/git/index/base.py b/git/index/base.py
index 39cc9143c..a95762dca 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -508,7 +508,7 @@ def iter_blobs(
 
         :param predicate:
             Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by
-            the iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you
+            the iterator. A default filter, the :class:`~git.index.typ.BlobFilter`, allows you
             to yield blobs only if they match a given list of paths.
         """
         for entry in self.entries.values():
@@ -655,7 +655,10 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
             raise InvalidGitRepositoryError("require non-bare repository")
         if not osp.normpath(str(path)).startswith(str(self.repo.working_tree_dir)):
             raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
-        return os.path.relpath(path, self.repo.working_tree_dir)
+        result = os.path.relpath(path, self.repo.working_tree_dir)
+        if str(path).endswith(os.sep) and not result.endswith(os.sep):
+            result += os.sep
+        return result
 
     def _preprocess_add_items(
         self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
@@ -767,7 +770,7 @@ def add(
             - path string
 
                 Strings denote a relative or absolute path into the repository pointing
-                to an existing file, e.g., ``CHANGES``, `lib/myfile.ext``,
+                to an existing file, e.g., ``CHANGES``, ``lib/myfile.ext``,
                 ``/home/gitrepo/lib/myfile.ext``.
 
                 Absolute paths must start with working tree directory of this index's
@@ -786,7 +789,7 @@ def add(
 
                 They are added at stage 0.
 
-            - :class:~`git.objects.blob.Blob` or
+            - :class:`~git.objects.blob.Blob` or
               :class:`~git.objects.submodule.base.Submodule` object
 
                 Blobs are added as they are assuming a valid mode is set.
@@ -812,7 +815,7 @@ def add(
 
             - :class:`~git.index.typ.BaseIndexEntry` or type
 
-                Handling equals the one of :class:~`git.objects.blob.Blob` objects, but
+                Handling equals the one of :class:`~git.objects.blob.Blob` objects, but
                 the stage may be explicitly set. Please note that Index Entries require
                 binary sha's.
 
@@ -995,7 +998,7 @@ def remove(
 
                 The path string may include globs, such as ``*.c``.
 
-            - :class:~`git.objects.blob.Blob` object
+            - :class:`~git.objects.blob.Blob` object
 
                 Only the path portion is used in this case.
 
diff --git a/git/objects/base.py b/git/objects/base.py
index eeaebc09b..faf600c6b 100644
--- a/git/objects/base.py
+++ b/git/objects/base.py
@@ -122,7 +122,7 @@ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject:
         :return:
             New :class:`Object` instance of a type appropriate to the object type behind
             `id`. The id of the newly created object will be a binsha even though the
-            input id may have been a `~git.refs.reference.Reference` or rev-spec.
+            input id may have been a :class:`~git.refs.reference.Reference` or rev-spec.
 
         :param id:
             :class:`~git.refs.reference.Reference`, rev-spec, or hexsha.
@@ -218,7 +218,7 @@ class IndexObject(Object):
     """Base for all objects that can be part of the index file.
 
     The classes representing git object types that can be part of the index file are
-    :class:`~git.objects.tree.Tree and :class:`~git.objects.blob.Blob`. In addition,
+    :class:`~git.objects.tree.Tree` and :class:`~git.objects.blob.Blob`. In addition,
     :class:`~git.objects.submodule.base.Submodule`, which is not really a git object
     type but can be part of an index file, is also a subclass.
     """
diff --git a/git/objects/commit.py b/git/objects/commit.py
index 0ceb46609..fbe0ee9c0 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -289,7 +289,7 @@ def name_rev(self) -> str:
         """
         :return:
             String describing the commits hex sha based on the closest
-            `~git.refs.reference.Reference`.
+            :class:`~git.refs.reference.Reference`.
 
         :note:
             Mostly useful for UI purposes.
@@ -349,7 +349,7 @@ def iter_items(
         return cls._iter_from_process_or_stream(repo, proc)
 
     def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> Iterator["Commit"]:
-        R"""Iterate _all_ parents of this commit.
+        R"""Iterate *all* parents of this commit.
 
         :param paths:
             Optional path or list of paths limiting the :class:`Commit`\s to those that
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index 510850b2e..1b90a3115 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -39,7 +39,6 @@
 if TYPE_CHECKING:
     from git.config import GitConfigParser
     from git.objects.commit import Actor
-    from git.refs import Head, TagReference, RemoteReference, Reference
     from git.refs.log import RefLogEntry
     from git.repo import Repo
 
@@ -387,17 +386,23 @@ def set_object(
         # set the commit on our reference
         return self._get_reference().set_object(object, logmsg)
 
-    commit = property(
-        _get_commit,
-        set_commit,  # type: ignore[arg-type]
-        doc="Query or set commits directly",
-    )
+    @property
+    def commit(self) -> "Commit":
+        """Query or set commits directly"""
+        return self._get_commit()
+
+    @commit.setter
+    def commit(self, commit: Union[Commit, "SymbolicReference", str]) -> "SymbolicReference":
+        return self.set_commit(commit)
+
+    @property
+    def object(self) -> AnyGitObject:
+        """Return the object our ref currently refers to"""
+        return self._get_object()
 
-    object = property(
-        _get_object,
-        set_object,  # type: ignore[arg-type]
-        doc="Return the object our ref currently refers to",
-    )
+    @object.setter
+    def object(self, object: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
+        return self.set_object(object)
 
     def _get_reference(self) -> "SymbolicReference":
         """
@@ -496,12 +501,14 @@ def set_reference(
         return self
 
     # Aliased reference
-    reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
-    reference = property(  # type: ignore[assignment]
-        _get_reference,
-        set_reference,  # type: ignore[arg-type]
-        doc="Returns the Reference we point to",
-    )
+    @property
+    def reference(self) -> "SymbolicReference":
+        return self._get_reference()
+
+    @reference.setter
+    def reference(self, ref: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
+        return self.set_reference(ref)
+
     ref = reference
 
     def is_valid(self) -> bool:
diff --git a/git/repo/base.py b/git/repo/base.py
index db89cdf41..7e918df8c 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -354,21 +354,19 @@ def __ne__(self, rhs: object) -> bool:
     def __hash__(self) -> int:
         return hash(self.git_dir)
 
-    # Description property
-    def _get_description(self) -> str:
+    @property
+    def description(self) -> str:
+        """The project's description"""
         filename = osp.join(self.git_dir, "description")
         with open(filename, "rb") as fp:
             return fp.read().rstrip().decode(defenc)
 
-    def _set_description(self, descr: str) -> None:
+    @description.setter
+    def description(self, descr: str) -> None:
         filename = osp.join(self.git_dir, "description")
         with open(filename, "wb") as fp:
             fp.write((descr + "\n").encode(defenc))
 
-    description = property(_get_description, _set_description, doc="the project's description")
-    del _get_description
-    del _set_description
-
     @property
     def working_tree_dir(self) -> Optional[PathLike]:
         """
@@ -514,7 +512,7 @@ def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule:
     def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
         """An iterator yielding Submodule instances.
 
-        See the `~git.objects.util.Traversable` interface for a description of `args`
+        See the :class:`~git.objects.util.Traversable` interface for a description of `args`
         and `kwargs`.
 
         :return:
@@ -885,13 +883,14 @@ def _set_daemon_export(self, value: object) -> None:
         elif not value and fileexists:
             os.unlink(filename)
 
-    daemon_export = property(
-        _get_daemon_export,
-        _set_daemon_export,
-        doc="If True, git-daemon may export this repository",
-    )
-    del _get_daemon_export
-    del _set_daemon_export
+    @property
+    def daemon_export(self) -> bool:
+        """If True, git-daemon may export this repository"""
+        return self._get_daemon_export()
+
+    @daemon_export.setter
+    def daemon_export(self, value: object) -> None:
+        self._set_daemon_export(value)
 
     def _get_alternates(self) -> List[str]:
         """The list of alternates for this repo from which objects can be retrieved.
@@ -929,11 +928,14 @@ def _set_alternates(self, alts: List[str]) -> None:
             with open(alternates_path, "wb") as f:
                 f.write("\n".join(alts).encode(defenc))
 
-    alternates = property(
-        _get_alternates,
-        _set_alternates,
-        doc="Retrieve a list of alternates paths or set a list paths to be used as alternates",
-    )
+    @property
+    def alternates(self) -> List[str]:
+        """Retrieve a list of alternates paths or set a list paths to be used as alternates"""
+        return self._get_alternates()
+
+    @alternates.setter
+    def alternates(self, alts: List[str]) -> None:
+        self._set_alternates(alts)
 
     def is_dirty(
         self,
diff --git a/git/repo/fun.py b/git/repo/fun.py
index 182cf82ed..125ba5936 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -301,7 +301,13 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
 
             # Handle type.
             if output_type == "commit":
-                pass  # Default.
+                obj = cast("TagObject", obj)
+                if obj and obj.type == "tag":
+                    obj = deref_tag(obj)
+                else:
+                    # Cannot do anything for non-tags.
+                    pass
+                # END handle tag
             elif output_type == "tree":
                 try:
                     obj = cast(AnyGitObject, obj)
diff --git a/git/util.py b/git/util.py
index 9e8ac821d..0d09c3f09 100644
--- a/git/util.py
+++ b/git/util.py
@@ -1200,8 +1200,6 @@ def __getattr__(self, attr: str) -> T_IterableObj:
         return list.__getattribute__(self, attr)
 
     def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj:  # type: ignore[override]
-        assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str"
-
         if isinstance(index, int):
             return list.__getitem__(self, index)
         elif isinstance(index, slice):
@@ -1214,8 +1212,6 @@ def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_Iterabl
         # END handle getattr
 
     def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None:
-        assert isinstance(index, (int, str)), "Index of IterableList should be an int or str"
-
         delindex = cast(int, index)
         if not isinstance(index, int):
             delindex = -1
diff --git a/test/test_git.py b/test/test_git.py
index 94e68ecf0..274511f8d 100644
--- a/test/test_git.py
+++ b/test/test_git.py
@@ -762,14 +762,14 @@ def test_environment(self, rw_dir):
     def test_handle_process_output(self):
         from git.cmd import handle_process_output, safer_popen
 
-        line_count = 5002
-        count = [None, 0, 0]
+        expected_line_count = 5002
+        actual_lines = [None, [], []]
 
-        def counter_stdout(line):
-            count[1] += 1
+        def stdout_handler(line):
+            actual_lines[1].append(line)
 
-        def counter_stderr(line):
-            count[2] += 1
+        def stderr_handler(line):
+            actual_lines[2].append(line)
 
         cmdline = [
             sys.executable,
@@ -784,10 +784,10 @@ def counter_stderr(line):
             shell=False,
         )
 
-        handle_process_output(proc, counter_stdout, counter_stderr, finalize_process)
+        handle_process_output(proc, stdout_handler, stderr_handler, finalize_process)
 
-        self.assertEqual(count[1], line_count)
-        self.assertEqual(count[2], line_count)
+        self.assertEqual(len(actual_lines[1]), expected_line_count, repr(actual_lines[1]))
+        self.assertEqual(len(actual_lines[2]), expected_line_count, repr(actual_lines[2]))
 
     def test_execute_kwargs_set_agrees_with_method(self):
         parameter_names = inspect.signature(cmd.Git.execute).parameters.keys()
diff --git a/test/test_index.py b/test/test_index.py
index c586a0b5a..c42032e70 100644
--- a/test/test_index.py
+++ b/test/test_index.py
@@ -16,6 +16,7 @@
 import subprocess
 import sys
 import tempfile
+from unittest import mock
 
 from gitdb.base import IStream
 
@@ -1015,6 +1016,27 @@ class Mocked:
         rel = index._to_relative_path(path)
         self.assertEqual(rel, os.path.relpath(path, root))
 
+    def test__to_relative_path_absolute_trailing_slash(self):
+        repo_root = os.path.join(osp.abspath(os.sep), "directory1", "repo_root")
+
+        class Mocked:
+            bare = False
+            git_dir = repo_root
+            working_tree_dir = repo_root
+
+        repo = Mocked()
+        path = os.path.join(repo_root, f"directory2{os.sep}")
+        index = IndexFile(repo)
+
+        expected_path = f"directory2{os.sep}"
+        actual_path = index._to_relative_path(path)
+        self.assertEqual(expected_path, actual_path)
+
+        with mock.patch("git.index.base.os.path") as ospath_mock:
+            ospath_mock.relpath.return_value = f"directory2{os.sep}"
+            actual_path = index._to_relative_path(path)
+            self.assertEqual(expected_path, actual_path)
+
     @pytest.mark.xfail(
         type(_win_bash_status) is WinBashStatus.Absent,
         reason="Can't run a hook on Windows without bash.exe.",
diff --git a/test/test_installation.py b/test/test_installation.py
index ae6472e98..a35826bd0 100644
--- a/test/test_installation.py
+++ b/test/test_installation.py
@@ -4,11 +4,19 @@
 import ast
 import os
 import subprocess
+import sys
+
+import pytest
 
 from test.lib import TestBase, VirtualEnvironment, with_rw_directory
 
 
 class TestInstallation(TestBase):
+    @pytest.mark.xfail(
+        sys.platform == "cygwin" and "CI" in os.environ,
+        reason="Trouble with pip on Cygwin CI, see issue #2004",
+        raises=subprocess.CalledProcessError,
+    )
     @with_rw_directory
     def test_installation(self, rw_dir):
         venv = self._set_up_venv(rw_dir)
diff --git a/test/test_repo.py b/test/test_repo.py
index e38da5bb6..bfa1bbb78 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -1064,9 +1064,9 @@ def test_rev_parse(self):
         # TODO: Dereference tag into a blob 0.1.7^{blob} - quite a special one.
         # Needs a tag which points to a blob.
 
-        # ref^0 returns commit being pointed to, same with ref~0, and ^{}
+        # ref^0 returns commit being pointed to, same with ref~0, ^{}, and ^{commit}
         tag = rev_parse("0.1.4")
-        for token in ("~0", "^0", "^{}"):
+        for token in ("~0", "^0", "^{}", "^{commit}"):
             self.assertEqual(tag.object, rev_parse("0.1.4%s" % token))
         # END handle multiple tokens