Diff
1diff --git a/pkg/backend/repo.go b/pkg/backend/repo.go
2index 0d2a5b546dbc0c059a495b3fd54853af174943bb..a0965338ec23139778690afa48b4af5a8f083412 100644
3--- a/pkg/backend/repo.go
4+++ b/pkg/backend/repo.go
5@@ -664,8 +664,15 @@ func (d *Backend) SetPrivate(ctx context.Context, name string, private bool) err
6 name = utils.SanitizeRepo(name)
7 rp := filepath.Join(d.repoPath(name))
8
9+ var changed bool
10 if err := db.WrapError(
11 d.db.TransactionContext(ctx, func(tx *db.Tx) error {
12+ m, err := d.store.GetRepoByName(ctx, tx, name)
13+ if err != nil {
14+ return err
15+ }
16+ changed = m.Private != private
17+
18 fp := filepath.Join(rp, "git-daemon-export-ok")
19 if !private {
20 if err := os.WriteFile(fp, []byte{}, fs.ModePerm); err != nil {
21@@ -684,27 +691,27 @@ func (d *Backend) SetPrivate(ctx context.Context, name string, private bool) err
22 return d.store.SetRepoIsPrivateByName(ctx, tx, name, private)
23 }),
24 ); err != nil {
25+ if errors.Is(err, db.ErrRecordNotFound) {
26+ return proto.ErrRepoNotFound
27+ }
28 return err
29 }
30
31- user := proto.UserFromContext(ctx)
32+ if !changed {
33+ return nil
34+ }
35+
36 repo, err := d.Repository(ctx, name)
37 if err != nil {
38 return err
39 }
40
41- if repo.IsPrivate() != !private {
42- wh, err := webhook.NewRepositoryEvent(ctx, user, repo, webhook.RepositoryEventActionVisibilityChange)
43- if err != nil {
44- return err
45- }
46-
47- if err := webhook.SendEvent(ctx, wh); err != nil {
48- return err
49- }
50+ wh, err := webhook.NewRepositoryEvent(ctx, proto.UserFromContext(ctx), repo, webhook.RepositoryEventActionVisibilityChange)
51+ if err != nil {
52+ return err
53 }
54
55- return nil
56+ return webhook.SendEvent(ctx, wh)
57 }
58
59 // SetProjectName sets the project name of a repository.
60diff --git a/pkg/backend/repo_visibility_webhook_test.go b/pkg/backend/repo_visibility_webhook_test.go
61new file mode 100644
62index 0000000000000000000000000000000000000000..8d54e96c2fa8344462e02f0e4bb9e3748b0d291e
63--- /dev/null
64+++ b/pkg/backend/repo_visibility_webhook_test.go
65@@ -0,0 +1,60 @@
66+package backend
67+
68+import (
69+ "context"
70+ "testing"
71+
72+ "github.com/charmbracelet/soft-serve/pkg/config"
73+ "github.com/charmbracelet/soft-serve/pkg/db"
74+ "github.com/charmbracelet/soft-serve/pkg/proto"
75+ "github.com/charmbracelet/soft-serve/pkg/store"
76+ "github.com/charmbracelet/soft-serve/pkg/webhook"
77+ "github.com/matryer/is"
78+)
79+
80+// TestSetPrivateWebhookOnlyOnChange verifies that the visibility change
81+// webhook fires when visibility changes and not when it is set to the
82+// value it already has.
83+func TestSetPrivateWebhookOnlyOnChange(t *testing.T) {
84+ is := is.New(t)
85+ be, cfg := newTestBackend(t)
86+
87+ ctx := context.Background()
88+ ctx = config.WithContext(ctx, cfg)
89+ ctx = db.WithContext(ctx, be.db)
90+ ctx = store.WithContext(ctx, be.store)
91+ admin, err := be.User(ctx, "admin")
92+ is.NoErr(err)
93+ ctx = proto.WithUserContext(ctx, admin)
94+
95+ repo, err := be.CreateRepository(ctx, "repo", nil, proto.RepositoryOptions{})
96+ is.NoErr(err)
97+
98+ // Deliveries to this address fail fast, but are still recorded.
99+ var hookID int64
100+ is.NoErr(be.db.TransactionContext(ctx, func(tx *db.Tx) error {
101+ hookID, err = be.store.CreateWebhook(ctx, tx, repo.ID(), "http://127.0.0.1:1/", "", int(webhook.ContentTypeJSON), true)
102+ if err != nil {
103+ return err
104+ }
105+ return be.store.CreateWebhookEvents(ctx, tx, hookID, []int{int(webhook.EventRepositoryVisibilityChange)})
106+ }))
107+
108+ deliveries := func() int {
109+ ds, err := be.store.ListWebhookDeliveriesByWebhookID(ctx, be.db, hookID)
110+ is.NoErr(err)
111+ return len(ds)
112+ }
113+
114+ is.NoErr(be.SetPrivate(ctx, "repo", false))
115+ is.Equal(deliveries(), 0)
116+
117+ is.NoErr(be.SetPrivate(ctx, "repo", true))
118+ is.Equal(deliveries(), 1)
119+
120+ is.NoErr(be.SetPrivate(ctx, "repo", true))
121+ is.Equal(deliveries(), 1)
122+
123+ is.NoErr(be.SetPrivate(ctx, "repo", false))
124+ is.Equal(deliveries(), 2)
125+}