diff --git a/.github/workflows/empty-diff.yml b/.github/workflows/empty-diff.yml new file mode 100644 index 0000000..9fdef63 --- /dev/null +++ b/.github/workflows/empty-diff.yml @@ -0,0 +1,49 @@ +name: empty-diff (reusable) + +# Fail a pull request when its diff against the base branch is empty. +# Eight no-op PRs landed on Glyndor/homebrew-tap and Glyndor/scoop-bucket +# before this gate existed: the work was already in main via squash-merge, +# the branch was recreated as `-v2`, and the second merge landed as a commit +# with zero files changed. CI is happy with that — a commit with no diff is +# valid git and a green GitHub Actions run, and only `git show --stat` flags +# it. +# +# Add this as an advisory caller in every repository that opens PRs against +# `main`, then promote it to a required status check once the emitted check +# name has stayed stable across a few weeks of green. Promoting it before +# the name has settled is how a phantom required check is created (see the +# `dco` and `line-limit` reusable docs for the by-name matching detail). + +on: + workflow_call: + +permissions: + contents: read + +concurrency: + group: empty-diff-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + empty-diff: + name: empty diff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Fail when the pull request changes nothing + env: + BASE: ${{ github.event.pull_request.base.ref }} + run: | + set -euo pipefail + if [ -z "${BASE:-}" ]; then + echo "::error::could not determine the pull request base ref — this reusable must be called from a pull_request-triggered workflow" + exit 1 + fi + if git diff --quiet "origin/${BASE}...HEAD"; then + echo "::error::this pull request has an empty diff against ${BASE}" + exit 1 + fi \ No newline at end of file diff --git a/docs/reusables/README.md b/docs/reusables/README.md index 703b483..9c5a009 100644 --- a/docs/reusables/README.md +++ b/docs/reusables/README.md @@ -10,6 +10,7 @@ which blocks every pull request until someone works out why. |---|---|---|---| | [`bun-ci`](bun-ci.md) | 1 | 0 | 3 | | [`dco`](dco.md) | 1 | 0 | 0 | +| [`empty-diff`](empty-diff.md) | 1 | 0 | 0 | | [`go-audit`](go-audit.md) | 2 | 0 | 4 | | [`go-ci`](go-ci.md) | 1 | 0 | 3 | | [`go-fuzz`](go-fuzz.md) | 1 | 0 | 3 | diff --git a/docs/reusables/empty-diff.md b/docs/reusables/empty-diff.md new file mode 100644 index 0000000..09d2539 --- /dev/null +++ b/docs/reusables/empty-diff.md @@ -0,0 +1,36 @@ +# empty-diff + +Fail a pull request when its diff against the base branch is empty. Eight no-op PRs landed on Glyndor/homebrew-tap and Glyndor/scoop-bucket before this gate existed: the work was already in main via squash-merge, the branch was recreated as `-v2`, and the second merge landed as a commit with zero files changed. CI is happy with that — a commit with no diff is valid git and a green GitHub Actions run, and only `git show --stat` flags it. Add this as an advisory caller in every repository that opens PRs against `main`, then promote it to a required status check once the emitted check name has stayed stable across a few weeks of green. Promoting it before the name has settled is how a phantom required check is created (see the `dco` and `line-limit` reusable docs for the by-name matching detail). + +## Calling it + +```yaml +# .github/workflows/ci.yml in the consuming repository +jobs: + example: + uses: Glyndor/.github/.github/workflows/empty-diff.yml@ # vX.Y.Z +``` + +Pin to a release commit SHA with the version in a comment. Never track a +branch: the SHA pin is what stops a change here reaching a repository +before that repository's own CI has passed on it. + +## Status checks it emits + +The name a consumer sees is ` / `, where `example` is +the caller's job id from the snippet above — a repository that names its job +`rust` sees `rust / …` instead. **These are the strings a ruleset matches**, and +a required check whose name nothing emits blocks every pull request. + +| Check | Emitted when | +|---|---| +| `example / empty diff` | always | + +## Inputs + +None. + +--- + +Generated from `.github/workflows/empty-diff.yml` by `scripts/render-reusable-docs.py`. +Edit the workflow, not this page.