From 68c3486216e3f4fd1e66302f18aef4489b3e6b33 Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Thu, 11 Jun 2026 10:56:56 +0000 Subject: [PATCH] =?UTF-8?q?fix(lvl5):=20lint=20executor=20PR-path=20?= =?UTF-8?q?=E2=80=94=20abra=20lint=20selects+checks=20out=20the=20repo=20D?= =?UTF-8?q?EFAULT=20BRANCH;=20scratch=20clone=20of=20a=20detached=20per-ru?= =?UTF-8?q?n=20tree=20has=20none=20(FATA,=20live=20400-402),=20and=20a=20s?= =?UTF-8?q?tale=20default=20would=20be=20silently=20linted=20instead=20of?= =?UTF-8?q?=20the=20PR=20head.=20Force=20local=20main=20AT=20the=20tested?= =?UTF-8?q?=20ref=20+=20repoint=20origin=20to=20the=20scratch=20itself=20(?= =?UTF-8?q?offline=20tag=20fetch,=20no=20drift).=20Regression=20test=20wit?= =?UTF-8?q?h=20detached=20two-commit=20source=20proves=20exact-ref=20conte?= =?UTF-8?q?nt=20is=20linted.=20247=20unit=20tests=20green;=20real-abra=20d?= =?UTF-8?q?etached-source=20smoke=20pass.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- runner/harness/lint.py | 37 +++++++++++++++++++++++++++++-------- tests/unit/test_lint.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/runner/harness/lint.py b/runner/harness/lint.py index 9e28ae1..78af02d 100644 --- a/runner/harness/lint.py +++ b/runner/harness/lint.py @@ -128,14 +128,35 @@ def run_lint(recipe: str, ref: str | None, out_dir: str | None) -> dict: text=True, timeout=LINT_TIMEOUT, ) - if ref: - subprocess.run( - ["git", "-C", clone, "checkout", "-f", "--quiet", ref], - check=True, - capture_output=True, - text=True, - timeout=LINT_TIMEOUT, - ) + # abra lint SELECTS AND CHECKS OUT THE REPO'S DEFAULT BRANCH before linting (observed + # live, build 400-402: a clone of a detached-HEAD per-run tree has no local branch → + # FATA "failed to select default branch"; and if a default branch existed at some OTHER + # commit, abra would silently lint THAT, not the tested ref). So: force a local `main` + # AT exactly the tested ref and make it the default everywhere abra could look — + # HEAD, and origin (repointed to the scratch itself, which also turns abra's tag + # force-fetch into an offline no-op; the run's true tags were already cloned in). + subprocess.run( + ["git", "-C", clone, "checkout", "-f", "--quiet", "-B", "main"] + + ([ref] if ref else []), + check=True, + capture_output=True, + text=True, + timeout=LINT_TIMEOUT, + ) + subprocess.run( + ["git", "-C", clone, "remote", "set-url", "origin", clone], + check=True, + capture_output=True, + text=True, + timeout=LINT_TIMEOUT, + ) + subprocess.run( + ["git", "-C", clone, "remote", "set-head", "origin", "main"], + check=False, # cosmetic: helps any origin-HEAD-based default-branch lookup + capture_output=True, + text=True, + timeout=LINT_TIMEOUT, + ) # catalogue: R006 (published catalogue version) reads it; servers: harmless, some abra # paths stat it. Symlink the live ones (read-only use). for shared in ("catalogue", "servers"): diff --git a/tests/unit/test_lint.py b/tests/unit/test_lint.py index 32e1edc..19dd365 100644 --- a/tests/unit/test_lint.py +++ b/tests/unit/test_lint.py @@ -183,6 +183,41 @@ def test_run_lint_missing_recipe_is_unver_not_raise(tmp_path, monkeypatch): assert (tmp_path / "artifacts" / "lint.txt").exists() +def test_run_lint_detached_pr_tree_lints_exact_ref(tmp_path, monkeypatch): + # PR-path regression (live builds 400-402): the per-run tree sits at a DETACHED HEAD (the PR + # sha), and abra lint selects+checks out the repo's DEFAULT BRANCH before linting. run_lint + # must (a) not FATA on the branchless clone, and (b) make the default branch BE the tested + # ref — never some other commit's content. Source repo: branch main at C1, detached at C2 + # (the "PR head", which adds marker-c2). The shim passes only if the linted tree contains + # marker-c2 AND HEAD is a local branch. + repo = _mkrecipe(tmp_path) + (repo / "marker-c2.txt").write_text("pr head\n") + subprocess.run(["git", "add", "."], cwd=repo, check=True) + subprocess.run( + ["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "c2"], + cwd=repo, + check=True, + ) + c2 = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=repo, check=True, capture_output=True, text=True + ).stdout.strip() + subprocess.run(["git", "branch", "-f", "main", "HEAD^"], cwd=repo, check=True) + subprocess.run(["git", "checkout", "-q", c2], cwd=repo, check=True) # detached, like a PR run + monkeypatch.setenv("ABRA_DIR", str(tmp_path / "abra")) + out = TABLE_OK.replace("\r\n", "\\n") + shim = ( + 'C="$ABRA_DIR/recipes/fakerec"\n' + 'git -C "$C" symbolic-ref HEAD >&2 || { echo "FATA no default branch" >&2; exit 1; }\n' + '[ -f "$C/marker-c2.txt" ] || { echo "FATA wrong ref linted" >&2; exit 1; }\n' + f'printf "{out}"\nexit 0\n' + ) + monkeypatch.setenv("PATH", _shim(tmp_path, shim) + os.pathsep + os.environ["PATH"]) + res = L.run_lint("fakerec", c2, str(tmp_path / "artifacts")) + assert res == {"status": "pass", "detail": "", "rules_failed": []} + txt = (tmp_path / "artifacts" / "lint.txt").read_text() + assert "refs/heads/main" in txt # HEAD was a local default branch when abra ran + + def test_run_lint_abra_blowup_is_unver(tmp_path, monkeypatch): _mkrecipe(tmp_path) monkeypatch.setenv("ABRA_DIR", str(tmp_path / "abra"))