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 <gavinmak@google.com>
Commit-Queue: Ajay Gupta <ajagup@qti.qualcomm.com>
Tested-by: Ajay Gupta <ajagup@qti.qualcomm.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com>
diff --git a/project.py b/project.py
index 649196b..b81fcd5 100644
--- a/project.py
+++ b/project.py
@@ -2674,11 +2674,12 @@
             # 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 @@
 
             # 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 @@
                     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 @@
                     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 ea75710..1ebeb85 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -716,6 +716,31 @@
             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: