Audit cask flight step conversions #268
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Close incomplete pull requests | |
| on: | |
| # `pull_request_target` has a write token, so this workflow must only ever run trusted | |
| # base-branch code and must never checkout or execute pull request head code. | |
| pull_request_target: | |
| types: | |
| - opened | |
| - edited | |
| - reopened | |
| permissions: {} | |
| defaults: | |
| run: | |
| shell: bash -euo pipefail {0} | |
| concurrency: | |
| group: "incomplete-pr-${{ github.event.pull_request.number }}" | |
| cancel-in-progress: true | |
| jobs: | |
| manage: | |
| # Restrict this write-token workflow to Homebrew/brew. The first step also | |
| # fails if a repository checkout has occurred. | |
| if: >- | |
| github.repository == 'Homebrew/brew' && | |
| github.event.pull_request.user.login != 'BrewTestBot' && | |
| github.event.pull_request.user.login != 'dependabot[bot]' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| # Read the trusted base-branch pull request template through the API; write only | |
| # the issue comment and pull request state needed here. | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| INCOMPLETE_TEMPLATE_COMMENT_MARKER: "<!-- incomplete-pr-template -->" | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_TEMPLATE_URL: ${{ github.server_url }}/${{ github.repository }}/blob/main/.github/PULL_REQUEST_TEMPLATE.md | |
| steps: | |
| - name: Verify no checkout | |
| run: | | |
| if git -C "${GITHUB_WORKSPACE:?}" rev-parse --is-inside-work-tree &>/dev/null | |
| then | |
| echo "Refusing to run after a repository checkout in ${GITHUB_WORKSPACE}." >&2 | |
| exit 1 | |
| fi | |
| - name: Write pull request body | |
| # Do not add a checkout here. `pull_request_target` has a write token, so this | |
| # step must only use inline trusted code and API responses from `main`. | |
| env: | |
| # Bind PR-controlled strings as environment variables instead of interpolating | |
| # them into shell code. | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| run: | | |
| # This workflow intentionally uses `pull_request_target` so it can close and | |
| # reopen forked pull requests. Keep this self-contained and never execute | |
| # pull request code from this step. | |
| mkdir -p "${RUNNER_TEMP:?}/incomplete-prs" | |
| printf "%s" "${PR_BODY}" >"${RUNNER_TEMP}/incomplete-prs/pr-body" | |
| - name: Fetch pull request template | |
| run: | | |
| gh api "repos/${GITHUB_REPOSITORY:?}/contents/.github/PULL_REQUEST_TEMPLATE.md?ref=main" \ | |
| --jq ".content" | | |
| base64 --decode >"${RUNNER_TEMP:?}/incomplete-prs/template" | |
| - name: Check pull request template | |
| id: template | |
| run: | | |
| complete_template="$( | |
| ruby - \ | |
| "${RUNNER_TEMP}/incomplete-prs/pr-body" \ | |
| "${RUNNER_TEMP}/incomplete-prs/template" <<'RUBY' | |
| CHECKBOX_MARKER = /\A- \[[ xX]\] / | |
| CHECKED_CHECKBOX_MARKER = /\A- \[[xX]\] / | |
| HTML_COMMENT_LINE = /\A<!--.*-->\z/ | |
| MARKDOWN_HORIZONTAL_LINE = /\A-+\z/ | |
| NORMALISED_CHECKBOX_MARKER = '- [ ] ' | |
| REQUIRED_TEMPLATE_PERCENTAGE = 75 | |
| PERCENTAGE_SCALE = 100 | |
| def lines(path) | |
| File.read(path, mode: 'rb') | |
| .encode('UTF-8', invalid: :replace, undef: :replace) | |
| .lines(chomp: true) | |
| end | |
| def normalise_lines(path) | |
| lines(path).each_with_object([]) do |line, normalised_lines| | |
| line = line.strip.sub(CHECKBOX_MARKER, NORMALISED_CHECKBOX_MARKER) | |
| # Ignore blank lines. | |
| next if line.empty? | |
| # Ignore --- markdown horizontal lines. | |
| next if line.match?(MARKDOWN_HORIZONTAL_LINE) | |
| # Ignore <!-- HTML comments --> | |
| next if line.match?(HTML_COMMENT_LINE) | |
| normalised_lines << line | |
| end.uniq | |
| end | |
| pr_body_path = ARGV.fetch(0) | |
| template_path = ARGV.fetch(1) | |
| pr_lines = normalise_lines(pr_body_path) | |
| template_lines = normalise_lines(template_path) | |
| matching_template_lines = template_lines.count { |line| pr_lines.include?(line) } | |
| scaled_matching_percentage = matching_template_lines * PERCENTAGE_SCALE | |
| scaled_required_percentage = template_lines.count * REQUIRED_TEMPLATE_PERCENTAGE | |
| preserves_template = scaled_matching_percentage >= scaled_required_percentage | |
| has_checked_checkbox = lines(pr_body_path).any? { |line| line.match?(CHECKED_CHECKBOX_MARKER) } | |
| has_non_template_content = (pr_lines - template_lines).any? | |
| puts preserves_template && (has_checked_checkbox || has_non_template_content) | |
| RUBY | |
| )" | |
| case "${complete_template}" in | |
| true | false) ;; | |
| *) | |
| echo "Unexpected template completion result: ${complete_template}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| echo "complete_template=${complete_template}" >>"${GITHUB_OUTPUT:?}" | |
| - name: Find incomplete template comment | |
| id: comments | |
| if: >- | |
| (github.event.pull_request.state == 'closed' && | |
| steps.template.outputs.complete_template == 'true') || | |
| (github.event.pull_request.state != 'closed' && | |
| steps.template.outputs.complete_template == 'false') | |
| run: | | |
| comment_ids="$( | |
| gh api --paginate "repos/${GITHUB_REPOSITORY:?}/issues/${PR_NUMBER:?}/comments" \ | |
| --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | contains(\"${INCOMPLETE_TEMPLATE_COMMENT_MARKER:?}\"))) | .id" | |
| )" | |
| if [[ -n "${comment_ids}" ]] | |
| then | |
| echo "has_incomplete_template_comment=true" >>"${GITHUB_OUTPUT:?}" | |
| else | |
| echo "has_incomplete_template_comment=false" >>"${GITHUB_OUTPUT:?}" | |
| fi | |
| - name: Find pull request closer | |
| id: closer | |
| if: >- | |
| github.event.pull_request.state == 'closed' && | |
| steps.template.outputs.complete_template == 'true' | |
| run: | | |
| closed_by="$(gh api "repos/${GITHUB_REPOSITORY:?}/issues/${PR_NUMBER:?}" --jq ".closed_by.login // \"\"")" | |
| echo "closed_by=${closed_by}" >>"${GITHUB_OUTPUT:?}" | |
| - name: Reopen completed pull request | |
| if: >- | |
| github.event.pull_request.state == 'closed' && | |
| steps.template.outputs.complete_template == 'true' && | |
| steps.closer.outputs.closed_by == 'github-actions[bot]' && | |
| steps.comments.outputs.has_incomplete_template_comment == 'true' | |
| run: | | |
| gh api --method PATCH "repos/${GITHUB_REPOSITORY:?}/pulls/${PR_NUMBER:?}" \ | |
| -f state=open | |
| - name: Comment on incomplete pull request | |
| if: >- | |
| github.event.pull_request.state != 'closed' && | |
| steps.template.outputs.complete_template == 'false' && | |
| steps.comments.outputs.has_incomplete_template_comment != 'true' | |
| run: | | |
| gh api --method POST "repos/${GITHUB_REPOSITORY:?}/issues/${PR_NUMBER:?}/comments" \ | |
| --raw-field body="$( | |
| cat <<COMMENT | |
| ${INCOMPLETE_TEMPLATE_COMMENT_MARKER} | |
| Thanks for your pull request. This has been closed because it appears to use an incomplete or outdated pull request template. | |
| Please edit the pull request body to fill in the current [pull request template](${PR_TEMPLATE_URL:?}). This workflow will reopen the pull request automatically once the template is complete. | |
| COMMENT | |
| )" | |
| - name: Close incomplete pull request | |
| if: >- | |
| github.event.pull_request.state != 'closed' && | |
| steps.template.outputs.complete_template == 'false' | |
| run: | | |
| gh api --method PATCH "repos/${GITHUB_REPOSITORY:?}/pulls/${PR_NUMBER:?}" \ | |
| -f state=closed |