Time travel¶
Plain-English summary. Every sync reposix runs — the first git
fetch, every scheduled poll, every accepted git push — writes a
permanent git tag inside the cache's bare repo. That means the full
history of what a backend record looked like at any past moment is a
git diff between two ordinary git refs, not a database query you
have to reconstruct by hand. This page shows where those tags live,
how to check one out, and the CLI shortcuts (reposix history,
reposix at <ts>) that find one for you.
The audit log says what reposix did; sync tags say what reposix observed. Together they are a fully replayable history of how the backend looked, sync by sync, all the way back to the first git fetch.
What gets tagged¶
Every successful Cache::sync writes one ref of the form:
For example, refs/reposix/sync/2026-04-25T01-13-00Z points at the synthesis commit produced by the sync that ran at 2026-04-25 01:13:00 UTC. Colons are illegal inside git ref names, so we substitute -; the format round-trips one-to-one with chrono::DateTime<Utc> (the chrono crate's UTC-fixed timestamp type — Rust's de facto standard date/time representation).
The tag lives inside the cache's bare repo at ~/.cache/reposix/<backend>-<project>.git, not in your working tree. git tag -l in the working tree never shows it. The reasons:
- The helper's
listadvertisement only mentionsrefs/heads/main. transfer.hideRefs = refs/reposix/sync/is set on the cache's bare repo sogit upload-pack --advertise-refsskips the namespace entirely.
This is private state for the cache. Inspecting it requires going to the cache directly.
Inspecting one historical sync¶
# The cache path is deterministic; reposix doctor prints it:
$ reposix doctor /tmp/repo | grep "cache DB"
OK cache.db: cache DB present at /home/me/.cache/reposix/sim-demo.git/cache.db
# Check out the sync from a known timestamp:
$ git -C /home/me/.cache/reposix/sim-demo.git checkout refs/reposix/sync/2026-04-25T01-13-00Z
$ git -C /home/me/.cache/reposix/sim-demo.git show HEAD:issues/PROJ-42.md
The bare repo doesn't have a working tree, but git show <ref>:<path> works without one. For visual diffing across two syncs:
$ git -C /home/me/.cache/reposix/sim-demo.git diff \
refs/reposix/sync/2026-04-24T22-30-00Z \
refs/reposix/sync/2026-04-25T01-13-00Z \
-- issues/PROJ-42.md
That diff is the literal byte-level change reposix observed for PROJ-42 between those two syncs. No reconstruction, no simulator replay, no rebuilding state from audit rows — just git diff.
CLI surface¶
Two subcommands surface the tag namespace from the working tree without you having to hand-construct the cache path:
$ reposix history /tmp/repo
2026-04-25T01-13-00Z commit 1a2b3c4 delta_sync (3 record(s) in this sync)
2026-04-25T01-08-00Z commit 0f9e8d7 delta_sync (1 record(s) in this sync)
2026-04-25T01-03-00Z commit deadbee tree_sync (47 record(s) in this sync)
3 sync tag(s). Earliest: 2026-04-25T01-03-00Z. Use `git -C /home/me/.cache/reposix/sim-demo.git checkout <tag>` to inspect a historical state.
$ reposix at 2026-04-25T01:00:00Z /tmp/repo
refs/reposix/sync/2026-04-25T00-58-00Z commit 7c2d4f1
(use: git -C /home/me/.cache/reposix/sim-demo.git checkout refs/reposix/sync/2026-04-25T00-58-00Z)
history lists most-recent first, capped at 10 entries by default (override with --limit). at <ts> finds the latest sync tag whose timestamp is ≤ the target — useful for "what did reposix see when I filed this bug?". The synthesis op (tree_sync for the seed sync, delta_sync for incrementals) and the record count are pulled from audit_events_cache on a best-effort basis.
Audit row pairing¶
Every tag write also produces one audit row:
| Column | Value |
|---|---|
op |
sync_tag_written |
oid |
synthesis commit OID |
reason |
full ref name (refs/reposix/sync/<slug>) |
So a forensic query that joins audit_events_cache.op = 'sync_tag_written' against the bare repo's ref store gives you (timestamp, commit, ref-name) triples plus everything else the sync row recorded. The row is part of the same append-only audit table as tree_sync, delta_sync, materialize, etc. — same WAL, same triggers, same security guarantees.
Cost¶
Each tag is one git ref — 41 bytes on disk in loose form, less when packed. A repo synced hourly for a year accumulates ~360 KB of refs — below the noise floor for normal use.
reposix gc exists, but it never touches sync tags
reposix gc (LRU / TTL / all-blob strategies, plus --orphans for
whole-cache cleanup) shipped and is real — but by design it evicts only
loose blob objects; tree/commit objects, refs, and sync tags are never
touched (crates/reposix-cache/src/gc.rs). TTL-based pruning of sync
tags specifically is not implemented by any command today — they
accumulate indefinitely, cheap enough that this has never been a
practical problem, but do not expect reposix gc to shrink the
refs/reposix/sync/* namespace.
Why this is interesting¶
Most issue-tracker integrations expose the current state and leave history as a database query against the backend. Sync tags expose history as the same primitive your version-control already speaks: refs and commits. An agent that knows git checkout and git diff can reconstruct what changed without ever learning a reposix-specific API.
The pattern is generalisable beyond reposix — any partial-clone promisor remote could write per-sync refs and turn its observation history into a checkable artefact. To our knowledge, reposix is the first to ship it. Design intent and prior-art search are recorded in .planning/research/v0.11.0/vision-and-innovations.md §3b.
Where to go next¶
Sync tags are a thin layer on top of the filesystem layer's cache, and most of them get written by a push:
- 🗂️ The filesystem layer — where blobs get lazily materialized; sync tags live in the same cache's bare repo.
- 🔀 The git layer — every accepted
git pushwrites adelta_synctag, same as a scheduled fetch. - 🛡️ The trust model — the
sync_tag_writtenaudit row and the append-only guarantees behind it. - 📖 Bare repo and audit log — the two primitives sync tags sit on top of.