summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2026-08-10 15:29:43 +0000
committergerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-08-10 13:37:04 -0700
commitb85886fa9f5b4e2189cc5b2f40bd0a80459d4c77 (patch)
tree8ff38152b13b3a9df4c9840294a5f2bf7d069431
parentd9da609d8c120bb882a43196a4a6b7f183418304 (diff)
downloadgit-repo-stable.tar.gz
project: skip manifest default fallback for MetaProjectsv2.66.1stablemain
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>
-rw-r--r--project.py31
-rw-r--r--tests/test_project.py84
2 files changed, 109 insertions, 6 deletions
diff --git a/project.py b/project.py
index 7186e58ad..67e3fe6b6 100644
--- a/project.py
+++ b/project.py
@@ -2859,12 +2859,17 @@ class Project:
2859 2859
2860 def _GetUpstreamFallback(self) -> Optional[str]: 2860 def _GetUpstreamFallback(self) -> Optional[str]:
2861 """Resolve a fallback upstream ref when revisionExpr is a SHA-1.""" 2861 """Resolve a fallback upstream ref when revisionExpr is a SHA-1."""
2862 for cand in ( 2862 default = self.manifest.default
2863 self.dest_branch, 2863 candidates = [self.dest_branch]
2864 self.manifest.default.upstreamExpr, 2864 if default:
2865 self.manifest.default.destBranchExpr, 2865 candidates.extend(
2866 self.manifest.default.revisionExpr, 2866 (
2867 ): 2867 default.upstreamExpr,
2868 default.destBranchExpr,
2869 default.revisionExpr,
2870 )
2871 )
2872 for cand in candidates:
2868 if cand and not IsId(cand): 2873 if cand and not IsId(cand):
2869 return cand 2874 return cand
2870 return None 2875 return None
@@ -4737,6 +4742,20 @@ class MetaProject(Project):
4737 # before manifest.xml has been linked into .repo/. 4742 # before manifest.xml has been linked into .repo/.
4738 return False 4743 return False
4739 4744
4745 def _GetUpstreamFallback(self) -> Optional[str]:
4746 # MetaProjects (the manifest repo and repo itself) do not have
4747 # defaults in a manifest. Returning None here also avoids
4748 # loading the manifest during `repo init`, before manifest.xml
4749 # has been linked into .repo/.
4750 return None
4751
4752 def _SharingProjectHasShallow(self) -> bool:
4753 # MetaProjects (the manifest repo and repo itself) are never
4754 # shared with other projects in the manifest. Returning False
4755 # here also avoids loading the manifest during `repo init`,
4756 # before manifest.xml has been linked into .repo/.
4757 return False
4758
4740 @property 4759 @property
4741 def HasChanges(self): 4760 def HasChanges(self):
4742 """Has the remote received new commits not yet checked out?""" 4761 """Has the remote received new commits not yet checked out?"""
diff --git a/tests/test_project.py b/tests/test_project.py
index 19da4f09e..cbffec859 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -742,6 +742,35 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
742 ) 742 )
743 self.assertFalse(os.path.exists(manifest_path)) 743 self.assertFalse(os.path.exists(manifest_path))
744 744
745 def test_get_upstream_fallback_metaproject_skips_manifest_load(
746 self,
747 ) -> None:
748 """MetaProjects must not parse manifest.xml during upstream fallback."""
749 with utils_for_test.TempGitTree() as tempdir:
750 fakeproj = self.setUpManifest(tempdir)
751 manifest_path = os.path.join(
752 tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
753 )
754 self.assertFalse(os.path.exists(manifest_path))
755
756 self.assertIsNone(fakeproj._GetUpstreamFallback())
757 self.assertFalse(os.path.exists(manifest_path))
758
759 def test_sharing_project_has_shallow_metaproject_skips_manifest_load(
760 self,
761 ) -> None:
762 """MetaProjects must not parse manifest.xml during sharing shallow
763 check."""
764 with utils_for_test.TempGitTree() as tempdir:
765 fakeproj = self.setUpManifest(tempdir)
766 manifest_path = os.path.join(
767 tempdir, ".repo", manifest_xml.MANIFEST_FILE_NAME
768 )
769 self.assertFalse(os.path.exists(manifest_path))
770
771 self.assertFalse(fakeproj._SharingProjectHasShallow())
772 self.assertFalse(os.path.exists(manifest_path))
773
745 def test_sync_use_local_gitdirs_worktree_conflict(self): 774 def test_sync_use_local_gitdirs_worktree_conflict(self):
746 """Test that --use-local-gitdirs conflicts with --worktree.""" 775 """Test that --use-local-gitdirs conflicts with --worktree."""
747 with utils_for_test.TempGitTree() as tempdir: 776 with utils_for_test.TempGitTree() as tempdir:
@@ -1403,6 +1432,61 @@ class SyncOptimizationTests(unittest.TestCase):
1403 "+refs/heads/*:refs/remotes/origin/*", cmd_args 1432 "+refs/heads/*:refs/remotes/origin/*", cmd_args
1404 ) 1433 )
1405 1434
1435 def test_remote_fetch_sha1_metaproject_without_manifest_xml(self) -> None:
1436 """Test MetaProject _RemoteFetch with SHA-1 fetches all branches."""
1437 sha = "4f8a3c0000000000000000000000000000000000"
1438 with utils_for_test.TempGitTree() as tempdir:
1439 repodir = os.path.join(tempdir, ".repo")
1440 manifest_dir = os.path.join(repodir, "manifests")
1441 manifest_file = os.path.join(
1442 repodir, manifest_xml.MANIFEST_FILE_NAME
1443 )
1444 os.mkdir(repodir)
1445 os.mkdir(manifest_dir)
1446 manifest = manifest_xml.XmlManifest(repodir, manifest_file)
1447 proj = project.ManifestProject(
1448 manifest,
1449 "test/manifest",
1450 os.path.join(tempdir, ".git"),
1451 tempdir,
1452 )
1453 proj.revisionExpr = sha
1454 proj.upstream = None
1455 proj._CheckForImmutableRevision = mock.MagicMock(return_value=False)
1456
1457 mock_remote = mock.MagicMock()
1458 mock_remote.name = "origin"
1459
1460 def _to_local(r: str) -> str:
1461 if r.startswith("refs/heads/"):
1462 return "refs/remotes/origin/" + r[11:]
1463 return r
1464
1465 mock_remote.ToLocal.side_effect = _to_local
1466 mock_remote.PreConnectFetch.return_value = True
1467 proj.GetRemote = mock.MagicMock(return_value=mock_remote)
1468
1469 with mock.patch("project.GitCommand") as mock_git_cmd:
1470 mock_cmd_instance = mock.MagicMock()
1471 mock_cmd_instance.Wait.return_value = 0
1472 mock_git_cmd.return_value = mock_cmd_instance
1473
1474 res = proj._RemoteFetch(current_branch_only=True)
1475
1476 self.assertTrue(res)
1477 mock_git_cmd.assert_called_once()
1478 cmd_args = mock_git_cmd.call_args[0][1]
1479 self.assertIn("+refs/heads/*:refs/remotes/origin/*", cmd_args)
1480
1481 def test_remote_fetch_sha1_none_manifest_default(self) -> None:
1482 """Test _GetUpstreamFallback when manifest.default is None."""
1483 sha = "4f8a3c0000000000000000000000000000000000"
1484 with utils_for_test.TempGitTree() as tempdir:
1485 proj = self._get_project(tempdir, revisionExpr=sha)
1486 proj.dest_branch = None
1487 proj.manifest.default = None
1488 self.assertIsNone(proj._GetUpstreamFallback())
1489
1406 1490
1407class GetEnvVarsTests(unittest.TestCase): 1491class GetEnvVarsTests(unittest.TestCase):
1408 """Tests for GetEnvVars project environment variable generation.""" 1492 """Tests for GetEnvVars project environment variable generation."""