sow repo

List, create, inspect and remove Repositories — the lock, transaction and Generation boundary.

A Repository owns one pool/, one dists/, one SQLite database and one private state directory. It is the boundary of locking, transaction recovery, Generation numbering and Changesets — nothing is deduplicated across Repositories and no cross-Repository commit is atomic. sow repo manages that boundary.

Synopsis

sow repo ls [-C DIR] [--json]
sow repo new NAME [-C DIR] [-T DUR | -N] [--json]
sow repo show [NAME] [-C DIR] [-r NAME] [--json]
sow repo rm NAME [-f|--force] [-C DIR] [-T DUR | -N] [--json]

Naming

A Repository name must match [a-z0-9][a-z0-9._-]* and may not be ., .., .sow, pool, dists, or collide with a Workspace reserved file.

sow repo new .sow
operation rejected: managed: operation rejected: name ".sow" must match [a-z0-9][a-z0-9._-]*

You cannot choose the path. A Repository always lives at <workspace>/<NAME>/.

sow repo ls

Read-only listing of every Repository in the Workspace.

sow repo ls
NAME	PROTECTED	DISTS	GENERATION	STATUS	PACKAGES	MEMBERSHIPS
infra	true	1	1	clean	0	0
pgsql	false	2	2	clean	0	0
FlagDescriptionDefault
-C, --workdir DIRWorkspace discovery start directorycurrent directory
--jsonEmit the versioned JSON envelopefalse

STATUS is one of clean, dirty, recovering or error. See Transactions & Recovery for what each one implies for clients.

sow repo new

Atomically updates sow.yml, then creates <workspace>/<NAME>/{pool,dists}, the SQLite database and the private state directory. A new Repository is Generation 0 and clean.

sow repo new pigsty
created pigsty: path=/srv/repo/pigsty protected=false dists=0 generation=0 status=clean packages=0 memberships=0
FlagDescriptionDefault
-C, --workdir DIRWorkspace discovery start directorycurrent directory
-T, --timeout DURMaximum lock wait; 0 waits indefinitely0
-N, --no-waitFail immediately when the lock is heldfalse
--jsonEmit the versioned JSON envelopefalse

repo new takes the Workspace lock, not a Repository lock — the Repository database does not exist yet. It does not accept -r; the positional argument already names the target.

Running it again on an existing Repository is a converging no-op that reports the current state, so it is safe in a provisioning script.

sow repo show

Read-only detail for one Repository. With NAME omitted, the usual Repository selection rules apply.

sow repo show pigsty
repository pigsty:
  path: /srv/repo/pigsty
  protected: false
  dists: 2
  generation: 6
  desired_revision: 6
  status: clean
  packages: 5
  memberships: 8
  config: {"protected":false,"signing":{"rpm":{"packages":{"mode":"never"}}},"dists":{"el9":{"format":"rpm","architectures":["x86_64","aarch64"],"limit":1,"exclude":[{"kind":["debuginfo","debugsource"]}]},"trixie":{"format":"deb","architectures":["x86_64","aarch64"],"limit":0,"exclude":null}}}
  dirty_reasons: []
  recent_operation: id=4142220455201181493 kind=add state=done error_class= created_at=2026-08-04T04:09:24.995538Z updated_at=2026-08-04T04:09:25.332772Z
FlagDescriptionDefault
-C, --workdir DIRWorkspace discovery start directorycurrent directory
-r, --repo NAMESelect the repository when NAME is omittedselection rules
--jsonEmit the versioned JSON envelopefalse

If you give both NAME and -r, they must agree; disagreement fails before any state is read:

sow repo show demo -r empty
operation rejected: repo show NAME "demo" and --repo "empty" select different repositories

sow repo rm

Removes a Repository: its sow.yml entry, database, pool/, dists/ and private state. It never follows symlinks and never steps outside the fixed Repository path.

Without -f, only an empty Repository — no Dists, no Memberships, no Package Objects — can be removed:

sow repo rm infra
removed repository infra
sow repo rm pgsql
operation rejected: managed: operation rejected: repository "pgsql" is not empty; use --force
sow repo rm pgsql -f
removed repository pgsql
FlagDescriptionDefault
-f, --forceRemove a non-empty unprotected repositoryfalse
-C, --workdir DIRWorkspace discovery start directorycurrent directory
-T, --timeout DURMaximum lock wait; 0 waits indefinitely0
-N, --no-waitFail immediately when the lock is heldfalse
--jsonEmit the versioned JSON envelopefalse

What -f actually downgrades

-f only relaxes the emptiness precondition. It does not bypass path safety, symlink refusal, or the protected gate.

protected

protected: true in sow.yml blocks Repository deletion outright, -f included:

sow repo rm alpha -f
operation rejected: managed: operation rejected: repository "alpha" is protected

To remove a protected Repository you must edit sow.yml, pass sow config check, and try again. There is no --yes and no temporary override.

protected scopes to Repository deletion only. Package-level work on a protected Repository is unaffected — add, rm, build, and even dist rm, all continue to work:

sow dist rm el9 -r alpha -f
removed dist el9 from alpha

Examples

Create the Repositories for a two-tier layout:

sow repo new infra
sow repo new pgsql

Fail fast in a cron job rather than queue behind another writer:

sow repo new nightly -N || echo "another writer holds the workspace lock"

Audit every Repository in one line each:

sow repo ls --json | jq -r '.result.repositories[] | "\(.name)\t\(.status)\tgen=\(.generation)"'

Exit codes

CodeTrigger
0Listed, created, shown or removed; or repo new converged an existing Repository
1Runtime I/O error creating or removing the tree
2Usage error, Workspace not found, or an ambiguous Repository selection
4Workspace lock held and --no-wait given or --timeout expired
5Integrity or recovery error in the Workspace journal
6Invalid name, unknown Repository, non-empty without -f, protected, or NAME conflicting with -r

See also

Last modified: 2026-08-08: init commit (fe725aa)