1name: PR template guard
2
3on:
4 pull_request_target:
5 types: [opened, reopened, edited, synchronize]
6
7permissions:
8 contents: read
9 pull-requests: write
10 issues: write
11
12jobs:
13 check:
14 runs-on: ubuntu-latest
15 if: >
16 github.repository == 'ikhsanddy/pixies' &&
17 !endsWith(github.event.pull_request.user.login, '[bot]')
18 steps:
19 - name: Check pull request body
20 id: check
21 env:
22 BODY: ${{ github.event.pull_request.body }}
23 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 REPO: ${{ github.repository }}
25 BASE_SHA: ${{ github.event.pull_request.base.sha }}
26 run: |
27 strip() { tr -d '\r' | sed -e 's/<!--.*-->//g' -e '/<!--/,/-->/d'; }
28
29 printf '%s' "$BODY" | strip > body.md
30
31 gh api "repos/$REPO/contents/.github/PULL_REQUEST_TEMPLATE.md?ref=$BASE_SHA" \
32 --jq '.content' | base64 -d | strip > template.md
33
34 section() {
35 awk -v want="$2" '
36 /^#{1,6} / { keep = (index(tolower($0), tolower(want)) > 0); next }
37 keep { print }
38 ' "$1"
39 }
40
41 items() {
42 section "$1" "$2" \
43 | grep -E '^[[:space:]]*- \[[ xX]\]' \
44 | sed -E -e 's/^[[:space:]]*- \[[ xX]\][[:space:]]*//' -e 's/[[:space:]]+$//' \
45 | tr -s ' ' | tr '[:upper:]' '[:lower:]' \
46 | grep -v 'other:' | sort || true
47 }
48
49 headings() {
50 grep -E '^#{1,6} ' "$1" \
51 | sed -E -e 's/^#{1,6}[[:space:]]*//' -e 's/[[:space:]]+$//' \
52 | tr -s ' ' | tr '[:upper:]' '[:lower:]' | sort || true
53 }
54
55 problems=""
56 add() { problems="${problems}- $1"$'\n'; }
57
58 tampered() {
59 items template.md "$1" > canonical.txt
60 items body.md "$1" > given.txt
61 [ -n "$(comm -3 canonical.txt given.txt || true)" ]
62 }
63
64 if ! grep -qi '^#\{1,6\} .*Changes made to' body.md || ! grep -qi '^#\{1,6\} .*Checklist' body.md; then
65 add "the pull request template was not used"
66 else
67 headings template.md > canonical_headings.txt
68 headings body.md > given_headings.txt
69 if [ -n "$(comm -3 canonical_headings.txt given_headings.txt || true)" ]; then
70 add "the section headings were edited, please leave them as they are"
71 fi
72
73 if grep -qi '^#\{1,6\} .*Description' body.md; then
74 description=$(section body.md "Description" | grep -v '^\s*$' || true)
75 if [ -z "$description" ]; then
76 add "the description is empty"
77 fi
78 fi
79
80 if tampered "Changes made to"; then
81 add "the options under \"Changes made to ...\" were edited, please leave them as they are"
82 elif ! section body.md "Changes made to" | grep -qi '^\s*- \[x\]'; then
83 add "no option is selected under \"Changes made to ...\""
84 fi
85
86 if tampered "Checklist"; then
87 add "the checklist was edited, please leave the items as they are"
88 elif section body.md "Checklist" | grep -q '^\s*- \[ \]'; then
89 add "not every item in the checklist is checked"
90 fi
91 fi
92
93 {
94 echo "problems<<EOF"
95 printf '%s' "$problems"
96 echo "EOF"
97 } >> "$GITHUB_OUTPUT"
98
99 - name: Update comment
100 env:
101 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102 PR: ${{ github.event.pull_request.number }}
103 REPO: ${{ github.repository }}
104 AUTHOR: ${{ github.event.pull_request.user.login }}
105 PROBLEMS: ${{ steps.check.outputs.problems }}
106 MARKER: "<!-- pr-template-guard -->"
107 run: |
108 comment_id=$(gh api --paginate "repos/$REPO/issues/$PR/comments" \
109 --jq ".[] | select(.body | contains(\"$MARKER\")) | .id" | head -n1)
110
111 if [ -z "$PROBLEMS" ]; then
112 if [ -n "$comment_id" ]; then
113 gh api -X DELETE "repos/$REPO/issues/comments/$comment_id"
114 fi
115 gh pr edit "$PR" --repo "$REPO" --remove-label "incomplete template" || true
116 exit 0
117 fi
118
119 {
120 echo "$MARKER"
121 echo "Hey @$AUTHOR, thanks for the pull request!"
122 echo
123 echo "Before we can review it, could you complete the pull request template? Right now:"
124 echo
125 printf '%s' "$PROBLEMS"
126 echo
127 echo "You can edit the description at any time, this comment disappears once everything is filled in."
128 } > comment.md
129
130 if [ -n "$comment_id" ]; then
131 gh api -X PATCH "repos/$REPO/issues/comments/$comment_id" -F body=@comment.md >/dev/null
132 else
133 gh pr comment "$PR" --repo "$REPO" --body-file comment.md
134 fi
135
136 gh pr edit "$PR" --repo "$REPO" --add-label "incomplete template" || true
137