Skip to content
quarry Artifact [96502423ca]
Login

Artifact [96502423ca]

Artifact 96502423caaa1f01c498f234cc20fa373824f1f57fb36d5d599868988f2db9c9:


// repoplace.go is where a repository has BEEN.
//
// A repository appears under a namespace — a team when a team holds it,
// its owner's handle otherwise — and that namespace changes when it
// moves into or out of a team. Every link anybody saved carries the
// namespace of the moment, so a move made them all 404 (c12a84b2, G-52
// as jva measured it from jan's seat): the bare /r/<repo> form was the
// only saved link that survived. The bare form redirects because the
// forge knows where the repository IS; the old namespaced form can only
// redirect if the forge remembers where it WAS. This table is that
// memory, and placeRepo is the one writer of a repository's namespace,
// so every move records its predecessor whichever door it came through.
package oltp

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"time"
)

const repoPlaceSchema = `
CREATE TABLE IF NOT EXISTS repo_place_past (
	repo      TEXT NOT NULL,
	namespace TEXT NOT NULL,   -- a namespace the repository once appeared under
	until     TEXT NOT NULL,   -- when it left
	PRIMARY KEY (repo, namespace)
);`

// execer is what placeRepo needs of a *sql.Tx or a *sql.DB.
type execer interface {
	Exec(query string, args ...any) (sql.Result, error)
	QueryRow(query string, args ...any) *sql.Row
}

// placeRepo moves a repository to ns, remembering the namespace it
// leaves. The namespace it arrives in stops being a past one, so a
// repository that comes home resolves there as current and not as a
// redirect to itself. An empty former namespace (a store nobody held)
// is nothing to remember: it had no URL of its own.
func placeRepo(x execer, repo, ns, by string, at time.Time) error {
	if shown, clash := shownClash(x, repo, ns); clash {
		return addressRefusal(x, repo, shown)
	}
	var cur string
	if err := x.QueryRow(`SELECT namespace FROM repo WHERE name = ?`, repo).Scan(&cur); err != nil && !errors.Is(err, sql.ErrNoRows) {
		return err
	}
	when := at.UTC().Format(time.RFC3339)
	if cur != "" && cur != ns {
		if _, err := x.Exec(`INSERT OR REPLACE INTO repo_place_past (repo, namespace, until) VALUES (?, ?, ?)`, repo, cur, when); err != nil {
			return err
		}
	}
	if _, err := x.Exec(`DELETE FROM repo_place_past WHERE repo = ? AND namespace = ?`, repo, ns); err != nil {
		return err
	}
	_, err := x.Exec(`UPDATE repo SET namespace = ?, namespace_by = ?, namespace_at = ? WHERE name = ?`, ns, by, when, repo)
	if err != nil && strings.Contains(err.Error(), "UNIQUE") {
		return addressRefusal(x, repo, ns)
	}
	return err
}

// privateTeamRepos is every repository a private team holds: these
// appear under their owner, not the team (placeOf).
const privateTeamRepos = `(SELECT tr.repo FROM team_repo tr JOIN team t ON t.name = tr.team WHERE t.private = 1)`

// shownClash reports the address readers would see repo at once it is
// placed under ns, and whether another repository already shows there
// (RFC 0025). A private team's repositories show under their owner, so
// a repository placed in one shows there, and the owner's own and their
// other private teams' repositories are what can be beside it.
func shownClash(x execer, repo, ns string) (string, bool) {
	shown := ns
	var private int
	_ = x.QueryRow(`SELECT private FROM team WHERE name = ?`, ns).Scan(&private)
	if private == 1 {
		_ = x.QueryRow(`SELECT owner FROM repo WHERE name = ?`, repo).Scan(&shown)
	}
	if shown == "" {
		return "", false
	}
	var one int
	err := x.QueryRow(`SELECT 1 FROM repo o, repo r WHERE r.name = ? AND o.handle = r.handle AND o.name <> r.name
		AND ((o.namespace = ? AND o.name NOT IN `+privateTeamRepos+`)
		  OR (o.owner = ? AND o.name IN `+privateTeamRepos+`))`, repo, shown, shown).Scan(&one)
	return shown, err == nil
}

// addressRefusal says that ns already has a repository called what repo
// is called (RFC 0025: one repository per address).
func addressRefusal(x execer, repo, ns string) error {
	var handle string
	_ = x.QueryRow(`SELECT handle FROM repo WHERE name = ?`, repo).Scan(&handle)
	return Refusal{fmt.Sprintf("%s already has a repository called %s", ns, handle)}
}

// WasPlacedAt reports whether the repository once appeared under ns and
// no longer does — the question a saved link asks.
func (d *DB) WasPlacedAt(repo, ns string) bool {
	if repo == "" || ns == "" {
		return false
	}
	var one int
	return d.db.QueryRow(`SELECT 1 FROM repo_place_past WHERE repo = ? AND namespace = ?`, repo, ns).Scan(&one) == nil
}