diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 2c627ba..4fffe23 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -1,9 +1,19 @@ name: Dependabot auto-merge -# Actions are the only ecosystem Dependabot watches here, and an action bump is -# the update that piles up unread until the queue is too long to review -# honestly. Auto-merge is queued, not immediate: GitHub still waits for the -# required checks, and `scripts/check_site.py` is what those checks run. +# What this does and does not do: +# +# auto-merged - GitHub Actions bumps, and patch bumps of anything else +# left for you - anything with a minor or major in it +# +# A patch release and an action bump are the updates that pile up unread until +# the queue is too long to review honestly. A minor bump can change behaviour, +# so it keeps a human. Auto-merge is queued, not immediate: GitHub still waits +# for the required checks to pass, and a red build leaves the PR open. +# +# Actions are the only ecosystem Dependabot watches here - the site is +# hand-written HTML with no package manifest - so in practice only the first +# branch below is ever taken. The rest is kept identical to the other +# repositories so the six files stay comparable. on: pull_request_target @@ -18,27 +28,85 @@ jobs: contents: write pull-requests: write steps: - # Reads the update type from the PR that Dependabot opened. Nothing from - # the branch is checked out or executed, which is what makes + # Reads the update metadata from the PR that Dependabot opened. Nothing + # from the branch is checked out or executed, which is what makes # pull_request_target safe to use here. - id: metadata uses: dependabot/fetch-metadata@v2 - - name: Approve and queue the merge - if: >- - steps.metadata.outputs.package-ecosystem == 'github_actions' || - steps.metadata.outputs.update-type == 'version-update:semver-patch' + - id: verdict + name: Decide whether this one can merge itself + env: + ECOSYSTEM: ${{ steps.metadata.outputs.package-ecosystem }} + UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }} + UPDATED: ${{ steps.metadata.outputs.updated-dependencies-json }} + # A grouped pull request has no single update type: fetch-metadata + # leaves `update-type` empty and puts one entry per dependency in + # `updated-dependencies-json`. Reading only `update-type` sent every + # grouped bump to a human, including a group where all seven were + # patches - which is exactly the group worth merging unattended, and the + # reason the groups exist at all. + run: | + PATCH="version-update:semver-patch" + + if [ "$ECOSYSTEM" = "github_actions" ]; then + echo "auto=true" >> "$GITHUB_OUTPUT" + echo "reason=an actions bump" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ -n "$UPDATE_TYPE" ] && [ "$UPDATE_TYPE" != "null" ]; then + if [ "$UPDATE_TYPE" = "$PATCH" ]; then + echo "auto=true" >> "$GITHUB_OUTPUT" + echo "reason=a patch bump" >> "$GITHUB_OUTPUT" + else + echo "auto=false" >> "$GITHUB_OUTPUT" + echo "reason=${UPDATE_TYPE#version-update:semver-} is not a patch" >> "$GITHUB_OUTPUT" + fi + exit 0 + fi + + total=$(jq 'length' <<<"$UPDATED") + if [ "$total" -eq 0 ]; then + # No metadata to read. Refusing is the only safe reading of silence. + echo "auto=false" >> "$GITHUB_OUTPUT" + echo "reason=no update metadata to read" >> "$GITHUB_OUTPUT" + exit 0 + fi + + patches=$(jq --arg p "$PATCH" '[.[] | select(.updateType == $p)] | length' <<<"$UPDATED") + if [ "$total" -eq "$patches" ]; then + echo "auto=true" >> "$GITHUB_OUTPUT" + echo "reason=a group of $total, every one a patch" >> "$GITHUB_OUTPUT" + else + echo "auto=false" >> "$GITHUB_OUTPUT" + echo "reason=a group of $total, $((total - patches)) beyond patch" >> "$GITHUB_OUTPUT" + fi + + - name: Queue the merge + if: steps.verdict.outputs.auto == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR: ${{ github.event.pull_request.html_url }} + REASON: ${{ steps.verdict.outputs.reason }} + # No `gh pr review --approve`. This organisation does not permit Actions + # to approve pull requests, so that call fails with + # + # GitHub Actions is not permitted to approve pull requests + # + # and under `bash -e` it took the whole step down before the merge was + # ever queued - which is how auto-merge came to be broken in every + # repository at once. + # + # The approval was never needed: branch protection here requires the CI + # check and no reviews. If a review requirement is ever added, this needs + # a token that is not GITHUB_TOKEN, not a retry. run: | - gh pr review --approve "$PR" + echo "auto-merging: $REASON" gh pr merge --auto --squash "$PR" - name: Explain why this one was left alone - if: >- - steps.metadata.outputs.package-ecosystem != 'github_actions' && - steps.metadata.outputs.update-type != 'version-update:semver-patch' + if: steps.verdict.outputs.auto != 'true' env: - TYPE: ${{ steps.metadata.outputs.update-type }} - run: echo "$TYPE is not auto-merged; this pull request needs a human." + REASON: ${{ steps.verdict.outputs.reason }} + run: echo "not auto-merged ($REASON); this pull request needs a human."