mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
The Validation step "Assert the base branch is fully merged into the PR head" ran main_merged.sh, which fetches the live tip of main when the job starts. PR runs wait in the queue for hours while image-update PRs land on main several times an hour, so nearly every queued run failed Validation on start and had to be re-pushed after another main merge (PR #1265: behind_by=0 at push, behind_by=9 while its run still queued). main_merged.sh takes a new INFINITO_MAIN_COMMIT override: when set, it fetches that full commit SHA from the remote and requires HEAD to contain it instead of the live tip; a set but malformed or empty value exits 2. The Validation step passes github.event.pull_request.base.sha, the main tip at event time, which the step's pull_request-only guard always provides. The run tests the merge against that base, so the gate still guarantees the tested tree contains it. The local pre-push hook (make test-main-merged) leaves the override unset and keeps checking the live tip: a push happens right now, so lagging the current main is the real defect there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
4.8 KiB
Python
135 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.files import PROJECT_ROOT
|
|
|
|
SCRIPT = PROJECT_ROOT / "scripts" / "git" / "assert" / "main_merged.sh"
|
|
MAIN_COMMIT_ENV = "INFINITO_MAIN_COMMIT" # nocheck: git maintainer tooling knob, not a deployment env key
|
|
|
|
|
|
class MainMergedFixture:
|
|
"""A bare ``origin`` whose ``main`` moved on after ``head`` branched off it.
|
|
|
|
Args:
|
|
tmp: directory that holds the bare remote and its two clones.
|
|
"""
|
|
|
|
def __init__(self, tmp: str) -> None:
|
|
root = Path(tmp)
|
|
inherited = {
|
|
k: v
|
|
for k, v in os.environ.items()
|
|
if not k.startswith(("GIT_", "INFINITO_"))
|
|
}
|
|
self.env = {
|
|
**inherited,
|
|
"GIT_CONFIG_GLOBAL": os.devnull,
|
|
"GIT_CONFIG_NOSYSTEM": "1",
|
|
"GIT_AUTHOR_NAME": "test",
|
|
"GIT_AUTHOR_EMAIL": "test@example.invalid",
|
|
"GIT_COMMITTER_NAME": "test",
|
|
"GIT_COMMITTER_EMAIL": "test@example.invalid",
|
|
}
|
|
origin = root / "origin.git"
|
|
maintainer = root / "maintainer"
|
|
self.head = root / "head"
|
|
self.git(root, "init", "--bare", "-b", "main", str(origin))
|
|
self.git(root, "init", "-b", "main", str(maintainer))
|
|
self.git(maintainer, "remote", "add", "origin", str(origin))
|
|
self.base = self.commit(maintainer, "base")
|
|
self.git(maintainer, "push", "origin", "main")
|
|
self.git(root, "clone", str(origin), str(self.head))
|
|
self.git(self.head, "switch", "-c", "feature")
|
|
self.commit(self.head, "feature")
|
|
self.main_tip = self.commit(maintainer, "main moved on")
|
|
self.git(maintainer, "push", "origin", "main")
|
|
|
|
def git(self, cwd: Path, *args: str) -> str:
|
|
return subprocess.run(
|
|
["git", *args],
|
|
cwd=cwd,
|
|
env=self.env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
).stdout.strip()
|
|
|
|
def commit(self, repo: Path, message: str) -> str:
|
|
self.git(repo, "commit", "--allow-empty", "-m", message)
|
|
return self.git(repo, "rev-parse", "HEAD")
|
|
|
|
def merge_main(self) -> None:
|
|
self.git(self.head, "pull", "--no-rebase", "--no-edit", "origin", "main")
|
|
|
|
def run_script(self, main_commit: str | None) -> subprocess.CompletedProcess:
|
|
"""Run the script in ``head``.
|
|
|
|
Args:
|
|
main_commit: value of the main-commit override; ``None`` leaves it unset.
|
|
"""
|
|
env = dict(self.env)
|
|
if main_commit is not None:
|
|
env[MAIN_COMMIT_ENV] = main_commit
|
|
return subprocess.run(
|
|
["bash", str(SCRIPT)],
|
|
cwd=self.head,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
class TestMainMergedPinnedCommit(unittest.TestCase):
|
|
def test_contained_commit_passes_after_main_moved_on(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
fixture = MainMergedFixture(tmp)
|
|
result = fixture.run_script(fixture.base)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertIn(f"origin/main@{fixture.base} is fully merged", result.stderr)
|
|
|
|
def test_commit_missing_from_head_fails(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
fixture = MainMergedFixture(tmp)
|
|
result = fixture.run_script(fixture.main_tip)
|
|
self.assertEqual(result.returncode, 1, result.stderr)
|
|
self.assertIn(
|
|
f"origin/main@{fixture.main_tip} has 1 commit(s) not merged into HEAD",
|
|
result.stderr,
|
|
)
|
|
|
|
def test_empty_commit_fails_loudly(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
fixture = MainMergedFixture(tmp)
|
|
result = fixture.run_script("")
|
|
self.assertEqual(result.returncode, 2, result.stderr)
|
|
self.assertIn("must be a full commit SHA", result.stderr)
|
|
|
|
|
|
class TestMainMergedLiveTip(unittest.TestCase):
|
|
def test_live_tip_missing_from_head_fails(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
fixture = MainMergedFixture(tmp)
|
|
result = fixture.run_script(None)
|
|
self.assertEqual(result.returncode, 1, result.stderr)
|
|
self.assertIn(
|
|
"origin/main has 1 commit(s) not merged into HEAD", result.stderr
|
|
)
|
|
|
|
def test_merged_live_tip_passes(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
fixture = MainMergedFixture(tmp)
|
|
fixture.merge_main()
|
|
result = fixture.run_script(None)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertIn("origin/main is fully merged into HEAD", result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|