sync: reuse status HEAD in the bloat scan The shallow-project bloat check already needs dirty state. Request branch metadata in that same porcelain-v2 snapshot and use branch.oid instead of launching a separate rev-parse, with the existing calls retained as a fallback. Bug: 543851900 Bug: 553599402 Change-Id: I3e7068c2e14c025a53adf92bfe8483b876cfda9d Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/632145 Tested-by: Gavin Mak <gavinmak@google.com> Reviewed-by: Brian Gan <brgan@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
diff --git a/subcmds/sync.py b/subcmds/sync.py index 460ebab..d60a256 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py
@@ -1614,10 +1614,23 @@ # Only check dirty or locally modified projects. These can't be # freshly cloned and will accumulate garbage. try: - is_dirty = project.IsDirty(consider_untracked=True) + status = project._GetStatusSnapshot( + untracked_files="normal", branch=True + ) + if status is not None: + is_dirty = status.is_dirty(consider_untracked=True) + head_rev = status.branch_oid + else: + is_dirty = project.IsDirty(consider_untracked=True) + head_rev = project.work_git.rev_parse(HEAD) + + if head_rev is None: + # Porcelain v2 reports an unborn branch as "(initial)". The + # legacy rev-parse path failed here and skipped the bloat + # calculation, so preserve that behavior. + return None manifest_rev = project.GetRevisionId(project.bare_ref.all) - head_rev = project.work_git.rev_parse(HEAD) has_local_commits = manifest_rev != head_rev if not (is_dirty or has_local_commits):
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 208ff60..c3d7728 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py
@@ -30,6 +30,7 @@ import command from error import GitError from error import RepoExitError +import git_status import manifest_xml from project import SyncNetworkHalfResult from subcmds import sync @@ -989,6 +990,42 @@ self.cmd.git_event_log = mock.MagicMock() self.cmd._bloated_projects = [] + def test_one_project_reuses_status_head_oid(self) -> None: + """The bloat scan gets dirty state and HEAD from one snapshot.""" + status = git_status.StatusSnapshot() + status.branch_oid = "local" + self.project._GetStatusSnapshot.return_value = status + self.project.GetRevisionId.return_value = "manifest" + self.project.bare_git.count_objects.return_value = ( + "packs: 0\nsize-pack: 0\nsize-garbage: 0\n" + ) + with mock.patch.object( + sync.Sync, + "get_parallel_context", + return_value={"projects": [self.project]}, + ): + self.assertIsNone(self.cmd._CheckOneBloatedProject(0)) + + self.project.IsDirty.assert_not_called() + self.project.work_git.rev_parse.assert_not_called() + self.project.bare_git.count_objects.assert_called_once_with("-v") + + def test_one_unborn_project_skips_bloat_check(self) -> None: + """A porcelain initial branch behaves like failed rev-parse HEAD.""" + status = git_status.StatusSnapshot() + status.index_changes["staged"] = git_status.StatusEntry("staged", "M") + self.project._GetStatusSnapshot.return_value = status + + with mock.patch.object( + sync.Sync, + "get_parallel_context", + return_value={"projects": [self.project]}, + ): + self.assertIsNone(self.cmd._CheckOneBloatedProject(0)) + + self.project.GetRevisionId.assert_not_called() + self.project.bare_git.count_objects.assert_not_called() + @mock.patch("subcmds.sync.git_require") def test_git_version_unsupported(self, mock_git_require): """Test that it returns early if git version is unsupported."""