diff --git a/.github/scripts/fetch-validation-history.sh b/.github/scripts/fetch-validation-history.sh new file mode 100755 index 0000000..65fddc1 --- /dev/null +++ b/.github/scripts/fetch-validation-history.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +base=${1:?Usage: fetch-validation-history.sh } +head=$(git rev-parse HEAD) + +if ! git cat-file -e "$base^{commit}" 2>/dev/null; then + git fetch --no-tags --depth=1 origin "$base" +fi + +base=$(git rev-parse "$base^{commit}") +shallow_file=$(git rev-parse --git-path shallow) + +# Deepening is needed until no range commit can reach $base through unfetched history. +range_needs_deepening() { + [[ -s $shallow_file ]] || return 1 + + local roots joins join + roots=$(git rev-list --max-parents=0 "$base..$head") || exit "$?" + [[ -z $roots ]] || return 0 + + joins=$(git rev-list --boundary "$base..$head" | sed -n 's/^-//p') || exit "$?" + for join in $joins; do + roots=$(git rev-list --max-parents=0 "$base" --not "$join") || exit "$?" + [[ -z $roots ]] || return 0 + done + return 1 +} + +deepen=8 +while range_needs_deepening; do + git fetch --no-tags --deepen="$deepen" origin "$head" "$base" + deepen=$((deepen * 2)) +done diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 0c5abcb..d76b99b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,6 +1,8 @@ name: Flake checks on: pull_request: + merge_group: + types: [checks_requested] permissions: actions: read # lets hestia detect evicted cache entries upfront contents: read @@ -8,14 +10,25 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - check: + validate-commits: runs-on: ubuntu-latest - timeout-minutes: 30 + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} steps: + - name: Compute initial fetch depth + id: fetch-depth + env: + PR_COMMITS: ${{ github.event.pull_request.commits || 0 }} + run: echo "depth=$((PR_COMMITS + 2))" >> "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: ${{ steps.fetch-depth.outputs.depth }} + - name: Fetch validation history + run: bash .github/scripts/fetch-validation-history.sh "$BASE_SHA" - uses: DeterminateSystems/nix-installer-action@3138316df39ed29be04236d7ffc686fa525866aa # v23 - uses: Mic92/hestia@dfed9ced335d28978ba74e513939a10db1f71025 # v3.1.0 with: # Do not upload paths that cache.nixos.org already serves. upstream-cache-filter: true - - run: nix flake check --no-update-lock-file + - name: Validate commits + run: nix run --no-update-lock-file '.?shallow=1#validate-commits' -- "$BASE_SHA..HEAD" diff --git a/flake.nix b/flake.nix index e5e1f62..c0e75f6 100644 --- a/flake.nix +++ b/flake.nix @@ -19,6 +19,7 @@ ./nix/site.nix ./nix/devshell.nix ./nix/spellcheck.nix + ./nix/apps/validate-commits ]; }; } diff --git a/nix/apps/validate-commits/default.nix b/nix/apps/validate-commits/default.nix new file mode 100644 index 0000000..3cf5a77 --- /dev/null +++ b/nix/apps/validate-commits/default.nix @@ -0,0 +1,22 @@ +{ + perSystem = + { pkgs, ... }: + let + validate-commits = pkgs.writeShellApplication { + name = "validate-commits"; + runtimeInputs = [ + pkgs.git + pkgs.nix + ]; + text = builtins.readFile ./validate-commits.sh; + }; + in + { + packages.validate-commits = validate-commits; + apps.validate-commits = { + type = "app"; + program = "${validate-commits}/bin/validate-commits"; + meta.description = "Validate the flake across pull request commits"; + }; + }; +} diff --git a/nix/apps/validate-commits/validate-commits.sh b/nix/apps/validate-commits/validate-commits.sh new file mode 100644 index 0000000..cc13aac --- /dev/null +++ b/nix/apps/validate-commits/validate-commits.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + echo "Usage: validate-commits " +} + +if [[ ${1:-} == --help ]]; then + usage + exit 0 +fi + +if [[ $# -ne 1 ]]; then + usage >&2 + exit 2 +fi + +repo_root=$(git rev-parse --show-toplevel) +revision_range=$1 +commits=$(git rev-list --reverse "$revision_range") + +if [[ -z $commits ]]; then + echo "No commits in range." + exit 0 +fi + +while read -r commit; do + echo "Validating $commit" + nix flake check --no-update-lock-file --print-build-logs \ + "git+file://$repo_root?rev=$commit&shallow=1" +done <<<"$commits"