Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions scripts/reset-release-tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import urllib.error
import urllib.request

RULESET = "Protect release tags"
VERSION_TAG = re.compile(r"^(src/)?v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$")
SHA = re.compile(r"^[0-9a-f]{7,40}$")

Expand Down Expand Up @@ -108,14 +107,15 @@ def packagist(repo: str) -> str:
return f"unknown ({e})"


def find_ruleset(repo: str) -> dict | None:
def find_rulesets(repo: str) -> list[dict]:
"""Every active tag ruleset, whatever its name: any of them can block the move or delete."""
try:
rulesets = json.loads(gh("api", f"repos/{repo}/rulesets?includes_parents=false"))
except RuntimeError as e:
if "403" in str(e) or "Upgrade to GitHub Pro" in str(e):
return None
return []
raise
return next((r for r in rulesets if r["name"] == RULESET and r["target"] == "tag"), None)
return [r for r in rulesets if r["target"] == "tag" and r.get("enforcement", "active") == "active"]


def set_enforcement(repo: str, ruleset_id: int, enforcement: str) -> None:
Expand Down Expand Up @@ -211,14 +211,16 @@ def main(argv: list[str]) -> int:
print(f"\nDry run. Nothing changed. Re-run with --apply --confirm {repo}.")
return 0

ruleset = find_ruleset(repo)
rulesets = find_rulesets(repo)
disabled: list[dict] = []
failed = False
try:
if ruleset:
if not rulesets:
print("\nNo active tag ruleset to disable.")
for ruleset in rulesets:
set_enforcement(repo, ruleset["id"], "disabled")
print(f"\nDisabled ruleset {ruleset['id']} ({RULESET}).")
else:
print(f"\nNo {RULESET!r} ruleset to disable.")
disabled.append(ruleset)
print(f"\nDisabled ruleset {ruleset['id']} ({ruleset['name']}).")
for n in moves:
gh("api", "-X", "PATCH", f"repos/{repo}/git/refs/tags/{n}", "--input", "-",
stdin=json.dumps({"sha": sha, "force": True}))
Expand All @@ -237,10 +239,10 @@ def main(argv: list[str]) -> int:
failed = True
print(f"Failed: {e}", file=sys.stderr)
finally:
if ruleset:
for ruleset in disabled:
set_enforcement(repo, ruleset["id"], "active")
state = json.loads(gh("api", f"repos/{repo}/rulesets/{ruleset['id']}"))["enforcement"]
print(f"Ruleset {ruleset['id']} ({RULESET}): {state}")
print(f"Ruleset {ruleset['id']} ({ruleset['name']}): {state}")
return 1 if failed else 0


Expand Down
24 changes: 24 additions & 0 deletions tests/Feature/ResetReleaseTagsScriptTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,27 @@ function resetWriteCalls(string $calls): array
'not a sha' => [['acme/widget', '--fresh', 'v0.1.0', '--to', 'main']],
'unknown option' => [['acme/widget', '--fresh', 'v0.1.0', '--to', RESET_SHA, '--force']],
])->skipOnWindows();

it('lifts every active tag ruleset whatever its name, and leaves branch and disabled rulesets alone', function (): void {
$fixture = resetFixture();
$fixture['api repos/acme/widget/rulesets?includes_parents=false'] = [
['id' => 7, 'name' => 'release tags are final', 'target' => 'tag', 'enforcement' => 'active'],
['id' => 8, 'name' => 'Protect release tags', 'target' => 'tag', 'enforcement' => 'active'],
['id' => 9, 'name' => 'old tag rule', 'target' => 'tag', 'enforcement' => 'disabled'],
['id' => 10, 'name' => 'default branch', 'target' => 'branch', 'enforcement' => 'active'],
];
$fixture['api repos/acme/widget/rulesets/8'] = ['id' => 8, 'enforcement' => 'active'];
[$process, $calls] = runResetTags(['acme/widget', '--move', 'v0.1.0', '--to', RESET_SHA, '--apply', '--confirm', 'acme/widget'], $fixture);

$writes = resetWriteCalls($calls);
expect($process->getExitCode())->toBe(0)
->and($writes[0])->toBe('api -X PUT repos/acme/widget/rulesets/7 {"enforcement": "disabled"}')
->and($writes[1])->toBe('api -X PUT repos/acme/widget/rulesets/8 {"enforcement": "disabled"}')
->and(array_slice($writes, -2))->toBe([
'api -X PUT repos/acme/widget/rulesets/7 {"enforcement": "active"}',
'api -X PUT repos/acme/widget/rulesets/8 {"enforcement": "active"}',
])
->and($calls)->not->toContain('rulesets/9 ')
->not->toContain('rulesets/10 ')
->and($process->getOutput())->toContain('Ruleset 7 (release tags are final): active');
})->skipOnWindows();
Loading