tenseleyflow/shithub / 2b36e97

Browse files

S27: regenerate sqlc — fork queries + cross-bucket Repo model updates

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
2b36e97b0086427a32fb4b8fafb959d49e04760e
Parents
b4ac4b4
Tree
a38cecd

12 changed files

StatusFile+-
M internal/auth/policy/sqlc/models.go 45 0
M internal/checks/sqlc/models.go 45 0
M internal/issues/sqlc/models.go 45 0
M internal/meta/sqlc/models.go 45 0
M internal/pulls/sqlc/models.go 45 0
M internal/repos/queries/repos.sql 59 4
M internal/repos/sqlc/models.go 45 0
M internal/repos/sqlc/querier.go 20 0
M internal/repos/sqlc/repos.sql.go 218 4
M internal/social/sqlc/models.go 45 0
M internal/users/sqlc/models.go 45 0
M internal/worker/sqlc/models.go 45 0
internal/auth/policy/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/checks/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/issues/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/meta/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/pulls/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/repos/queries/repos.sqlmodified
@@ -12,7 +12,7 @@ RETURNING id, owner_user_id, owner_org_id, name, description, visibility,
1212
           disk_used_bytes, fork_of_repo_id, license_key, primary_language,
1313
           has_issues, has_pulls, created_at, updated_at, default_branch_oid,
1414
           allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
15
-          star_count, watcher_count;
15
+          star_count, watcher_count, fork_count, init_status;
1616
 
1717
 -- name: GetRepoByID :one
1818
 SELECT id, owner_user_id, owner_org_id, name, description, visibility,
@@ -20,7 +20,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility,
2020
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
2121
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
2222
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
23
-       star_count, watcher_count
23
+       star_count, watcher_count, fork_count, init_status
2424
 FROM repos
2525
 WHERE id = $1;
2626
 
@@ -39,7 +39,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility,
3939
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
4040
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
4141
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
42
-       star_count, watcher_count
42
+       star_count, watcher_count, fork_count, init_status
4343
 FROM repos
4444
 WHERE owner_user_id = $1 AND name = $2 AND deleted_at IS NULL;
4545
 
@@ -55,7 +55,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility,
5555
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
5656
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
5757
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
58
-       star_count, watcher_count
58
+       star_count, watcher_count, fork_count, init_status
5959
 FROM repos
6060
 WHERE owner_user_id = $1 AND deleted_at IS NULL
6161
 ORDER BY updated_at DESC;
@@ -88,3 +88,58 @@ FROM repos r
8888
 JOIN users u ON u.id = r.owner_user_id
8989
 WHERE r.deleted_at IS NULL
9090
 ORDER BY r.id;
91
+
92
+-- ─── S27 forks ─────────────────────────────────────────────────────
93
+
94
+-- name: CreateForkRepo :one
95
+-- Insert a fork shell. Distinct from CreateRepo because forks set
96
+-- `fork_of_repo_id` (which fires the fork_count trigger) and start
97
+-- at init_status='init_pending' so the worker job can flip them to
98
+-- 'initialized' once `git clone --bare --shared` finishes.
99
+INSERT INTO repos (
100
+    owner_user_id, owner_org_id, name, description, visibility,
101
+    default_branch, fork_of_repo_id, init_status
102
+) VALUES (
103
+    $1, $2, $3, $4, $5, $6, $7, 'init_pending'
104
+)
105
+RETURNING id, owner_user_id, owner_org_id, name, description, visibility,
106
+          default_branch, is_archived, archived_at, deleted_at,
107
+          disk_used_bytes, fork_of_repo_id, license_key, primary_language,
108
+          has_issues, has_pulls, created_at, updated_at, default_branch_oid,
109
+          allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
110
+          star_count, watcher_count, fork_count, init_status;
111
+
112
+-- name: SetRepoInitStatus :exec
113
+-- Promotes a fork from init_pending to initialized (or init_failed).
114
+-- The DB row is created up-front so the URL resolves immediately and
115
+-- the user sees a "preparing your fork" placeholder while the worker
116
+-- runs `git clone --bare --shared`.
117
+UPDATE repos SET init_status = $2 WHERE id = $1;
118
+
119
+-- name: ListForksOfRepo :many
120
+-- Forks of a given source repo, paginated, recency-sorted. Joined
121
+-- with users for the owner display name. Excludes soft-deleted.
122
+SELECT r.id, r.name, r.description, r.visibility, r.created_at,
123
+       r.star_count, r.fork_count, r.init_status,
124
+       u.username AS owner_username, u.display_name AS owner_display_name
125
+FROM repos r
126
+JOIN users u ON u.id = r.owner_user_id
127
+WHERE r.fork_of_repo_id = $1
128
+  AND r.deleted_at IS NULL
129
+ORDER BY r.created_at DESC
130
+LIMIT $2 OFFSET $3;
131
+
132
+-- name: CountForksOfRepo :one
133
+SELECT count(*) FROM repos
134
+WHERE fork_of_repo_id = $1 AND deleted_at IS NULL;
135
+
136
+-- name: ListForksOfRepoForRepack :many
137
+-- Used by S16's hard-delete cascade (S27 amendment): before deleting
138
+-- a source repo, every fork must `git repack -a -d --no-shared` so
139
+-- it has its own copy of the objects. Returns just enough to locate
140
+-- the bare repo on disk.
141
+SELECT r.id, r.name, u.username AS owner_username
142
+FROM repos r
143
+JOIN users u ON u.id = r.owner_user_id
144
+WHERE r.fork_of_repo_id = $1
145
+  AND r.deleted_at IS NULL;
internal/repos/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/repos/sqlc/querier.gomodified
@@ -14,12 +14,19 @@ type Querier interface {
1414
 	AcceptTransferRequest(ctx context.Context, db DBTX, id int64) error
1515
 	ArchiveRepo(ctx context.Context, db DBTX, id int64) error
1616
 	CancelTransferRequest(ctx context.Context, db DBTX, id int64) error
17
+	CountForksOfRepo(ctx context.Context, db DBTX, forkOfRepoID pgtype.Int8) (int64, error)
1718
 	// ─── rename rate limit support ─────────────────────────────────────────
1819
 	// Used to enforce the 5-per-30-days rename rate limit. The redirect
1920
 	// row is the audit trail for renames; counting them per repo gives a
2021
 	// reliable cap.
2122
 	CountRecentRedirectsForRepo(ctx context.Context, db DBTX, repoID int64) (int32, error)
2223
 	CountReposForOwnerUser(ctx context.Context, db DBTX, ownerUserID pgtype.Int8) (int64, error)
24
+	// ─── S27 forks ─────────────────────────────────────────────────────
25
+	// Insert a fork shell. Distinct from CreateRepo because forks set
26
+	// `fork_of_repo_id` (which fires the fork_count trigger) and start
27
+	// at init_status='init_pending' so the worker job can flip them to
28
+	// 'initialized' once `git clone --bare --shared` finishes.
29
+	CreateForkRepo(ctx context.Context, db DBTX, arg CreateForkRepoParams) (Repo, error)
2330
 	// SPDX-License-Identifier: AGPL-3.0-or-later
2431
 	CreateRepo(ctx context.Context, db DBTX, arg CreateRepoParams) (Repo, error)
2532
 	DeclineTransferRequest(ctx context.Context, db DBTX, id int64) error
@@ -56,6 +63,14 @@ type Querier interface {
5663
 	ListAllRepoFullNames(ctx context.Context, db DBTX) ([]ListAllRepoFullNamesRow, error)
5764
 	// SPDX-License-Identifier: AGPL-3.0-or-later
5865
 	ListBranchProtectionRules(ctx context.Context, db DBTX, repoID int64) ([]BranchProtectionRule, error)
66
+	// Forks of a given source repo, paginated, recency-sorted. Joined
67
+	// with users for the owner display name. Excludes soft-deleted.
68
+	ListForksOfRepo(ctx context.Context, db DBTX, arg ListForksOfRepoParams) ([]ListForksOfRepoRow, error)
69
+	// Used by S16's hard-delete cascade (S27 amendment): before deleting
70
+	// a source repo, every fork must `git repack -a -d --no-shared` so
71
+	// it has its own copy of the objects. Returns just enough to locate
72
+	// the bare repo on disk.
73
+	ListForksOfRepoForRepack(ctx context.Context, db DBTX, forkOfRepoID pgtype.Int8) ([]ListForksOfRepoForRepackRow, error)
5974
 	// Inbox view: pending offers a user can act on.
6075
 	ListPendingTransfersForUser(ctx context.Context, db DBTX, toPrincipalID int64) ([]RepoTransferRequest, error)
6176
 	// ─── soft-delete sweep query ───────────────────────────────────────────
@@ -84,6 +99,11 @@ type Querier interface {
8499
 	// redirect row is INSERTed in the same tx.
85100
 	RenameRepo(ctx context.Context, db DBTX, arg RenameRepoParams) error
86101
 	RestoreRepo(ctx context.Context, db DBTX, id int64) error
102
+	// Promotes a fork from init_pending to initialized (or init_failed).
103
+	// The DB row is created up-front so the URL resolves immediately and
104
+	// the user sees a "preparing your fork" placeholder while the worker
105
+	// runs `git clone --bare --shared`.
106
+	SetRepoInitStatus(ctx context.Context, db DBTX, arg SetRepoInitStatusParams) error
87107
 	SetRepoVisibility(ctx context.Context, db DBTX, arg SetRepoVisibilityParams) error
88108
 	SoftDeleteRepo(ctx context.Context, db DBTX, id int64) error
89109
 	// Distinct name from S11's SoftDeleteRepo so future code that wants to
internal/repos/sqlc/repos.sql.gomodified
@@ -11,6 +11,18 @@ import (
1111
 	"github.com/jackc/pgx/v5/pgtype"
1212
 )
1313
 
14
+const countForksOfRepo = `-- name: CountForksOfRepo :one
15
+SELECT count(*) FROM repos
16
+WHERE fork_of_repo_id = $1 AND deleted_at IS NULL
17
+`
18
+
19
+func (q *Queries) CountForksOfRepo(ctx context.Context, db DBTX, forkOfRepoID pgtype.Int8) (int64, error) {
20
+	row := db.QueryRow(ctx, countForksOfRepo, forkOfRepoID)
21
+	var count int64
22
+	err := row.Scan(&count)
23
+	return count, err
24
+}
25
+
1426
 const countReposForOwnerUser = `-- name: CountReposForOwnerUser :one
1527
 SELECT count(*) FROM repos
1628
 WHERE owner_user_id = $1 AND deleted_at IS NULL
@@ -23,6 +35,80 @@ func (q *Queries) CountReposForOwnerUser(ctx context.Context, db DBTX, ownerUser
2335
 	return count, err
2436
 }
2537
 
38
+const createForkRepo = `-- name: CreateForkRepo :one
39
+
40
+INSERT INTO repos (
41
+    owner_user_id, owner_org_id, name, description, visibility,
42
+    default_branch, fork_of_repo_id, init_status
43
+) VALUES (
44
+    $1, $2, $3, $4, $5, $6, $7, 'init_pending'
45
+)
46
+RETURNING id, owner_user_id, owner_org_id, name, description, visibility,
47
+          default_branch, is_archived, archived_at, deleted_at,
48
+          disk_used_bytes, fork_of_repo_id, license_key, primary_language,
49
+          has_issues, has_pulls, created_at, updated_at, default_branch_oid,
50
+          allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
51
+          star_count, watcher_count, fork_count, init_status
52
+`
53
+
54
+type CreateForkRepoParams struct {
55
+	OwnerUserID   pgtype.Int8
56
+	OwnerOrgID    pgtype.Int8
57
+	Name          string
58
+	Description   string
59
+	Visibility    RepoVisibility
60
+	DefaultBranch string
61
+	ForkOfRepoID  pgtype.Int8
62
+}
63
+
64
+// ─── S27 forks ─────────────────────────────────────────────────────
65
+// Insert a fork shell. Distinct from CreateRepo because forks set
66
+// `fork_of_repo_id` (which fires the fork_count trigger) and start
67
+// at init_status='init_pending' so the worker job can flip them to
68
+// 'initialized' once `git clone --bare --shared` finishes.
69
+func (q *Queries) CreateForkRepo(ctx context.Context, db DBTX, arg CreateForkRepoParams) (Repo, error) {
70
+	row := db.QueryRow(ctx, createForkRepo,
71
+		arg.OwnerUserID,
72
+		arg.OwnerOrgID,
73
+		arg.Name,
74
+		arg.Description,
75
+		arg.Visibility,
76
+		arg.DefaultBranch,
77
+		arg.ForkOfRepoID,
78
+	)
79
+	var i Repo
80
+	err := row.Scan(
81
+		&i.ID,
82
+		&i.OwnerUserID,
83
+		&i.OwnerOrgID,
84
+		&i.Name,
85
+		&i.Description,
86
+		&i.Visibility,
87
+		&i.DefaultBranch,
88
+		&i.IsArchived,
89
+		&i.ArchivedAt,
90
+		&i.DeletedAt,
91
+		&i.DiskUsedBytes,
92
+		&i.ForkOfRepoID,
93
+		&i.LicenseKey,
94
+		&i.PrimaryLanguage,
95
+		&i.HasIssues,
96
+		&i.HasPulls,
97
+		&i.CreatedAt,
98
+		&i.UpdatedAt,
99
+		&i.DefaultBranchOid,
100
+		&i.AllowSquashMerge,
101
+		&i.AllowRebaseMerge,
102
+		&i.AllowMergeCommit,
103
+		&i.DefaultMergeMethod,
104
+		&i.StarCount,
105
+		&i.WatcherCount,
106
+		&i.ForkCount,
107
+		&i.InitStatus,
108
+	)
109
+	return i, err
110
+}
111
+
26112
 const createRepo = `-- name: CreateRepo :one
27113
 
28114
 INSERT INTO repos (
@@ -36,7 +122,7 @@ RETURNING id, owner_user_id, owner_org_id, name, description, visibility,
36122
           disk_used_bytes, fork_of_repo_id, license_key, primary_language,
37123
           has_issues, has_pulls, created_at, updated_at, default_branch_oid,
38124
           allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
39
-          star_count, watcher_count
125
+          star_count, watcher_count, fork_count, init_status
40126
 `
41127
 
42128
 type CreateRepoParams struct {
@@ -89,6 +175,8 @@ func (q *Queries) CreateRepo(ctx context.Context, db DBTX, arg CreateRepoParams)
89175
 		&i.DefaultMergeMethod,
90176
 		&i.StarCount,
91177
 		&i.WatcherCount,
178
+		&i.ForkCount,
179
+		&i.InitStatus,
92180
 	)
93181
 	return i, err
94182
 }
@@ -118,7 +206,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility,
118206
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
119207
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
120208
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
121
-       star_count, watcher_count
209
+       star_count, watcher_count, fork_count, init_status
122210
 FROM repos
123211
 WHERE id = $1
124212
 `
@@ -152,6 +240,8 @@ func (q *Queries) GetRepoByID(ctx context.Context, db DBTX, id int64) (Repo, err
152240
 		&i.DefaultMergeMethod,
153241
 		&i.StarCount,
154242
 		&i.WatcherCount,
243
+		&i.ForkCount,
244
+		&i.InitStatus,
155245
 	)
156246
 	return i, err
157247
 }
@@ -162,7 +252,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility,
162252
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
163253
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
164254
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
165
-       star_count, watcher_count
255
+       star_count, watcher_count, fork_count, init_status
166256
 FROM repos
167257
 WHERE owner_user_id = $1 AND name = $2 AND deleted_at IS NULL
168258
 `
@@ -201,6 +291,8 @@ func (q *Queries) GetRepoByOwnerUserAndName(ctx context.Context, db DBTX, arg Ge
201291
 		&i.DefaultMergeMethod,
202292
 		&i.StarCount,
203293
 		&i.WatcherCount,
294
+		&i.ForkCount,
295
+		&i.InitStatus,
204296
 	)
205297
 	return i, err
206298
 }
@@ -266,13 +358,115 @@ func (q *Queries) ListAllRepoFullNames(ctx context.Context, db DBTX) ([]ListAllR
266358
 	return items, nil
267359
 }
268360
 
361
+const listForksOfRepo = `-- name: ListForksOfRepo :many
362
+SELECT r.id, r.name, r.description, r.visibility, r.created_at,
363
+       r.star_count, r.fork_count, r.init_status,
364
+       u.username AS owner_username, u.display_name AS owner_display_name
365
+FROM repos r
366
+JOIN users u ON u.id = r.owner_user_id
367
+WHERE r.fork_of_repo_id = $1
368
+  AND r.deleted_at IS NULL
369
+ORDER BY r.created_at DESC
370
+LIMIT $2 OFFSET $3
371
+`
372
+
373
+type ListForksOfRepoParams struct {
374
+	ForkOfRepoID pgtype.Int8
375
+	Limit        int32
376
+	Offset       int32
377
+}
378
+
379
+type ListForksOfRepoRow struct {
380
+	ID               int64
381
+	Name             string
382
+	Description      string
383
+	Visibility       RepoVisibility
384
+	CreatedAt        pgtype.Timestamptz
385
+	StarCount        int64
386
+	ForkCount        int64
387
+	InitStatus       RepoInitStatus
388
+	OwnerUsername    string
389
+	OwnerDisplayName string
390
+}
391
+
392
+// Forks of a given source repo, paginated, recency-sorted. Joined
393
+// with users for the owner display name. Excludes soft-deleted.
394
+func (q *Queries) ListForksOfRepo(ctx context.Context, db DBTX, arg ListForksOfRepoParams) ([]ListForksOfRepoRow, error) {
395
+	rows, err := db.Query(ctx, listForksOfRepo, arg.ForkOfRepoID, arg.Limit, arg.Offset)
396
+	if err != nil {
397
+		return nil, err
398
+	}
399
+	defer rows.Close()
400
+	items := []ListForksOfRepoRow{}
401
+	for rows.Next() {
402
+		var i ListForksOfRepoRow
403
+		if err := rows.Scan(
404
+			&i.ID,
405
+			&i.Name,
406
+			&i.Description,
407
+			&i.Visibility,
408
+			&i.CreatedAt,
409
+			&i.StarCount,
410
+			&i.ForkCount,
411
+			&i.InitStatus,
412
+			&i.OwnerUsername,
413
+			&i.OwnerDisplayName,
414
+		); err != nil {
415
+			return nil, err
416
+		}
417
+		items = append(items, i)
418
+	}
419
+	if err := rows.Err(); err != nil {
420
+		return nil, err
421
+	}
422
+	return items, nil
423
+}
424
+
425
+const listForksOfRepoForRepack = `-- name: ListForksOfRepoForRepack :many
426
+SELECT r.id, r.name, u.username AS owner_username
427
+FROM repos r
428
+JOIN users u ON u.id = r.owner_user_id
429
+WHERE r.fork_of_repo_id = $1
430
+  AND r.deleted_at IS NULL
431
+`
432
+
433
+type ListForksOfRepoForRepackRow struct {
434
+	ID            int64
435
+	Name          string
436
+	OwnerUsername string
437
+}
438
+
439
+// Used by S16's hard-delete cascade (S27 amendment): before deleting
440
+// a source repo, every fork must `git repack -a -d --no-shared` so
441
+// it has its own copy of the objects. Returns just enough to locate
442
+// the bare repo on disk.
443
+func (q *Queries) ListForksOfRepoForRepack(ctx context.Context, db DBTX, forkOfRepoID pgtype.Int8) ([]ListForksOfRepoForRepackRow, error) {
444
+	rows, err := db.Query(ctx, listForksOfRepoForRepack, forkOfRepoID)
445
+	if err != nil {
446
+		return nil, err
447
+	}
448
+	defer rows.Close()
449
+	items := []ListForksOfRepoForRepackRow{}
450
+	for rows.Next() {
451
+		var i ListForksOfRepoForRepackRow
452
+		if err := rows.Scan(&i.ID, &i.Name, &i.OwnerUsername); err != nil {
453
+			return nil, err
454
+		}
455
+		items = append(items, i)
456
+	}
457
+	if err := rows.Err(); err != nil {
458
+		return nil, err
459
+	}
460
+	return items, nil
461
+}
462
+
269463
 const listReposForOwnerUser = `-- name: ListReposForOwnerUser :many
270464
 SELECT id, owner_user_id, owner_org_id, name, description, visibility,
271465
        default_branch, is_archived, archived_at, deleted_at,
272466
        disk_used_bytes, fork_of_repo_id, license_key, primary_language,
273467
        has_issues, has_pulls, created_at, updated_at, default_branch_oid,
274468
        allow_squash_merge, allow_rebase_merge, allow_merge_commit, default_merge_method,
275
-       star_count, watcher_count
469
+       star_count, watcher_count, fork_count, init_status
276470
 FROM repos
277471
 WHERE owner_user_id = $1 AND deleted_at IS NULL
278472
 ORDER BY updated_at DESC
@@ -313,6 +507,8 @@ func (q *Queries) ListReposForOwnerUser(ctx context.Context, db DBTX, ownerUserI
313507
 			&i.DefaultMergeMethod,
314508
 			&i.StarCount,
315509
 			&i.WatcherCount,
510
+			&i.ForkCount,
511
+			&i.InitStatus,
316512
 		); err != nil {
317513
 			return nil, err
318514
 		}
@@ -324,6 +520,24 @@ func (q *Queries) ListReposForOwnerUser(ctx context.Context, db DBTX, ownerUserI
324520
 	return items, nil
325521
 }
326522
 
523
+const setRepoInitStatus = `-- name: SetRepoInitStatus :exec
524
+UPDATE repos SET init_status = $2 WHERE id = $1
525
+`
526
+
527
+type SetRepoInitStatusParams struct {
528
+	ID         int64
529
+	InitStatus RepoInitStatus
530
+}
531
+
532
+// Promotes a fork from init_pending to initialized (or init_failed).
533
+// The DB row is created up-front so the URL resolves immediately and
534
+// the user sees a "preparing your fork" placeholder while the worker
535
+// runs `git clone --bare --shared`.
536
+func (q *Queries) SetRepoInitStatus(ctx context.Context, db DBTX, arg SetRepoInitStatusParams) error {
537
+	_, err := db.Exec(ctx, setRepoInitStatus, arg.ID, arg.InitStatus)
538
+	return err
539
+}
540
+
327541
 const softDeleteRepo = `-- name: SoftDeleteRepo :exec
328542
 UPDATE repos SET deleted_at = now() WHERE id = $1
329543
 `
internal/social/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/users/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {
internal/worker/sqlc/models.gomodified
@@ -580,6 +580,49 @@ func (ns NullPrReviewState) Value() (driver.Value, error) {
580580
 	return string(ns.PrReviewState), nil
581581
 }
582582
 
583
+type RepoInitStatus string
584
+
585
+const (
586
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
587
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
588
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
589
+)
590
+
591
+func (e *RepoInitStatus) Scan(src interface{}) error {
592
+	switch s := src.(type) {
593
+	case []byte:
594
+		*e = RepoInitStatus(s)
595
+	case string:
596
+		*e = RepoInitStatus(s)
597
+	default:
598
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
599
+	}
600
+	return nil
601
+}
602
+
603
+type NullRepoInitStatus struct {
604
+	RepoInitStatus RepoInitStatus
605
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
606
+}
607
+
608
+// Scan implements the Scanner interface.
609
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
610
+	if value == nil {
611
+		ns.RepoInitStatus, ns.Valid = "", false
612
+		return nil
613
+	}
614
+	ns.Valid = true
615
+	return ns.RepoInitStatus.Scan(value)
616
+}
617
+
618
+// Value implements the driver Valuer interface.
619
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
620
+	if !ns.Valid {
621
+		return nil, nil
622
+	}
623
+	return string(ns.RepoInitStatus), nil
624
+}
625
+
583626
 type RepoVisibility string
584627
 
585628
 const (
@@ -1083,6 +1126,8 @@ type Repo struct {
10831126
 	DefaultMergeMethod PrMergeMethod
10841127
 	StarCount          int64
10851128
 	WatcherCount       int64
1129
+	ForkCount          int64
1130
+	InitStatus         RepoInitStatus
10861131
 }
10871132
 
10881133
 type RepoCollaborator struct {