repos

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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

View Source
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.

View Source
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

func IsReservedRepoName(name string) bool

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

func NormalizeName(name string) string

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

func NormalizeTopics(raw []string) ([]string, error)

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

func ReplaceTopics(ctx context.Context, deps Deps, repoID int64, topics []string) error

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

func ValidateDescription(desc string) error

ValidateDescription enforces the DB CHECK length cap.

func ValidateName

func ValidateName(name string) error

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.

func Create

func Create(ctx context.Context, deps Deps, p Params) (Result, error)

Create validates, rate-limits, inserts the DB row, initializes the bare repo on disk, optionally builds the initial commit, audit-logs, and returns. On post-DB failure the tx rolls back and the partial repo dir is best-effort removed.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL