From b56d014b879d97213ec5c0e692d9d26e079d33d4 Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Sun, 8 Mar 2026 20:41:07 -0500 Subject: [PATCH 01/21] temporary scripts for branch maintenance --- DeleteBranches.groovy | 180 +++++++++++++++++++++++++++++++++++++++++ ProtectBranches.groovy | 99 +++++++++++++++++++++++ build.gradle | 18 ++++- 3 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 DeleteBranches.groovy create mode 100644 ProtectBranches.groovy diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy new file mode 100644 index 00000000000..c7cdc742ae3 --- /dev/null +++ b/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}" + } + } catch (Exception e) { + println "ERROR: ${e.message}" + } +} diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy new file mode 100644 index 00000000000..97a6707d20a --- /dev/null +++ b/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}" + } + } catch (Exception e) { + println "ERROR: ${e.message}" + } +} diff --git a/build.gradle b/build.gradle index ae6246d0e9b..2e4d4f0b2cb 100644 --- a/build.gradle +++ b/build.gradle @@ -88,4 +88,20 @@ apply { // logger.lifecycle("\t- ${dep}") // } // } -//} \ No newline at end of file +//} + +tasks.register('deleteBranches') { + group = 'Maintenance' + description = 'Executes the DeleteBranches.groovy script to clean up remote branches.' + doLast { + new GroovyShell().evaluate(file('DeleteBranches.groovy')) + } +} + +tasks.register('protectBranches') { + group = 'Maintenance' + description = 'Executes the ProtectBranches.groovy script to set protection on release branches.' + doLast { + new GroovyShell().evaluate(file('ProtectBranches.groovy')) + } +} \ No newline at end of file From 53ebfc2e75309b79d66c43c8167f85153b1638ba Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Sun, 8 Mar 2026 20:51:41 -0500 Subject: [PATCH 02/21] ASL --- DeleteBranches.groovy | 20 ++++++++++++++++++-- ProtectBranches.groovy | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index c7cdc742ae3..4ed4a3e65a0 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -1,5 +1,21 @@ -import groovy.json.JsonBuilder - +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ // --- Configuration --- def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN" def repoOwner = "apache" diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 97a6707d20a..03bf9903753 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ import groovy.json.JsonBuilder // --- Configuration --- From 6f429c41d0f2436a219e152b79afe76c9792cc38 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 20:53:40 -0500 Subject: [PATCH 03/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 03bf9903753..a40b0ac9696 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -19,7 +19,10 @@ import groovy.json.JsonBuilder // --- Configuration --- -def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN" +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.") +} def repoOwner = "apache" def repoName = "grails-core" def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" From 090a103e53eb8a83200e4d3740426149eeb076fe Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 20:54:38 -0500 Subject: [PATCH 04/21] Update DeleteBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DeleteBranches.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 4ed4a3e65a0..853230cb9e7 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -// --- Configuration --- def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN" def repoOwner = "apache" def repoName = "grails-core" From b43da02d9d47e3ee57d3a5e754909479eeca5aa5 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 20:54:56 -0500 Subject: [PATCH 05/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index a40b0ac9696..eb1564b5bd7 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -18,7 +18,6 @@ */ import groovy.json.JsonBuilder -// --- Configuration --- 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.") From 2b807a49f2d5adf3d593ea8471f4a4fe1ff8b0d7 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 20:55:15 -0500 Subject: [PATCH 06/21] Update DeleteBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DeleteBranches.groovy | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 853230cb9e7..9c4df3304a5 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -16,11 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -def githubToken = "YOUR_PERSONAL_ACCESS_TOKEN" +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 tableData = """ | origin/GRAILS-6737-Groovy-1.7.5 | 2010-09-17 | CLOSED | | origin/GRAILS-6278 | 2010-09-17 | CLOSED | From 96b6f7a7e73ddc9b91343d1ef3c9b5674272ce1e Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 20:55:35 -0500 Subject: [PATCH 07/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index eb1564b5bd7..c27b1b0d3a4 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -85,12 +85,19 @@ 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 + 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 ]).toString() sendRequest(url, token, "PUT", body) From 1ffc7bbe9cb33d32cd392c6a434d17e1a827db58 Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Sun, 8 Mar 2026 20:58:15 -0500 Subject: [PATCH 08/21] fix PR comments --- DeleteBranches.groovy | 9 +++++++-- ProtectBranches.groovy | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 9c4df3304a5..910b25042fa 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -178,9 +178,12 @@ def deleteBranch(baseUrl, token, branch) { } def sendRequest(url, token, method, body) { + HttpURLConnection conn = null try { - def conn = url.openConnection() as HttpURLConnection - conn.requestMethod = method + conn = url.openConnection() as HttpURLConnection + conn.connectTimeout = 10_000 + conn.readTimeout = 30_000 + conn.requestMethod = method conn.setRequestProperty("Authorization", "token ${token}") conn.setRequestProperty("Accept", "application/vnd.github.v3+json") if (body) { @@ -196,5 +199,7 @@ def sendRequest(url, token, method, body) { } } catch (Exception e) { println "ERROR: ${e.message}" + } finally { + conn?.disconnect() } } diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index c27b1b0d3a4..b0bb5963e88 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -104,9 +104,12 @@ def protectBranch(baseUrl, token, branch) { } def sendRequest(url, token, method, body) { + HttpURLConnection conn = null try { - def conn = url.openConnection() as HttpURLConnection - conn.requestMethod = method + conn = url.openConnection() as HttpURLConnection + conn.connectTimeout = 10_000 + conn.readTimeout = 30_000 + conn.requestMethod = method conn.setRequestProperty("Authorization", "token ${token}") conn.setRequestProperty("Accept", "application/vnd.github.v3+json") if (body) { @@ -122,5 +125,7 @@ def sendRequest(url, token, method, body) { } } catch (Exception e) { println "ERROR: ${e.message}" + } finally { + conn?.disconnect() } } From c82b35af323037499f6dbc1c66b661def4a16e50 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 21:03:40 -0500 Subject: [PATCH 09/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index b0bb5963e88..85e30414d8d 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -121,10 +121,13 @@ def sendRequest(url, token, method, body) { if (conn.responseCode in [200, 201, 204]) { println "SUCCESS: ${conn.responseCode}" } else { - println "FAILED: ${conn.responseCode} - ${conn.errorStream?.text}" + def errorText = conn.errorStream?.text + println "FAILED: ${conn.responseCode} - ${errorText}" + throw new IllegalStateException("Request to ${url} failed with HTTP ${conn.responseCode}: ${errorText}") } } catch (Exception e) { println "ERROR: ${e.message}" + throw e } finally { conn?.disconnect() } From 3f735394f19c328e9579b65fab18641129f62f19 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Sun, 8 Mar 2026 21:03:51 -0500 Subject: [PATCH 10/21] Update DeleteBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DeleteBranches.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 910b25042fa..9ca65ce1407 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -154,11 +154,11 @@ def tableData = """ 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 parts = line.tokenize('|').collect { it.trim() } + if (parts.size() < 3) return null - def branch = parts[1].replace("origin/", "") - def type = parts[3] + def branch = parts[0].replace("origin/", "") + def type = parts[2] if (type == "MERGE" || type == "CLOSED") { deleteBranch(baseApiUrl, githubToken, branch) From 81d9d19ca11313d3941e8e102f53789de91b3fd9 Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Sun, 8 Mar 2026 21:06:59 -0500 Subject: [PATCH 11/21] more fix PR comments --- DeleteBranches.groovy | 18 ++++++++++++---- ProtectBranches.groovy | 48 ++++++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 9ca65ce1407..5e336863229 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -151,6 +151,8 @@ def tableData = """ | origin/fix/groovy-joint-ci-stability | 2026-03-03 | MERGE | """ +def failedBranches = [] + tableData.eachLine { line -> if (!line.contains("|") || line.contains("---") || line.contains("Branch")) return null @@ -161,13 +163,22 @@ tableData.eachLine { line -> def type = parts[2] if (type == "MERGE" || type == "CLOSED") { - deleteBranch(baseApiUrl, githubToken, branch) + try { + deleteBranch(baseApiUrl, githubToken, branch) + } catch (Exception e) { + println "CRITICAL ERROR processing ${branch}: ${e.message}" + failedBranches << branch + } } else { println "SKIPPING [${type}]: ${branch}" } return null } +if (failedBranches) { + throw new RuntimeException("Failed to delete the following branches: ${failedBranches.join(', ')}. Check logs for details.") +} + /** * Removes the branch from the remote. */ @@ -195,10 +206,9 @@ def sendRequest(url, token, method, body) { if (conn.responseCode in [200, 201, 204]) { println "SUCCESS: ${conn.responseCode}" } else { - println "FAILED: ${conn.responseCode} - ${conn.errorStream?.text}" + def errorText = conn.errorStream?.text ?: "No error stream available" + throw new RuntimeException("HTTP ${conn.responseCode} - ${errorText}") } - } catch (Exception e) { - println "ERROR: ${e.message}" } finally { conn?.disconnect() } diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 85e30414d8d..0f74b432fc9 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -18,14 +18,18 @@ */ import groovy.json.JsonBuilder -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.") -} +// --- Configuration --- +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 tableData = """ | origin/1.1.x | 2009-11-26 | RELEASE | | origin/1.3.0.RC2 | 2010-04-23 | RELEASE | @@ -54,29 +58,45 @@ def tableData = """ | 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.0.x-hibernate6 | 2025-10-15 | RELEASE | +| origin/7.0.x-binding-error-14947-15147 | 2025-10-21 | RELEASE | +| origin/7.1.x-hibernate6 | 2025-12-03 | RELEASE | | origin/7.1.x | 2026-02-27 | RELEASE | | 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/main | 2026-03-04 | RELEASE | +| origin | 2026-03-04 | RELEASE | +| origin/8.0.x-hibernate7-dev | 2026-03-05 | RELEASE | """ +def failedBranches = [] + 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 parts = line.tokenize('|').collect { it.trim() } + if (parts.size() < 3) return null - def branch = parts[1].replace("origin/", "") - def type = parts[3] + def branch = parts[0].replace("origin/", "") + def type = parts[2] if (type == "RELEASE") { - protectBranch(baseApiUrl, githubToken, branch) + try { + protectBranch(baseApiUrl, githubToken, branch) + } catch (Exception e) { + println "CRITICAL ERROR processing ${branch}: ${e.message}" + failedBranches << branch + } } else { println "SKIPPING [${type}]: ${branch}" } return null } +if (failedBranches) { + throw new RuntimeException("Failed to protect the following branches: ${failedBranches.join(', ')}. Check logs for details.") +} + /** * Sets the branch to 'Read Only' and prevents deletion * unless the 'enforce_admins' rule is manually toggled. @@ -121,13 +141,9 @@ def sendRequest(url, token, method, body) { if (conn.responseCode in [200, 201, 204]) { println "SUCCESS: ${conn.responseCode}" } else { - def errorText = conn.errorStream?.text - println "FAILED: ${conn.responseCode} - ${errorText}" - throw new IllegalStateException("Request to ${url} failed with HTTP ${conn.responseCode}: ${errorText}") + def errorText = conn.errorStream?.text ?: "No error stream available" + throw new RuntimeException("HTTP ${conn.responseCode} - ${errorText}") } - } catch (Exception e) { - println "ERROR: ${e.message}" - throw e } finally { conn?.disconnect() } From 1614fd775c1877a3816eef26af9be0a50bcd464a Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:38:38 -0500 Subject: [PATCH 12/21] Update DeleteBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DeleteBranches.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 5e336863229..9e471a14aff 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -197,6 +197,7 @@ def sendRequest(url, token, method, body) { conn.requestMethod = method conn.setRequestProperty("Authorization", "token ${token}") conn.setRequestProperty("Accept", "application/vnd.github.v3+json") + conn.setRequestProperty("User-Agent", "DeleteBranchesScript/1.0") if (body) { conn.doOutput = true conn.setRequestProperty("Content-Type", "application/json") From 21ff47149115564a81691a5775788eb3bbe5dfc4 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:39:00 -0500 Subject: [PATCH 13/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 0f74b432fc9..f7c9cb606ad 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -116,8 +116,8 @@ def protectBranch(baseUrl, token, branch) { require_code_owner_reviews : false ], restrictions : null, - allow_force_pushes : false, - allow_deletions : false + allow_force_pushes : [enabled: false], + allow_deletions : [enabled: false] ]).toString() sendRequest(url, token, "PUT", body) From 9fd7b841940dcca23d4d8121552b58d24fcf61c8 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:39:26 -0500 Subject: [PATCH 14/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index f7c9cb606ad..1a5fe404f06 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -98,8 +98,8 @@ if (failedBranches) { } /** - * 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. */ def protectBranch(baseUrl, token, branch) { println "PROTECTING: ${branch}" From dba1910807be5f7531136803e5db2a378abfd78d Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:39:45 -0500 Subject: [PATCH 15/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 1a5fe404f06..fa03bd895ce 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -132,6 +132,7 @@ def sendRequest(url, token, method, body) { conn.requestMethod = method conn.setRequestProperty("Authorization", "token ${token}") conn.setRequestProperty("Accept", "application/vnd.github.v3+json") + conn.setRequestProperty("User-Agent", "apache-grails-core-protect-branches-script") if (body) { conn.doOutput = true conn.setRequestProperty("Content-Type", "application/json") From 8bad7a62f06a1191c6ee89cb102b8f57f934544f Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:40:24 -0500 Subject: [PATCH 16/21] Update DeleteBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DeleteBranches.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 9e471a14aff..907ba8dc6dd 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') +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 or the -Dgithub.token system property." + "GitHub token is required. Set the GITHUB_TOKEN environment variable." ) } def tableData = """ From 44d8a6ef783c16fa8e842bf58170c4ef6e625f18 Mon Sep 17 00:00:00 2001 From: Walter B Duque de Estrada Date: Mon, 9 Mar 2026 07:40:57 -0500 Subject: [PATCH 17/21] Update ProtectBranches.groovy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ProtectBranches.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index fa03bd895ce..d371dcabaf9 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -19,14 +19,14 @@ import groovy.json.JsonBuilder // --- Configuration --- -def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') +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 or the -Dgithub.token system property." + "GitHub token is required. Set the GITHUB_TOKEN environment variable." ) } From a593ce81d0cc05866e0658440e30e36adce9405a Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Mon, 9 Mar 2026 07:42:28 -0500 Subject: [PATCH 18/21] more fix PR comments --- ProtectBranches.groovy | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index d371dcabaf9..a2c363e38e5 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -57,16 +57,9 @@ def tableData = """ | 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.0.x-hibernate6 | 2025-10-15 | RELEASE | -| origin/7.0.x-binding-error-14947-15147 | 2025-10-21 | RELEASE | -| origin/7.1.x-hibernate6 | 2025-12-03 | RELEASE | | origin/7.1.x | 2026-02-27 | RELEASE | | 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 | """ def failedBranches = [] From 37535a2f311124c34808814324c6121bf857ac3d Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Mon, 9 Mar 2026 07:59:23 -0500 Subject: [PATCH 19/21] more PR comments --- DeleteBranches.groovy | 4 ++-- ProtectBranches.groovy | 31 ++++++++++++++++++------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 907ba8dc6dd..518044a6fa1 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -def githubToken = System.getenv('GITHUB_TOKEN') +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") { +if (!githubToken) { throw new IllegalStateException( "GitHub token is required. Set the GITHUB_TOKEN environment variable." ) diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index a2c363e38e5..090c417d849 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -19,14 +19,14 @@ import groovy.json.JsonBuilder // --- Configuration --- -def githubToken = System.getenv('GITHUB_TOKEN') +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." + "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." ) } @@ -57,9 +57,15 @@ def tableData = """ | 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.0.x-hibernate6 | 2025-10-15 | RELEASE | +| origin/7.0.x-binding-error-14947-15147 | 2025-10-21 | RELEASE | +| origin/7.1.x-hibernate6 | 2025-12-03 | RELEASE | | origin/7.1.x | 2026-02-27 | RELEASE | | 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/8.0.x-hibernate7-dev | 2026-03-05 | RELEASE | """ def failedBranches = [] @@ -73,6 +79,9 @@ tableData.eachLine { line -> def branch = parts[0].replace("origin/", "") def type = parts[2] + // Skip the invalid 'origin' (no branch name) entry if it appears + if (branch == "origin" || !branch) return null + if (type == "RELEASE") { try { protectBranch(baseApiUrl, githubToken, branch) @@ -91,26 +100,23 @@ if (failedBranches) { } /** - * Applies branch protection that requires status checks and at least one - * approving review, applies to admins, and disallows force pushes and deletions. + * Sets the branch to 'Protected' with required reviews. + * Does not require specific status checks (null) to avoid 'useless' empty list config. */ 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 : [ - strict : true, - contexts: [] - ], + required_status_checks : null, required_pull_request_reviews: [ required_approving_review_count: 1, - dismiss_stale_reviews : true, - require_code_owner_reviews : false + dismiss_stale_reviews : true ], restrictions : null, - allow_force_pushes : [enabled: false], - allow_deletions : [enabled: false] + allow_force_pushes : false, + allow_deletions : false, + lock_branch : false // Set to true only if you want the branch to be strictly read-only ]).toString() sendRequest(url, token, "PUT", body) @@ -125,7 +131,6 @@ def sendRequest(url, token, method, body) { conn.requestMethod = method conn.setRequestProperty("Authorization", "token ${token}") conn.setRequestProperty("Accept", "application/vnd.github.v3+json") - conn.setRequestProperty("User-Agent", "apache-grails-core-protect-branches-script") if (body) { conn.doOutput = true conn.setRequestProperty("Content-Type", "application/json") From 268419b80933268412a5957495fe9f4ee65c1659 Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Mon, 9 Mar 2026 13:15:51 -0500 Subject: [PATCH 20/21] more PR comments --- DeleteBranches.groovy | 2 +- ProtectBranches.groovy | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index 518044a6fa1..d15c85bdb2c 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -23,7 +23,7 @@ def baseApiUrl = "https://api.github.com/repos/${repoOwner}/${repoName}" if (!githubToken) { throw new IllegalStateException( - "GitHub token is required. Set the GITHUB_TOKEN environment variable." + "GitHub token is required. Set the GITHUB_TOKEN environment variable or the -Dgithub.token system property." ) } def tableData = """ diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy index 090c417d849..c7f2ce90e7d 100644 --- a/ProtectBranches.groovy +++ b/ProtectBranches.groovy @@ -18,7 +18,6 @@ */ import groovy.json.JsonBuilder -// --- Configuration --- def githubToken = System.getenv('GITHUB_TOKEN') ?: System.getProperty('github.token') def repoOwner = "apache" def repoName = "grails-core" From 8f6e555f7ad6a86102135dcf0e32e73698396a4c Mon Sep 17 00:00:00 2001 From: Walter Duque de Estrada Date: Wed, 11 Mar 2026 08:41:16 -0500 Subject: [PATCH 21/21] pivoted to asf.yml and delete of a branch that is already deleted will not fail the script --- .asf.yaml | 151 +++++++++++++++++++++++++++++++++++++++++ DeleteBranches.groovy | 2 + ProtectBranches.groovy | 148 ---------------------------------------- 3 files changed, 153 insertions(+), 148 deletions(-) delete mode 100644 ProtectBranches.groovy diff --git a/.asf.yaml b/.asf.yaml index 395c07b4e59..57aea17f583 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -75,3 +75,154 @@ notifications: issues_status: notifications@grails.apache.org issues_comment: notifications@grails.apache.org discussions: users@grails.apache.org +protected_branches: + 1.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 1.3.0.RC2: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 1.2.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 1.3.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.2.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.3.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.4.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 3.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 3.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 3.1.x-issue-9058: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 3.2.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + master: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 4.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 5.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 5.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 5.2.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 2.5.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 3.3.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 4.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 5.3.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 6.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 6.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 5.4.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 6.2.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + gh-pages: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 7.1.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 7.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true + 8.0.x: + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 + enforce_admins: true diff --git a/DeleteBranches.groovy b/DeleteBranches.groovy index d15c85bdb2c..76247b9ca52 100644 --- a/DeleteBranches.groovy +++ b/DeleteBranches.groovy @@ -206,6 +206,8 @@ def sendRequest(url, token, method, body) { if (conn.responseCode in [200, 201, 204]) { println "SUCCESS: ${conn.responseCode}" + } else if (method == "DELETE" && conn.responseCode == 404) { + println "SKIPPED (Not Found): ${conn.responseCode}" } else { def errorText = conn.errorStream?.text ?: "No error stream available" throw new RuntimeException("HTTP ${conn.responseCode} - ${errorText}") diff --git a/ProtectBranches.groovy b/ProtectBranches.groovy deleted file mode 100644 index c7f2ce90e7d..00000000000 --- a/ProtectBranches.groovy +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import groovy.json.JsonBuilder - -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 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.0.x-hibernate6 | 2025-10-15 | RELEASE | -| origin/7.0.x-binding-error-14947-15147 | 2025-10-21 | RELEASE | -| origin/7.1.x-hibernate6 | 2025-12-03 | RELEASE | -| origin/7.1.x | 2026-02-27 | RELEASE | -| 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/8.0.x-hibernate7-dev | 2026-03-05 | RELEASE | -""" - -def failedBranches = [] - -tableData.eachLine { line -> - if (!line.contains("|") || line.contains("---") || line.contains("Branch")) return null - - def parts = line.tokenize('|').collect { it.trim() } - if (parts.size() < 3) return null - - def branch = parts[0].replace("origin/", "") - def type = parts[2] - - // Skip the invalid 'origin' (no branch name) entry if it appears - if (branch == "origin" || !branch) return null - - if (type == "RELEASE") { - try { - protectBranch(baseApiUrl, githubToken, branch) - } catch (Exception e) { - println "CRITICAL ERROR processing ${branch}: ${e.message}" - failedBranches << branch - } - } else { - println "SKIPPING [${type}]: ${branch}" - } - return null -} - -if (failedBranches) { - throw new RuntimeException("Failed to protect the following branches: ${failedBranches.join(', ')}. Check logs for details.") -} - -/** - * Sets the branch to 'Protected' with required reviews. - * Does not require specific status checks (null) to avoid 'useless' empty list config. - */ -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: [ - required_approving_review_count: 1, - dismiss_stale_reviews : true - ], - restrictions : null, - allow_force_pushes : false, - allow_deletions : false, - lock_branch : false // Set to true only if you want the branch to be strictly read-only - ]).toString() - - sendRequest(url, token, "PUT", body) -} - -def sendRequest(url, token, method, body) { - HttpURLConnection conn = null - try { - conn = url.openConnection() as HttpURLConnection - conn.connectTimeout = 10_000 - conn.readTimeout = 30_000 - 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 { - def errorText = conn.errorStream?.text ?: "No error stream available" - throw new RuntimeException("HTTP ${conn.responseCode} - ${errorText}") - } - } finally { - conn?.disconnect() - } -}