diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-08-10 15:29:43 +0000 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-08-10 13:37:04 -0700 |
| commit | b85886fa9f5b4e2189cc5b2f40bd0a80459d4c77 (patch) | |
| tree | 8ff38152b13b3a9df4c9840294a5f2bf7d069431 | |
| parent | d9da609d8c120bb882a43196a4a6b7f183418304 (diff) | |
| download | git-repo-stable.tar.gz | |
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.py | 31 | ||||
| -rw-r--r-- | tests/test_project.py | 84 |
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 | ||
| 1407 | class GetEnvVarsTests(unittest.TestCase): | 1491 | class GetEnvVarsTests(unittest.TestCase): |
| 1408 | """Tests for GetEnvVars project environment variable generation.""" | 1492 | """Tests for GetEnvVars project environment variable generation.""" |
