Documentation menu

Repositories and versions

What a repository stores, what a version is, and why recording the whole working tree instead of a staged delta changes how history behaves.

This page defines the two things everything else is built from. Read it first: the branch and workspace model on the next page is entirely a statement about how versions are organised, and it will not land without these two terms in place.

What a repository is

A repository is three things that always travel together:

  • an object database — every file, directory and version ever recorded;
  • a set of references — the names that point at objects, such as which version a branch has selected;
  • one unit of synchronisation — the thing that gets reconciled with a peer or with Rinko Hub, whole.

That third point is what makes a repository a boundary rather than a folder. You synchronise a repository; you do not synchronise half of one. On Rinko Hub the same object is addressed as owner/repo, and grouping several repositories together is a Rinko Hub concern rather than a protocol one.

Creating one is a single command, run in the directory you want to become a repository. It reports that it created an empty repository and an initial workspace called main:

Command
nexa init
Expected output
Initialised empty Nexa repository in .
Created workspace `main`

Objects and content addressing

Everything a repository stores is an object, and every object is immutable — once written, its bytes never change. An object's identifier is the BLAKE3 hash of its content, which means the identifier is not a label assigned to the object but a fact derived from it. Two identical trees recorded on opposite sides of the world get the same identifier without coordinating.

Three consequences follow, and all three show up in everyday use:

  • Nothing is modified in place. Correcting a mistake means recording a new object, never editing an old one.
  • Verification is local. Re-hashing an object's content and comparing it to the identifier proves the object is intact, with nothing to consult and no one to trust.
  • Transfer is trivially convergent. An object either exists on a peer or it does not, so reconciling two repositories means exchanging what each lacks — there is no merge step at the storage layer.
Note: Identity is fixed before compression and encryption

The pipeline runs serialize, then hash, then compress, then encrypt — in that order. The identifier is computed from the serialized content, before either storage concern touches it. So recompressing a repository, or sealing a path that was previously readable, never changes any object's identifier. Identity is a property of the content; compression and encryption are policy applied to it afterwards.

What a version is

A version is a complete, selectable state of a tree, together with who recorded it and when. The emphasis belongs on complete:

Warning: A version is a whole tree, not a delta

A version is not a patch against its parent. It names a complete tree. Reading any single version never requires replaying the versions before it, and history is a sequence of states rather than a sequence of changes to be applied in order.

This is also why there is no staging area. Nothing corresponds to git add, and there is no index. Since a version is a whole tree, the working tree already is the staging area, and a second persistent state would be one more thing that can disagree with reality. nexa record captures the working tree as it stands:

Command
nexa record -m "First version"
Expected output
Created version 40f877ccd802
  workspace: main
  files:     1

Selected on branch main.

To record only part of the tree, name the paths at the moment of recording — which is the replacement for staging a subset first and recording it second:

Command
nexa record -m "Tidy the parser" src/

Say version rather than commit. The different word marks a genuinely different object: a version is a first-class alternative that can sit alongside competing answers to the same question, which is the subject of the next page.

History

History is append-only. Versions are immutable and are never deleted, so history can be added to but never rewritten. There is no rebase, no amend and no force-push, because none of them is expressible: they all describe replacing a recorded state with a different one.

nexa history shows a workspace's versions, most recent first:

Command
nexa history
Expected output
9a8fa956bb4d  First version
  author:  device:31d2-a804-e396-299a
  status:  selected

The status line is the part worth noticing. It is not stored in the version — it is read from a reference. That distinction is the last piece of the model:

Note: References are the only mutable state

Objects never change and history only grows, so every mutable fact in a repository lives in a reference: which version a branch has selected, which workspace you are in, what a branch is called. A version's status is a property of the branch that points at it, never of the version itself — which is why selecting a different candidate later changes nothing about any of the objects involved.

Next: branches and workspaces, where those references turn out to hold a set of competing candidates rather than a single pointer.