Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Refuse a protected-ref pattern that can never match

93c7cae8ebb7d6d853dca476851e2bd0cbe10079 · Keenan Nemetz · 19h ago · parent 2c988ecd

Refuse a protected-ref pattern that can never match

acl protect stored any string: refs/head/main read like a protected branch in
acl show while the hook compared it against refs/heads/main and matched
nothing, so approvals and required checks were off on the branch everything
lands on.

Validate the pattern in forge/access, where the rule is stored, and call it
from both acl protect add|set and the repository settings form. Only the two
shapes IsProtected understands are accepted, exact ref and one trailing
wildcard, the ref part must be a name git could store, and a singular
category is named as the typo it is.

server/integration_test.go +54

@@ -3854,6 +3854,60 @@ func TestACLCommandsNameTheFieldTheyChange(t *testing.T) {
3854 3854 }
3855 3855 }
3856 3856
3857 // TestACLProtectRefusesAPatternThatCannotMatch: protection is configured once and
3858 // assumed afterwards, so a typo in the category must fail at the command rather
3859 // than leave the branch open behind a line that reads like a correct one.
3860 func TestACLProtectRefusesAPatternThatCannotMatch(t *testing.T) {
3861 t.Parallel()
3862 f := newFixture(t)
3863 f.publish(f.repo)
3864 cfg := f.cfgFile(t)
3865 protected := func() string {
3866 t.Helper()
3867 a, err := mustRepo(t, f).ACL()
3868 if err != nil {
3869 t.Fatal(err)
3870 }
3871 return strings.Join(a.Protected, ",")
3872 }
3873
3874 _, errs, err := f.cli("", "acl", "protect", "add", f.repo, "refs/head/main", "--config", cfg)
3875 if err == nil {
3876 t.Fatalf("a pattern that cannot match was stored:\n%s", errs)
3877 }
3878 if !strings.Contains(errs, `refs/heads/main`) {
3879 t.Fatalf("the refusal does not name the fix:\n%s", errs)
3880 }
3881 if got := protected(); got != "" {
3882 t.Fatalf("the refusal stored %q", got)
3883 }
3884
3885 // One bad pattern in a list refuses the whole list rather than storing part
3886 // of it, which would read as a complete rule set.
3887 if _, errs, err := f.cli("", "acl", "protect", "add", f.repo, "refs/heads/main", "refs/heads/ma in", "--config", cfg); err == nil {
3888 t.Fatalf("a list with a bad pattern was accepted:\n%s", errs)
3889 }
3890 if got := protected(); got != "" {
3891 t.Fatalf("a refused list stored part of itself: %q", got)
3892 }
3893 if _, errs, err := f.cli("", "acl", "protect", "set", f.repo, "heads/main", "--config", cfg); err == nil {
3894 t.Fatalf("protect set stored a pattern that cannot match:\n%s", errs)
3895 }
3896
3897 for _, pattern := range []string{"refs/heads/main", "refs/heads/release/*"} {
3898 if _, errs, err := f.cli("", "acl", "protect", "add", f.repo, pattern, "--config", cfg); err != nil {
3899 t.Fatalf("%s: %v\n%s", pattern, err, errs)
3900 }
3901 }
3902 if got, want := protected(), "refs/heads/main,refs/heads/release/*"; got != want {
3903 t.Fatalf("protected = %q, want %q", got, want)
3904 }
3905 out, _, err := f.cli("", "acl", "show", f.repo, "--config", cfg)
3906 if err != nil || !strings.Contains(out, "refs/heads/release/*") {
3907 t.Fatalf("acl show does not round-trip the pattern:\n%s", out)
3908 }
3909 }
3910
3857 3911 // TestFailingCheckBlocksTheMerge: a green approval does not outvote a red build.
3858 3912 func TestFailingCheckBlocksTheMerge(t *testing.T) {
3859 3913 t.Parallel()