From 1729aaebaed8912cec20e5191d961400c08e98b1 Mon Sep 17 00:00:00 2001 From: Ajay Gupta Date: Fri, 17 Apr 2026 12:43:57 +0530 Subject: [PATCH] project: Skip superproject upstream check for MetaProjects The superproject-gated upstream check in _CheckForImmutableRevision only applies to user projects listed in the manifest. MetaProjects (ManifestProject and RepoProject) never participate in a superproject relationship, so evaluating git_superproject.UseSuperproject(..., self.manifest) for them serves no purpose and, worse, calls the manifest.superproject property which forces a manifest load. During repo init, ManifestProject._ConfigureDependencies calls self.Sync_NetworkHalf before manifest.xml has been linked into .repo/. That reaches _CheckForImmutableRevision, which triggered the manifest load and failed with: ManifestParseError: .../.repo/manifest.xml: [Errno 2] No such file or directory breaking fresh repo init with SHA-based --manifest-branch combined with --manifest-upstream-branch. Factor the "should we consult the superproject for upstream?" decision into a small overridable hook, _UseSuperprojectForUpstream. Project's default delegates to git_superproject.UseSuperproject; MetaProject overrides it to return False, localizing the MetaProject-specific behavior to MetaProject. Test verifies that calling _CheckForImmutableRevision on a ManifestProject whose manifest.xml is not yet on disk returns False without raising and does not create the file. Change-Id: I22059109243d914036c06c6fe0081a5aba05da89 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/574201 Reviewed-by: Gavin Mak Commit-Queue: Ajay Gupta Tested-by: Ajay Gupta Reviewed-by: Mike Frysinger Reviewed-by: Nasser Grainawi --- project.py | 30 ++++++++++++++++++++++++------ tests/test_project.py | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/project.py b/project.py index 649196b01..b81fcd5f0 100644 --- a/project.py +++ b/project.py @@ -2674,11 +2674,12 @@ class Project: # throws an error. revs = [f"{self.revisionExpr}^0"] upstream_rev = None + use_superproject_for_upstream = self.upstream and ( + self._UseSuperprojectForUpstream(use_superproject) + ) # Only check upstream when using superproject. - if self.upstream and git_superproject.UseSuperproject( - use_superproject, self.manifest - ): + if use_superproject_for_upstream: upstream_rev = self.GetRemote().ToLocal(self.upstream) revs.append(upstream_rev) @@ -2692,9 +2693,7 @@ class Project: # Only verify upstream relationship for superproject scenarios # without affecting plain usage. - if self.upstream and git_superproject.UseSuperproject( - use_superproject, self.manifest - ): + if use_superproject_for_upstream: self.bare_git.merge_base( "--is-ancestor", self.revisionExpr, @@ -2723,6 +2722,16 @@ class Project: return True return False + def _UseSuperprojectForUpstream( + self, use_superproject: Optional[bool] = None + ) -> bool: + """Whether to include upstream in the immutability check. + + The upstream ancestry check is only meaningful for projects + that participate in a superproject relationship. + """ + return git_superproject.UseSuperproject(use_superproject, self.manifest) + def _FetchArchive(self, tarpath, cwd=None): cmd = ["archive", "-v", "-o", tarpath] cmd.append("--remote=%s" % self.remote.url) @@ -4677,6 +4686,15 @@ class MetaProject(Project): self.revisionExpr = base self.revisionId = None + def _UseSuperprojectForUpstream( + self, use_superproject: Optional[bool] = None + ) -> bool: + # MetaProjects (the manifest repo and repo itself) never + # participate in a superproject relationship. 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?""" diff --git a/tests/test_project.py b/tests/test_project.py index ea7571030..1ebeb85d7 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -716,6 +716,31 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase): fakeproj.config.SetString("manifest.platform", "auto") self.assertEqual(fakeproj.manifest_platform, "auto") + def test_check_immutable_revision_metaproject_skips_manifest_load(self): + """MetaProjects must not parse manifest.xml during immutable check. + + During `repo init` the manifestProject's own Sync_NetworkHalf runs + before manifest.xml has been linked into .repo/, so + _CheckForImmutableRevision must not touch it. + """ + + 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)) + + fakeproj.revisionExpr = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + fakeproj.upstream = "refs/heads/main" + + # Must return False without raising ManifestParseError, and + # must leave the absent manifest.xml untouched. + self.assertFalse( + fakeproj._CheckForImmutableRevision(use_superproject=None) + ) + 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: