acl protect stored any string it was given. refs/head/main (singular head)
read like a protected branch in acl show while the hook compared it against
refs/heads/main and matched nothing, so approval gating and required checks were
off on the branch everything lands on — with no signal beyond a line that looked
correct.
What changed
access.ValidateProtectedPattern/ValidateProtectedPatternsinforge/access, where the rule is stored, accept the two shapesIsProtectedactually understands (an exact ref, or a prefix ending in one*) and require the ref part to be a name git could store. A pattern that can never match is refused, and a singular category is named as the typo it is:protected pattern "refs/head/main" cannot match any ref: did you mean "refs/heads/main"?- Both surfaces that write the list call it:
acl protect add|set(the local CLI incmd/vole/acl.goand the SSH admin path inserver/ssh/admin.go) and the repository Settings form (server/web/admin_access.go). The browser form therefore cannot bypass what the CLI learns, and a list with one bad pattern is refused whole rather than stored in part. - A prefix for a namespace with nothing in it yet is still allowed:
refs/heads/release/*is a legitimate rule for the first release branch, andrefs/*is allowed as the deliberately broad rule. docs/cli.mdanddocs/accounts-and-access.mddescribe the two shapes and show the refusal.
Not done, from the issue’s “consider” list: marking, in acl show, patterns that
currently match no ref in the repository, and warning at write time when a
pattern matches nothing yet. Both need the repository’s ref list and would report
on a legitimate future rule; they are follow-up material, not part of the
acceptance criteria.
Verification
TestValidateProtectedPatterncovers the accepted shapes and the refusals (typoed category, missingrefs/, inner wildcard, empty path component,.., trailing dot/.lock, whitespace), andTestAcceptedPatternsRoundTripchecks an accepted pattern still round-trips and still protects the refs it names.TestACLProtectRefusesAPatternThatCannotMatchcovers the local CLI: a bad pattern fails and stores nothing, a list with one bad entry stores none of it, and valid patterns still round-trip throughacl show.TestRepositoryAccessRefusesAPatternThatCannotMatchcovers the Settings form (400 with the suggested ref, nothing stored, valid patterns still save).TestRemoteAdminManagesAccountsAndReposnow also checks the SSH admin surface../vole workflow run verifypasses.
Closes #240.