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..dcfe01d5c 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -369,11 +369,38 @@ def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs 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..f8457e250 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 @@ -164,6 +164,25 @@ def check_entries(d, has_change_type=False): 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") files = commit.stats.files