Skip to content

Commit cb7cc04

Browse files
Add Fix-BranchRuleset.ps1 and update Setup-BranchRuleset.ps1
- Add Fix-BranchRuleset.ps1: inspects, disables, and renames existing rulesets so Setup-BranchRuleset.ps1 can recreate them cleanly - Update Setup-BranchRuleset.ps1 to match current repo-template with correct repository name and status check names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 08efc35 commit cb7cc04

File tree

2 files changed

+257
-23
lines changed

2 files changed

+257
-23
lines changed

scripts/Fix-BranchRuleset.ps1

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<#
2+
.SYNOPSIS
3+
Fixes branch rulesets by disabling existing ones and recreating with the correct configuration.
4+
5+
.DESCRIPTION
6+
This script inspects the existing branch rulesets for a repository, disables all of them,
7+
and renames any ruleset named "Protect main branch" to "Protect main branch (old)" so that
8+
Setup-BranchRuleset.ps1 can create a fresh ruleset without conflicts.
9+
10+
The script presents a plan of all changes before executing and prompts for confirmation.
11+
12+
.PARAMETER Repository
13+
The repository in owner/repo format. If not provided, uses the current repository.
14+
15+
.EXAMPLE
16+
.\Fix-BranchRuleset.ps1
17+
Inspects and fixes rulesets for the current repository
18+
19+
.EXAMPLE
20+
.\Fix-BranchRuleset.ps1 -Repository "Chris-Wolfgang/my-repo"
21+
Inspects and fixes rulesets for a specific repository
22+
23+
.NOTES
24+
Requires: GitHub CLI (gh) authenticated with admin permissions
25+
Install gh: https://cli.github.com/
26+
#>
27+
28+
[CmdletBinding()]
29+
param(
30+
[Parameter()]
31+
[string]$Repository = "Chris-Wolfgang/ETL-FixedWidth"
32+
)
33+
34+
# Check if gh CLI is installed
35+
try {
36+
$null = gh --version
37+
} catch {
38+
Write-Error "GitHub CLI (gh) is not installed or not in PATH."
39+
Write-Host "Install from: https://cli.github.com/" -ForegroundColor Yellow
40+
exit 1
41+
}
42+
43+
# Check if authenticated
44+
try {
45+
$null = gh auth status 2>&1
46+
if ($LASTEXITCODE -ne 0) {
47+
Write-Error "Not authenticated with GitHub CLI."
48+
Write-Host "Run: gh auth login" -ForegroundColor Yellow
49+
exit 1
50+
}
51+
} catch {
52+
Write-Error "Failed to check GitHub CLI authentication status."
53+
exit 1
54+
}
55+
56+
# Determine repository
57+
if ($Repository -eq "Chris-Wolfgang/ETL-FixedWidth" -or -not $Repository) {
58+
Write-Host "Detecting current repository..." -ForegroundColor Cyan
59+
try {
60+
$repoInfo = gh repo view --json nameWithOwner | ConvertFrom-Json
61+
$Repository = $repoInfo.nameWithOwner
62+
Write-Host "Using repository: $Repository" -ForegroundColor Green
63+
} catch {
64+
if ($Repository -eq "Chris-Wolfgang/ETL-FixedWidth") {
65+
Write-Error "Could not detect repository. Please run the setup script first to replace placeholders, or specify -Repository parameter."
66+
} else {
67+
Write-Error "Could not detect repository. Please run from within a git repository or specify -Repository parameter."
68+
}
69+
exit 1
70+
}
71+
} else {
72+
Write-Host "Using specified repository: $Repository" -ForegroundColor Green
73+
}
74+
75+
# Fetch all rulesets
76+
Write-Host "`nFetching existing rulesets..." -ForegroundColor Cyan
77+
78+
try {
79+
$rulesetsJson = gh api `
80+
-H "Accept: application/vnd.github+json" `
81+
-H "X-GitHub-Api-Version: 2022-11-28" `
82+
"/repos/$Repository/rulesets" `
83+
--paginate 2>&1
84+
85+
if ($LASTEXITCODE -ne 0) {
86+
Write-Error "Failed to fetch rulesets: $rulesetsJson"
87+
exit 1
88+
}
89+
90+
$rulesets = $rulesetsJson | ConvertFrom-Json
91+
} catch {
92+
Write-Error "Failed to fetch rulesets: $($_.Exception.Message)"
93+
exit 1
94+
}
95+
96+
if (-not $rulesets -or $rulesets.Count -eq 0) {
97+
Write-Host "No rulesets found for $Repository. Nothing to fix." -ForegroundColor Green
98+
exit 0
99+
}
100+
101+
# Build the plan
102+
$plan = @()
103+
$targetRulesetName = "Protect main branch"
104+
105+
Write-Host "`nFound $($rulesets.Count) ruleset(s):" -ForegroundColor Cyan
106+
Write-Host ""
107+
108+
foreach ($ruleset in $rulesets) {
109+
$status = if ($ruleset.enforcement -eq "disabled") { "disabled" } else { $ruleset.enforcement }
110+
Write-Host " [$($ruleset.id)] $($ruleset.name) (enforcement: $status)" -ForegroundColor Gray
111+
112+
$actions = @()
113+
114+
# If this is the target name, rename it
115+
if ($ruleset.name -eq $targetRulesetName) {
116+
$actions += @{
117+
type = "rename"
118+
description = "Rename '$($ruleset.name)' -> '$($ruleset.name) (old)'"
119+
newName = "$($ruleset.name) (old)"
120+
}
121+
}
122+
123+
# If not already disabled, disable it
124+
if ($ruleset.enforcement -ne "disabled") {
125+
$actions += @{
126+
type = "disable"
127+
description = "Disable '$($ruleset.name)' (currently: $status)"
128+
}
129+
}
130+
131+
if ($actions.Count -gt 0) {
132+
$plan += @{
133+
ruleset = $ruleset
134+
actions = $actions
135+
}
136+
}
137+
}
138+
139+
Write-Host ""
140+
141+
# Present the plan
142+
if ($plan.Count -eq 0) {
143+
Write-Host "All rulesets are already disabled and none need renaming. Nothing to do." -ForegroundColor Green
144+
exit 0
145+
}
146+
147+
Write-Host "Planned changes:" -ForegroundColor Yellow
148+
Write-Host ""
149+
150+
$stepNumber = 1
151+
foreach ($item in $plan) {
152+
foreach ($action in $item.actions) {
153+
Write-Host " $stepNumber. $($action.description)" -ForegroundColor White
154+
$stepNumber++
155+
}
156+
}
157+
158+
Write-Host ""
159+
160+
# Prompt for confirmation
161+
$response = Read-Host "Proceed with these changes? (y/N)"
162+
if ($response -ne 'y' -and $response -ne 'Y') {
163+
Write-Host "Cancelled. No changes were made." -ForegroundColor Yellow
164+
exit 0
165+
}
166+
167+
Write-Host ""
168+
169+
# Execute the plan
170+
$errors = 0
171+
172+
foreach ($item in $plan) {
173+
$ruleset = $item.ruleset
174+
$rulesetId = $ruleset.id
175+
176+
# Build the update payload — apply rename and disable together in one API call
177+
$updatePayload = @{}
178+
179+
foreach ($action in $item.actions) {
180+
switch ($action.type) {
181+
"rename" {
182+
$updatePayload["name"] = $action.newName
183+
}
184+
"disable" {
185+
$updatePayload["enforcement"] = "disabled"
186+
}
187+
}
188+
}
189+
190+
if ($updatePayload.Count -gt 0) {
191+
$descriptions = ($item.actions | ForEach-Object { $_.description }) -join " + "
192+
Write-Host " Updating ruleset [$rulesetId]: $descriptions..." -ForegroundColor Cyan
193+
194+
$jsonPayload = $updatePayload | ConvertTo-Json -Depth 5
195+
$tempFile = [System.IO.Path]::GetTempFileName()
196+
$jsonPayload | Out-File -FilePath $tempFile -Encoding utf8NoBOM
197+
198+
try {
199+
$result = gh api `
200+
--method PUT `
201+
-H "Accept: application/vnd.github+json" `
202+
-H "X-GitHub-Api-Version: 2022-11-28" `
203+
"/repos/$Repository/rulesets/$rulesetId" `
204+
--input $tempFile 2>&1
205+
206+
if ($LASTEXITCODE -eq 0) {
207+
Write-Host " Done." -ForegroundColor Green
208+
} else {
209+
Write-Host " Failed: $result" -ForegroundColor Red
210+
$errors++
211+
}
212+
} catch {
213+
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
214+
$errors++
215+
} finally {
216+
if (Test-Path $tempFile) {
217+
Remove-Item $tempFile -Force
218+
}
219+
}
220+
}
221+
}
222+
223+
Write-Host ""
224+
225+
if ($errors -gt 0) {
226+
Write-Host "$errors action(s) failed. Review the errors above." -ForegroundColor Red
227+
exit 1
228+
} else {
229+
Write-Host "All changes applied successfully." -ForegroundColor Green
230+
Write-Host ""
231+
Write-Host "Next step: Run .\Setup-BranchRuleset.ps1 to create a fresh ruleset." -ForegroundColor Cyan
232+
Write-Host "View rulesets at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan
233+
}

scripts/Setup-BranchRuleset.ps1

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
[CmdletBinding()]
5555
param(
5656
[Parameter()]
57-
[string]$Repository = "@Chris-Wolfgang/ETL-FixedWidth",
57+
[string]$Repository = "Chris-Wolfgang/ETL-FixedWidth",
5858

5959
[Parameter()]
6060
[string]$BranchName = "main"
@@ -83,15 +83,15 @@ try {
8383
}
8484

8585
# Determine repository
86-
if ($Repository -eq "@Chris-Wolfgang/ETL-FixedWidth" -or -not $Repository) {
86+
if ($Repository -eq "Chris-Wolfgang/ETL-FixedWidth" -or -not $Repository) {
8787
# Placeholders not replaced or no repository specified - auto-detect
8888
Write-Host "🔍 Detecting current repository..." -ForegroundColor Cyan
8989
try {
9090
$repoInfo = gh repo view --json nameWithOwner | ConvertFrom-Json
9191
$Repository = $repoInfo.nameWithOwner
9292
Write-Host "✅ Using repository: $Repository" -ForegroundColor Green
9393
} catch {
94-
if ($Repository -eq "@Chris-Wolfgang/ETL-FixedWidth") {
94+
if ($Repository -eq "Chris-Wolfgang/ETL-FixedWidth") {
9595
Write-Error "❌ Could not detect repository. Please run the setup script (pwsh ./scripts/setup.ps1) first to replace placeholders, or specify -Repository parameter."
9696
} else {
9797
Write-Error "❌ Could not detect repository. Please run from within a git repository or specify -Repository parameter."
@@ -108,28 +108,33 @@ Write-Host "📌 Protected branch: $BranchName`n" -ForegroundColor Cyan
108108
# Check if ruleset already exists
109109
Write-Host "🔍 Checking for existing rulesets..." -ForegroundColor Yellow
110110
try {
111-
$matchingRulesets = gh api `
111+
$rulesetOutput = gh api `
112112
-H "Accept: application/vnd.github+json" `
113113
-H "X-GitHub-Api-Version: 2022-11-28" `
114114
"/repos/$Repository/rulesets" `
115115
--paginate `
116-
--jq '.[] | select(.name == "Protect main branch")' | ConvertFrom-Json
117-
118-
$existingRuleset = $matchingRulesets | Select-Object -First 1
119-
120-
if ($existingRuleset) {
121-
Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green
122-
Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan
123-
$response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)"
124-
if ($response -ne 'y' -and $response -ne 'Y') {
125-
Write-Host "Exiting." -ForegroundColor Yellow
126-
exit 0
116+
--jq '.[] | select(.name == "Protect main branch")' 2>&1
117+
118+
if ($LASTEXITCODE -ne 0) {
119+
Write-Warning "⚠️ Could not check for existing rulesets (API returned exit code $LASTEXITCODE). Continuing..."
120+
} elseif ($rulesetOutput) {
121+
$matchingRulesets = $rulesetOutput | ConvertFrom-Json
122+
$existingRuleset = $matchingRulesets | Select-Object -First 1
123+
124+
if ($existingRuleset) {
125+
Write-Host "✅ Ruleset 'Protect main branch' already exists!" -ForegroundColor Green
126+
Write-Host " View it at: https://github.com/$Repository/settings/rules" -ForegroundColor Cyan
127+
$response = Read-Host "`nDo you want to continue anyway? This may fail. (y/N)"
128+
if ($response -ne 'y' -and $response -ne 'Y') {
129+
Write-Host "Exiting." -ForegroundColor Yellow
130+
exit 0
131+
}
127132
}
128133
} else {
129134
Write-Host "ℹ️ Ruleset 'Protect main branch' does not exist yet." -ForegroundColor Gray
130135
}
131136
} catch {
132-
Write-Warning "⚠️ Could not check for existing rulesets. Continuing..."
137+
Write-Warning "⚠️ Could not check for existing rulesets: $($_.Exception.Message). Continuing..."
133138
}
134139

135140
# Prompt for repository type
@@ -195,7 +200,7 @@ $rulesetConfig = @{
195200
@{ context = "Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" },
196201
@{ context = "Stage 3: macOS Tests (.NET 6.0-10.0)" },
197202
@{ context = "Security Scan (DevSkim)" },
198-
@{ context = "Security Scan (CodeQL)" }
203+
@{ context = "CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" }
199204
)
200205
}
201206
},
@@ -239,9 +244,6 @@ $rulesetConfig = @{
239244
},
240245
@{
241246
type = "deletion"
242-
},
243-
@{
244-
type = "update"
245247
}
246248
)
247249
}
@@ -251,7 +253,7 @@ $jsonConfig = $rulesetConfig | ConvertTo-Json -Depth 10
251253

252254
# Save to temporary file
253255
$tempFile = [System.IO.Path]::GetTempFileName()
254-
$jsonConfig | Out-File -FilePath $tempFile -Encoding UTF8
256+
$jsonConfig | Out-File -FilePath $tempFile -Encoding utf8NoBOM
255257

256258
try {
257259
Write-Host "🚀 Creating branch ruleset..." -ForegroundColor Cyan
@@ -279,7 +281,7 @@ try {
279281
Write-Host " - Stage 2: Windows Tests (.NET 5.0-10.0, Framework 4.6.2-4.8.1)" -ForegroundColor DarkGray
280282
Write-Host " - Stage 3: macOS Tests (.NET 6.0-10.0)" -ForegroundColor DarkGray
281283
Write-Host " - Security Scan (DevSkim)" -ForegroundColor DarkGray
282-
Write-Host " - Security Scan (CodeQL)" -ForegroundColor DarkGray
284+
Write-Host " - CodeQL Security Analysis / Security Scan (CodeQL) (csharp) (pull_request)" -ForegroundColor DarkGray
283285
Write-Host " ✅ Branches must be up to date before merging" -ForegroundColor Gray
284286
Write-Host " ✅ Conversation resolution required before merging" -ForegroundColor Gray
285287
Write-Host " ✅ Stale reviews dismissed when new commits are pushed" -ForegroundColor Gray
@@ -332,4 +334,3 @@ try {
332334
}
333335

334336
Write-Host "`n🎉 Setup complete!" -ForegroundColor Green
335-

0 commit comments

Comments
 (0)