Artifact 8dd8295ec626eed5a54831dec8f9c06f7baf5d9e7b2cce512d64a2121747b4cd:
- File
internal/oltp/repo.go
— part of check-in
[87a492fe4f]
at
2026-08-18 01:59:53
on branch trunk
— repo: a create flow a person can actually use
Nobody picks a filesystem path when they make a repository on a forge, and requiring one made repo.add usable only by the machine's operator. --repos-root gives the server somewhere to put them; path is now optional and the response says where it landed.
Repositories gain a description โ every forge has one and quarry had nowhere to put it, so the listing could only show names and counts.
Three surfaces on one op: quarry repo new|clone|list|remove drives the HTTP API rather than the database, so the CLI has no privileged path and anything it can do a client can; /new is the same three questions a forge always asks; and an empty repository shows how to push to it instead of a bare file table, because it is waiting, not broken.
A rejected form comes back as a form with the reason on it โ an error page throws the reader's input away. (user: dmestas, size: 3950) [annotate] [blame] [check-ins using]
// repo.go stores which repositories the forge serves. 0001 ยง8 lists the // repo registry as forge OLTP state alongside accounts and tokens; until // now it lived only in argv, so adding a repository meant editing the // server's command line and restarting every other repository with it. package oltp import ( "database/sql" "errors" "fmt" "path/filepath" "regexp" "strings" "time" ) const repoSchema = ` CREATE TABLE IF NOT EXISTS repo ( name TEXT PRIMARY KEY, -- the forge's name, as it appears in URLs path TEXT NOT NULL, -- directory holding .zit, on this node description TEXT NOT NULL DEFAULT '', created TEXT NOT NULL );` // repoMigrations are additive columns for registries written by an // earlier schema. A duplicate-column error means it is already there. var repoMigrations = []string{ `ALTER TABLE repo ADD COLUMN description TEXT NOT NULL DEFAULT ''`, } // RepoName is the accepted repository name. Same rule the --repo flag // enforces, kept here so a name that arrives through the API cannot be // one the flag would have refused. // // It is deliberately narrow: the name appears in a URL path segment, so // anything needing escaping to be safe there is better rejected than // encoded. var RepoName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`) // Repo is one registered repository. type Repo struct { Name string Path string // Description is the one line that says what this repository is. // Every forge has it and quarry had nowhere to put it, so the // repository list could only ever show names and counts. Description string Created string } // AddRepo registers a repository. The path is stored absolute: a // relative one would resolve against whatever directory the server // happened to start in, which is not a property anybody intends to // depend on and changes silently between a shell and a service manager. func (d *DB) AddRepo(name, path, description string) error { if !RepoName.MatchString(name) { return fmt.Errorf("oltp: repository name %q: use letters, digits, dot, dash, underscore", name) } if path == "" { return errors.New("oltp: repository needs a path") } abs, err := filepath.Abs(path) if err != nil { return err } _, err = d.db.Exec(`INSERT INTO repo (name, path, description, created) VALUES (?, ?, ?, ?)`, name, abs, description, time.Now().UTC().Format(time.RFC3339)) if err != nil && strings.Contains(err.Error(), "UNIQUE") { return fmt.Errorf("oltp: repository %q already registered", name) } return err } // DeleteRepo unregisters a repository. It removes the registration only // โ the store on disk and its derived index are left alone, because // "stop serving this" and "destroy this" are different intentions and // only one of them is reversible. func (d *DB) DeleteRepo(name string) error { _, err := d.db.Exec(`DELETE FROM repo WHERE name = ?`, name) return err } // SetRepoDescription updates the one-line summary. func (d *DB) SetRepoDescription(name, description string) error { _, err := d.db.Exec(`UPDATE repo SET description = ? WHERE name = ?`, description, name) return err } // Repos lists registered repositories, name-ordered. func (d *DB) Repos() ([]Repo, error) { rows, err := d.db.Query(`SELECT name, path, description, created FROM repo ORDER BY name`) if err != nil { return nil, err } defer rows.Close() var out []Repo for rows.Next() { var r Repo if err := rows.Scan(&r.Name, &r.Path, &r.Description, &r.Created); err != nil { return nil, err } out = append(out, r) } return out, rows.Err() } // RepoByName returns one registration. func (d *DB) RepoByName(name string) (Repo, bool, error) { var r Repo err := d.db.QueryRow(`SELECT name, path, description, created FROM repo WHERE name = ?`, name). Scan(&r.Name, &r.Path, &r.Description, &r.Created) if errors.Is(err, sql.ErrNoRows) { return Repo{}, false, nil } return r, err == nil, err }