blob: 4d227b9f8cc1d05663dfabf4ab70de676c5a3e80 [file]
name: Changelog Comment Commands
# Lets a PR author (or any committer) toggle the 'no-changelog' label by
# commenting on the PR, without needing the repo write access that setting a
# label directly requires:
#
# /no-changelog - mark the PR as not needing a changelog entry
# /needs-changelog - undo that
#
# The command applies/removes the label, and the label change re-triggers the
# "Validate Changelog" workflow (which listens for labeled/unlabeled events),
# so the red check turns green on its own.
#
# This workflow deliberately never checks out or executes PR code: it only
# reads the comment payload and calls the labels API, so running with a
# write-scoped token is safe.
on:
issue_comment:
types:
- created
concurrency:
group: changelog-comment-command-${{ github.event.issue.number }}
permissions:
issues: write # add/remove labels, add a reaction, post a reply
pull-requests: write
jobs:
handle-command:
# Pull request comments only, from a human, that mention one of our commands.
# The exact (line-anchored) match happens in the parse step below.
if: |
github.event.issue.pull_request &&
github.event.comment.user.type != 'Bot' &&
(contains(github.event.comment.body, '/no-changelog') ||
contains(github.event.comment.body, '/needs-changelog'))
runs-on: ubuntu-latest
steps:
- name: Parse command
id: parse
env:
BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
# A command must be alone on its own line, so that quoted text ("> /no-changelog")
# or a mention inside a sentence does not trigger it.
command=none
if printf '%s\n' "$BODY" | grep -iE '^[[:space:]]*/no-changelog[[:space:]]*\r?$' > /dev/null; then
command=add
fi
if printf '%s\n' "$BODY" | grep -iE '^[[:space:]]*/needs-changelog[[:space:]]*\r?$' > /dev/null; then
if [ "$command" = "add" ]; then
command=conflict
else
command=remove
fi
fi
echo "command=${command}" >> "$GITHUB_OUTPUT"
echo "Parsed command: ${command}"
- name: Check who is asking
id: authz
if: steps.parse.outputs.command != 'none'
env:
COMMENTER: ${{ github.event.comment.user.login }}
PR_AUTHOR: ${{ github.event.issue.user.login }}
ASSOCIATION: ${{ github.event.comment.author_association }}
run: |
set -euo pipefail
# The PR author may always toggle it on their own PR; otherwise the
# commenter needs write access, which author_association reflects.
if [ "$COMMENTER" = "$PR_AUTHOR" ]; then
allowed=true
else
case "$ASSOCIATION" in
OWNER|MEMBER|COLLABORATOR) allowed=true ;;
*) allowed=false ;;
esac
fi
echo "allowed=${allowed}" >> "$GITHUB_OUTPUT"
echo "Commenter '${COMMENTER}' (${ASSOCIATION}) allowed: ${allowed}"
- name: Apply label change
if: steps.authz.outputs.allowed == 'true' && (steps.parse.outputs.command == 'add' || steps.parse.outputs.command == 'remove')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMAND: ${{ steps.parse.outputs.command }}
run: |
set -euo pipefail
LABEL=no-changelog
has_label=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" \
--jq "[.[].name] | index(\"${LABEL}\") != null")
if [ "$COMMAND" = "remove" ]; then
if [ "$has_label" = "true" ]; then
gh api -X DELETE "repos/${REPO}/issues/${PR_NUMBER}/labels/${LABEL}" --silent
echo "::notice::Removed '${LABEL}' from PR#${PR_NUMBER}"
else
echo "::notice::PR#${PR_NUMBER} does not have '${LABEL}'; nothing to do"
fi
exit 0
fi
if [ "$has_label" = "true" ]; then
echo "::notice::PR#${PR_NUMBER} already has '${LABEL}'; nothing to do"
exit 0
fi
gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/labels" -f "labels[]=${LABEL}" --silent
echo "::notice::Added '${LABEL}' to PR#${PR_NUMBER}"
- name: Acknowledge with a reaction
if: steps.authz.outputs.allowed == 'true' && (steps.parse.outputs.command == 'add' || steps.parse.outputs.command == 'remove')
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
COMMENT_ID: ${{ github.event.comment.id }}
run: |
gh api -X POST "repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \
-f content='+1' --silent
- name: Explain why the command was not applied
if: |
steps.parse.outputs.command != 'none' &&
(steps.authz.outputs.allowed != 'true' || steps.parse.outputs.command == 'conflict')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
ALLOWED: ${{ steps.authz.outputs.allowed }}
COMMENTER: ${{ github.event.comment.user.login }}
run: |
set -euo pipefail
if [ "$ALLOWED" != "true" ]; then
printf '%s\n' \
"Sorry @${COMMENTER}, only the author of this pull request or a Solr committer" \
"can toggle the \`no-changelog\` label. Please ask a committer to do it for you." \
> reply.md
else
printf '%s\n' \
"That comment contains both changelog commands, so nothing was changed." \
"Please comment just one of them, alone on its own line." \
> reply.md
fi
gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file reply.md