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.