This is an automated email from the ASF dual-hosted git repository.

janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 52fba69a4eb Better handling of "no-changelog" label (#4941)
52fba69a4eb is described below

commit 52fba69a4eb3c3787680605cef83d1c5458e1e3c
Author: Jan Høydahl <[email protected]>
AuthorDate: Wed Oct 7 15:10:19 2026 +0200

    Better handling of "no-changelog" label (#4941)
    
    Co-authored-by: Eric Pugh <[email protected]>
    Co-authored-by: David Smiley <[email protected]>
---
 .github/workflows/changelog-comment-command.yml | 150 ++++++++++++++++++++++++
 .github/workflows/validate-changelog.yml        |  35 ++++--
 dev-docs/changelog.adoc                         |  19 ++-
 3 files changed, 194 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/changelog-comment-command.yml 
b/.github/workflows/changelog-comment-command.yml
new file mode 100644
index 00000000000..4d227b9f8cc
--- /dev/null
+++ b/.github/workflows/changelog-comment-command.yml
@@ -0,0 +1,150 @@
+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
diff --git a/.github/workflows/validate-changelog.yml 
b/.github/workflows/validate-changelog.yml
index f4adbb83d71..66fcc1c0481 100644
--- a/.github/workflows/validate-changelog.yml
+++ b/.github/workflows/validate-changelog.yml
@@ -2,6 +2,14 @@ name: Validate Changelog
 
 on:
   pull_request:
+    # 'labeled'/'unlabeled' are included so that adding or removing the
+    # 'no-changelog' label re-runs this check on an already-failed PR.
+    types:
+      - opened
+      - reopened
+      - synchronize
+      - labeled
+      - unlabeled
     branches:
       - '*'
 
@@ -12,8 +20,15 @@ concurrency:
 jobs:
   validate-changelog:
     name: Check changelog entry
-    # Skip validation for Renovate PRs (solrbot) - they get changelog entries 
automatically
-    if: github.event.pull_request.user.login != 'solrbot'
+    # Skip validation for Renovate PRs (solrbot) - they get changelog entries 
automatically.
+    # On label events, only re-run for the 'no-changelog' label; other label 
churn
+    # (e.g. from the labeler workflow) leaves the existing check result 
untouched.
+    if: |
+      github.event.pull_request.user.login != 'solrbot' &&
+      (
+        (github.event.action != 'labeled' && github.event.action != 
'unlabeled') ||
+        github.event.label.name == 'no-changelog'
+      )
     runs-on: ubuntu-latest
 
     steps:
@@ -24,12 +39,13 @@ jobs:
 
     - name: Check for no-changelog label
       id: check-label
+      env:
+        HAS_NO_CHANGELOG_LABEL: ${{ 
contains(github.event.pull_request.labels.*.name, 'no-changelog') }}
       run: |
-        LABELS='${{ toJson(github.event.pull_request.labels) }}'
-        if echo "$LABELS" | grep -q '"no-changelog"'; then
-          echo "skip=true" >> $GITHUB_OUTPUT
+        if [ "$HAS_NO_CHANGELOG_LABEL" = "true" ]; then
+          echo "skip=true" >> "$GITHUB_OUTPUT"
         else
-          echo "skip=false" >> $GITHUB_OUTPUT
+          echo "skip=false" >> "$GITHUB_OUTPUT"
         fi
 
     - name: Check for changelog entry
@@ -47,8 +63,8 @@ jobs:
         # Check if only docs/tests/comments were changed (common exceptions)
         HAS_NON_DOCS_CHANGES=false
         while IFS= read -r file; do
-          # Skip changelog, docs, tests, and certain config files
-          if ! echo "$file" | grep -qE 
"(^changelog/|^solr/solr-ref-guide/|^dev-docs/|\.md$|\.adoc$|^solr/.*/test|\.gradle$|\.properties$|README|NOTICE|LICENSE)";
 then
+          # Skip changelog, docs, tests, CI config and certain config files
+          if ! echo "$file" | grep -qE 
"(^changelog/|^solr/solr-ref-guide/|^dev-docs/|^\.github/|\.md$|\.adoc$|^solr/.*/test|\.gradle$|\.properties$|README|NOTICE|LICENSE)";
 then
             HAS_NON_DOCS_CHANGES=true
             break
           fi
@@ -69,7 +85,8 @@ jobs:
         echo "For more information, see: dev-docs/changelog.adoc"
         echo ""
         echo "If this PR should not have a changelog entry (e.g., refactoring, 
internal cleanup),"
-        echo "add the 'no-changelog' label to this PR."
+        echo "comment '/no-changelog' on the PR (or add the 'no-changelog' 
label)."
+        echo "This check then re-runs automatically. To undo, comment 
'/needs-changelog'."
         exit 1
 
     - name: Validate changelog YAML structure
diff --git a/dev-docs/changelog.adoc b/dev-docs/changelog.adoc
index 0a0e9e5a1bd..70536cc0f04 100644
--- a/dev-docs/changelog.adoc
+++ b/dev-docs/changelog.adoc
@@ -127,7 +127,24 @@ The `validate-changelog` GitHub workflow automatically 
checks that:
 . **CHANGES.txt is not edited directly** - All changes must use the YAML 
fragment approach
 . **A changelog entry is added** - Code changes must include a corresponding 
YAML file in `changelog/unreleased/`
 
-If your change does not require a changelog entry, it is still possible to 
merge the PR.
+Pull requests that only touch documentation, tests, build files or CI 
configuration
+are exempt and pass without an entry.  Subjectively, some minor changes don't 
need one either.
+
+=== 4.1 Opting out of the changelog requirement
+
+If your change does not require a changelog entry (refactoring, internal 
cleanup,
+and so on), it is still possible to merge the PR. Mark it by adding the
+`no-changelog` label, which you can set in either of two ways:
+
+* Comment `/no-changelog` on the pull request, alone on its own line. This 
works
+  for the PR author as well as for committers, so a contributor without write
+  access does not need to ask anyone to set the label. Comment 
`/needs-changelog`
+  to undo it. Use a regular comment in the "Conversation" tab; commands inside
+  code review comments are not picked up.
+* Add or remove the `no-changelog` label directly (committers only).
+
+Either way the check re-runs automatically on the label change, so an already
+failed "Check changelog entry" run turns green without pushing a new commit.
 
 == 5. For release managers
 

Reply via email to