mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
Merge pull request #1282 from infinito-nexus/fix/ci/validation-main-merged-base-sha
fix(ci): check the PR head against the main it was tested with
This commit is contained in:
6 files changed
+164
-9
No files matched your search
@@ -109,12 +109,13 @@ jobs:
|
||||
PR_SCOPE: ${{ inputs.pr_scope }}
|
||||
run: ./scripts/meta/resolve/pr/branch_prefix.sh
|
||||
|
||||
- name: 🔀 Assert the base branch is fully merged into the PR head
|
||||
- name: 🔀 Assert the event's base commit is merged into the PR head
|
||||
if: ${{ github.event_name == 'pull_request' && inputs.pr_scope != '' }}
|
||||
working-directory: pr-head
|
||||
env:
|
||||
INFINITO_MAIN_REMOTE: origin
|
||||
INFINITO_MAIN_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
INFINITO_MAIN_COMMIT: ${{ github.event.pull_request.base.sha }}
|
||||
run: bash "${GITHUB_WORKSPACE}/.base-tooling/scripts/git/assert/main_merged.sh"
|
||||
|
||||
- name: 🔧 Set up Python
|
||||
|
||||
@@ -4,23 +4,37 @@
|
||||
# from the current tip, so a branch can never be pushed while it lags main.
|
||||
#
|
||||
# Env overrides: INFINITO_MAIN_REMOTE (default origin), INFINITO_MAIN_BRANCH
|
||||
# (default main).
|
||||
# (default main), INFINITO_MAIN_COMMIT (unset: check the live branch tip; set:
|
||||
# fetch and check this full commit SHA of that branch instead, e.g. the base
|
||||
# SHA a CI event carries).
|
||||
set -euo pipefail
|
||||
|
||||
remote="${INFINITO_MAIN_REMOTE:-origin}" # nocheck: git maintainer tooling knob, not a deployment env key
|
||||
branch="${INFINITO_MAIN_BRANCH:-main}" # nocheck: git maintainer tooling knob, not a deployment env key
|
||||
|
||||
if [[ -v INFINITO_MAIN_COMMIT ]]; then
|
||||
target="${INFINITO_MAIN_COMMIT}" # nocheck: git maintainer tooling knob, not a deployment env key
|
||||
if [[ ! "$target" =~ ^[0-9a-f]{40}([0-9a-f]{24})?$ ]]; then
|
||||
echo "❌ assert-main-merged: INFINITO_MAIN_COMMIT must be a full commit SHA, got '${target}'." >&2
|
||||
exit 2
|
||||
fi
|
||||
label="${remote}/${branch}@${target}"
|
||||
else
|
||||
target="$branch"
|
||||
label="${remote}/${branch}"
|
||||
fi
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
echo "assert-main-merged: fetching ${remote}/${branch} ..." >&2
|
||||
git fetch --quiet "$remote" "$branch"
|
||||
echo "assert-main-merged: fetching ${label} ..." >&2
|
||||
git fetch --quiet "$remote" "$target"
|
||||
|
||||
if git merge-base --is-ancestor FETCH_HEAD HEAD; then
|
||||
echo "✅ assert-main-merged: ${remote}/${branch} is fully merged into HEAD." >&2
|
||||
echo "✅ assert-main-merged: ${label} is fully merged into HEAD." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
behind="$(git rev-list --count HEAD..FETCH_HEAD)"
|
||||
echo "❌ assert-main-merged: ${remote}/${branch} has ${behind} commit(s) not merged into HEAD." >&2
|
||||
echo "❌ assert-main-merged: ${label} has ${behind} commit(s) not merged into HEAD." >&2
|
||||
echo " Merge upstream main first, e.g.: git pull ${remote} ${branch}" >&2
|
||||
exit 1
|
||||
@@ -71,10 +71,16 @@ class TestWorktreeSwarmLabSubnet(unittest.TestCase):
|
||||
with self.subTest(slot=slot):
|
||||
subnet = self._subnet(slot)
|
||||
clashes = sorted(
|
||||
name for name, network in self.reserved.items() if subnet.overlaps(network)
|
||||
name
|
||||
for name, network in self.reserved.items()
|
||||
if subnet.overlaps(network)
|
||||
)
|
||||
self.assertEqual(
|
||||
clashes, [], f"slot {slot} lab {subnet} overlaps {clashes}"
|
||||
)
|
||||
self.assertNotIn(
|
||||
subnet, seen, f"slot {slot} repeats slot {seen.get(subnet)}"
|
||||
)
|
||||
self.assertEqual(clashes, [], f"slot {slot} lab {subnet} overlaps {clashes}")
|
||||
self.assertNotIn(subnet, seen, f"slot {slot} repeats slot {seen.get(subnet)}")
|
||||
seen[subnet] = slot
|
||||
|
||||
|
||||
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,134 @@
|
||||
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()
|
||||
Reference in new issue
Block a user