summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2026-08-25 08:48:50 -0700
committergerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-09-21 13:39:21 -0700
commitaaadd5da35ce63799f902b3bdecc3091676530c9 (patch)
tree22f74767b77fe79a6770e5b71afc4cd7af8c205f
parentc9448f986ef952487006e9c2d22989e98655c591 (diff)
downloadgit-repo-aaadd5da35ce63799f902b3bdecc3091676530c9.tar.gz
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>
-rw-r--r--subcmds/sync.py17
-rw-r--r--tests/test_subcmds_sync.py37
2 files changed, 52 insertions, 2 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 460ebabd1..d60a256b4 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -1614,10 +1614,23 @@ later is required to fix a server side protocol bug.
1614 # Only check dirty or locally modified projects. These can't be 1614 # Only check dirty or locally modified projects. These can't be
1615 # freshly cloned and will accumulate garbage. 1615 # freshly cloned and will accumulate garbage.
1616 try: 1616 try:
1617 is_dirty = project.IsDirty(consider_untracked=True) 1617 status = project._GetStatusSnapshot(
1618 untracked_files="normal", branch=True
1619 )
1620 if status is not None:
1621 is_dirty = status.is_dirty(consider_untracked=True)
1622 head_rev = status.branch_oid
1623 else:
1624 is_dirty = project.IsDirty(consider_untracked=True)
1625 head_rev = project.work_git.rev_parse(HEAD)
1626
1627 if head_rev is None:
1628 # Porcelain v2 reports an unborn branch as "(initial)". The
1629 # legacy rev-parse path failed here and skipped the bloat
1630 # calculation, so preserve that behavior.
1631 return None
1618 1632
1619 manifest_rev = project.GetRevisionId(project.bare_ref.all) 1633 manifest_rev = project.GetRevisionId(project.bare_ref.all)
1620 head_rev = project.work_git.rev_parse(HEAD)
1621 has_local_commits = manifest_rev != head_rev 1634 has_local_commits = manifest_rev != head_rev
1622 1635
1623 if not (is_dirty or has_local_commits): 1636 if not (is_dirty or has_local_commits):
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py
index 208ff60b2..c3d772868 100644
--- a/tests/test_subcmds_sync.py
+++ b/tests/test_subcmds_sync.py
@@ -30,6 +30,7 @@ import pytest
30import command 30import command
31from error import GitError 31from error import GitError
32from error import RepoExitError 32from error import RepoExitError
33import git_status
33import manifest_xml 34import manifest_xml
34from project import SyncNetworkHalfResult 35from project import SyncNetworkHalfResult
35from subcmds import sync 36from subcmds import sync
@@ -989,6 +990,42 @@ class CheckForBloatedProjects(unittest.TestCase):
989 self.cmd.git_event_log = mock.MagicMock() 990 self.cmd.git_event_log = mock.MagicMock()
990 self.cmd._bloated_projects = [] 991 self.cmd._bloated_projects = []
991 992
993 def test_one_project_reuses_status_head_oid(self) -> None:
994 """The bloat scan gets dirty state and HEAD from one snapshot."""
995 status = git_status.StatusSnapshot()
996 status.branch_oid = "local"
997 self.project._GetStatusSnapshot.return_value = status
998 self.project.GetRevisionId.return_value = "manifest"
999 self.project.bare_git.count_objects.return_value = (
1000 "packs: 0\nsize-pack: 0\nsize-garbage: 0\n"
1001 )
1002 with mock.patch.object(
1003 sync.Sync,
1004 "get_parallel_context",
1005 return_value={"projects": [self.project]},
1006 ):
1007 self.assertIsNone(self.cmd._CheckOneBloatedProject(0))
1008
1009 self.project.IsDirty.assert_not_called()
1010 self.project.work_git.rev_parse.assert_not_called()
1011 self.project.bare_git.count_objects.assert_called_once_with("-v")
1012
1013 def test_one_unborn_project_skips_bloat_check(self) -> None:
1014 """A porcelain initial branch behaves like failed rev-parse HEAD."""
1015 status = git_status.StatusSnapshot()
1016 status.index_changes["staged"] = git_status.StatusEntry("staged", "M")
1017 self.project._GetStatusSnapshot.return_value = status
1018
1019 with mock.patch.object(
1020 sync.Sync,
1021 "get_parallel_context",
1022 return_value={"projects": [self.project]},
1023 ):
1024 self.assertIsNone(self.cmd._CheckOneBloatedProject(0))
1025
1026 self.project.GetRevisionId.assert_not_called()
1027 self.project.bare_git.count_objects.assert_not_called()
1028
992 @mock.patch("subcmds.sync.git_require") 1029 @mock.patch("subcmds.sync.git_require")
993 def test_git_version_unsupported(self, mock_git_require): 1030 def test_git_version_unsupported(self, mock_git_require):
994 """Test that it returns early if git version is unsupported.""" 1031 """Test that it returns early if git version is unsupported."""