#!/usr/bin/env bash set -euo pipefail # The composite action's executable boundary (#218). Keeping the gather here # lets the offline contract test replace the forge and prove that failed and # partial reads cannot accidentally produce a green verdict. # # THE GATHER IS REST, THROUGH THE SHIM (#199). It was a single GraphQL query # issued through `gh`, asking GitHub for `closingIssuesReferences` — its own # parse of the closing keywords. Forgejo serves no GraphQL surface at all: # `/api/graphql` 404s on this instance, and a real forgejo-runner job arrives # with GITHUB_GRAPHQL_URL set to the empty string (lib/forge.sh's header). # There was nothing to translate it to, so it is re-expressed — exactly as # #188 re-expressed its own two GraphQL sites — over two reads both backends # already serve, plus a parser this repo owns. # # WHAT THE GRAPH GAVE THAT TWO READS MUST REPLACE. This file used to call the # graph "authoritative because it includes both closing keywords and sidebar # links". Those two halves resolve differently here: # # sidebar links Forgejo has no such concept — an issue is closed by a # keyword, never by a manual link. Nothing is lost. # commit messages Forgejo DOES honour closing keywords in commit messages. # A body-only parse would miss a PR that closes an issue # from a commit subject and let through exactly the # contradiction this action exists to catch. # # Hence both reads, unioned. The commit half is not optional. # shellcheck source=lib/forge.sh . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../lib/forge.sh" # shellcheck source=lib/issue_references.sh . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../lib/issue_references.sh" # shellcheck source=lib/closes_references.sh . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../lib/closes_references.sh" # No CEREMONY_FORGE_CLIENT declaration any more (#199 removes #198's): this # file speaks the shim, not a client. Fail CLOSED at the action boundary all # the same — "this action cannot produce a verdict" is the ACTION's contract # and stays a refusal, while "this check should not block the board" is the # CALLER's decision (@codex-reviewer-andresmgsl, #198). forge_preflight || exit 1 forge_select "" || exit 1 REPO="${REPO:-${GITHUB_REPOSITORY:-}}" [ -n "$REPO" ] || { echo "refs-not-closing: set REPO or GITHUB_REPOSITORY to owner/name" >&2 exit 1 } [ -n "${PR_NUMBER:-}" ] || { echo "refs-not-closing: pull request number is unavailable" >&2 exit 1 } body_file="$(mktemp)" closing_file="$(mktemp)" trap 'rm -f "$body_file" "$closing_file"' EXIT # A read that fails must never reach the parser: an empty body parses to an # empty closing set, which is a PASSING verdict this action never earned. # `set -e` covers the assignment, and the explicit checks below name which # read failed rather than leaving the operator to guess. if ! forge_api "repos/$REPO/pulls/$PR_NUMBER" --jq '.body // ""' >"$body_file"; then echo "refs-not-closing: could not read PR $PR_NUMBER's body — refusing a verdict" >&2 exit 1 fi # --paginate carries the completeness proof: the forgejo backend walks pages # and then compares what it collected against the server's declared # x-total-count, refusing a short gather (#188, #4699). That IS this action's # `hasNextPage` refusal, relocated rather than reinvented — upstream refused # past 100 closing references rather than issue a partial verdict, and an # incomplete commit read is the same failure wearing REST's clothes. commits_file="$(mktemp)" trap 'rm -f "$body_file" "$closing_file" "$commits_file"' EXIT if ! forge_api --paginate "repos/$REPO/pulls/$PR_NUMBER/commits" \ --jq '.[].commit.message' >"$commits_file"; then echo "refs-not-closing: could not read PR $PR_NUMBER's commits completely — refusing a partial verdict" >&2 exit 1 fi # The union. closes_references is line-oriented, so concatenating the body and # every commit message and parsing once IS the union of parsing each — and it # keeps one parse to reason about instead of two that could drift. cat "$body_file" "$commits_file" | closes_references >"$closing_file" mapfile -t closing_issues <"$closing_file" bash "$GITHUB_ACTION_PATH/refs-not-closing.sh" \ "$body_file" "${closing_issues[@]}"