# 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:

```sh
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:

```sh
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.
