diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f3ce49e15ec..454fc692ae3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -365,6 +365,9 @@ jobs: - name: Validate package test selection run: python3 tests/test_ci_select_package_tests.py + - name: Validate shallow complexity comparison fetching + run: python3 tests/test_ci_fetch_complexity_base.py + - name: Validate Ghostty helper cache failure handling run: bash tests/test_ghostty_cli_helper_cache.sh diff --git a/.github/workflows/web-complexity.yml b/.github/workflows/web-complexity.yml index 80edebbc9345..37ef381a1692 100644 --- a/.github/workflows/web-complexity.yml +++ b/.github/workflows/web-complexity.yml @@ -29,9 +29,14 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 + fetch-depth: 1 persist-credentials: false + - name: Fetch comparison revision + env: + COMPARISON_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + run: bash scripts/ci/fetch-complexity-base.sh "$COMPARISON_SHA" + - name: Detect web changes id: scope shell: bash diff --git a/scripts/ci/fetch-complexity-base.sh b/scripts/ci/fetch-complexity-base.sh new file mode 100644 index 000000000000..9b4188fa7574 --- /dev/null +++ b/scripts/ci/fetch-complexity-base.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Complexity compares trees and baseline files, not ancestry. Fetch the exact +# comparison commit without downloading the repository's entire history. +set -euo pipefail + +revision="${1:-}" +if [ -z "$revision" ] || [ "$revision" = "0000000000000000000000000000000000000000" ]; then + exit 0 +fi +if [[ ! "$revision" =~ ^[0-9a-fA-F]{40}$ ]]; then + echo "Expected a full comparison commit SHA" >&2 + exit 2 +fi +if ! git cat-file -e "${revision}^{commit}" 2>/dev/null; then + git -c protocol.version=2 fetch --no-tags --depth=1 origin "$revision" +fi +git cat-file -e "${revision}^{commit}" diff --git a/tests/test_ci_fetch_complexity_base.py b/tests/test_ci_fetch_complexity_base.py new file mode 100644 index 000000000000..4a2fc32101d0 --- /dev/null +++ b/tests/test_ci_fetch_complexity_base.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Exercise comparison-commit fetching against actual shallow Git repositories.""" + +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +HELPER = ROOT / "scripts/ci/fetch-complexity-base.sh" + + +class ComparisonBaseTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + root = Path(self.temp.name) + self.origin = root / "origin" + self.origin.mkdir() + self.git(self.origin, "init", "-q") + (self.origin / "web").mkdir() + (self.origin / "web/oxlint-complexity-baseline.txt").write_text("original baseline\n") + self.git(self.origin, "add", ".") + self.git(self.origin, "commit", "-qm", "base") + self.base = self.git(self.origin, "rev-parse", "HEAD") + for number in range(8): + (self.origin / "web/change.ts").write_text(f"export const value = {number};\n") + self.git(self.origin, "add", ".") + self.git(self.origin, "commit", "-qm", f"change {number}") + self.head = self.git(self.origin, "rev-parse", "HEAD") + self.clone = root / "checkout" + self.git(root, "clone", "-q", "--depth=1", self.origin.as_uri(), str(self.clone)) + + def git(self, cwd, *args): + return subprocess.check_output([ + "git", "-c", "user.name=CI", "-c", "user.email=ci@example.test", + "-c", "core.hooksPath=/dev/null", *args, + ], cwd=cwd, text=True, stderr=subprocess.PIPE).strip() + + def fetch(self, revision): + return subprocess.run(["bash", str(HELPER), revision], cwd=self.clone, + text=True, capture_output=True) + + def test_old_base_is_available_without_unshallowing_or_moving_head(self): + self.assertEqual(self.git(self.clone, "rev-list", "--count", "HEAD"), "1") + result = self.fetch(self.base) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.git(self.clone, "show", f"{self.base}:web/oxlint-complexity-baseline.txt"), + "original baseline") + self.assertEqual(self.git(self.clone, "diff", "--name-only", self.base, self.head), "web/change.ts") + self.assertEqual(self.git(self.clone, "rev-parse", "HEAD"), self.head) + self.assertEqual(self.git(self.clone, "rev-parse", "--is-shallow-repository"), "true") + self.assertEqual(self.git(self.clone, "rev-list", "--count", self.head, self.base), "2") + + def test_available_commit_needs_no_network(self): + self.git(self.clone, "remote", "set-url", "origin", "/nonexistent/cmux-comparison-test") + result = self.fetch(self.head) + self.assertEqual(result.returncode, 0, result.stderr) + + def test_initial_push_without_a_base_needs_no_network(self): + self.git(self.clone, "remote", "set-url", "origin", "/nonexistent/cmux-comparison-test") + for revision in ("", "0" * 40): + result = self.fetch(revision) + self.assertEqual(result.returncode, 0, result.stderr) + + def test_invalid_or_unavailable_base_fails(self): + for revision in ("main", "--upload-pack=anything", "a" * 40): + result = self.fetch(revision) + self.assertNotEqual(result.returncode, 0) + + +if __name__ == "__main__": + unittest.main()