|
13 | 13 | NIGHTLY_TOOLCHAIN: nightly-2026-02-05 |
14 | 14 |
|
15 | 15 | jobs: |
16 | | - check-ci-fmt: |
17 | | - name: "Check if CI fmt failed" |
18 | | - # Only act on PR runs that completed (success or failure — the fmt step |
19 | | - # uses continue-on-error so the workflow can "succeed" even when fmt fails). |
20 | | - if: github.event.workflow_run.event == 'pull_request' |
| 16 | + check-lint-failed: |
| 17 | + name: "Check if rust-lint failed" |
| 18 | + # Only run when the triggering workflow was for a PR. |
| 19 | + if: >- |
| 20 | + github.event.workflow_run.event == 'pull_request' |
| 21 | + && github.event.workflow_run.conclusion == 'failure' |
21 | 22 | runs-on: ubuntu-latest |
22 | 23 | timeout-minutes: 5 |
23 | 24 | outputs: |
24 | | - fmt-failed: ${{ steps.check.outputs.fmt-failed }} |
25 | | - head-sha: ${{ steps.check.outputs.head-sha }} |
26 | | - head-ref: ${{ steps.check.outputs.head-ref }} |
| 25 | + lint-failed: ${{ steps.check-job.outputs.lint-failed }} |
| 26 | + pr-head-sha: ${{ steps.resolve.outputs.head-sha }} |
| 27 | + pr-head-ref: ${{ steps.resolve.outputs.head-ref }} |
27 | 28 | steps: |
28 | | - - name: Check CI fmt step outcome |
29 | | - id: check |
| 29 | + - name: Resolve PR ref |
| 30 | + id: resolve |
30 | 31 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 |
31 | 32 | with: |
32 | 33 | script: | |
33 | | - const runId = context.payload.workflow_run.id; |
34 | | - const headSha = context.payload.workflow_run.head_sha; |
35 | | - const headBranch = context.payload.workflow_run.head_branch; |
| 34 | + const wr = context.payload.workflow_run; |
| 35 | + core.setOutput('head-sha', wr.head_sha); |
| 36 | + core.setOutput('head-ref', wr.head_branch); |
36 | 37 |
|
37 | | - // Find the "Rust (lint)" job in the CI run. |
38 | | - const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({ |
39 | | - ...context.repo, |
40 | | - run_id: runId, |
| 38 | + - name: Check if rust-lint job failed |
| 39 | + id: check-job |
| 40 | + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 |
| 41 | + with: |
| 42 | + script: | |
| 43 | + const jobs = await github.rest.actions.listJobsForWorkflowRun({ |
| 44 | + owner: context.repo.owner, |
| 45 | + repo: context.repo.repo, |
| 46 | + run_id: context.payload.workflow_run.id, |
41 | 47 | }); |
42 | | - const lintJob = jobs.find(j => j.name === 'Rust (lint)'); |
43 | | - if (!lintJob) { |
44 | | - core.info('Rust (lint) job not found — skipping'); |
45 | | - core.setOutput('fmt-failed', 'false'); |
46 | | - return; |
47 | | - } |
48 | | -
|
49 | | - // Find the fmt step by its id ("fmt") or name. |
50 | | - const fmtStep = lintJob.steps.find( |
51 | | - s => s.name === 'Rust Lint - Format' |
52 | | - ); |
53 | | - if (!fmtStep) { |
54 | | - core.info('fmt step not found — skipping'); |
55 | | - core.setOutput('fmt-failed', 'false'); |
56 | | - return; |
| 48 | + const lintJob = jobs.data.jobs.find(j => j.name === 'Rust (lint)'); |
| 49 | + const failed = lintJob && lintJob.conclusion === 'failure'; |
| 50 | + core.setOutput('lint-failed', failed ? 'true' : 'false'); |
| 51 | + if (!failed) { |
| 52 | + core.info('rust-lint did not fail, skipping fmt fix'); |
57 | 53 | } |
58 | 54 |
|
59 | | - const failed = fmtStep.conclusion === 'failure'; |
60 | | - core.info(`fmt step conclusion: ${fmtStep.conclusion}`); |
61 | | - core.setOutput('fmt-failed', String(failed)); |
62 | | - core.setOutput('head-sha', headSha); |
63 | | - core.setOutput('head-ref', headBranch); |
64 | | -
|
65 | 55 | apply-fmt: |
66 | 56 | name: "Apply formatting fix" |
67 | | - needs: check-ci-fmt |
68 | | - if: needs.check-ci-fmt.outputs.fmt-failed == 'true' |
| 57 | + needs: check-lint-failed |
| 58 | + if: needs.check-lint-failed.outputs.lint-failed == 'true' |
69 | 59 | runs-on: ubuntu-latest |
70 | 60 | timeout-minutes: 15 |
| 61 | + # The fmt-fix environment must be configured with required reviewers |
| 62 | + # in the repo settings. This creates an accept/reject gate on the PR |
| 63 | + # before the fmt commit is pushed. |
71 | 64 | environment: fmt-fix |
72 | 65 | permissions: |
73 | 66 | contents: write |
74 | 67 | steps: |
75 | 68 | - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 |
76 | 69 | with: |
77 | | - # Check out the exact commit that CI ran against, not the latest |
78 | | - # branch HEAD (which may have moved during the approval wait). |
79 | | - ref: ${{ needs.check-ci-fmt.outputs.head-sha }} |
| 70 | + ref: ${{ needs.check-lint-failed.outputs.pr-head-sha }} |
80 | 71 |
|
81 | 72 | - name: Install nightly for fmt |
82 | 73 | run: rustup toolchain install $NIGHTLY_TOOLCHAIN --component rustfmt |
83 | 74 |
|
84 | 75 | - name: Run cargo fmt |
85 | 76 | run: cargo +$NIGHTLY_TOOLCHAIN fmt --all |
86 | 77 |
|
87 | | - - name: Commit fix |
| 78 | + - name: Commit and push |
| 79 | + env: |
| 80 | + EXPECTED_SHA: ${{ needs.check-lint-failed.outputs.pr-head-sha }} |
| 81 | + BRANCH: ${{ needs.check-lint-failed.outputs.pr-head-ref }} |
88 | 82 | run: | |
89 | 83 | git config user.name "github-actions[bot]" |
90 | 84 | git config user.email "github-actions[bot]@users.noreply.github.com" |
|
94 | 88 |
|
95 | 89 | Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>" |
96 | 90 |
|
97 | | - - name: Push (fails if branch moved since CI ran) |
98 | | - env: |
99 | | - EXPECTED_SHA: ${{ needs.check-ci-fmt.outputs.head-sha }} |
100 | | - BRANCH: ${{ needs.check-ci-fmt.outputs.head-ref }} |
101 | | - run: | |
102 | 91 | # --force-with-lease atomically checks that the remote branch HEAD |
103 | 92 | # is still EXPECTED_SHA before pushing. If someone else pushed a |
104 | | - # commit to the PR between the CI run and approval, this fails safely. |
| 93 | + # commit to the PR between trigger and approval, this fails safely. |
105 | 94 | git push origin \ |
106 | 95 | "HEAD:refs/heads/$BRANCH" \ |
107 | 96 | "--force-with-lease=refs/heads/$BRANCH:$EXPECTED_SHA" |
0 commit comments