Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 6s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m45s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m22s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m56s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m33s
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
37 lines
2.4 KiB
Plaintext
37 lines
2.4 KiB
Plaintext
# THE governing-branch-protection-rule classifier, as ONE program with two callers (ersatztv#787).
|
|
#
|
|
# Input : the `GET /repos/{owner}/{repo}/branch_protections` array. Arg `$b`: the branch to resolve.
|
|
# Output: {verdict:"exact", rule:<the rule>} | {verdict:"undecidable"} | {verdict:"none"}
|
|
#
|
|
# It lived inline in `.claude/hooks/pretooluse-merge-consent.sh` until #787 needed the same question
|
|
# answered for `main` from a second caller. It was EXTRACTED rather than copied: this is a security
|
|
# predicate, and that hook's own history is the argument — the docs-only file enumeration was written
|
|
# twice, four rounds of hardening landed on one copy and never reached the other, and the copy with
|
|
# real authority ended up strictly weaker than the copy without. Two copies of a security predicate
|
|
# drift; one cannot.
|
|
#
|
|
# DO NOT "simplify" the undecidable-first ordering. Gitea picks the governing rule with
|
|
# `GetFirstMatched` over a list sorted by Priority, then by plain-name-ness, so a GLOB rule with a
|
|
# better Priority outranks an exactly-named one. Preferring the exact rule would inspect a rule Gitea
|
|
# might not be applying. `superset` deliberately over-approximates a glob (literal prefix + `.*` +
|
|
# literal suffix) instead of reimplementing gobwas/glob: refusing whenever a glob COULD match is
|
|
# sound without knowing the dialect, which is the only claim either caller makes.
|
|
def esc: gsub("(?<c>[.+?^${}()|\\[\\]\\\\])"; "\\" + .c);
|
|
def offs: [match("[*?\\[\\]{}\\\\]"; "g").offset];
|
|
def superset: . as $n | (offs) as $o
|
|
| ($n[0:$o[0]] | esc) + ".*" + ($n[($o[-1]+1):] | esc);
|
|
def nonascii: explode | any(. > 127);
|
|
. as $rules | $b as $base |
|
|
($rules | map(select((.branch_name // .rule_name // "") as $n
|
|
| (($n|offs|length) == 0)
|
|
and (($n|ascii_downcase) == ($base|ascii_downcase))))) as $exacts |
|
|
(($base|nonascii) or ($rules | any((.branch_name // .rule_name // "") as $n
|
|
| ($n|offs|length) == 0 and ($n|nonascii)))) as $unfoldable |
|
|
if ($rules | any((.branch_name // .rule_name // "") as $n
|
|
| (($n|offs|length) > 0)
|
|
and ($base | test("^" + ($n|superset) + "$")))) then {verdict:"undecidable"}
|
|
elif $unfoldable then {verdict:"undecidable"}
|
|
elif ($exacts | length) > 1 then {verdict:"undecidable"}
|
|
elif ($exacts | length) == 1 then {verdict:"exact", rule:($exacts | first)}
|
|
else {verdict:"none"} end
|