project: skip manifest default fallback for MetaProjects

Avoid loading manifest.xml during repo init when syncing a manifest
commit SHA. Override _GetUpstreamFallback and _SharingProjectHasShallow
in MetaProject to prevent premature manifest parsing.

Bug: 544041102
Change-Id: I7aa54a7c1282e5bfe811d59e977b44163a37653c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/617081
Commit-Queue: Gavin Mak <gavinmak@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Brian Gan <brgan@google.com>
This commit is contained in:
Gavin Mak
2026-08-10 15:29:43 +00:00
committed by gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com
parent d9da609d8c
commit b85886fa9f
2 changed files with 109 additions and 6 deletions
+25 -6
View File
@@ -2859,12 +2859,17 @@ class Project:
def _GetUpstreamFallback(self) -> Optional[str]:
"""Resolve a fallback upstream ref when revisionExpr is a SHA-1."""
for cand in (
self.dest_branch,
self.manifest.default.upstreamExpr,
self.manifest.default.destBranchExpr,
self.manifest.default.revisionExpr,
):
default = self.manifest.default
candidates = [self.dest_branch]
if default:
candidates.extend(
(
default.upstreamExpr,
default.destBranchExpr,
default.revisionExpr,
)
)
for cand in candidates:
if cand and not IsId(cand):
return cand
return None
@@ -4737,6 +4742,20 @@ class MetaProject(Project):
# before manifest.xml has been linked into .repo/.
return False
def _GetUpstreamFallback(self) -> Optional[str]:
# MetaProjects (the manifest repo and repo itself) do not have
# defaults in a manifest. Returning None here also avoids
# loading the manifest during `repo init`, before manifest.xml
# has been linked into .repo/.
return None
def _SharingProjectHasShallow(self) -> bool:
# MetaProjects (the manifest repo and repo itself) are never
# shared with other projects in the manifest. Returning False
# here also avoids loading the manifest during `repo init`,
# before manifest.xml has been linked into .repo/.
return False
@property
def HasChanges(self):
"""Has the remote received new commits not yet checked out?"""
+84
View File
@@ -742,6 +742,35 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
)
self.assertFalse(os.path.exists(manifest_path))
def test_get_upstream_fallback_metaproject_skips_manifest_load(
self,
) -> None:
"""MetaProjects must not parse manifest.xml during upstream fallback."""
with utils_for_test.TempGitTree() as tempdir:
fakeproj = self.setUpManifest(tempdir)
manifest_path = os.path.join(
tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
)
self.assertFalse(os.path.exists(manifest_path))
self.assertIsNone(fakeproj._GetUpstreamFallback())
self.assertFalse(os.path.exists(manifest_path))
def test_sharing_project_has_shallow_metaproject_skips_manifest_load(
self,
) -> None:
"""MetaProjects must not parse manifest.xml during sharing shallow
check."""
with utils_for_test.TempGitTree() as tempdir:
fakeproj = self.setUpManifest(tempdir)
manifest_path = os.path.join(
tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
)
self.assertFalse(os.path.exists(manifest_path))
self.assertFalse(fakeproj._SharingProjectHasShallow())
self.assertFalse(os.path.exists(manifest_path))
def test_sync_use_local_gitdirs_worktree_conflict(self):
"""Test that --use-local-gitdirs conflicts with --worktree."""
with utils_for_test.TempGitTree() as tempdir:
@@ -1403,6 +1432,61 @@ class SyncOptimizationTests(unittest.TestCase):
"+refs/heads/*:refs/remotes/origin/*", cmd_args
)
def test_remote_fetch_sha1_metaproject_without_manifest_xml(self) -> None:
"""Test MetaProject _RemoteFetch with SHA-1 fetches all branches."""
sha = "4f8a3c0000000000000000000000000000000000"
with utils_for_test.TempGitTree() as tempdir:
repodir = os.path.join(tempdir, ".repo")
manifest_dir = os.path.join(repodir, "manifests")
manifest_file = os.path.join(
repodir, manifest_xml.MANIFEST_FILE_NAME
)
os.mkdir(repodir)
os.mkdir(manifest_dir)
manifest = manifest_xml.XmlManifest(repodir, manifest_file)
proj = project.ManifestProject(
manifest,
"test/manifest",
os.path.join(tempdir, ".git"),
tempdir,
)
proj.revisionExpr = sha
proj.upstream = None
proj._CheckForImmutableRevision = mock.MagicMock(return_value=False)
mock_remote = mock.MagicMock()
mock_remote.name = "origin"
def _to_local(r: str) -> str:
if r.startswith("refs/heads/"):
return "refs/remotes/origin/" + r[11:]
return r
mock_remote.ToLocal.side_effect = _to_local
mock_remote.PreConnectFetch.return_value = True
proj.GetRemote = mock.MagicMock(return_value=mock_remote)
with mock.patch("project.GitCommand") as mock_git_cmd:
mock_cmd_instance = mock.MagicMock()
mock_cmd_instance.Wait.return_value = 0
mock_git_cmd.return_value = mock_cmd_instance
res = proj._RemoteFetch(current_branch_only=True)
self.assertTrue(res)
mock_git_cmd.assert_called_once()
cmd_args = mock_git_cmd.call_args[0][1]
self.assertIn("+refs/heads/*:refs/remotes/origin/*", cmd_args)
def test_remote_fetch_sha1_none_manifest_default(self) -> None:
"""Test _GetUpstreamFallback when manifest.default is None."""
sha = "4f8a3c0000000000000000000000000000000000"
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir, revisionExpr=sha)
proj.dest_branch = None
proj.manifest.default = None
self.assertIsNone(proj._GetUpstreamFallback())
class GetEnvVarsTests(unittest.TestCase):
"""Tests for GetEnvVars project environment variable generation."""