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."""