temporary scripts for branch maintenance#15488
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Gradle-invokable maintenance automation for GitHub branch hygiene in apache/grails-core (protect release branches, delete stale branches) via two Groovy scripts executed from the root build.gradle.
Changes:
- Registers two new Gradle maintenance tasks:
deleteBranchesandprotectBranches. - Introduces
ProtectBranches.groovyto apply GitHub branch protection to a curated list of release branches. - Introduces
DeleteBranches.groovyto delete a curated list of merged/closed branches via the GitHub API.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| build.gradle | Adds Gradle tasks that execute the maintenance Groovy scripts via GroovyShell. |
| ProtectBranches.groovy | New script that calls GitHub’s branch protection API for listed branches. |
| DeleteBranches.groovy | New script that calls GitHub’s refs API to delete listed branches. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| conn.readTimeout = 30_000 | ||
| conn.requestMethod = method | ||
| conn.setRequestProperty("Authorization", "token ${token}") | ||
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") |
There was a problem hiding this comment.
GitHub’s REST API requires a valid User-Agent header on requests; HttpURLConnection won’t add one automatically. Add a User-Agent request property to avoid 403/invalid request failures.
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") | |
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") | |
| conn.setRequestProperty("User-Agent", "DeleteBranchesScript/1.0") |
| allow_force_pushes : false, | ||
| allow_deletions : false |
There was a problem hiding this comment.
allow_force_pushes / allow_deletions are sent as booleans, but GitHub’s branch protection API expects these to be objects (e.g., { enabled: false }) or omitted. With the current JSON shape, the PUT is likely to be rejected and protection won’t be applied.
| allow_force_pushes : false, | |
| allow_deletions : false | |
| allow_force_pushes : [enabled: false], | |
| allow_deletions : [enabled: false] |
| * Sets the branch to 'Read Only' and prevents deletion | ||
| * unless the 'enforce_admins' rule is manually toggled. |
There was a problem hiding this comment.
This comment states protection is “Read Only” and that deletion depends on toggling enforce_admins, but enforce_admins only controls whether admins are subject to the protection rules. Please update the comment to accurately describe the protections being applied (e.g., review requirements, status checks, force-push/deletion settings).
| * Sets the branch to 'Read Only' and prevents deletion | |
| * unless the 'enforce_admins' rule is manually toggled. | |
| * Applies branch protection that requires status checks and at least one | |
| * approving review, applies to admins, and disallows force pushes and deletions. |
| conn.readTimeout = 30_000 | ||
| conn.requestMethod = method | ||
| conn.setRequestProperty("Authorization", "token ${token}") | ||
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") |
There was a problem hiding this comment.
GitHub’s REST API requires a valid User-Agent header on requests; HttpURLConnection will not add one automatically. Add a User-Agent request property (and keep it consistent with other outbound HTTP usage in the repo) to avoid 403/invalid request failures.
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") | |
| conn.setRequestProperty("Accept", "application/vnd.github.v3+json") | |
| conn.setRequestProperty("User-Agent", "apache-grails-core-protect-branches-script") |
| | origin/8.0.x | 2026-02-28 | RELEASE | | ||
| | origin/8.0.x-hibernate7 | 2026-03-01 | RELEASE | | ||
| | origin/7.0.x | 2026-03-04 | RELEASE | | ||
| | origin | 2026-03-04 | RELEASE | | ||
| | origin/8.0.x-hibernate7-dev | 2026-03-05 | RELEASE | |
There was a problem hiding this comment.
The embedded table includes an origin entry. After replace('origin/', ''), that becomes branch name origin, which is unlikely to exist and will make the script fail for that row. Consider filtering out non-branch rows (e.g., origin, origin/HEAD -> ...) before calling the API.
| def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') | ||
| def repoOwner = "apache" | ||
| def repoName = "grails-core" | ||
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | ||
|
|
||
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | ||
| throw new IllegalStateException( | ||
| "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." |
There was a problem hiding this comment.
The githubToken resolution here allows reading the GitHub PAT from System.getProperty('github.token') and explicitly suggests passing it via -Dgithub.token on the command line. On shared build agents or multi-user systems, command-line JVM system properties may be observable via process listings or logs, which can expose the token and let other users perform unauthorized operations on this repository. To avoid this exposure, rely on environment variables or a dedicated secret mechanism for the token and remove the system property option and its mention in the error message.
| def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') | |
| def repoOwner = "apache" | |
| def repoName = "grails-core" | |
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | |
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | |
| throw new IllegalStateException( | |
| "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." | |
| def githubToken = System.getenv('GITHUB_TOKEN') | |
| def repoOwner = "apache" | |
| def repoName = "grails-core" | |
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | |
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | |
| throw new IllegalStateException( | |
| "GitHub token is required. Set the GITHUB_TOKEN environment variable." |
| def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') | ||
| def repoOwner = "apache" | ||
| def repoName = "grails-core" | ||
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | ||
|
|
||
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | ||
| throw new IllegalStateException( | ||
| "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." |
There was a problem hiding this comment.
The githubToken resolution here allows reading the GitHub PAT from System.getProperty('github.token') and encourages use of -Dgithub.token on the command line. On shared build agents or multi-user systems, JVM system properties set via command-line arguments can be exposed via process listings or logs, leaking the token and allowing other users to gain unauthorized access to this repository. To reduce this risk, load the token only from secure channels such as environment variables or a secret store and remove the system property path and related documentation.
| def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') | |
| def repoOwner = "apache" | |
| def repoName = "grails-core" | |
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | |
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | |
| throw new IllegalStateException( | |
| "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." | |
| def githubToken = System.getenv('GITHUB_TOKEN') | |
| def repoOwner = "apache" | |
| def repoName = "grails-core" | |
| def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" | |
| if (!githubToken || githubToken == "YOUR_PERSONAL_ACCESS_TOKEN") { | |
| throw new IllegalStateException( | |
| "GitHub token is required. Set the GITHUB_TOKEN environment variable." |
Two tasks one to protect the release branches and another to delete stale branches