From de6d177aaa8ccd6d82576ab0b3de5074cf7647fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:05:41 +0000 Subject: [PATCH 01/26] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/alpine-test.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/cygwin-test.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pythonpackage.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index ceba11fb8..a9c29117e 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -26,7 +26,7 @@ jobs: adduser runner docker shell: sh -exo pipefail {0} # Run this as root, not the "runner" user. - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2bee952af..9191471c3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index cc9e1edf0..5c42c8583 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -34,7 +34,7 @@ jobs: git config --global core.autocrlf false # Affects the non-Cygwin git. shell: pwsh # Do this outside Cygwin, to affect actions/checkout. - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ceba0dd85..16978f9a8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 4457a341f..4e5d82a55 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -39,7 +39,7 @@ jobs: shell: bash --noprofile --norc -exo pipefail {0} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 From fe81519436a8ab8b735a40a3973c8c5bd9cfec47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:42:38 +0000 Subject: [PATCH 02/26] Bump git/ext/gitdb from `335c0f6` to `39d7dbf` Bumps [git/ext/gitdb](https://github.com/gitpython-developers/gitdb) from `335c0f6` to `39d7dbf`. - [Release notes](https://github.com/gitpython-developers/gitdb/releases) - [Commits](https://github.com/gitpython-developers/gitdb/compare/335c0f66173eecdc7b2597c2b6c3d1fde795df30...39d7dbf285df058e44ea501c23ea8d31ae8bce0e) --- updated-dependencies: - dependency-name: git/ext/gitdb dependency-version: 39d7dbf285df058e44ea501c23ea8d31ae8bce0e dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- git/ext/gitdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/ext/gitdb b/git/ext/gitdb index 335c0f661..39d7dbf28 160000 --- a/git/ext/gitdb +++ b/git/ext/gitdb @@ -1 +1 @@ -Subproject commit 335c0f66173eecdc7b2597c2b6c3d1fde795df30 +Subproject commit 39d7dbf285df058e44ea501c23ea8d31ae8bce0e From ca51dad69071898af377c8e62210c69e8d211c69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:41:33 +0000 Subject: [PATCH 03/26] Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- .github/workflows/pythonpackage.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 16978f9a8..ed535a914 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 4e5d82a55..7088310e5 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -44,7 +44,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: ${{ matrix.experimental }} From 9f913ec0cb0c6f7ab3eea7245657d01048fd7065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:00:47 +0000 Subject: [PATCH 04/26] Bump git/ext/gitdb from `39d7dbf` to `f8fdfec` Bumps [git/ext/gitdb](https://github.com/gitpython-developers/gitdb) from `39d7dbf` to `f8fdfec`. - [Release notes](https://github.com/gitpython-developers/gitdb/releases) - [Commits](https://github.com/gitpython-developers/gitdb/compare/39d7dbf285df058e44ea501c23ea8d31ae8bce0e...f8fdfec0fd0a0aed9171c6cf2c5cb8d73e2bb305) --- updated-dependencies: - dependency-name: git/ext/gitdb dependency-version: f8fdfec0fd0a0aed9171c6cf2c5cb8d73e2bb305 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- git/ext/gitdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/ext/gitdb b/git/ext/gitdb index 39d7dbf28..f8fdfec0f 160000 --- a/git/ext/gitdb +++ b/git/ext/gitdb @@ -1 +1 @@ -Subproject commit 39d7dbf285df058e44ea501c23ea8d31ae8bce0e +Subproject commit f8fdfec0fd0a0aed9171c6cf2c5cb8d73e2bb305 From 7c55a2b839e05f10a9dc3cf2bc53785350372c88 Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Tue, 30 Sep 2025 17:47:14 +0300 Subject: [PATCH 05/26] Fix type hint for `SymbolicReference.reference` property Signed-off-by: Emmanuel Ferdman --- git/refs/symbolic.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 1b90a3115..74bb1fe0a 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -40,6 +40,7 @@ from git.config import GitConfigParser from git.objects.commit import Actor from git.refs.log import RefLogEntry + from git.refs.reference import Reference from git.repo import Repo @@ -404,7 +405,7 @@ def object(self) -> AnyGitObject: def object(self, object: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference": return self.set_object(object) - def _get_reference(self) -> "SymbolicReference": + def _get_reference(self) -> "Reference": """ :return: :class:`~git.refs.reference.Reference` object we point to @@ -416,7 +417,7 @@ def _get_reference(self) -> "SymbolicReference": sha, target_ref_path = self._get_ref_info(self.repo, self.path) if target_ref_path is None: raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) - return self.from_path(self.repo, target_ref_path) + return cast("Reference", self.from_path(self.repo, target_ref_path)) def set_reference( self, @@ -502,7 +503,7 @@ def set_reference( # Aliased reference @property - def reference(self) -> "SymbolicReference": + def reference(self) -> "Reference": return self._get_reference() @reference.setter From bcdcccdc7ea7d50ec5831aad961ba80df0f1379b Mon Sep 17 00:00:00 2001 From: Brunno Vanelli Date: Tue, 7 Oct 2025 20:49:46 +0200 Subject: [PATCH 06/26] feat: Add support for hasconfig git rule. --- git/config.py | 8 ++++++-- test/test_config.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/git/config.py b/git/config.py index 345290a39..ffe1c8ccd 100644 --- a/git/config.py +++ b/git/config.py @@ -66,7 +66,7 @@ CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository") """The configuration level of a configuration file.""" -CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"") +CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"") """Section pattern to detect conditional includes. See: https://git-scm.com/docs/git-config#_conditional_includes @@ -590,7 +590,11 @@ def _included_paths(self) -> List[Tuple[str, str]]: if fnmatch.fnmatchcase(branch_name, value): paths += self.items(section) - + elif keyword == "hasconfig:remote.*.url": + for remote in self._repo.remotes: + if fnmatch.fnmatch(remote.url, value): + paths += self.items(section) + break return paths def read(self) -> None: # type: ignore[override] diff --git a/test/test_config.py b/test/test_config.py index 8e1007d9e..56ac0f304 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -373,6 +373,41 @@ def test_conditional_includes_from_branch_name_error(self, rw_dir): assert not config._has_includes() assert config._included_paths() == [] + @with_rw_directory + def test_conditional_includes_remote_url(self, rw_dir): + # Initiate mocked repository. + repo = mock.Mock() + repo.remotes = [mock.Mock(url="https://github.com/foo/repo")] + + # Initiate config files. + path1 = osp.join(rw_dir, "config1") + path2 = osp.join(rw_dir, "config2") + template = '[includeIf "hasconfig:remote.*.url:{}"]\n path={}\n' + + # Ensure that config with hasconfig and full url is correct. + with open(path1, "w") as stream: + stream.write(template.format("https://github.com/foo/repo", path2)) + + with GitConfigParser(path1, repo=repo) as config: + assert config._has_includes() + assert config._included_paths() == [("path", path2)] + + # Ensure that config with hasconfig and incorrect url is incorrect. + with open(path1, "w") as stream: + stream.write(template.format("incorrect", path2)) + + with GitConfigParser(path1, repo=repo) as config: + assert not config._has_includes() + assert config._included_paths() == [] + + # Ensure that config with hasconfig and url using glob pattern is correct. + with open(path1, "w") as stream: + stream.write(template.format("**/**github.com*/**", path2)) + + with GitConfigParser(path1, repo=repo) as config: + assert config._has_includes() + assert config._included_paths() == [("path", path2)] + def test_rename(self): file_obj = self._to_memcache(fixture_path("git_config")) with GitConfigParser(file_obj, read_only=False, merge_includes=False) as cw: From 6cf863374820a1bcf1fa14b3c2ea87214752bf74 Mon Sep 17 00:00:00 2001 From: Brunno Vanelli Date: Tue, 7 Oct 2025 21:19:31 +0200 Subject: [PATCH 07/26] fix: Use fnmatch instead. --- git/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/config.py b/git/config.py index ffe1c8ccd..200c81bb7 100644 --- a/git/config.py +++ b/git/config.py @@ -592,7 +592,7 @@ def _included_paths(self) -> List[Tuple[str, str]]: paths += self.items(section) elif keyword == "hasconfig:remote.*.url": for remote in self._repo.remotes: - if fnmatch.fnmatch(remote.url, value): + if fnmatch.fnmatchcase(remote.url, value): paths += self.items(section) break return paths From a6247a585600c09894a9fae85e11f7581bfccbe0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:32:58 +0000 Subject: [PATCH 08/26] Bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9191471c3..32d5e84e4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -57,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -85,6 +85,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" From 9dd0081213d57f41919ed37e93656410277bfb0b Mon Sep 17 00:00:00 2001 From: Andreas Oberritter Date: Tue, 21 Oct 2025 11:11:16 +0200 Subject: [PATCH 09/26] Use actual return type in annotation for method submodule_update Fixes #2077 --- git/repo/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/repo/base.py b/git/repo/base.py index 7e918df8c..6ea96aad2 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -520,7 +520,7 @@ def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: """ return RootModule(self).traverse(*args, **kwargs) - def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: + def submodule_update(self, *args: Any, **kwargs: Any) -> RootModule: """Update the submodules, keeping the repository consistent as it will take the previous state into consideration. From 74ff8e5e1cb814fbf3b916111d7181bd6e3f3906 Mon Sep 17 00:00:00 2001 From: Yikai Zhao Date: Sun, 2 Nov 2025 10:25:33 +0800 Subject: [PATCH 10/26] Support index format v3 --- git/index/fun.py | 16 +++++++++++----- git/index/typ.py | 15 ++++++++++++++- test/test_index.py | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/git/index/fun.py b/git/index/fun.py index d03ec6759..0b3d79cf1 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -36,7 +36,7 @@ ) from git.util import IndexFileSHA1Writer, finalize_process -from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT +from .typ import CE_EXTENDED, BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT from .util import pack, unpack # typing ----------------------------------------------------------------------------- @@ -158,7 +158,7 @@ def write_cache( write = stream_sha.write # Header - version = 2 + version = 3 if any(entry.extended_flags for entry in entries) else 2 write(b"DIRC") write(pack(">LL", version, len(entries))) @@ -172,6 +172,8 @@ def write_cache( plen = len(path) & CE_NAMEMASK # Path length assert plen == len(path), "Path %s too long to fit into index" % entry.path flags = plen | (entry.flags & CE_NAMEMASK_INV) # Clear possible previous values. + if entry.extended_flags: + flags |= CE_EXTENDED write( pack( ">LLLLLL20sH", @@ -185,6 +187,8 @@ def write_cache( flags, ) ) + if entry.extended_flags: + write(pack(">H", entry.extended_flags)) write(path) real_size = (tell() - beginoffset + 8) & ~7 write(b"\0" * ((beginoffset + real_size) - tell())) @@ -206,8 +210,7 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]: unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2))) version, num_entries = unpacked - # TODO: Handle version 3: extended data, see read-cache.c. - assert version in (1, 2), "Unsupported git index version %i, only 1 and 2 are supported" % version + assert version in (1, 2, 3), "Unsupported git index version %i, only 1, 2, and 3 are supported" % version return version, num_entries @@ -260,12 +263,15 @@ def read_cache( ctime = unpack(">8s", read(8))[0] mtime = unpack(">8s", read(8))[0] (dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2)) + extended_flags = 0 + if flags & CE_EXTENDED: + extended_flags = unpack(">H", read(2))[0] path_size = flags & CE_NAMEMASK path = read(path_size).decode(defenc) real_size = (tell() - beginoffset + 8) & ~7 read((beginoffset + real_size) - tell()) - entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size)) + entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size, extended_flags)) # entry_key would be the method to use, but we save the effort. entries[(path, entry.stage)] = entry count += 1 diff --git a/git/index/typ.py b/git/index/typ.py index 974252528..4bcb604ab 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -32,6 +32,9 @@ CE_VALID = 0x8000 CE_STAGESHIFT = 12 +CE_EXT_SKIP_WORKTREE = 0x4000 +CE_EXT_INTENT_TO_ADD = 0x2000 + # } END invariants @@ -87,6 +90,8 @@ class BaseIndexEntryHelper(NamedTuple): uid: int = 0 gid: int = 0 size: int = 0 + # version 3 extended flags, only when (flags & CE_EXTENDED) is set + extended_flags: int = 0 class BaseIndexEntry(BaseIndexEntryHelper): @@ -102,7 +107,7 @@ def __new__( cls, inp_tuple: Union[ Tuple[int, bytes, int, PathLike], - Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int], + Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int, int], ], ) -> "BaseIndexEntry": """Override ``__new__`` to allow construction from a tuple for backwards @@ -134,6 +139,14 @@ def stage(self) -> int: """ return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT + @property + def skip_worktree(self) -> bool: + return (self.extended_flags & CE_EXT_SKIP_WORKTREE) > 0 + + @property + def intent_to_add(self) -> bool: + return (self.extended_flags & CE_EXT_INTENT_TO_ADD) > 0 + @classmethod def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry": """:return: Fully equipped BaseIndexEntry at the given stage""" diff --git a/test/test_index.py b/test/test_index.py index cf3b90fa6..6d90d7965 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -1218,6 +1218,31 @@ def test_index_add_non_normalized_path(self, rw_repo): rw_repo.index.add(non_normalized_path) + @with_rw_directory + def test_index_version_v3(self, tmp_dir): + tmp_dir = Path(tmp_dir) + with cwd(tmp_dir): + subprocess.run(["git", "init", "-q"], check=True) + file = tmp_dir / "file.txt" + file.write_text("hello") + subprocess.run(["git", "add", "-N", "file.txt"], check=True) + + repo = Repo(tmp_dir) + + assert len(repo.index.entries) == 1 + entry = list(repo.index.entries.values())[0] + assert entry.path == "file.txt" + assert entry.intent_to_add + + file2 = tmp_dir / "file2.txt" + file2.write_text("world") + repo.index.add(["file2.txt"]) + repo.index.write() + + status_str = subprocess.check_output(["git", "status", "--porcelain"], text=True) + assert " A file.txt\n" in status_str + assert "A file2.txt\n" in status_str + class TestIndexUtils: @pytest.mark.parametrize("file_path_type", [str, Path]) From 3150ebdaa43df5be2c27e717807381724131b128 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:08:06 +0000 Subject: [PATCH 11/26] Bump git/ext/gitdb from `f8fdfec` to `65321a2` Bumps [git/ext/gitdb](https://github.com/gitpython-developers/gitdb) from `f8fdfec` to `65321a2`. - [Release notes](https://github.com/gitpython-developers/gitdb/releases) - [Commits](https://github.com/gitpython-developers/gitdb/compare/f8fdfec0fd0a0aed9171c6cf2c5cb8d73e2bb305...65321a28b586df60b9d1508228e2f53a35f938eb) --- updated-dependencies: - dependency-name: git/ext/gitdb dependency-version: 65321a28b586df60b9d1508228e2f53a35f938eb dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- git/ext/gitdb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/ext/gitdb b/git/ext/gitdb index f8fdfec0f..65321a28b 160000 --- a/git/ext/gitdb +++ b/git/ext/gitdb @@ -1 +1 @@ -Subproject commit f8fdfec0fd0a0aed9171c6cf2c5cb8d73e2bb305 +Subproject commit 65321a28b586df60b9d1508228e2f53a35f938eb From 8a884fea3ff91f1444a36785cc22c8d7fc6bf329 Mon Sep 17 00:00:00 2001 From: Yikai Zhao Date: Sat, 8 Nov 2025 14:01:41 +0800 Subject: [PATCH 12/26] improve unit test --- test/fixtures/index_extended_flags | Bin 0 -> 436 bytes test/test_index.py | 37 +++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/index_extended_flags diff --git a/test/fixtures/index_extended_flags b/test/fixtures/index_extended_flags new file mode 100644 index 0000000000000000000000000000000000000000..f03713b684711d4a5aedab0bafd6b254137c9e5d GIT binary patch literal 436 zcmZ?q402{*U|l!>^Ob+tC2sPz z@}frL4{5V^!oJTI8&x~7IWT1AWtQlbFff4htEWE|gV7LkPHLi=!|-hGquHA-UUT;D z)?8N}b>q;Jp5TcNoDK}drAhjUDJiKbK+8Y?WR7Zr=1~|8b=Nnd%;P~auOvSoVcry8 zhad+>tlD0`4JmGI|5U?^ V?tRm?Z1&1ue!#pbGJMwrUjTmQph*A# literal 0 HcmV?d00001 diff --git a/test/test_index.py b/test/test_index.py index 6d90d7965..bb05d3108 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -1218,30 +1218,47 @@ def test_index_add_non_normalized_path(self, rw_repo): rw_repo.index.add(non_normalized_path) + def test_index_file_v3(self): + index = IndexFile(self.rorepo, fixture_path("index_extended_flags")) + assert index.entries + assert index.version == 3 + assert len(index.entries) == 4 + assert index.entries[('init.t', 0)].skip_worktree + + # Write the data - it must match the original. + with tempfile.NamedTemporaryFile() as tmpfile: + index.write(tmpfile.name) + assert Path(tmpfile.name).read_bytes() == Path(fixture_path("index_extended_flags")).read_bytes() + @with_rw_directory - def test_index_version_v3(self, tmp_dir): + def test_index_file_v3_with_git_command(self, tmp_dir): tmp_dir = Path(tmp_dir) with cwd(tmp_dir): - subprocess.run(["git", "init", "-q"], check=True) + git = Git(tmp_dir) + git.init() + file = tmp_dir / "file.txt" file.write_text("hello") - subprocess.run(["git", "add", "-N", "file.txt"], check=True) + git.add("--intent-to-add", "file.txt") # intent-to-add sets extended flag repo = Repo(tmp_dir) + index = repo.index - assert len(repo.index.entries) == 1 - entry = list(repo.index.entries.values())[0] + assert len(index.entries) == 1 + assert index.version == 3 + entry = list(index.entries.values())[0] assert entry.path == "file.txt" assert entry.intent_to_add file2 = tmp_dir / "file2.txt" file2.write_text("world") - repo.index.add(["file2.txt"]) - repo.index.write() + index.add(["file2.txt"]) + index.write() - status_str = subprocess.check_output(["git", "status", "--porcelain"], text=True) - assert " A file.txt\n" in status_str - assert "A file2.txt\n" in status_str + status_str = git.status(porcelain=True) + status_lines = status_str.splitlines() + assert " A file.txt" in status_lines + assert "A file2.txt" in status_lines class TestIndexUtils: From 107b1b44e91a19ebbe5e41cd7312ce7838534732 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 9 Nov 2025 08:41:10 +0100 Subject: [PATCH 13/26] make linter happy --- test/test_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_index.py b/test/test_index.py index bb05d3108..711b43a0b 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -1223,7 +1223,7 @@ def test_index_file_v3(self): assert index.entries assert index.version == 3 assert len(index.entries) == 4 - assert index.entries[('init.t', 0)].skip_worktree + assert index.entries[("init.t", 0)].skip_worktree # Write the data - it must match the original. with tempfile.NamedTemporaryFile() as tmpfile: From 98e860d1c3a0855e2ff29bf24c5adaca7a57366f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:52:55 +0000 Subject: [PATCH 14/26] Bump actions/checkout from 5 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/alpine-test.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/cygwin-test.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pythonpackage.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index a9c29117e..b7de7482e 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -26,7 +26,7 @@ jobs: adduser runner docker shell: sh -exo pipefail {0} # Run this as root, not the "runner" user. - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 32d5e84e4..e243416a8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index 5c42c8583..327e1f10c 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -34,7 +34,7 @@ jobs: git config --global core.autocrlf false # Affects the non-Cygwin git. shell: pwsh # Do this outside Cygwin, to affect actions/checkout. - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ed535a914..956b38963 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 7088310e5..975c2e29d 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -39,7 +39,7 @@ jobs: shell: bash --noprofile --norc -exo pipefail {0} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 From 497ca401fe094fcae11410a46518e8f56d7bd665 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 09:04:19 +0000 Subject: [PATCH 15/26] Pin mypy==1.18.2 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 75e9e81fa..460597539 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ coverage[toml] ddt >= 1.1.1, != 1.4.3 mock ; python_version < "3.8" -mypy +mypy==1.18.2 # pin mypy to avoid new errors pre-commit pytest >= 7.3.1 pytest-cov From 50762f112fef28230deea55c2d0ca344c6c6cb2c Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 09:08:42 +0000 Subject: [PATCH 16/26] Fail remote pipeline when mypy fails --- .github/workflows/pythonpackage.yml | 1 - pyproject.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 975c2e29d..4666f3480 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -103,7 +103,6 @@ jobs: PYTHON_VERSION: ${{ matrix.python-version }} # With new versions of mypy new issues might arise. This is a problem if there is # nobody able to fix them, so we have to ignore errors until that changes. - continue-on-error: true - name: Test with pytest run: | diff --git a/pyproject.toml b/pyproject.toml index 58ed81f17..149f2dc92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ testpaths = "test" # Space separated list of paths from root e.g test tests doc # filterwarnings ignore::WarningType # ignores those warnings [tool.mypy] -python_version = "3.8" files = ["git/", "test/deprecation/"] disallow_untyped_defs = true no_implicit_optional = true From 8469a1292f51d5e211e69849844f418d773268e1 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 09:37:54 +0000 Subject: [PATCH 17/26] Fix or ignore all mypy errors --- git/config.py | 6 ++---- git/diff.py | 6 ++++-- git/index/typ.py | 4 ++-- git/objects/commit.py | 2 +- git/objects/submodule/base.py | 3 ++- git/refs/head.py | 6 +----- git/refs/log.py | 2 +- git/refs/symbolic.py | 3 +-- git/refs/tag.py | 8 ++++---- git/repo/base.py | 10 +++------- git/repo/fun.py | 2 +- git/types.py | 6 +++--- git/util.py | 8 +++++--- test/deprecation/test_types.py | 2 +- test/lib/helper.py | 4 ++-- test/test_submodule.py | 2 +- 16 files changed, 34 insertions(+), 40 deletions(-) diff --git a/git/config.py b/git/config.py index 200c81bb7..458151d05 100644 --- a/git/config.py +++ b/git/config.py @@ -574,7 +574,7 @@ def _included_paths(self) -> List[Tuple[str, str]]: if keyword.endswith("/i"): value = re.sub( r"[a-zA-Z]", - lambda m: "[{}{}]".format(m.group().lower(), m.group().upper()), + lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]", value, ) if self._repo.git_dir: @@ -633,8 +633,6 @@ def read(self) -> None: # type: ignore[override] file_path = cast(IO[bytes], file_path) self._read(file_path, file_path.name) else: - # Assume a path if it is not a file-object. - file_path = cast(PathLike, file_path) try: with open(file_path, "rb") as fp: file_ok = True @@ -768,7 +766,7 @@ def _assure_writable(self, method_name: str) -> None: if self.read_only: raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name)) - def add_section(self, section: str) -> None: + def add_section(self, section: str | cp._UNNAMED_SECTION) -> None: """Assures added options will stay in order.""" return super().add_section(section) diff --git a/git/diff.py b/git/diff.py index 9c6ae59e0..2b1fd928c 100644 --- a/git/diff.py +++ b/git/diff.py @@ -21,15 +21,17 @@ Any, Iterator, List, + Literal, Match, Optional, + Sequence, Tuple, TYPE_CHECKING, TypeVar, Union, cast, ) -from git.types import Literal, PathLike +from git.types import PathLike if TYPE_CHECKING: from subprocess import Popen @@ -289,7 +291,7 @@ class DiffIndex(List[T_Diff]): The class improves the diff handling convenience. """ - change_type = ("A", "C", "D", "R", "M", "T") + change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") """Change type invariant identifying possible ways a blob can have changed: * ``A`` = Added diff --git a/git/index/typ.py b/git/index/typ.py index 4bcb604ab..927633a9f 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -192,7 +192,7 @@ def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry": Instance of type :class:`BaseIndexEntry`. """ time = pack(">LL", 0, 0) - return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) + return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) # type: ignore[arg-type] @classmethod def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry": @@ -211,5 +211,5 @@ def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry": 0, 0, blob.size, - ) + ) # type: ignore[arg-type] ) diff --git a/git/objects/commit.py b/git/objects/commit.py index fbe0ee9c0..8c51254a2 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -900,7 +900,7 @@ def co_authors(self) -> List[Actor]: if self.message: results = re.findall( r"^Co-authored-by: (.*) <(.*?)>$", - self.message, + str(self.message), re.MULTILINE, ) for author in results: diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 5031a2e71..b4a4ca467 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -25,6 +25,7 @@ ) from git.objects.base import IndexObject, Object from git.objects.util import TraversableIterableObj +from ...refs.remote import RemoteReference from git.util import ( IterableList, RemoteProgress, @@ -355,7 +356,7 @@ def _clone_repo( module_checkout_path = osp.join(str(repo.working_tree_dir), path) if url.startswith("../"): - remote_name = repo.active_branch.tracking_branch().remote_name + remote_name = cast(RemoteReference, repo.active_branch.tracking_branch()).remote_name repo_remote_url = repo.remote(remote_name).url url = os.path.join(repo_remote_url, url) diff --git a/git/refs/head.py b/git/refs/head.py index 683634451..3c43993e7 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -22,7 +22,6 @@ from git.types import Commit_ish, PathLike if TYPE_CHECKING: - from git.objects import Commit from git.refs import RemoteReference from git.repo import Repo @@ -44,9 +43,6 @@ class HEAD(SymbolicReference): __slots__ = () - # TODO: This can be removed once SymbolicReference.commit has static type hints. - commit: "Commit" - def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None: if path != self._HEAD_NAME: raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)) @@ -149,7 +145,7 @@ class Head(Reference): k_config_remote_ref = "merge" # Branch to merge from remote. @classmethod - def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: + def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, **kwargs: Any) -> None: # type: ignore[override] """Delete the given heads. :param force: diff --git a/git/refs/log.py b/git/refs/log.py index 8f2f2cd38..4e3666993 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -145,7 +145,7 @@ def from_line(cls, line: bytes) -> "RefLogEntry": actor = Actor._from_string(info[82 : email_end + 1]) time, tz_offset = parse_date(info[email_end + 2 :]) # skipcq: PYL-W0621 - return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg)) + return RefLogEntry(oldhexsha, newhexsha, actor, (time, tz_offset), msg) # type: ignore[call-arg] class RefLog(List[RefLogEntry], Serializable): diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 74bb1fe0a..f0d2abcf4 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -916,8 +916,7 @@ def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_Refere SymbolicReference, ): try: - instance: T_References - instance = ref_type(repo, path) + instance = cast(T_References, ref_type(repo, path)) if instance.__class__ is SymbolicReference and instance.is_detached: raise ValueError("SymbolicRef was detached, we drop it") else: diff --git a/git/refs/tag.py b/git/refs/tag.py index 1e38663ae..4525b09cb 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -45,8 +45,8 @@ class TagReference(Reference): _common_default = "tags" _common_path_default = Reference._common_path_default + "/" + _common_default - @property - def commit(self) -> "Commit": # type: ignore[override] # LazyMixin has unrelated commit method + @property # type: ignore[misc] + def commit(self) -> "Commit": # LazyMixin has unrelated commit method """:return: Commit object the tag ref points to :raise ValueError: @@ -80,8 +80,8 @@ def tag(self) -> Union["TagObject", None]: return None # Make object read-only. It should be reasonably hard to adjust an existing tag. - @property - def object(self) -> AnyGitObject: # type: ignore[override] + @property # type: ignore[misc] + def object(self) -> AnyGitObject: return Reference._get_object(self) @classmethod diff --git a/git/repo/base.py b/git/repo/base.py index 6ea96aad2..1ef7114af 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -684,11 +684,7 @@ def _config_reader( git_dir: Optional[PathLike] = None, ) -> GitConfigParser: if config_level is None: - files = [ - self._get_config_path(cast(Lit_config_levels, f), git_dir) - for f in self.config_level - if cast(Lit_config_levels, f) - ] + files = [self._get_config_path(f, git_dir) for f in self.config_level if f] else: files = [self._get_config_path(config_level, git_dir)] return GitConfigParser(files, read_only=True, repo=self) @@ -1484,7 +1480,7 @@ def clone( self.common_dir, path, type(self.odb), - progress, + progress, # type: ignore[arg-type] multi_options, allow_unsafe_protocols=allow_unsafe_protocols, allow_unsafe_options=allow_unsafe_options, @@ -1545,7 +1541,7 @@ def clone_from( url, to_path, GitCmdObjectDB, - progress, + progress, # type: ignore[arg-type] multi_options, allow_unsafe_protocols=allow_unsafe_protocols, allow_unsafe_options=allow_unsafe_options, diff --git a/git/repo/fun.py b/git/repo/fun.py index 1c995c6c6..3f00e60ea 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -286,7 +286,7 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject: # END handle refname else: if ref is not None: - obj = cast("Commit", ref.commit) + obj = ref.commit # END handle ref # END initialize obj on first token diff --git a/git/types.py b/git/types.py index cce184530..c6dbb717b 100644 --- a/git/types.py +++ b/git/types.py @@ -13,7 +13,7 @@ Sequence as Sequence, Tuple, TYPE_CHECKING, - Type, + TypeAlias, TypeVar, Union, ) @@ -117,7 +117,7 @@ object types. """ -GitObjectTypeString = Literal["commit", "tag", "blob", "tree"] +GitObjectTypeString: TypeAlias = Literal["commit", "tag", "blob", "tree"] """Literal strings identifying git object types and the :class:`~git.objects.base.Object`-based types that represent them. @@ -130,7 +130,7 @@ https://git-scm.com/docs/gitglossary#def_object_type """ -Lit_commit_ish: Type[Literal["commit", "tag"]] +Lit_commit_ish: TypeAlias = Literal["commit", "tag"] """Deprecated. Type of literal strings identifying typically-commitish git object types. Prior to a bugfix, this type had been defined more broadly. Any usage is in practice diff --git a/git/util.py b/git/util.py index 0aff5eb64..54a5b7bd1 100644 --- a/git/util.py +++ b/git/util.py @@ -1143,7 +1143,7 @@ def _obtain_lock(self) -> None: # END endless loop -class IterableList(List[T_IterableObj]): +class IterableList(List[T_IterableObj]): # type: ignore[type-var] """List of iterable objects allowing to query an object by id or by named index:: heads = repo.heads @@ -1214,14 +1214,16 @@ def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_Iterabl raise ValueError("Index should be an int or str") else: try: + if not isinstance(index, str): + raise AttributeError(f"{index} is not a valid attribute") return getattr(self, index) except AttributeError as e: - raise IndexError("No item found with id %r" % (self._prefix + index)) from e + raise IndexError(f"No item found with id {self._prefix}{index}") from e # END handle getattr def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None: delindex = cast(int, index) - if not isinstance(index, int): + if isinstance(index, str): delindex = -1 name = self._prefix + index for i, item in enumerate(self): diff --git a/test/deprecation/test_types.py b/test/deprecation/test_types.py index f97375a85..d3c6af645 100644 --- a/test/deprecation/test_types.py +++ b/test/deprecation/test_types.py @@ -36,7 +36,7 @@ def test_can_access_lit_commit_ish_but_it_is_not_usable() -> None: assert 'Literal["commit", "tag"]' in message, "Has new definition." assert "GitObjectTypeString" in message, "Has new type name for old definition." - _: Lit_commit_ish = "commit" # type: ignore[valid-type] + _: Lit_commit_ish = "commit" # It should be as documented (even though deliberately unusable in static checks). assert Lit_commit_ish == Literal["commit", "tag"] diff --git a/test/lib/helper.py b/test/lib/helper.py index 241d27341..b4615f400 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -149,7 +149,7 @@ def repo_creator(self): os.chdir(rw_repo.working_dir) try: return func(self, rw_repo) - except: # noqa: E722 B001 + except: # noqa: E722 _logger.info("Keeping repo after failure: %s", repo_dir) repo_dir = None raise @@ -309,7 +309,7 @@ def remote_repo_creator(self): with cwd(rw_repo.working_dir): try: return func(self, rw_repo, rw_daemon_repo) - except: # noqa: E722 B001 + except: # noqa: E722 _logger.info( "Keeping repos after failure: \n rw_repo_dir: %s \n rw_daemon_repo_dir: %s", rw_repo_dir, diff --git a/test/test_submodule.py b/test/test_submodule.py index 4a248eb60..a92dd8fd4 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -932,7 +932,7 @@ def assert_exists(sm, value=True): csm.repo.index.commit("Have to commit submodule change for algorithm to pick it up") assert csm.url == "bar" - self.assertRaises( + self.assertRaises( # noqa: B017 Exception, rsm.update, recursive=True, From a9756bc0c8997482a7f69cc8e46a9f461afea8f6 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 09:46:47 +0000 Subject: [PATCH 18/26] Fix typing so that code can run --- git/config.py | 2 +- git/objects/submodule/base.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/git/config.py b/git/config.py index 458151d05..bccf61258 100644 --- a/git/config.py +++ b/git/config.py @@ -766,7 +766,7 @@ def _assure_writable(self, method_name: str) -> None: if self.read_only: raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name)) - def add_section(self, section: str | cp._UNNAMED_SECTION) -> None: + def add_section(self, section: "cp._SectionName") -> None: """Assures added options will stay in order.""" return super().add_section(section) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index b4a4ca467..20f3e9ccf 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -25,7 +25,6 @@ ) from git.objects.base import IndexObject, Object from git.objects.util import TraversableIterableObj -from ...refs.remote import RemoteReference from git.util import ( IterableList, RemoteProgress, @@ -67,7 +66,7 @@ if TYPE_CHECKING: from git.index import IndexFile from git.objects.commit import Commit - from git.refs import Head + from git.refs import Head, RemoteReference from git.repo import Repo # ----------------------------------------------------------------------------- @@ -356,7 +355,7 @@ def _clone_repo( module_checkout_path = osp.join(str(repo.working_tree_dir), path) if url.startswith("../"): - remote_name = cast(RemoteReference, repo.active_branch.tracking_branch()).remote_name + remote_name = cast("RemoteReference", repo.active_branch.tracking_branch()).remote_name repo_remote_url = repo.remote(remote_name).url url = os.path.join(repo_remote_url, url) From 0aba3e7bdec7544a86b6e6ba4b0ad8e2ac5cd2c7 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 10:02:07 +0000 Subject: [PATCH 19/26] Stop Lit_commit_ish being imported --- git/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git/types.py b/git/types.py index c6dbb717b..100fff43f 100644 --- a/git/types.py +++ b/git/types.py @@ -13,7 +13,6 @@ Sequence as Sequence, Tuple, TYPE_CHECKING, - TypeAlias, TypeVar, Union, ) @@ -117,7 +116,7 @@ object types. """ -GitObjectTypeString: TypeAlias = Literal["commit", "tag", "blob", "tree"] +GitObjectTypeString = Literal["commit", "tag", "blob", "tree"] """Literal strings identifying git object types and the :class:`~git.objects.base.Object`-based types that represent them. @@ -130,7 +129,8 @@ https://git-scm.com/docs/gitglossary#def_object_type """ -Lit_commit_ish: TypeAlias = Literal["commit", "tag"] +if TYPE_CHECKING: + Lit_commit_ish = Literal["commit", "tag"] """Deprecated. Type of literal strings identifying typically-commitish git object types. Prior to a bugfix, this type had been defined more broadly. Any usage is in practice From 019f270785fd01558b48d21fe1469b9a2132d04b Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 10:02:45 +0000 Subject: [PATCH 20/26] Set __test__ = False in not tested classes --- test/test_remote.py | 5 ++++- test/test_submodule.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_remote.py b/test/test_remote.py index 5ddb41bc0..b1d686f05 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -44,7 +44,7 @@ class TestRemoteProgress(RemoteProgress): __slots__ = ("_seen_lines", "_stages_per_op", "_num_progress_messages") - def __init__(self): + def __init__(self) -> None: super().__init__() self._seen_lines = [] self._stages_per_op = {} @@ -103,6 +103,9 @@ def assert_received_message(self): assert self._num_progress_messages +TestRemoteProgress.__test__ = False # type: ignore + + class TestRemote(TestBase): def tearDown(self): gc.collect() diff --git a/test/test_submodule.py b/test/test_submodule.py index a92dd8fd4..edff064c4 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -58,6 +58,7 @@ def update(self, op, cur_count, max_count, message=""): print(op, cur_count, max_count, message) +TestRootProgress.__test__ = False prog = TestRootProgress() From ca5a2e817829861c5a0830806c0a40a33a5ab83f Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 11:40:23 +0000 Subject: [PATCH 21/26] Add missing parentheses around tuple constructor --- git/refs/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/refs/log.py b/git/refs/log.py index 4e3666993..4751cff99 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -145,7 +145,7 @@ def from_line(cls, line: bytes) -> "RefLogEntry": actor = Actor._from_string(info[82 : email_end + 1]) time, tz_offset = parse_date(info[email_end + 2 :]) # skipcq: PYL-W0621 - return RefLogEntry(oldhexsha, newhexsha, actor, (time, tz_offset), msg) # type: ignore[call-arg] + return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg)) # type: ignore [arg-type] class RefLog(List[RefLogEntry], Serializable): From c75790837d0fd5bb9a7ba26a48b957b5d70987fb Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 11:43:21 +0000 Subject: [PATCH 22/26] Install mypy for Python >= 3.9 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 460597539..e6e01c683 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ coverage[toml] ddt >= 1.1.1, != 1.4.3 mock ; python_version < "3.8" -mypy==1.18.2 # pin mypy to avoid new errors +mypy==1.18.2 ; python_version >= "3.9" # pin mypy version to avoid new errors pre-commit pytest >= 7.3.1 pytest-cov From 9decf740ad2f1d89b55bda3a42880fa4f7b652ea Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 11:49:10 +0000 Subject: [PATCH 23/26] Skip mypy when Python < 3.9 --- .github/workflows/pythonpackage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 4666f3480..9e05b3fe6 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -95,6 +95,7 @@ jobs: continue-on-error: true - name: Check types with mypy + if: matrix.python-version != '3.7' && matrix.python-version != '3.8' run: | mypy --python-version="${PYTHON_VERSION%t}" # Version only, with no "t" for free-threaded. env: From a1f094c81fcf4a6b559c2a26fc622c89e4f19735 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Fri, 28 Nov 2025 12:10:04 +0000 Subject: [PATCH 24/26] Use git.types.Literal instead of typing.Literal --- git/diff.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/git/diff.py b/git/diff.py index 2b1fd928c..23cb5675e 100644 --- a/git/diff.py +++ b/git/diff.py @@ -21,7 +21,6 @@ Any, Iterator, List, - Literal, Match, Optional, Sequence, @@ -31,7 +30,7 @@ Union, cast, ) -from git.types import PathLike +from git.types import PathLike, Literal if TYPE_CHECKING: from subprocess import Popen @@ -291,7 +290,7 @@ class DiffIndex(List[T_Diff]): The class improves the diff handling convenience. """ - change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") + change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") # noqa: F821 """Change type invariant identifying possible ways a blob can have changed: * ``A`` = Added From b5c834af59531456551d406eb857934e7e87f1ce Mon Sep 17 00:00:00 2001 From: George Ogden Date: Sat, 29 Nov 2025 11:49:23 +0000 Subject: [PATCH 25/26] Remve comment about skipping mypy --- .github/workflows/pythonpackage.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 9e05b3fe6..ac764d9a7 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -102,8 +102,6 @@ jobs: MYPY_FORCE_COLOR: "1" TERM: "xterm-256color" # For color: https://github.com/python/mypy/issues/13817 PYTHON_VERSION: ${{ matrix.python-version }} - # With new versions of mypy new issues might arise. This is a problem if there is - # nobody able to fix them, so we have to ignore errors until that changes. - name: Test with pytest run: | From eb15123b82dbd13f9cc88606b8a580c424335fe7 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Sat, 29 Nov 2025 11:51:47 +0000 Subject: [PATCH 26/26] Use cast to allow silent getattrs --- git/util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/git/util.py b/git/util.py index 54a5b7bd1..1f1595f1c 100644 --- a/git/util.py +++ b/git/util.py @@ -1214,9 +1214,7 @@ def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_Iterabl raise ValueError("Index should be an int or str") else: try: - if not isinstance(index, str): - raise AttributeError(f"{index} is not a valid attribute") - return getattr(self, index) + return getattr(self, cast(str, index)) except AttributeError as e: raise IndexError(f"No item found with id {self._prefix}{index}") from e # END handle getattr