Documentation
¶
Overview ¶
Package repos owns repository creation, validation, and identity. It sits above sqlc-generated reposdb queries and orchestrates the DB + filesystem dance that produces a usable bare repo. The git plumbing helpers used to build the optional initial commit live in internal/repos/git.
Index ¶
- Constants
- Variables
- func IsReservedRepoName(name string) bool
- func NormalizeName(name string) string
- func NormalizeTopics(raw []string) ([]string, error)
- func ReplaceTopics(ctx context.Context, deps Deps, repoID int64, topics []string) error
- func ValidateDescription(desc string) error
- func ValidateName(name string) error
- type Deps
- type Params
- type Result
Constants ¶
const ( CreateRateLimitMax = 10 CreateRateLimitWindow = time.Hour )
CreateRateLimit caps how many repos one user can create per hour. The spec calls for 10/hour; the value is exported so the operator can override via the throttle plumbing if needed.
const ( MaxTopicsPerRepo = 20 MaxTopicLength = 50 )
MaxTopicsPerRepo and MaxTopicLength match the spec's day-1 lean (20 topics, 50 chars each). The shape regex covers GitHub's topic rules: lowercase letters/digits/hyphens, can't start/end with a hyphen, 1–50 chars.
const ( MaxNameLen = 100 MaxDescriptionLen = 350 )
MaxNameLen / MaxDescriptionLen mirror the DB CHECK constraints in migration 0017. Validate before insert so we surface a friendly error rather than a 500 from the constraint.
Variables ¶
var ( // ErrInvalidName is returned when the requested repo name fails the // shape whitelist (length, characters, edges, dot-dot, etc.). ErrInvalidName = errors.New("repos: invalid name") // ErrReservedName is returned when the name is on the reserved list. // Distinguished from ErrInvalidName so the UI can say "reserved" vs // "format" — they're different fixes for the user. ErrReservedName = errors.New("repos: reserved name") // ErrTaken is returned when an active repo (deleted_at IS NULL) // already exists for this owner with the same name. Surfaces as // either the explicit pre-check OR the unique-constraint error. ErrTaken = errors.New("repos: name taken for owner") // ErrNoVerifiedEmail is returned when the user has no verified // primary email. We refuse rather than fabricate an author identity. ErrNoVerifiedEmail = errors.New("repos: no verified primary email") // ErrUnknownLicense / ErrUnknownGitignore — picker chose a key not // in our curated set. ErrUnknownLicense = errors.New("repos: unknown license key") ErrUnknownGitignore = errors.New("repos: unknown gitignore key") // ErrDescriptionTooLong mirrors the DB CHECK on description (≤350). ErrDescriptionTooLong = errors.New("repos: description too long") )
User-facing errors. Handlers map these to friendly UI strings; lower layers wrap them with %w so handlers can errors.Is for routing.
var ( ErrTooManyTopics = errors.New("repos: too many topics (max 20)") ErrInvalidTopic = errors.New("repos: topic must be lowercase letters, digits, and hyphens; 1-50 chars") )
Errors surfaced by topic mutation.
Functions ¶
func IsReservedRepoName ¶
IsReservedRepoName reports whether name (case-insensitive) is on the reserved list. Callers MUST also reject leading dots and the empty string via ValidateName.
func NormalizeName ¶
NormalizeName lowercases and trims. Repos are case-preserved in the DB (citext does case-insensitive uniqueness), but disk paths are lowercased. We lowercase up-front so the same string drives both.
func NormalizeTopics ¶
NormalizeTopics dedups + lowercases an incoming list and validates each entry. Returns the cleaned slice (stable-ordered, dedup'd) or the first validation error.
func ReplaceTopics ¶
ReplaceTopics atomically swaps the topic set for a repo. The orchestrator owns tx ordering: DELETE + per-row INSERT in one tx so a partial failure rolls back. The unique index on (repo_id, topic) makes the inserts idempotent against a concurrent run.
func ValidateDescription ¶
ValidateDescription enforces the DB CHECK length cap.
func ValidateName ¶
ValidateName enforces the shape whitelist. Returns ErrInvalidName / ErrReservedName wrapped with a precise reason on failure.
Caller is expected to have lowercased the name first; uppercase input is rejected as a defensive check in case a future caller forgets.
Types ¶
type Deps ¶
type Deps struct {
Pool *pgxpool.Pool
RepoFS *storage.RepoFS
Audit *audit.Recorder
Limiter *throttle.Limiter
Logger *slog.Logger
Now func() time.Time
// ShithubdPath is the absolute path to the running shithubd binary,
// baked into the hook shims so push -> hook -> shithubd round-trip
// works in dev and prod. Empty disables hook installation (tests
// that don't care about hooks; the full E2E happy path provides it).
ShithubdPath string
}
Deps wires the repo orchestrator. Inject from the web layer; no global state.
type Params ¶
type Params struct {
OwnerUserID int64
OwnerUsername string
OwnerOrgID int64
OwnerSlug string
// ActorUserID is the user performing the create. Used for
// audit-log + rate-limiting + initial-commit author. Defaults to
// OwnerUserID for personal repos when zero.
ActorUserID int64
Name string // already lowercased + trimmed
Description string
Visibility string // "public" | "private"
InitReadme bool
LicenseKey string // "" = none
GitignoreKey string // "" = none
// Optional override for the initial commit timestamp; tests pin this
// for determinism. Production callers leave it zero and let
// orchestrator default to deps.Now().
InitialCommitWhen time.Time
}
Params describes one repo-create request as it arrives from the handler, normalized but not yet validated against the DB.
Owner is XOR (S30): either OwnerUserID set OR OwnerOrgID set. OwnerUsername / OwnerSlug carry the slug for path generation (the FS layer's per-owner directory uses it). ActorUserID is who initiated the create — defaults to OwnerUserID for personal repos and is required for org-owned creates.
type Result ¶
type Result struct {
Repo reposdb.Repo
InitialCommitOID string // "" when InitReadme/License/Gitignore were all unset
DiskPath string // bare-repo on-disk path
}
Result is what Create returns on success.
Directories
¶
| Path | Synopsis |
|---|---|
|
diff
|
|
|
parse
Package parse wraps go-gitdiff.
|
Package parse wraps go-gitdiff. |
|
render
Package render produces HTML for parsed diffs.
|
Package render produces HTML for parsed diffs. |
|
source
Package source produces raw git-diff patch bytes for the parser.
|
Package source produces raw git-diff patch bytes for the parser. |
|
Package finder implements the "Go to file" fuzzy match used by the /find/{ref} endpoint.
|
Package finder implements the "Go to file" fuzzy match used by the /find/{ref} endpoint. |
|
Package fork owns S27's fork orchestration.
|
Package fork owns S27's fork orchestration. |
|
Package git wraps the git plumbing commands we need to build the optional initial commit on a freshly-init'd bare repo.
|
Package git wraps the git plumbing commands we need to build the optional initial commit on a freshly-init'd bare repo. |
|
Package highlight wraps Chroma so the rest of the project doesn't import it directly.
|
Package highlight wraps Chroma so the rest of the project doesn't import it directly. |
|
Package identity resolves a commit author's email to a shithub user.
|
Package identity resolves a commit author's email to a shithub user. |
|
Package lifecycle owns the post-creation mutations of a repo: rename, transfer, archive/unarchive, visibility flip, soft-delete, restore, and hard-delete.
|
Package lifecycle owns the post-creation mutations of a repo: rename, transfer, archive/unarchive, visibility flip, soft-delete, restore, and hard-delete. |
|
Package protection enforces branch-protection rules on incoming pushes.
|
Package protection enforces branch-protection rules on incoming pushes. |
|
Package templates embeds the curated set of license, gitignore, and README templates offered by the repo-creation form.
|
Package templates embeds the curated set of license, gitignore, and README templates offered by the repo-creation form. |