Copilot commented on code in PR #15488:
URL: https://github.com/apache/grails-core/pull/15488#discussion_r2902802727
##########
DeleteBranches.groovy:
##########
@@ -0,0 +1,180 @@
+import groovy.json.JsonBuilder
+
Review Comment:
Unused import: JsonBuilder is imported but never referenced in this script.
Remove it to keep the script minimal.
```suggestion
```
##########
ProtectBranches.groovy:
##########
@@ -0,0 +1,99 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
+def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN"
Review Comment:
The script currently embeds the GitHub token as a string literal. Even with
a placeholder, this invites replacing it locally and accidentally committing
credentials. Read the token from an environment variable/Gradle property
instead and hard-fail when it’s missing/left as the placeholder.
```suggestion
def githubToken = System.getenv("GITHUB_TOKEN") ?:
System.getProperty("github.token")
if (!githubToken) {
throw new IllegalStateException("GitHub token not configured. Set the
GITHUB_TOKEN environment variable or the 'github.token' system property.")
}
```
##########
DeleteBranches.groovy:
##########
@@ -0,0 +1,180 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
+def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN"
+def repoOwner = "apache"
+def repoName = "grails-core"
+def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}"
+
+def tableData = """
+| origin/GRAILS-6737-Groovy-1.7.5 | 2010-09-17 | CLOSED |
+| origin/GRAILS-6278 | 2010-09-17 | CLOSED |
+| origin/GRAILS-5087 | 2012-12-07 | CLOSED |
+| origin/GRAILS-9997 | 2013-07-30 | CLOSED |
+| origin/GRAILS-10533 | 2013-10-14 | CLOSED |
+| origin/GRAILS-10512 | 2013-10-14 | CLOSED |
+| origin/GRAILS-10613 | 2013-10-15 | CLOSED |
+| origin/GRAILS-10660 | 2013-10-29 | CLOSED |
+| origin/GRAILS-10631 | 2013-11-05 | CLOSED |
+| origin/GRAILS-10728 | 2013-11-06 | CLOSED |
+| origin/GRAILS-10448 | 2013-11-06 | CLOSED |
+| origin/GRAILS-10780 | 2013-11-19 | CLOSED |
+| origin/GRAILS-10813 | 2013-11-21 | CLOSED |
+| origin/GRAILS-10838 | 2013-11-25 | CLOSED |
+| origin/GRAILS-10826 | 2013-11-26 | CLOSED |
+| origin/GRAILS-10835 | 2013-11-27 | CLOSED |
+| origin/GRAILS-10853 | 2013-12-02 | CLOSED |
+| origin/GRAILS-10868 | 2013-12-03 | CLOSED |
+| origin/GRAILS-10871 | 2013-12-03 | CLOSED |
+| origin/GRAILS-9664 | 2013-12-04 | CLOSED |
+| origin/GRAILS-10882 | 2013-12-09 | CLOSED |
+| origin/GRAILS-10852 | 2013-12-13 | CLOSED |
+| origin/GRAILS-10910 | 2013-12-13 | CLOSED |
+| origin/GRAILS-10908 | 2013-12-14 | CLOSED |
+| origin/GRAILS-10683b | 2013-12-23 | CLOSED |
+| origin/GRAILS-10897 | 2014-01-08 | CLOSED |
+| origin/GRAILS-8426 | 2014-01-10 | CLOSED |
+| origin/GRAILS-10973 | 2014-01-13 | CLOSED |
+| origin/GRAILS-11003 | 2014-01-22 | CLOSED |
+| origin/GRAILS-11011 | 2014-01-22 | CLOSED |
+| origin/GRAILS-11075 | 2014-02-04 | CLOSED |
+| origin/GRAILS-11093 | 2014-02-06 | CLOSED |
+| origin/GRAILS-11104 | 2014-02-11 | CLOSED |
+| origin/GRAILS-10683 | 2014-02-13 | CLOSED |
+| origin/GRAILS-11145 | 2014-02-26 | CLOSED |
+| origin/GRAILS-11197 | 2014-03-10 | CLOSED |
+| origin/GRAILS-9686 | 2014-03-14 | CLOSED |
+| origin/GRAILS-10031 | 2014-03-17 | CLOSED |
+| origin/GRAILS-11222 | 2014-03-18 | CLOSED |
+| origin/GRAILS-11238 | 2014-03-19 | CLOSED |
+| origin/GRAILS-11242 | 2014-03-24 | CLOSED |
+| origin/GRAILS-11204 | 2014-05-01 | CLOSED |
+| origin/GRAILS-6766 | 2014-05-01 | CLOSED |
+| origin/GRAILS-9996 | 2014-05-08 | CLOSED |
+| origin/GRAILS-10905 | 2014-05-08 | CLOSED |
+| origin/GRAILS-11448 | 2014-05-27 | CLOSED |
+| origin/GRAILS-11453 | 2014-05-28 | CLOSED |
+| origin/GRAILS-11462 | 2014-06-02 | CLOSED |
+| origin/GRAILS-11129 | 2014-06-10 | CLOSED |
+| origin/GRAILS-11505 | 2014-06-13 | CLOSED |
+| origin/GRAILS-11505B | 2014-06-17 | CLOSED |
+| origin/GRAILS-11505C | 2014-06-17 | CLOSED |
+| origin/GRAILS-11585 | 2014-07-16 | CLOSED |
+| origin/GRAILS-11576 | 2014-07-25 | CLOSED |
+| origin/GRAILS-11625 | 2014-08-04 | CLOSED |
+| origin/GRAILS-11543 | 2014-08-14 | CLOSED |
+| origin/GRAILS-11666 | 2014-08-18 | CLOSED |
+| origin/GRAILS-11661 | 2014-08-18 | CLOSED |
+| origin/GRAILS-11686 | 2014-08-26 | CLOSED |
+| origin/GRAILS-11680 | 2014-10-21 | CLOSED |
+| origin/GRAILS-11791 | 2014-10-23 | CLOSED |
+| origin/GRAILS-11748 | 2014-10-23 | CLOSED |
+| origin/GRAILS-11806 | 2014-10-30 | CLOSED |
+| origin/GRAILS-11638 | 2014-10-30 | CLOSED |
+| origin/GRAILS-11976 | 2015-02-09 | CLOSED |
+| origin/GRAILS-11973 | 2015-02-09 | CLOSED |
+| origin/GRAILS-11958 | 2015-02-09 | CLOSED |
+| origin/GRAILS-12112 | 2015-03-25 | CLOSED |
+| origin/issue_9183 | 2015-08-13 | CLOSED |
+| origin/issue10188 | 2016-10-27 | CLOSED |
+| origin/issue10282 | 2016-11-19 | CLOSED |
+| origin/GRAILS-10300-10315 | 2016-12-09 | CLOSED |
+| origin/issue10423 | 2017-01-24 | CLOSED |
+| origin/GRAILS-10392 | 2017-02-09 | CLOSED |
+| origin/issue10502 | 2017-02-27 | CLOSED |
+| origin/issue10600 | 2017-04-22 | CLOSED |
+| origin/issue-10844 | 2018-02-26 | CLOSED |
+| origin/issue-10844_take2 | 2020-11-05 | CLOSED |
+| origin/feature/scaffolding-5.1.0 | 2024-09-11 | CLOSED |
+| origin/renovate/major-jansi.version | 2024-10-24 | CLOSED |
+| origin/renovate/major-javahamcrest-monorepo | 2024-10-24 | CLOSED |
+| origin/add-grails-events-transform | 2024-12-23 | CLOSED |
+| origin/renovate/alpine-3.x | 2024-12-27 | CLOSED |
+| origin/renovate/actions-upload-artifact-4.x | 2025-02-21 | CLOSED |
+| origin/renovate/com.gradle.develocity-3.x | 2025-02-25 | CLOSED |
+| origin/renovate/io.micronaut.serde-micronaut-serde-jackson-2.x | 2025-03-03
| CLOSED |
+| origin/renovate/io.micronaut-micronaut-http-client-4.x | 2025-03-13 | CLOSED
|
+| origin/issue-14804 | 2025-06-11 | CLOSED |
+| origin/retry-build-step | 2025-06-18 | CLOSED |
+| origin/renovate/micronautversion | 2025-07-12 | CLOSED |
+| origin/addition-micronaut-feature-test | 2025-08-06 | CLOSED |
+| origin/update-rest-transform-dependency | 2025-08-07 | CLOSED |
+| origin/JavaExec-argsFile | 2025-09-22 | CLOSED |
+| origin/java-25-support-test | 2025-09-24 | CLOSED |
+| origin/fix-starter-published-dependencies | 2025-10-04 | CLOSED |
+| origin/fix/null-constructor-arg-groovy4 | 2026-03-03 | CLOSED |
+| origin/issue_8974 | 2015-03-23 | MERGE |
+| origin/issue_610 | 2015-04-06 | MERGE |
+| origin/issue-11211 | 2019-01-03 | MERGE |
+| origin/patch-decouple-gradle | 2024-07-24 | MERGE |
+| origin/web-profile-jar-artifact | 2025-10-08 | MERGE |
+| origin/jrebelFeatureFix | 2025-10-29 | MERGE |
+| origin/chore/gsp_and_gson_dependencies_and_apply | 2025-11-12 | MERGE |
+| origin/fix/issue_15228-respond-errors | 2025-11-18 | MERGE |
+| origin/banner-versions | 2025-11-19 | MERGE |
+| origin/remove-webjars-locator-core-dep | 2025-11-26 | MERGE |
+| origin/merge-back-7.0.5 | 2026-01-12 | MERGE |
+| origin/invokeDynamicDisable | 2026-01-17 | MERGE |
+| origin/deps/update-java-gradle-groovy-versions | 2026-01-27 | MERGE |
+| origin/task/add-agents-md-15145 | 2026-01-30 | MERGE |
+| origin/matrei-patch-1 | 2026-02-12 | MERGE |
+| origin/fix/flaky-geb-tests | 2026-02-19 | MERGE |
+| origin/refactor/centralize-groovydoc-plugin | 2026-02-19 | MERGE |
+| origin/test/query-connection-routing | 2026-02-21 | MERGE |
+| origin/micronaut-fixes-2 | 2026-02-21 | MERGE |
+| origin/forgeReloadingChanges | 2026-02-23 | MERGE |
+| origin/database-cleanup-feature | 2026-02-25 | MERGE |
+| origin/fix-detachedcriteria-join-get-hibernate7 | 2026-02-25 | MERGE |
+| origin/fix-detachedcriteria-join-get | 2026-02-25 | MERGE |
+| origin/fix/where-query-bugs | 2026-02-26 | MERGE |
+| origin/fix/async-promise-spec-read-timeout | 2026-03-03 | MERGE |
+| origin/fix/groovy-joint-ci-stability | 2026-03-03 | MERGE |
+"""
+
+tableData.eachLine { line ->
+ if (!line.contains("|") || line.contains("---") ||
line.contains("Branch")) return null
+
+ def parts = line.split("\\|").collect { it.trim() }
+ if (parts.size() < 4) return null
+
+ def branch = parts[1].replace("origin/", "")
+ def type = parts[3]
+
+ if (type == "MERGE" || type == "CLOSED") {
+ deleteBranch(baseApiUrl, githubToken, branch)
+ } else {
+ println "SKIPPING [${type}]: ${branch}"
+ }
+ return null
+}
+
+/**
+ * Removes the branch from the remote.
+ */
+def deleteBranch(baseUrl, token, branch) {
+ println "DELETING: ${branch}"
+ def url = new URL("${baseUrl}/git/refs/heads/${branch}")
+ sendRequest(url, token, "DELETE", null)
+}
+
+def sendRequest(url, token, method, body) {
+ try {
+ def conn = url.openConnection() as HttpURLConnection
+ conn.requestMethod = method
+ conn.setRequestProperty("Authorization", "token ${token}")
+ conn.setRequestProperty("Accept", "application/vnd.github.v3+json")
+ if (body) {
+ conn.doOutput = true
+ conn.setRequestProperty("Content-Type", "application/json")
+ conn.outputStream.withWriter { it << body }
+ }
+
+ if (conn.responseCode in [200, 201, 204]) {
+ println "SUCCESS: ${conn.responseCode}"
+ } else {
+ println "FAILED: ${conn.responseCode} - ${conn.errorStream?.text}"
+ }
Review Comment:
sendRequest() never sets connect/read timeouts and doesn’t explicitly
disconnect the HttpURLConnection, which can cause the task to hang indefinitely
or leak connections when deleting many branches. Set reasonable timeouts and
ensure the connection/streams are closed/disconnected in a finally block.
##########
ProtectBranches.groovy:
##########
@@ -0,0 +1,99 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
+def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN"
+def repoOwner = "apache"
+def repoName = "grails-core"
+def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}"
+
+def tableData = """
+| origin/1.1.x | 2009-11-26 | RELEASE |
+| origin/1.3.0.RC2 | 2010-04-23 | RELEASE |
+| origin/1.2.x | 2010-10-11 | RELEASE |
+| origin/1.3.x | 2012-06-01 | RELEASE |
+| origin/2.0.x | 2013-05-30 | RELEASE |
+| origin/2.1.x | 2013-09-21 | RELEASE |
+| origin/2.2.x | 2014-07-27 | RELEASE |
+| origin/2.3.x | 2015-06-17 | RELEASE |
+| origin/2.4.x | 2015-09-08 | RELEASE |
+| origin/3.0.x | 2016-07-27 | RELEASE |
+| origin/3.1.x | 2017-05-09 | RELEASE |
+| origin/3.1.x-issue-9058 | 2017-05-23 | RELEASE |
+| origin/3.2.x | 2019-10-10 | RELEASE |
+| origin/master | 2021-11-24 | RELEASE |
+| origin/4.0.x | 2022-06-03 | RELEASE |
+| origin/5.1.x | 2022-10-13 | RELEASE |
+| origin/5.0.x | 2022-11-25 | RELEASE |
+| origin/5.2.x | 2023-02-13 | RELEASE |
+| origin/2.5.x | 2023-12-17 | RELEASE |
+| origin/3.3.x | 2024-01-09 | RELEASE |
+| origin/4.1.x | 2024-01-26 | RELEASE |
+| origin/5.3.x | 2024-01-26 | RELEASE |
+| origin/6.1.x | 2024-02-27 | RELEASE |
+| origin/6.0.x | 2024-04-09 | RELEASE |
+| origin/5.4.x | 2024-09-11 | RELEASE |
+| origin/6.2.x | 2025-01-03 | RELEASE |
+| origin/gh-pages | 2025-01-07 | RELEASE |
+| origin/7.1.x | 2026-02-27 | RELEASE |
+| origin/8.0.x | 2026-02-28 | RELEASE |
+| origin/7.0.x | 2026-03-04 | RELEASE |
+| origin/main | 2026-03-04 | RELEASE |
+"""
+
+tableData.eachLine { line ->
+ if (!line.contains("|") || line.contains("---") ||
line.contains("Branch")) return null
+
+ def parts = line.split("\\|").collect { it.trim() }
+ if (parts.size() < 4) return null
+
+ def branch = parts[1].replace("origin/", "")
+ def type = parts[3]
+
+ if (type == "RELEASE") {
+ protectBranch(baseApiUrl, githubToken, branch)
+ } else {
+ println "SKIPPING [${type}]: ${branch}"
+ }
+ return null
+}
+
+/**
+ * Sets the branch to 'Read Only' and prevents deletion
+ * unless the 'enforce_admins' rule is manually toggled.
+ */
+def protectBranch(baseUrl, token, branch) {
+ println "PROTECTING: ${branch}"
+ def url = new URL("${baseUrl}/branches/${branch}/protection")
+ def body = new JsonBuilder([
+ enforce_admins: true,
+ required_status_checks: null,
+ required_pull_request_reviews: null,
+ restrictions: null,
+ allow_force_pushes: false,
+ allow_deletions: false
Review Comment:
The JSON payload disables status checks and PR review requirements (both
null) and doesn’t add push restrictions, so the branch will still allow direct
pushes; it’s not effectively “read only”. If the intent is to prevent direct
pushes, update the protection settings to require PRs and/or restrict who can
push (rather than only disabling force-pushes/deletions).
```suggestion
enforce_admins : true,
required_status_checks : [
strict : true,
contexts: []
],
required_pull_request_reviews: [
required_approving_review_count: 1,
dismiss_stale_reviews : true,
require_code_owner_reviews : false
],
restrictions : null,
allow_force_pushes : false,
allow_deletions : false
```
##########
DeleteBranches.groovy:
##########
@@ -0,0 +1,180 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
+def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN"
+def repoOwner = "apache"
+def repoName = "grails-core"
+def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}"
+
Review Comment:
The script currently embeds the GitHub token as a string literal. Even with
a placeholder, this invites replacing it locally and accidentally committing
credentials. Read the token from an environment variable/Gradle property
instead and hard-fail when it’s missing/left as the placeholder.
```suggestion
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."
)
}
```
##########
ProtectBranches.groovy:
##########
@@ -0,0 +1,99 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
Review Comment:
Repo convention: AGENTS.md explicitly discourages section separator/grouping
comments like "// --- Domain classes ---" (AGENTS.md:190-192). Consider
removing this "// --- Configuration ---" separator to align with that rule.
```suggestion
```
##########
ProtectBranches.groovy:
##########
@@ -0,0 +1,99 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
+def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN"
+def repoOwner = "apache"
+def repoName = "grails-core"
+def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}"
+
+def tableData = """
+| origin/1.1.x | 2009-11-26 | RELEASE |
+| origin/1.3.0.RC2 | 2010-04-23 | RELEASE |
+| origin/1.2.x | 2010-10-11 | RELEASE |
+| origin/1.3.x | 2012-06-01 | RELEASE |
+| origin/2.0.x | 2013-05-30 | RELEASE |
+| origin/2.1.x | 2013-09-21 | RELEASE |
+| origin/2.2.x | 2014-07-27 | RELEASE |
+| origin/2.3.x | 2015-06-17 | RELEASE |
+| origin/2.4.x | 2015-09-08 | RELEASE |
+| origin/3.0.x | 2016-07-27 | RELEASE |
+| origin/3.1.x | 2017-05-09 | RELEASE |
+| origin/3.1.x-issue-9058 | 2017-05-23 | RELEASE |
+| origin/3.2.x | 2019-10-10 | RELEASE |
+| origin/master | 2021-11-24 | RELEASE |
+| origin/4.0.x | 2022-06-03 | RELEASE |
+| origin/5.1.x | 2022-10-13 | RELEASE |
+| origin/5.0.x | 2022-11-25 | RELEASE |
+| origin/5.2.x | 2023-02-13 | RELEASE |
+| origin/2.5.x | 2023-12-17 | RELEASE |
+| origin/3.3.x | 2024-01-09 | RELEASE |
+| origin/4.1.x | 2024-01-26 | RELEASE |
+| origin/5.3.x | 2024-01-26 | RELEASE |
+| origin/6.1.x | 2024-02-27 | RELEASE |
+| origin/6.0.x | 2024-04-09 | RELEASE |
+| origin/5.4.x | 2024-09-11 | RELEASE |
+| origin/6.2.x | 2025-01-03 | RELEASE |
+| origin/gh-pages | 2025-01-07 | RELEASE |
+| origin/7.1.x | 2026-02-27 | RELEASE |
+| origin/8.0.x | 2026-02-28 | RELEASE |
+| origin/7.0.x | 2026-03-04 | RELEASE |
+| origin/main | 2026-03-04 | RELEASE |
+"""
+
+tableData.eachLine { line ->
+ if (!line.contains("|") || line.contains("---") ||
line.contains("Branch")) return null
+
+ def parts = line.split("\\|").collect { it.trim() }
+ if (parts.size() < 4) return null
+
+ def branch = parts[1].replace("origin/", "")
+ def type = parts[3]
+
+ if (type == "RELEASE") {
+ protectBranch(baseApiUrl, githubToken, branch)
+ } else {
+ println "SKIPPING [${type}]: ${branch}"
+ }
+ return null
+}
+
+/**
+ * Sets the branch to 'Read Only' and prevents deletion
+ * unless the 'enforce_admins' rule is manually toggled.
+ */
+def protectBranch(baseUrl, token, branch) {
+ println "PROTECTING: ${branch}"
+ def url = new URL("${baseUrl}/branches/${branch}/protection")
+ def body = new JsonBuilder([
+ enforce_admins: true,
+ required_status_checks: null,
+ required_pull_request_reviews: null,
+ restrictions: null,
+ allow_force_pushes: false,
+ allow_deletions: false
+ ]).toString()
+
+ sendRequest(url, token, "PUT", body)
+}
+
+def sendRequest(url, token, method, body) {
+ try {
+ def conn = url.openConnection() as HttpURLConnection
+ conn.requestMethod = method
+ conn.setRequestProperty("Authorization", "token ${token}")
+ conn.setRequestProperty("Accept", "application/vnd.github.v3+json")
+ if (body) {
+ conn.doOutput = true
+ conn.setRequestProperty("Content-Type", "application/json")
+ conn.outputStream.withWriter { it << body }
+ }
+
+ if (conn.responseCode in [200, 201, 204]) {
+ println "SUCCESS: ${conn.responseCode}"
+ } else {
+ println "FAILED: ${conn.responseCode} - ${conn.errorStream?.text}"
+ }
Review Comment:
sendRequest() never sets connect/read timeouts and doesn’t explicitly
disconnect the HttpURLConnection, which can cause the task to hang indefinitely
or leak connections when processing many branches. Set reasonable timeouts and
ensure the connection/streams are closed/disconnected in a finally block.
##########
DeleteBranches.groovy:
##########
@@ -0,0 +1,180 @@
+import groovy.json.JsonBuilder
+
+// --- Configuration ---
Review Comment:
Repo convention: AGENTS.md explicitly discourages section separator/grouping
comments like "// --- Domain classes ---" (AGENTS.md:190-192). Consider
removing this "// --- Configuration ---" separator to align with that rule.
```suggestion
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]