From 9eb865bb0209ef7c4391bb171a26e97c5b73b296 Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Mon, 21 Sep 2026 15:54:39 +0800 Subject: [PATCH] ci: skip the heavy jobs when nothing but documentation changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every push and PR paid the full four-minute job — fmt, clippy, the offline suite, a release-profile build and the dependency audit — even when the diff was a README or AGENTS edit, and every master push built and published an image for a commit that cannot have changed it. `ci.yml` gains a `changes` gate job: a push or PR whose *entire* diff is markdown skips `test`, which then reports as *skipped* instead of missing — the reason this is a gate job and not a workflow-level `paths` filter, which leaves a required status check waiting for a check run that will never appear. Anything non-markdown (and an empty diff, e.g. a re-run of the same commit) runs the full job, so a new directory of code cannot slip through a stale allowlist. `schedule`/`workflow_dispatch`, which have no `before` commit, also run it. `docker.yml`'s `should-build` gate now also skips a branch push that touched none of the image's inputs (`Dockerfile`, `docker-entrypoint.sh`, `.dockerignore`, the manifests, `Cargo.lock`, this workflow, anything under `crates/`); tag pushes always build. Checked against this repo's real ranges: the docs-only commit 667f523 (AGENTS.md) → `code=false` (test skipped) and skip, a workflow commit → `code=true` and build, the h2 bump (Cargo.lock) → build. --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++ .github/workflows/docker.yml | 25 ++++++++++++++----- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b71fb1b..fd89abe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ name: CI # job that exercises the real source sites and the token-gated pixiv tests. # # Layering: +# changes — decides whether anything but documentation changed; a docs-only +# push/PR skips `test` (which then reports as skipped, not missing). # test — fmt + clippy + the full offline unit suite + a release-profile # build + cargo-audit dependency gate. Runs on every push and PR, # including forks (it needs no secrets). @@ -46,7 +48,53 @@ env: RUST_BACKTRACE: 1 jobs: + # Docs-only changes skip the heavy job: a README edit does not need a four + # minute Rust build (and it cannot break one). A gate job rather than a + # workflow-level `paths` filter — that leaves the run without a `test` check + # at all, and a required status check then waits for something that will + # never be reported, while a *skipped* job reports as neutral. + changes: + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + code: ${{ steps.diff.outputs.code }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 # the diff below needs the pushed range + - id: diff + shell: bash + run: | + set -euo pipefail + zero=0000000000000000000000000000000000000000 + if [ "${{ github.event_name }}" = "pull_request" ]; then + base="origin/${{ github.base_ref }}" + git fetch --quiet --no-tags origin "${{ github.base_ref }}" + changed="$(git diff --name-only "$base...HEAD")" + else + before="${{ github.event.before }}" + if [ -z "$before" ] || [ "$before" = "$zero" ]; then + # New branch or force push: no usable base to compare against, + # so the full suite runs. Same for schedule/dispatch, which have + # no `before` at all. + changed="" + else + changed="$(git diff --name-only "$before..${{ github.sha }}")" + fi + fi + # Only a change that is *entirely* markdown may skip the job; + # anything else — and an empty diff, i.e. a re-run of the same + # commit — counts as code. + code=true + if [ -n "$changed" ] && ! grep -qvE '\.md$' <<<"$changed"; then + code=false + fi + echo "changed: ${changed:-}" + echo "code=$code" >> "$GITHUB_OUTPUT" + test: + needs: changes + if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest # Generous on purpose: the release-profile build below is cold on the very # first run (thin LTO + codegen-units = 1 across every dependency), and a diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index af76284..4d41e1a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -75,6 +75,7 @@ jobs: - id: check shell: bash run: | + zero=0000000000000000000000000000000000000000 if [ "$GITHUB_REF_TYPE" = "branch" ]; then # A branch run can start before the release tag for its commit # reaches the remote — pushing master first is the usual way to hit @@ -87,13 +88,25 @@ jobs: # pushing the branch and the tag together # (`git push origin master vX.Y.Z`) removes it. git fetch --tags --force --quiet origin + if git tag --points-at "$GITHUB_SHA" | grep -q .; then + echo "commit already tagged; the tag run builds the image" + echo "build=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + # Nothing the image is made of changed — a documentation or + # workflow-only commit — so there is no new image to publish. The + # PR trigger's path list plus the crate sources, which the image + # compiles into the binary. + before="${{ github.event.before }}" + if [ -n "$before" ] && [ "$before" != "$zero" ] \ + && ! git diff --name-only "$before..$GITHUB_SHA" \ + | grep -qE '^(Dockerfile|docker-entrypoint\.sh|\.dockerignore|Cargo\.toml|Cargo\.lock|\.github/workflows/docker\.yml|crates/)'; then + echo "no build input changed; skipping the image build" + echo "build=false" >> "$GITHUB_OUTPUT" + exit 0 + fi fi - if [ "$GITHUB_REF_TYPE" = "branch" ] && git tag --points-at "$GITHUB_SHA" | grep -q .; then - echo "commit already tagged; the tag run builds the image" - echo "build=false" >> "$GITHUB_OUTPUT" - else - echo "build=true" >> "$GITHUB_OUTPUT" - fi + echo "build=true" >> "$GITHUB_OUTPUT" # No `actions/checkout` here on purpose: `docker/build-push-action` defaults # to the Git context (`https://github.com//.git#`), so