tenseleyflow/shithub / c8ddb9c

Browse files

S20: branch_protection_rules table + queries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
c8ddb9cd09a1d79f58185ffb54b84deea0ba90a8
Parents
fc23ff8
Tree
d2dfb80

9 changed files

StatusFile+-
M internal/auth/policy/sqlc/models.go 15 0
M internal/meta/sqlc/models.go 15 0
A internal/migrationsfs/migrations/0021_branch_protection_rules.sql 40 0
A internal/repos/queries/branch_protection.sql 47 0
A internal/repos/sqlc/branch_protection.sql.go 180 0
M internal/repos/sqlc/models.go 15 0
M internal/repos/sqlc/querier.go 9 0
M internal/users/sqlc/models.go 15 0
M internal/worker/sqlc/models.go 15 0
internal/auth/policy/sqlc/models.gomodified
@@ -204,6 +204,21 @@ type AuthThrottle struct {
204204
 	WindowStartedAt pgtype.Timestamptz
205205
 }
206206
 
207
+type BranchProtectionRule struct {
208
+	ID                   int64
209
+	RepoID               int64
210
+	Pattern              string
211
+	PreventForcePush     bool
212
+	PreventDeletion      bool
213
+	RequirePrForPush     bool
214
+	AllowedPusherUserIds []int64
215
+	RequireSignedCommits bool
216
+	StatusChecksRequired []string
217
+	CreatedAt            pgtype.Timestamptz
218
+	UpdatedAt            pgtype.Timestamptz
219
+	CreatedByUserID      pgtype.Int8
220
+}
221
+
207222
 type EmailVerification struct {
208223
 	ID          int64
209224
 	UserEmailID int64
internal/meta/sqlc/models.gomodified
@@ -204,6 +204,21 @@ type AuthThrottle struct {
204204
 	WindowStartedAt pgtype.Timestamptz
205205
 }
206206
 
207
+type BranchProtectionRule struct {
208
+	ID                   int64
209
+	RepoID               int64
210
+	Pattern              string
211
+	PreventForcePush     bool
212
+	PreventDeletion      bool
213
+	RequirePrForPush     bool
214
+	AllowedPusherUserIds []int64
215
+	RequireSignedCommits bool
216
+	StatusChecksRequired []string
217
+	CreatedAt            pgtype.Timestamptz
218
+	UpdatedAt            pgtype.Timestamptz
219
+	CreatedByUserID      pgtype.Int8
220
+}
221
+
207222
 type EmailVerification struct {
208223
 	ID          int64
209224
 	UserEmailID int64
internal/migrationsfs/migrations/0021_branch_protection_rules.sqladded
@@ -0,0 +1,40 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Branch protection rules. Each rule is one (repo, glob pattern) tuple
4
+-- with a set of bool/array fields the pre-receive hook checks. Pattern
5
+-- matching is glob-style (filepath.Match: `*`, `?`, `[abc]`) — matches
6
+-- GitHub's UX and is easy to author.
7
+--
8
+-- Some columns are placeholders for later sprints:
9
+--   require_signed_commits   — post-MVP signing surface
10
+--   require_pr_for_push      — post-MVP "no direct push" enforcement
11
+--   status_checks_required   — S24 ships the check engine
12
+--
13
+-- The pre-receive hook ignores those columns until their owning sprint
14
+-- wires real enforcement; the schema is forward-compatible.
15
+
16
+-- +goose Up
17
+CREATE TABLE branch_protection_rules (
18
+    id                          bigserial   PRIMARY KEY,
19
+    repo_id                     bigint      NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
20
+    pattern                     text        NOT NULL,
21
+    prevent_force_push          boolean     NOT NULL DEFAULT true,
22
+    prevent_deletion            boolean     NOT NULL DEFAULT true,
23
+    require_pr_for_push         boolean     NOT NULL DEFAULT false,
24
+    allowed_pusher_user_ids     bigint[]    NOT NULL DEFAULT '{}',
25
+    require_signed_commits      boolean     NOT NULL DEFAULT false,
26
+    status_checks_required      text[]      NOT NULL DEFAULT '{}',
27
+    created_at                  timestamptz NOT NULL DEFAULT now(),
28
+    updated_at                  timestamptz NOT NULL DEFAULT now(),
29
+    created_by_user_id          bigint      REFERENCES users(id) ON DELETE SET NULL,
30
+
31
+    CONSTRAINT branch_protection_rules_pattern_length CHECK (char_length(pattern) BETWEEN 1 AND 200)
32
+);
33
+
34
+CREATE INDEX branch_protection_rules_repo_idx ON branch_protection_rules (repo_id, pattern);
35
+
36
+CREATE TRIGGER set_updated_at BEFORE UPDATE ON branch_protection_rules
37
+    FOR EACH ROW EXECUTE FUNCTION tg_set_updated_at();
38
+
39
+-- +goose Down
40
+DROP TABLE IF EXISTS branch_protection_rules;
internal/repos/queries/branch_protection.sqladded
@@ -0,0 +1,47 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+-- name: ListBranchProtectionRules :many
4
+SELECT id, repo_id, pattern,
5
+       prevent_force_push, prevent_deletion, require_pr_for_push,
6
+       allowed_pusher_user_ids,
7
+       require_signed_commits, status_checks_required,
8
+       created_at, updated_at, created_by_user_id
9
+FROM branch_protection_rules
10
+WHERE repo_id = $1
11
+ORDER BY pattern;
12
+
13
+-- name: GetBranchProtectionRule :one
14
+SELECT id, repo_id, pattern,
15
+       prevent_force_push, prevent_deletion, require_pr_for_push,
16
+       allowed_pusher_user_ids,
17
+       require_signed_commits, status_checks_required,
18
+       created_at, updated_at, created_by_user_id
19
+FROM branch_protection_rules
20
+WHERE id = $1;
21
+
22
+-- name: UpsertBranchProtectionRule :one
23
+INSERT INTO branch_protection_rules (
24
+    repo_id, pattern,
25
+    prevent_force_push, prevent_deletion, require_pr_for_push,
26
+    allowed_pusher_user_ids, created_by_user_id
27
+) VALUES (
28
+    $1, $2, $3, $4, $5, $6, sqlc.narg(created_by_user_id)::bigint
29
+)
30
+RETURNING id;
31
+
32
+-- name: UpdateBranchProtectionRule :exec
33
+UPDATE branch_protection_rules
34
+SET pattern = $2,
35
+    prevent_force_push = $3,
36
+    prevent_deletion = $4,
37
+    require_pr_for_push = $5,
38
+    allowed_pusher_user_ids = $6
39
+WHERE id = $1;
40
+
41
+-- name: DeleteBranchProtectionRule :exec
42
+DELETE FROM branch_protection_rules WHERE id = $1;
43
+
44
+-- name: UpdateRepoDefaultBranch :exec
45
+-- Used by the default-branch settings handler. The on-disk HEAD update
46
+-- is a separate step done via `git symbolic-ref` from the orchestrator.
47
+UPDATE repos SET default_branch = $2, updated_at = now() WHERE id = $1;
internal/repos/sqlc/branch_protection.sql.goadded
@@ -0,0 +1,180 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: branch_protection.sql
5
+
6
+package reposdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const deleteBranchProtectionRule = `-- name: DeleteBranchProtectionRule :exec
15
+DELETE FROM branch_protection_rules WHERE id = $1
16
+`
17
+
18
+func (q *Queries) DeleteBranchProtectionRule(ctx context.Context, db DBTX, id int64) error {
19
+	_, err := db.Exec(ctx, deleteBranchProtectionRule, id)
20
+	return err
21
+}
22
+
23
+const getBranchProtectionRule = `-- name: GetBranchProtectionRule :one
24
+SELECT id, repo_id, pattern,
25
+       prevent_force_push, prevent_deletion, require_pr_for_push,
26
+       allowed_pusher_user_ids,
27
+       require_signed_commits, status_checks_required,
28
+       created_at, updated_at, created_by_user_id
29
+FROM branch_protection_rules
30
+WHERE id = $1
31
+`
32
+
33
+func (q *Queries) GetBranchProtectionRule(ctx context.Context, db DBTX, id int64) (BranchProtectionRule, error) {
34
+	row := db.QueryRow(ctx, getBranchProtectionRule, id)
35
+	var i BranchProtectionRule
36
+	err := row.Scan(
37
+		&i.ID,
38
+		&i.RepoID,
39
+		&i.Pattern,
40
+		&i.PreventForcePush,
41
+		&i.PreventDeletion,
42
+		&i.RequirePrForPush,
43
+		&i.AllowedPusherUserIds,
44
+		&i.RequireSignedCommits,
45
+		&i.StatusChecksRequired,
46
+		&i.CreatedAt,
47
+		&i.UpdatedAt,
48
+		&i.CreatedByUserID,
49
+	)
50
+	return i, err
51
+}
52
+
53
+const listBranchProtectionRules = `-- name: ListBranchProtectionRules :many
54
+
55
+SELECT id, repo_id, pattern,
56
+       prevent_force_push, prevent_deletion, require_pr_for_push,
57
+       allowed_pusher_user_ids,
58
+       require_signed_commits, status_checks_required,
59
+       created_at, updated_at, created_by_user_id
60
+FROM branch_protection_rules
61
+WHERE repo_id = $1
62
+ORDER BY pattern
63
+`
64
+
65
+// SPDX-License-Identifier: AGPL-3.0-or-later
66
+func (q *Queries) ListBranchProtectionRules(ctx context.Context, db DBTX, repoID int64) ([]BranchProtectionRule, error) {
67
+	rows, err := db.Query(ctx, listBranchProtectionRules, repoID)
68
+	if err != nil {
69
+		return nil, err
70
+	}
71
+	defer rows.Close()
72
+	items := []BranchProtectionRule{}
73
+	for rows.Next() {
74
+		var i BranchProtectionRule
75
+		if err := rows.Scan(
76
+			&i.ID,
77
+			&i.RepoID,
78
+			&i.Pattern,
79
+			&i.PreventForcePush,
80
+			&i.PreventDeletion,
81
+			&i.RequirePrForPush,
82
+			&i.AllowedPusherUserIds,
83
+			&i.RequireSignedCommits,
84
+			&i.StatusChecksRequired,
85
+			&i.CreatedAt,
86
+			&i.UpdatedAt,
87
+			&i.CreatedByUserID,
88
+		); err != nil {
89
+			return nil, err
90
+		}
91
+		items = append(items, i)
92
+	}
93
+	if err := rows.Err(); err != nil {
94
+		return nil, err
95
+	}
96
+	return items, nil
97
+}
98
+
99
+const updateBranchProtectionRule = `-- name: UpdateBranchProtectionRule :exec
100
+UPDATE branch_protection_rules
101
+SET pattern = $2,
102
+    prevent_force_push = $3,
103
+    prevent_deletion = $4,
104
+    require_pr_for_push = $5,
105
+    allowed_pusher_user_ids = $6
106
+WHERE id = $1
107
+`
108
+
109
+type UpdateBranchProtectionRuleParams struct {
110
+	ID                   int64
111
+	Pattern              string
112
+	PreventForcePush     bool
113
+	PreventDeletion      bool
114
+	RequirePrForPush     bool
115
+	AllowedPusherUserIds []int64
116
+}
117
+
118
+func (q *Queries) UpdateBranchProtectionRule(ctx context.Context, db DBTX, arg UpdateBranchProtectionRuleParams) error {
119
+	_, err := db.Exec(ctx, updateBranchProtectionRule,
120
+		arg.ID,
121
+		arg.Pattern,
122
+		arg.PreventForcePush,
123
+		arg.PreventDeletion,
124
+		arg.RequirePrForPush,
125
+		arg.AllowedPusherUserIds,
126
+	)
127
+	return err
128
+}
129
+
130
+const updateRepoDefaultBranch = `-- name: UpdateRepoDefaultBranch :exec
131
+UPDATE repos SET default_branch = $2, updated_at = now() WHERE id = $1
132
+`
133
+
134
+type UpdateRepoDefaultBranchParams struct {
135
+	ID            int64
136
+	DefaultBranch string
137
+}
138
+
139
+// Used by the default-branch settings handler. The on-disk HEAD update
140
+// is a separate step done via `git symbolic-ref` from the orchestrator.
141
+func (q *Queries) UpdateRepoDefaultBranch(ctx context.Context, db DBTX, arg UpdateRepoDefaultBranchParams) error {
142
+	_, err := db.Exec(ctx, updateRepoDefaultBranch, arg.ID, arg.DefaultBranch)
143
+	return err
144
+}
145
+
146
+const upsertBranchProtectionRule = `-- name: UpsertBranchProtectionRule :one
147
+INSERT INTO branch_protection_rules (
148
+    repo_id, pattern,
149
+    prevent_force_push, prevent_deletion, require_pr_for_push,
150
+    allowed_pusher_user_ids, created_by_user_id
151
+) VALUES (
152
+    $1, $2, $3, $4, $5, $6, $7::bigint
153
+)
154
+RETURNING id
155
+`
156
+
157
+type UpsertBranchProtectionRuleParams struct {
158
+	RepoID               int64
159
+	Pattern              string
160
+	PreventForcePush     bool
161
+	PreventDeletion      bool
162
+	RequirePrForPush     bool
163
+	AllowedPusherUserIds []int64
164
+	CreatedByUserID      pgtype.Int8
165
+}
166
+
167
+func (q *Queries) UpsertBranchProtectionRule(ctx context.Context, db DBTX, arg UpsertBranchProtectionRuleParams) (int64, error) {
168
+	row := db.QueryRow(ctx, upsertBranchProtectionRule,
169
+		arg.RepoID,
170
+		arg.Pattern,
171
+		arg.PreventForcePush,
172
+		arg.PreventDeletion,
173
+		arg.RequirePrForPush,
174
+		arg.AllowedPusherUserIds,
175
+		arg.CreatedByUserID,
176
+	)
177
+	var id int64
178
+	err := row.Scan(&id)
179
+	return id, err
180
+}
internal/repos/sqlc/models.gomodified
@@ -204,6 +204,21 @@ type AuthThrottle struct {
204204
 	WindowStartedAt pgtype.Timestamptz
205205
 }
206206
 
207
+type BranchProtectionRule struct {
208
+	ID                   int64
209
+	RepoID               int64
210
+	Pattern              string
211
+	PreventForcePush     bool
212
+	PreventDeletion      bool
213
+	RequirePrForPush     bool
214
+	AllowedPusherUserIds []int64
215
+	RequireSignedCommits bool
216
+	StatusChecksRequired []string
217
+	CreatedAt            pgtype.Timestamptz
218
+	UpdatedAt            pgtype.Timestamptz
219
+	CreatedByUserID      pgtype.Int8
220
+}
221
+
207222
 type EmailVerification struct {
208223
 	ID          int64
209224
 	UserEmailID int64
internal/repos/sqlc/querier.gomodified
@@ -23,6 +23,7 @@ type Querier interface {
2323
 	// SPDX-License-Identifier: AGPL-3.0-or-later
2424
 	CreateRepo(ctx context.Context, db DBTX, arg CreateRepoParams) (Repo, error)
2525
 	DeclineTransferRequest(ctx context.Context, db DBTX, id int64) error
26
+	DeleteBranchProtectionRule(ctx context.Context, db DBTX, id int64) error
2627
 	// Used by hard-delete: drop the redirect rows pointing at this repo
2728
 	// (they would dangle once the repos row is gone; the FK ON DELETE
2829
 	// CASCADE would handle it, but explicit is auditable).
@@ -31,6 +32,7 @@ type Querier interface {
3132
 	// Called by the periodic worker (transfers:expire) — flips pending
3233
 	// offers past their expires_at to the expired terminal state.
3334
 	ExpirePendingTransfers(ctx context.Context, db DBTX) (int64, error)
35
+	GetBranchProtectionRule(ctx context.Context, db DBTX, id int64) (BranchProtectionRule, error)
3436
 	GetRepoByID(ctx context.Context, db DBTX, id int64) (Repo, error)
3537
 	GetRepoByOwnerUserAndName(ctx context.Context, db DBTX, arg GetRepoByOwnerUserAndNameParams) (Repo, error)
3638
 	// Returns the owner_username for a repo. Used by size-recalc and other
@@ -48,6 +50,8 @@ type Querier interface {
4850
 	// Used by `shithubd hooks reinstall --all` to enumerate every active
4951
 	// bare repo on disk and re-link its hooks.
5052
 	ListAllRepoFullNames(ctx context.Context, db DBTX) ([]ListAllRepoFullNamesRow, error)
53
+	// SPDX-License-Identifier: AGPL-3.0-or-later
54
+	ListBranchProtectionRules(ctx context.Context, db DBTX, repoID int64) ([]BranchProtectionRule, error)
5155
 	// Inbox view: pending offers a user can act on.
5256
 	ListPendingTransfersForUser(ctx context.Context, db DBTX, toPrincipalID int64) ([]RepoTransferRequest, error)
5357
 	// ─── soft-delete sweep query ───────────────────────────────────────────
@@ -86,12 +90,17 @@ type Querier interface {
8690
 	// so a user→org transfer flips both columns atomically.
8791
 	TransferRepoOwner(ctx context.Context, db DBTX, arg TransferRepoOwnerParams) error
8892
 	UnarchiveRepo(ctx context.Context, db DBTX, id int64) error
93
+	UpdateBranchProtectionRule(ctx context.Context, db DBTX, arg UpdateBranchProtectionRuleParams) error
94
+	// Used by the default-branch settings handler. The on-disk HEAD update
95
+	// is a separate step done via `git symbolic-ref` from the orchestrator.
96
+	UpdateRepoDefaultBranch(ctx context.Context, db DBTX, arg UpdateRepoDefaultBranchParams) error
8997
 	// Set when push:process detects a commit on the repo's default branch.
9098
 	// Pass NULL to clear (e.g. when the branch is force-deleted in a future
9199
 	// sprint). The repo home view reads this to decide between empty and
92100
 	// populated layouts.
93101
 	UpdateRepoDefaultBranchOID(ctx context.Context, db DBTX, arg UpdateRepoDefaultBranchOIDParams) error
94102
 	UpdateRepoDiskUsed(ctx context.Context, db DBTX, arg UpdateRepoDiskUsedParams) error
103
+	UpsertBranchProtectionRule(ctx context.Context, db DBTX, arg UpsertBranchProtectionRuleParams) (int64, error)
95104
 }
96105
 
97106
 var _ Querier = (*Queries)(nil)
internal/users/sqlc/models.gomodified
@@ -204,6 +204,21 @@ type AuthThrottle struct {
204204
 	WindowStartedAt pgtype.Timestamptz
205205
 }
206206
 
207
+type BranchProtectionRule struct {
208
+	ID                   int64
209
+	RepoID               int64
210
+	Pattern              string
211
+	PreventForcePush     bool
212
+	PreventDeletion      bool
213
+	RequirePrForPush     bool
214
+	AllowedPusherUserIds []int64
215
+	RequireSignedCommits bool
216
+	StatusChecksRequired []string
217
+	CreatedAt            pgtype.Timestamptz
218
+	UpdatedAt            pgtype.Timestamptz
219
+	CreatedByUserID      pgtype.Int8
220
+}
221
+
207222
 type EmailVerification struct {
208223
 	ID          int64
209224
 	UserEmailID int64
internal/worker/sqlc/models.gomodified
@@ -204,6 +204,21 @@ type AuthThrottle struct {
204204
 	WindowStartedAt pgtype.Timestamptz
205205
 }
206206
 
207
+type BranchProtectionRule struct {
208
+	ID                   int64
209
+	RepoID               int64
210
+	Pattern              string
211
+	PreventForcePush     bool
212
+	PreventDeletion      bool
213
+	RequirePrForPush     bool
214
+	AllowedPusherUserIds []int64
215
+	RequireSignedCommits bool
216
+	StatusChecksRequired []string
217
+	CreatedAt            pgtype.Timestamptz
218
+	UpdatedAt            pgtype.Timestamptz
219
+	CreatedByUserID      pgtype.Int8
220
+}
221
+
207222
 type EmailVerification struct {
208223
 	ID          int64
209224
 	UserEmailID int64