1- name : Close incomplete pull requests
1+ name : Check pull requests
22
33on :
44 # `pull_request_target` has a write token, so this workflow must only ever run trusted
@@ -16,7 +16,7 @@ defaults:
1616 shell : bash -euo pipefail {0}
1717
1818concurrency :
19- group : " incomplete -pr-${{ github.event.pull_request.number }}"
19+ group : " check -pr-${{ github.event.pull_request.number }}"
2020 cancel-in-progress : true
2121
2222jobs :
2929 github.event.pull_request.user.login != 'dependabot[bot]'
3030 runs-on : ubuntu-latest
3131 permissions :
32- # Read the trusted base-branch pull request template through the API; write only
33- # the issue comment and pull request state needed here.
32+ # Read the trusted base-branch pull request template and checker through
33+ # the API; write only the issue comment and pull request state needed here.
3434 contents : read
3535 issues : write
3636 pull-requests : write
@@ -60,64 +60,28 @@ jobs:
6060 # reopen forked pull requests. Keep this self-contained and never execute
6161 # pull request code from this step.
6262
63- mkdir -p "${RUNNER_TEMP:?}/incomplete -prs"
64- printf "%s" "${PR_BODY}" >"${RUNNER_TEMP}/incomplete -prs/pr- body"
63+ mkdir -p "${RUNNER_TEMP:?}/check -prs"
64+ printf "%s" "${PR_BODY}" >"${RUNNER_TEMP}/check -prs/body"
6565
6666 - name : Fetch pull request template
6767 run : |
6868 gh api "repos/${GITHUB_REPOSITORY:?}/contents/.github/PULL_REQUEST_TEMPLATE.md?ref=main" \
6969 --jq ".content" |
70- base64 --decode >"${RUNNER_TEMP:?}/incomplete-prs/template"
70+ base64 --decode >"${RUNNER_TEMP:?}/check-prs/template"
71+
72+ - name : Fetch template checker
73+ run : |
74+ gh api "repos/${GITHUB_REPOSITORY:?}/contents/.github/scripts/check_template.rb?ref=main" \
75+ --jq ".content" |
76+ base64 --decode >"${RUNNER_TEMP:?}/check_template.rb"
7177
7278 - name : Check pull request template
7379 id : template
7480 run : |
7581 complete_template="$(
76- ruby - \
77- "${RUNNER_TEMP}/incomplete-prs/pr-body" \
78- "${RUNNER_TEMP}/incomplete-prs/template" <<'RUBY'
79- CHECKBOX_MARKER = /\A- \[[ xX]\] /
80- CHECKED_CHECKBOX_MARKER = /\A- \[[xX]\] /
81- HTML_COMMENT_LINE = /\A<!--.*-->\z/
82- MARKDOWN_HORIZONTAL_LINE = /\A-+\z/
83- NORMALISED_CHECKBOX_MARKER = '- [ ] '
84- REQUIRED_TEMPLATE_PERCENTAGE = 75
85- PERCENTAGE_SCALE = 100
86-
87- def lines(path)
88- File.read(path, mode: 'rb')
89- .encode('UTF-8', invalid: :replace, undef: :replace)
90- .lines(chomp: true)
91- end
92-
93- def normalise_lines(path)
94- lines(path).each_with_object([]) do |line, normalised_lines|
95- line = line.strip.sub(CHECKBOX_MARKER, NORMALISED_CHECKBOX_MARKER)
96- # Ignore blank lines.
97- next if line.empty?
98- # Ignore --- markdown horizontal lines.
99- next if line.match?(MARKDOWN_HORIZONTAL_LINE)
100- # Ignore <!-- HTML comments -->
101- next if line.match?(HTML_COMMENT_LINE)
102-
103- normalised_lines << line
104- end.uniq
105- end
106-
107- pr_body_path = ARGV.fetch(0)
108- template_path = ARGV.fetch(1)
109-
110- pr_lines = normalise_lines(pr_body_path)
111- template_lines = normalise_lines(template_path)
112- matching_template_lines = template_lines.count { |line| pr_lines.include?(line) }
113- scaled_matching_percentage = matching_template_lines * PERCENTAGE_SCALE
114- scaled_required_percentage = template_lines.count * REQUIRED_TEMPLATE_PERCENTAGE
115- preserves_template = scaled_matching_percentage >= scaled_required_percentage
116- has_checked_checkbox = lines(pr_body_path).any? { |line| line.match?(CHECKED_CHECKBOX_MARKER) }
117- has_non_template_content = (pr_lines - template_lines).any?
118-
119- puts preserves_template && (has_checked_checkbox || has_non_template_content)
120- RUBY
82+ ruby "${RUNNER_TEMP:?}/check_template.rb" pull-request \
83+ "${RUNNER_TEMP}/check-prs/body" \
84+ "${RUNNER_TEMP}/check-prs/template"
12185 )"
12286 case "${complete_template}" in
12387 true | false) ;;
0 commit comments