~bigbes/sr-ht-spec

ref: 8219ede1804c144de2a2f2e42f3f476bff4b622a sr-ht-spec/gitx/merge_test.go -rw-r--r-- 20.6 KiB
29f6e881 — Eugene Blikh 27 days ago
feat: gitx — bare space repos, the git-object read path, and the id-keyed merge

One storage tier: there is no checkout on disk, so every read resolves a tree
and reads blobs. The approved head, a pinned ?rev= and a proposal branch are
the same code path with a different revision.

Lifecycle: Create makes a bare repo at <root>/~<owner>/<space> with HEAD on the
approved branch and one empty initial commit, so nothing downstream has to
special-case an unborn branch. Open reads the approved branch back from HEAD.

Merge: tree-splice, exactly as designed. Staleness is keyed by document ID, not
path — each changed document is resolved to its path on the approved head
through its frontmatter id before its blob is compared, so a rename between the
base and the head neither invents a conflict nor resurrects the moved path. On
staleness a *StaleError carries the current head for the caller's 409.
Otherwise the approved tree is rewritten with each document's blob at its path
in the head and committed with two explicit parents [approvedHead,
proposalHead]. Repository.Merge is never called; go-git v5 supports only
FastForwardMerge and this is pure object.Tree plumbing.

Refs rule: CheckRefUpdate is a pure predicate over (principal, ref, ancestry) —
agents may only touch proposals/*, the approved branch takes fast-forwards from
the human only and is never force-updated or deleted. hooks/ calls it without a
repository, which is why ancestry is an input rather than a lookup.

Concurrency: a process-wide per-space mutex keyed by repository directory guards
every write, and every ref move is a compare-and-swap that rebuilds and retries
when it loses to a concurrent native receive-pack push.

Bounded throughout: context deadlines on every operation, a per-blob cap and
per-walk byte and entry budgets. Nothing is truncated — a partially read
document would be indexed and served as though whole, so it fails instead.

Tests build every fixture programmatically through this package's own plumbing,
never the git binary, and cover the rename between base and head, each
staleness case, the two-parent merge shape, the refs rule for both principals
including force-update rejection, oversized blobs, and the CAS retry.