From 837a4f068e6b7e0f35847d32b868c354226788a5 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 5 Jul 2026 18:27:41 +0530 Subject: [PATCH 1/2] Add Commit.is_shallow property and document stats() limitation at shallow boundary Accessing .stats on a commit at the boundary of a shallow clone raises GitCommandError because the commit's parent SHA was never fetched. This adds an is_shallow property to detect this case ahead of time by checking the repository's shallow file, and documents the limitation on stats(). Co-authored-by: Claude --- AUTHORS | 1 + git/objects/commit.py | 26 ++++++++++++++++++++++++++ test/test_commit.py | 21 ++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 15333e1e5..95205f439 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,5 +57,6 @@ Contributors are: -Jonas Scharpf -Gordon Marx -Enji Cooper +-Harshita Yadav Portions derived from other open source works and are clearly marked. diff --git a/git/objects/commit.py b/git/objects/commit.py index da7677ee0..b273da986 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -368,12 +368,38 @@ def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs kwargs["skip"] = skip return self.iter_items(self.repo, self, paths, **kwargs) + @property + def is_shallow(self) -> bool: + """Check whether this commit is a shallow boundary (graft) commit. + + A commit at the boundary of a shallow clone appears to have no + parents from Git's perspective, even though the underlying commit + object still references a parent SHA that was never fetched. Calling + :attr:`stats` (or anything else that diffs against the parent) on + such a commit will raise :exc:`~git.exc.GitCommandError` because the + parent object does not exist locally. + :return: + True if this commit's hexsha appears in the repository's + ``shallow`` file, False otherwise (including for non-shallow + repositories). + """ + shallow_file = os.path.join(self.repo.git_dir, "shallow") + if not os.path.isfile(shallow_file): + return False + with open(shallow_file, "r") as f: + return self.hexsha in f.read().split() + @property def stats(self) -> Stats: """Create a git stat from changes between this commit and its first parent or from all changes done if this is the very first commit. + :note: + If this commit is at the boundary of a shallow clone (see + :attr:`is_shallow`), this will raise :exc:`~git.exc.GitCommandError` + because the parent object was never fetched. + :return: :class:`Stats` """ diff --git a/test/test_commit.py b/test/test_commit.py index b56ad3a18..989cc8d5b 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -14,7 +14,7 @@ from gitdb import IStream -from git import Actor, Commit, Repo +from git import Actor, Commit, GitCommandError, Repo from git.objects.util import tzoffset, utc from git.repo.fun import touch @@ -163,6 +163,25 @@ def check_entries(d, has_change_type=False): self.assertEqual(commit.author_tz_offset, 14400, commit.author_tz_offset) self.assertEqual(commit.committer_tz_offset, 14400, commit.committer_tz_offset) self.assertEqual(commit.message, "initial project\n") + + @with_rw_directory + def test_is_shallow(self, rw_dir): + """A commit at the shallow boundary should report is_shallow, and + accessing its stats should raise GitCommandError.""" + full_repo = self.rorepo + shallow_path = osp.join(rw_dir, "shallow_clone") + shallow_repo = Repo.clone_from(full_repo.git_dir, shallow_path, depth=2, no_local=True) + + commits = list(shallow_repo.iter_commits()) + boundary_commit = commits[-1] + + self.assertTrue(boundary_commit.is_shallow) + with self.assertRaises(GitCommandError): + boundary_commit.stats + + if len(commits) > 1: + non_boundary_commit = commits[0] + self.assertFalse(non_boundary_commit.is_shallow) def test_renames(self): commit = self.rorepo.commit("185d847ec7647fd2642a82d9205fb3d07ea71715") From 6b1dd78720dc8132c526c1ac73ceee6f4dc4ee8a Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 5 Jul 2026 18:48:24 +0530 Subject: [PATCH 2/2] Fix formatting flagged by ruff (pre-commit.ci) --- git/objects/commit.py | 3 ++- test/test_commit.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index b273da986..dcfe01d5c 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -368,6 +368,7 @@ def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs kwargs["skip"] = skip return self.iter_items(self.repo, self, paths, **kwargs) + @property def is_shallow(self) -> bool: """Check whether this commit is a shallow boundary (graft) commit. @@ -389,7 +390,7 @@ def is_shallow(self) -> bool: return False with open(shallow_file, "r") as f: return self.hexsha in f.read().split() - + @property def stats(self) -> Stats: """Create a git stat from changes between this commit and its first parent diff --git a/test/test_commit.py b/test/test_commit.py index 989cc8d5b..f8457e250 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -163,7 +163,7 @@ def check_entries(d, has_change_type=False): self.assertEqual(commit.author_tz_offset, 14400, commit.author_tz_offset) self.assertEqual(commit.committer_tz_offset, 14400, commit.committer_tz_offset) self.assertEqual(commit.message, "initial project\n") - + @with_rw_directory def test_is_shallow(self, rw_dir): """A commit at the shallow boundary should report is_shallow, and