Skip to content

Commit 35c7297

Browse files
authored
Fix link check on pushes that have no base commit (#21644)
## Problem `.github/workflows/_link_check.yml` decides which commits to diff by inspecting the event that triggered its *caller*. That is the wrong place to make the decision. A reusable workflow is called from more than one context, and the value it reaches for is not present in all of them. For a pull request it uses the pull request base. For everything else it uses `github.event.before`, which is only meaningful for a push to a branch that already existed. | trigger | `github.event.before` | before this change | | --- | --- | --- | | pull request | not used, PR base instead | changed lines checked | | push to an existing branch | previous branch tip | changed lines checked | | push creating a tag | all zeros | job fails, exit 128 | | push creating a branch | all zeros | job fails, exit 128 | | scheduled run | absent | whole tree checked, by accident | | manual dispatch | absent | whole tree checked, by accident | The all-zero rows fail outright, which is what every `ciflow/nightly/*` tag push hits: ``` git fetch --no-tags --depth=1 origin 0000000000000000000000000000000000000000 fatal: remote error: upload-pack: not our ref 0000000000000000000000000000000000000000 ##[error]Process completed with exit code 128 ``` The two "by accident" rows are worth spelling out, because the behavior there is correct but nothing makes it so. With `before` absent, the fetch becomes `git fetch --no-tags --depth=1 origin ""`, which quietly succeeds by fetching the default head. Then in ```yaml ./scripts/lint_urls.sh $( ... echo "${{ github.event.before }}" "${{ github.sha }}" ) ``` the unquoted command substitution word splits the empty first field away, so the script gets one argument rather than two, fails its `[ $# -eq 2 ]` test, and falls through to whole tree mode. The right thing happens for the wrong reason, and only for as long as that substitution stays unquoted. ## Fix Take the base as an input instead: ```yaml base_ref: description: Commit to diff against. Empty, or all zeros, means check the whole tree. type: string required: false default: '' ``` Empty means there is nothing to diff against. The three lint scripts already support that: given two arguments they diff a range, given none they scan the whole tree. So the empty case needs no fallback logic, it just calls them with no arguments. The all-zero SHA maps to empty, because a caller forwarding `github.event.before` has no way to avoid producing it. With the base known up front, the separate `Fetch base ref` step has nothing left to decide, so it folds into the lint step. Each job loses a step and the file gets shorter, 107 lines to 100: ```yaml args=() # A push creating a tag or branch reports an all zero SHA no remote can serve. if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then git fetch --no-tags --depth=1 origin "$BASE_REF" args=("$BASE_REF" "$HEAD_REF") fi ./scripts/lint_urls.sh "${args[@]}" || { ``` `lint.yml` passes the pull request base on pull requests and `github.event.before` otherwise, one added line. The job level `if:` conditions still read `github.event_name` and the pull request labels. Those decide whether a job runs at all, which is a different question and out of scope here. What changes is how the diff base is computed. One incidental correctness gain: the old steps checked out `inputs.ref` but diffed against `github.sha` or `head.sha` read from the caller's event. Now the checked out commit and the diff head are the same input, so they cannot drift apart. That mismatch was not hypothetical, see below. ## What `nightly.yml` stops running, and why that is also a fix `nightly.yml` now runs the check only on the schedule and on a manual dispatch. That drops two triggers: pull requests touching `.github/workflows/nightly.yml`, and `ciflow/nightly/*` tag pushes. **The pull request trigger was not providing coverage.** `nightly.yml` passes `ref: ${{ github.sha }}`, which on a pull request is the merge commit. The head commit is therefore never checked out and never fetched, so `git diff base..head` cannot resolve: ``` Checking changed files between bcceab1..7e66c47 fatal: Invalid revision range bcceab1..7e66c47 ``` `lint_urls.sh` and `lint_xrefs.sh` consume that diff through a process substitution, so the failure is swallowed and they report success having checked zero URLs. `lint_file_size.sh` reads it into a plain assignment, which `set -e` turns into a hard failure, and there is no `skip-file-size-lint` label to escape it. Run 31125514863 shows all three: two green jobs that examined nothing, and one red one. So this removes two no-ops and one permanently red, unskippable check. Pull requests keep the real check from `lint.yml`, against their own base, on the path where the head commit is actually checked out. **The tag push trigger is a policy call, not a bug fix.** To be precise about causation: the exit 128 there is removed by the zero-SHA guard plus the empty default, not by the `if:`. Left alone, a `ciflow/nightly/*` push would now run a clean whole tree scan. It is skipped because there were 19 such tag pushes in the last day alone, a whole tree URL scan takes about six minutes, and it would be red on the pre-existing dead links every time. **One narrow gap remains.** A pull request targeting the `nightly` branch gets no link check at all, since `lint.yml` excludes that branch through `branches-ignore`. Before this change it got the two no-ops and the red file-size job, so nothing that worked is lost. Happy to add a correct `pull_request` arm to `nightly.yml` if reviewers would rather close it. ## Result | trigger | after this change | | --- | --- | | pull request, via `lint.yml` | changed lines only, unchanged | | push to `main` or an existing `release/*` | changed lines only, unchanged | | push creating a `release/*` branch | whole tree, instead of exit 128 | | push of a `ciflow/nightly/*` tag | skipped, instead of exit 128 | | pull request touching `nightly.yml` | skipped, was two no-ops and one red job | | scheduled nightly | whole tree, unchanged, now by design | | manual dispatch, either workflow | whole tree, unchanged | `lint.yml` declares no `tags:` in its push trigger, so the tag case only ever reached `_link_check.yml` through `nightly.yml`. ## Testing - All three workflow files parse, and every `run` block in `_link_check.yml` is clean under `shellcheck -S style`. - Ran the rewritten lint step as a standalone script for every shape of input, across all three jobs: a real base SHA fetches and passes two arguments; an all-zero SHA and an empty value skip the fetch and pass none; an unservable base exits 128 with git's own message and never reaches the lint script. Repeated with and without `set -u`, and with stray positional parameters already set, so the argument list cannot depend on how the runner invokes the step. - Ran `scripts/lint_file_size.sh` with no arguments to confirm the whole tree path works end to end: 9033 files checked, no failures. - The whole tree paths of `lint_urls.sh` and `lint_xrefs.sh` use `git grep -P`, which needs a git built with PCRE support that I did not have locally, so I did not run those myself. They are already exercised in CI though, by the current scheduled nightly, which reaches whole tree mode through the accident described above. Its most recent `lint-urls` log checks about 2148 URLs and reports 2126 OK, 15 WARN, 7 FAIL. Those 7 are genuine dead links, unrelated to this change, and it neither fixes nor hides them, so the scheduled job stays red until they are updated. - On this pull request, `Lint` runs the changed workflows against themselves and all three link check jobs pass with `BASE_REF` and `HEAD_REF` taken from the inputs. The `nightly` run on the `ciflow/nightly` tag, the case that used to exit 128, is skipped. ## Not addressed here `lint_urls.sh` and `lint_xrefs.sh` end their input pipeline with `|| true`, needed because `git grep` exits 1 when it finds nothing. It also means that if `git grep` fails outright the lint reports zero findings and passes. That is the same swallowing described above, it is pre-existing, and it deserves its own change.
1 parent 793b4d6 commit 35c7297

3 files changed

Lines changed: 38 additions & 42 deletions

File tree

.github/workflows/_link_check.yml

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ on:
44
ref:
55
type: string
66
required: true
7+
base_ref:
8+
description: Commit to diff against. Empty, or all zeros, means check the whole tree.
9+
type: string
10+
required: false
11+
default: ''
712

813
jobs:
914
lint-urls:
@@ -16,22 +21,18 @@ jobs:
1621
- uses: actions/checkout@v4
1722
with:
1823
ref: ${{ inputs.ref }}
19-
- name: Fetch base ref
20-
run: |
21-
if [ "${{ github.event_name }}" = "pull_request" ]; then
22-
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}"
23-
else
24-
git fetch --no-tags --depth=1 origin "${{ github.event.before }}"
25-
fi
2624
- name: Lint URLs
25+
env:
26+
BASE_REF: ${{ inputs.base_ref }}
27+
HEAD_REF: ${{ inputs.ref }}
2728
run: |
28-
./scripts/lint_urls.sh $(
29-
if [ "${{ github.event_name }}" = "pull_request" ]; then
30-
echo "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}"
31-
else
32-
echo "${{ github.event.before }}" "${{ github.sha }}"
33-
fi
34-
) || {
29+
args=()
30+
# A push creating a tag or branch reports an all zero SHA no remote can serve.
31+
if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then
32+
git fetch --no-tags --depth=1 origin "$BASE_REF"
33+
args=("$BASE_REF" "$HEAD_REF")
34+
fi
35+
./scripts/lint_urls.sh "${args[@]}" || {
3536
echo
3637
echo "URL lint failed."
3738
echo "If this is a transient outage, you can bypass it by adding the \`skip-url-lint\` label to your PR."
@@ -49,22 +50,18 @@ jobs:
4950
- uses: actions/checkout@v4
5051
with:
5152
ref: ${{ inputs.ref }}
52-
- name: Fetch base ref
53-
run: |
54-
if [ "${{ github.event_name }}" = "pull_request" ]; then
55-
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}"
56-
else
57-
git fetch --no-tags --depth=1 origin "${{ github.event.before }}"
58-
fi
5953
- name: Lint cross-references
54+
env:
55+
BASE_REF: ${{ inputs.base_ref }}
56+
HEAD_REF: ${{ inputs.ref }}
6057
run: |
61-
./scripts/lint_xrefs.sh $(
62-
if [ "${{ github.event_name }}" = "pull_request" ]; then
63-
echo "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}"
64-
else
65-
echo "${{ github.event.before }}" "${{ github.sha }}"
66-
fi
67-
) || {
58+
args=()
59+
# A push creating a tag or branch reports an all zero SHA no remote can serve.
60+
if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then
61+
git fetch --no-tags --depth=1 origin "$BASE_REF"
62+
args=("$BASE_REF" "$HEAD_REF")
63+
fi
64+
./scripts/lint_xrefs.sh "${args[@]}" || {
6865
echo
6966
echo "Xref lint failed."
7067
echo "If this is a transient outage, you can bypass it by adding the \`skip-xref-lint\` label to your PR."
@@ -82,23 +79,19 @@ jobs:
8279
- uses: actions/checkout@v4
8380
with:
8481
ref: ${{ inputs.ref }}
85-
- name: Fetch base ref
86-
run: |
87-
if [ "${{ github.event_name }}" = "pull_request" ]; then
88-
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}"
89-
else
90-
git fetch --no-tags --depth=1 origin "${{ github.event.before }}"
91-
fi
9282
- name: Lint file sizes
83+
env:
84+
BASE_REF: ${{ inputs.base_ref }}
85+
HEAD_REF: ${{ inputs.ref }}
9386
run: |
87+
args=()
88+
# A push creating a tag or branch reports an all zero SHA no remote can serve.
89+
if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then
90+
git fetch --no-tags --depth=1 origin "$BASE_REF"
91+
args=("$BASE_REF" "$HEAD_REF")
92+
fi
9493
chmod +x ./scripts/lint_file_size.sh
95-
./scripts/lint_file_size.sh $(
96-
if [ "${{ github.event_name }}" = "pull_request" ]; then
97-
echo "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}"
98-
else
99-
echo "${{ github.event.before }}" "${{ github.sha }}"
100-
fi
101-
) || {
94+
./scripts/lint_file_size.sh "${args[@]}" || {
10295
echo
10396
echo "File size lint failed: some files exceed the 1 MB limit."
10497
echo "If you really need large files, consider using Git LFS or storing them elsewhere."

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,4 @@ jobs:
125125
uses: ./.github/workflows/_link_check.yml
126126
with:
127127
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
128+
base_ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}

.github/workflows/nightly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
pytorchbot-token: ${{ secrets.GH_PYTORCHBOT_TOKEN }}
3333

3434
link-check:
35+
# Whole tree scans only. Pull requests get the same check from lint.yml.
36+
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
3537
needs: update-pytorch-commit-hash
3638
uses: ./.github/workflows/_link_check.yml
3739
with:

0 commit comments

Comments
 (0)