diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-08-25 08:48:50 -0700 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-09-21 13:39:21 -0700 |
| commit | aaadd5da35ce63799f902b3bdecc3091676530c9 (patch) | |
| tree | 22f74767b77fe79a6770e5b71afc4cd7af8c205f | |
| parent | c9448f986ef952487006e9c2d22989e98655c591 (diff) | |
| download | git-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.py | 17 | ||||
| -rw-r--r-- | tests/test_subcmds_sync.py | 37 |
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 | |||
| 30 | import command | 30 | import command |
| 31 | from error import GitError | 31 | from error import GitError |
| 32 | from error import RepoExitError | 32 | from error import RepoExitError |
| 33 | import git_status | ||
| 33 | import manifest_xml | 34 | import manifest_xml |
| 34 | from project import SyncNetworkHalfResult | 35 | from project import SyncNetworkHalfResult |
| 35 | from subcmds import sync | 36 | from 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.""" |
