project: Extract project envvar generation to GetEnvVars

Move project environment variable setup from subcmds/forall.py to a
reusable Project.GetEnvVars() helper method.

Bug: 513329573
Change-Id: I3b4b113aa5a086e5fa5eaf4461c7ce517d928610
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/583881
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
This commit is contained in:
Gavin Mak
2026-05-26 11:10:44 -07:00
committed by gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com
parent c883613e31
commit e0bd39c691
3 changed files with 133 additions and 21 deletions
+45 -1
View File
@@ -28,7 +28,7 @@ import sys
import tarfile import tarfile
import tempfile import tempfile
import time import time
from typing import List, NamedTuple, Optional from typing import Dict, List, NamedTuple, Optional
import urllib.parse import urllib.parse
from color import Coloring from color import Coloring
@@ -651,6 +651,50 @@ class Project:
return self.relpath return self.relpath
return os.path.join(self.manifest.path_prefix, self.relpath) return os.path.join(self.manifest.path_prefix, self.relpath)
def GetEnvVars(self, local: bool = True) -> Dict[str, str]:
"""Get project-context environment variables.
Args:
local: If True, REPO_PATH is relative to the local (sub)manifest.
If False, it is relative to the outermost manifest.
Returns:
A dictionary mapping environment variable names to their values.
Environment Variables:
See the Environment section in `repo help forall` or
`subcmds/forall.py` for details on the available variables.
Note that `forall.py` also documents some extra variables that are
specific to how the `repo forall` command iterates over projects
(e.g., `REPO_COUNT` and `REPO_I`).
"""
env = {}
def setenv(name, val):
if val is None:
val = ""
env[name] = val
setenv("REPO_PROJECT", self.name)
setenv("REPO_OUTERPATH", self.manifest.path_prefix)
setenv("REPO_INNERPATH", self.relpath)
setenv("REPO_PATH", self.RelPath(local=local))
setenv("REPO_REMOTE", self.remote.name)
try:
lrev = "" if self.manifest.IsMirror else self.GetRevisionId()
except ManifestInvalidRevisionError:
lrev = ""
setenv("REPO_LREV", lrev)
setenv("REPO_RREV", self.revisionExpr)
setenv("REPO_UPSTREAM", self.upstream)
setenv("REPO_DEST_BRANCH", self.dest_branch)
for annotation in self.annotations:
setenv(f"REPO__{annotation.name}", annotation.value)
return env
def SetRevision(self, revisionExpr, revisionId=None): def SetRevision(self, revisionExpr, revisionId=None):
"""Set revisionId based on revision expression and id""" """Set revisionId based on revision expression and id"""
self.revisionExpr = revisionExpr self.revisionExpr = revisionExpr
+2 -20
View File
@@ -25,7 +25,6 @@ from color import Coloring
from command import Command from command import Command
from command import DEFAULT_LOCAL_JOBS from command import DEFAULT_LOCAL_JOBS
from command import MirrorSafeCommand from command import MirrorSafeCommand
from error import ManifestInvalidRevisionError
from repo_logging import RepoLogger from repo_logging import RepoLogger
@@ -339,25 +338,8 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
val = "" val = ""
env[name] = val env[name] = val
setenv("REPO_PROJECT", project.name) env.update(project.GetEnvVars(local=opt.this_manifest_only))
setenv("REPO_OUTERPATH", project.manifest.path_prefix) env["REPO_I"] = str(cnt + 1)
setenv("REPO_INNERPATH", project.relpath)
setenv("REPO_PATH", project.RelPath(local=opt.this_manifest_only))
setenv("REPO_REMOTE", project.remote.name)
try:
# If we aren't in a fully synced state and we don't have the ref the
# manifest wants, then this will fail. Ignore it for the purposes of
# this code.
lrev = "" if mirror else project.GetRevisionId()
except ManifestInvalidRevisionError:
lrev = ""
setenv("REPO_LREV", lrev)
setenv("REPO_RREV", project.revisionExpr)
setenv("REPO_UPSTREAM", project.upstream)
setenv("REPO_DEST_BRANCH", project.dest_branch)
setenv("REPO_I", str(cnt + 1))
for annotation in project.annotations:
setenv("REPO__%s" % (annotation.name), annotation.value)
if mirror: if mirror:
setenv("GIT_DIR", project.gitdir) setenv("GIT_DIR", project.gitdir)
+86
View File
@@ -1007,3 +1007,89 @@ class SyncOptimizationTests(unittest.TestCase):
self.assertTrue(res) self.assertTrue(res)
mock_git_cmd.assert_not_called() mock_git_cmd.assert_not_called()
class GetEnvVarsTests(unittest.TestCase):
"""Tests for GetEnvVars project environment variable generation."""
def _get_project(self, tempdir, revisionExpr="main"):
proj = _create_mock_project(tempdir, revisionExpr=revisionExpr)
proj.GetRevisionId = mock.MagicMock(return_value="1234abcd")
return proj
def test_get_env_vars_basic(self):
"""Test that all basic environment variables are set correctly."""
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir)
proj.manifest.path_prefix = "sub-manifest"
proj.upstream = "upstream-branch"
proj.dest_branch = "dest-branch"
env = proj.GetEnvVars(local=True)
self.assertEqual(env["REPO_PROJECT"], "test-project")
self.assertEqual(env["REPO_OUTERPATH"], "sub-manifest")
self.assertEqual(env["REPO_INNERPATH"], "test-project")
self.assertEqual(env["REPO_PATH"], "test-project")
self.assertEqual(env["REPO_REMOTE"], "origin")
self.assertEqual(env["REPO_LREV"], "1234abcd")
self.assertEqual(env["REPO_RREV"], "main")
self.assertEqual(env["REPO_UPSTREAM"], "upstream-branch")
self.assertEqual(env["REPO_DEST_BRANCH"], "dest-branch")
def test_get_env_vars_non_local(self):
"""Test environment variables generation with local=False."""
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir)
proj.manifest.path_prefix = "sub-manifest"
env = proj.GetEnvVars(local=False)
# REPO_PATH should be relative to outermost manifest
# (sub-manifest/test-project)
self.assertEqual(env["REPO_PATH"], "sub-manifest/test-project")
def test_get_env_vars_mirror(self):
"""Test environment variables generation in mirror mode."""
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir)
proj.manifest.IsMirror = True
env = proj.GetEnvVars()
# In mirror mode, REPO_LREV should be empty, and GetRevisionId must
# not be called
self.assertEqual(env["REPO_LREV"], "")
proj.GetRevisionId.assert_not_called()
def test_get_env_vars_annotations(self):
"""Test that project annotations are added correctly."""
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir)
annotation1 = mock.MagicMock()
annotation1.name = "key1"
annotation1.value = "value1"
annotation2 = mock.MagicMock()
annotation2.name = "key2"
annotation2.value = "value2"
proj.annotations = [annotation1, annotation2]
env = proj.GetEnvVars()
self.assertEqual(env["REPO__key1"], "value1")
self.assertEqual(env["REPO__key2"], "value2")
def test_get_env_vars_invalid_revision_graceful(self):
"""Test that invalid revision error is handled gracefully."""
with utils_for_test.TempGitTree() as tempdir:
proj = self._get_project(tempdir)
proj.GetRevisionId.side_effect = error.ManifestInvalidRevisionError(
"revision not found"
)
env = proj.GetEnvVars()
self.assertEqual(env["REPO_LREV"], "")