How it works
git client otisk
---------- -----
git push --ssh--> git-gateway
otisk ... --ssh--> |
| authenticate by key fingerprint
| authorise before anything runs
v
hydrate from the object store
|
v
stock git-receive-pack
|
v
repack, upload packs,
then commit the manifest
|
+-----------+-----------+
v v
object store PostgreSQL
(source of truth) (metadata + ref mirror)
repos/<id>/manifest.json
repos/<id>/packs/*.pack
repos/<id>/packs/*.idx
Packs are uploaded before the manifest is committed, and the manifest is the only thing that publishes refs — so refs can never become visible before the objects they need. The manifest commit is conditional on the generation it was read at, so a writer working from stale state is refused rather than overwriting a concurrent update.
Git itself does the git work. otisk hydrates a repository onto disk, runs
stock git-receive-pack or git-upload-pack against it, and makes the result
durable. The disk is a cache: delete it and the next request rebuilds it.
Watching it happen
The gateway logs each request's lifecycle: the command, whether the repository was cold, how long hydration took, and what the durable write recorded.
control command handle=yuritomanek command="repo create notes"
git command handle=yuritomanek command=git-receive-pack repo=yuritomanek/notes
hydrated repo=yuritomanek/notes cold=true generation=0 packs=0 refs=0 took=18ms
git finished repo=yuritomanek/notes command=git-receive-pack took=8ms
durable write repo=01a0a561-... generation=1 packs=1 refs=1 bytes=1361 took=16ms
Looking at what is stored
Against a development stack, give mc credentials first — the alias the MinIO
image ships with has none, and listing without them is denied:
docker compose exec minio mc alias set otisk http://127.0.0.1:9000 otiskdev otiskdevsecret
docker compose exec minio mc ls --recursive otisk/otisk/repos/
01a0a54b-.../manifest.json 350B
01a0a54b-.../packs/pack-a745a039....idx 1.1KiB
01a0a54b-.../packs/pack-a745a039....pack 222B
The local cache can be deleted at any time; the next request hydrates it back:
rm -rf var/cache
otisk clone yuritomanek/notes roundtrip
Where a review lives
A change is a commit, identified by a Change-Id trailer. Each push of it
becomes an immutable ref, refs/changes/<number>/<revision>, so a revision
you pushed is never lost even when you amend past it. The conversation is an
append-only log of commits on refs/changes/<number>/meta.
All of that is git, in the repository, in the object store — replicated and restored exactly as your code is. PostgreSQL holds an index of it, which is a fold of the log and can be rebuilt from it.