@@ -11,6 +11,18 @@ import ( |
| 11 | 11 | "github.com/jackc/pgx/v5/pgtype" |
| 12 | 12 | ) |
| 13 | 13 | |
| 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 | + |
| 14 | 26 | const countReposForOwnerUser = `-- name: CountReposForOwnerUser :one |
| 15 | 27 | SELECT count(*) FROM repos |
| 16 | 28 | WHERE owner_user_id = $1 AND deleted_at IS NULL |
@@ -23,6 +35,80 @@ func (q *Queries) CountReposForOwnerUser(ctx context.Context, db DBTX, ownerUser |
| 23 | 35 | return count, err |
| 24 | 36 | } |
| 25 | 37 | |
| 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 | + |
| 26 | 112 | const createRepo = `-- name: CreateRepo :one |
| 27 | 113 | |
| 28 | 114 | INSERT INTO repos ( |
@@ -36,7 +122,7 @@ RETURNING id, owner_user_id, owner_org_id, name, description, visibility, |
| 36 | 122 | disk_used_bytes, fork_of_repo_id, license_key, primary_language, |
| 37 | 123 | has_issues, has_pulls, created_at, updated_at, default_branch_oid, |
| 38 | 124 | 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 |
| 40 | 126 | ` |
| 41 | 127 | |
| 42 | 128 | type CreateRepoParams struct { |
@@ -89,6 +175,8 @@ func (q *Queries) CreateRepo(ctx context.Context, db DBTX, arg CreateRepoParams) |
| 89 | 175 | &i.DefaultMergeMethod, |
| 90 | 176 | &i.StarCount, |
| 91 | 177 | &i.WatcherCount, |
| 178 | + &i.ForkCount, |
| 179 | + &i.InitStatus, |
| 92 | 180 | ) |
| 93 | 181 | return i, err |
| 94 | 182 | } |
@@ -118,7 +206,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility, |
| 118 | 206 | disk_used_bytes, fork_of_repo_id, license_key, primary_language, |
| 119 | 207 | has_issues, has_pulls, created_at, updated_at, default_branch_oid, |
| 120 | 208 | 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 |
| 122 | 210 | FROM repos |
| 123 | 211 | WHERE id = $1 |
| 124 | 212 | ` |
@@ -152,6 +240,8 @@ func (q *Queries) GetRepoByID(ctx context.Context, db DBTX, id int64) (Repo, err |
| 152 | 240 | &i.DefaultMergeMethod, |
| 153 | 241 | &i.StarCount, |
| 154 | 242 | &i.WatcherCount, |
| 243 | + &i.ForkCount, |
| 244 | + &i.InitStatus, |
| 155 | 245 | ) |
| 156 | 246 | return i, err |
| 157 | 247 | } |
@@ -162,7 +252,7 @@ SELECT id, owner_user_id, owner_org_id, name, description, visibility, |
| 162 | 252 | disk_used_bytes, fork_of_repo_id, license_key, primary_language, |
| 163 | 253 | has_issues, has_pulls, created_at, updated_at, default_branch_oid, |
| 164 | 254 | 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 |
| 166 | 256 | FROM repos |
| 167 | 257 | WHERE owner_user_id = $1 AND name = $2 AND deleted_at IS NULL |
| 168 | 258 | ` |
@@ -201,6 +291,8 @@ func (q *Queries) GetRepoByOwnerUserAndName(ctx context.Context, db DBTX, arg Ge |
| 201 | 291 | &i.DefaultMergeMethod, |
| 202 | 292 | &i.StarCount, |
| 203 | 293 | &i.WatcherCount, |
| 294 | + &i.ForkCount, |
| 295 | + &i.InitStatus, |
| 204 | 296 | ) |
| 205 | 297 | return i, err |
| 206 | 298 | } |
@@ -266,13 +358,115 @@ func (q *Queries) ListAllRepoFullNames(ctx context.Context, db DBTX) ([]ListAllR |
| 266 | 358 | return items, nil |
| 267 | 359 | } |
| 268 | 360 | |
| 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 | + |
| 269 | 463 | const listReposForOwnerUser = `-- name: ListReposForOwnerUser :many |
| 270 | 464 | SELECT id, owner_user_id, owner_org_id, name, description, visibility, |
| 271 | 465 | default_branch, is_archived, archived_at, deleted_at, |
| 272 | 466 | disk_used_bytes, fork_of_repo_id, license_key, primary_language, |
| 273 | 467 | has_issues, has_pulls, created_at, updated_at, default_branch_oid, |
| 274 | 468 | 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 |
| 276 | 470 | FROM repos |
| 277 | 471 | WHERE owner_user_id = $1 AND deleted_at IS NULL |
| 278 | 472 | ORDER BY updated_at DESC |
@@ -313,6 +507,8 @@ func (q *Queries) ListReposForOwnerUser(ctx context.Context, db DBTX, ownerUserI |
| 313 | 507 | &i.DefaultMergeMethod, |
| 314 | 508 | &i.StarCount, |
| 315 | 509 | &i.WatcherCount, |
| 510 | + &i.ForkCount, |
| 511 | + &i.InitStatus, |
| 316 | 512 | ); err != nil { |
| 317 | 513 | return nil, err |
| 318 | 514 | } |
@@ -324,6 +520,24 @@ func (q *Queries) ListReposForOwnerUser(ctx context.Context, db DBTX, ownerUserI |
| 324 | 520 | return items, nil |
| 325 | 521 | } |
| 326 | 522 | |
| 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 | + |
| 327 | 541 | const softDeleteRepo = `-- name: SoftDeleteRepo :exec |
| 328 | 542 | UPDATE repos SET deleted_at = now() WHERE id = $1 |
| 329 | 543 | ` |