ref:86dbb1b7d02532f5d13352caabae5b6d60374d5f

docs: CHANGELOG + README protocol-v2 capability matrix

Surfaces the protocol-v2 work so library consumers actually know what's supported. CHANGELOG.md [Unreleased] gets: - an "Added" block covering the full capability set (ls-refs/fetch/atomic), telemetry events, and the new Pack.Filter module - a "Fixed" block covering the four bug-fix commits (has_complete_command? delim, build_acknowledgments, shallow walker perf, sanitize_error, loud filter-parse errors) README.md gets a new "Git protocol v2 capability matrix" section with two tables (UploadPack and ReceivePack) listing every capability / sub-argument in the protocol-v2 spec with status (supported / advertised / not supported) and a one-line note. Also tightens the existing "Known Limitations" bullet to acknowledge atomic's storage-layer transactionality ceiling and points at the telemetry event operators should alert on for a failed rollback. No code changes.
SHA: 86dbb1b7d02532f5d13352caabae5b6d60374d5f
Author: Cole Christensen <cole.christensen@macmillan.com>
Date: 2026-04-19 01:21
Parents: 796cf40
2 files changed +95 -6
Type
CHANGELOG.md +59 −0
@@ -9,6 +9,65 @@
### Added
- **Full protocol-v2 capability coverage for UploadPackV2.** Capability
advertisement now includes `ls-refs=unborn`, `fetch=shallow
wait-for-done filter`, `server-option`, and `object-format=sha1`.
Real `git` clients can now perform: `git clone` (symrefs, peel,
unborn-HEAD, annotated tags); `git clone --depth=N`,
`git fetch --deepen=N`, `git clone --shallow-since=<date>`,
`git clone --shallow-exclude=<ref>`; `git clone --filter=blob:none`,
`--filter=blob:limit=…`, `--filter=tree:N`, `--filter=object:type=…`,
`--filter=sparse:oid=…`, `--filter=combine:a+b`;
`git fetch --negotiate-only` (via `wait-for-done`); multi-round
negotiation on stateful transports.
- **Atomic push in ReceivePack.** `@capabilities` now includes
`atomic`. When the client sets the capability, ReceivePack validates
every ref-update in Phase 1 and applies them in Phase 2; if any
apply fails mid-batch we roll back every ref we already touched
from a pre-flight snapshot. Storage-layer failures during rollback
are logged and emitted via telemetry
(`[:ex_git_objectstore, :protocol, :receive_pack, :rollback_failed]`).
- **Partial-clone promisor support.** Fetches whose wants resolve to
blobs or trees (lazy-fetch from a promisor remote) bypass the
session filter so the exact requested object is returned. This is
what makes `git clone --filter=blob:none` (no `--no-checkout`)
work end-to-end.
- **Telemetry** spans + events for every new path:
- `[:ex_git_objectstore, :protocol, :fetch]` — span (start/stop)
with mode (`:full` / `:shallow` / `:filtered` /
`:shallow_filtered` / `:wait_for_done`), wants, haves, object
count, pack bytes.
- `[:ex_git_objectstore, :protocol, :receive_pack, :atomic]` —
span with outcome (`:committed` / `:rolled_back`), commands,
validation_failures.
- `[:ex_git_objectstore, :pack, :filter]` — event with
`objects_in`, `objects_out`, and spec kind.
- `ExGitObjectstore.Pack.Filter` — public parser + applicator for
every filter spec listed in
`Documentation/rev-list-options.txt`.
### Fixed
- `UploadPackV2.feed/2` now treats only flush (`0000`) as a command
terminator, not delim (`0001`). Previously a TCP chunking split
that fell between the `command=` line's delim and the request's
final flush caused premature processing and dropped the rest of
the request. Closes ex_git_objectstore#29.
- `build_acknowledgments/3` always emits an `acknowledgments` section
when the client sent any have, preventing the two regressions
(`no 'ready'` + `expected 'acknowledgments'`) that landed on
production during the earlier fix iteration.
- Shallow walker no longer uses `rest ++ parent_items` for its
queue; O(n²) dropped to O(n). A 500-commit shallow clone now
completes in under 2 s (was > 10 s).
- `sanitize_error/1` in ReceivePack preserves up to 80 chars of
error detail. Git clients now see `ng <ref> hook_rejected:
CODEOWNERS approval required for main` instead of the bare tag.
- Unparseable `filter` specs in fetch are now rejected with a
band-3 protocol error (`ERR filter spec not supported: …`)
instead of being silently ignored. Prevents partial-clone clients
from recording `remote.origin.promisor=true` against a full pack.
- **Merge / rebase toolkit** — complete set of primitives for performing
merges and rebases in-process, without a working directory or any
shell-out to `git`. Unblocks fangorn/anvil#45. See fangorn/ex_git_objectstore#24.
README.md +36 −6
@@ -118,14 +118,44 @@
Stores everything in an ETS table. Useful for testing and ephemeral operations.
## Known Limitations
## Git protocol v2 capability matrix
UploadPackV2 and ReceivePack are validated against a real `git` CLI
via the integration test suite. The table below lists every
capability / sub-argument the protocol v2 spec defines and the
current implementation status.
This is v0.1.0 — a functional foundation with some protocol features still in progress:
### UploadPack
- **ofs-delta generation** — pack writer stores full objects only, no delta compression
- **multi_ack_detailed** — upload-pack uses NAK-only negotiation
- **side-band-64k** — receive-pack doesn't use side-band for status reporting
These are tracked as TODOs in the source — the scaffolding is in place for implementation.
| Capability / argument | Status | Notes |
|---------------------------|---------------|-------|
| `version 2` | ✅ supported | Capability advert emitted on connect. |
| `ls-refs` | ✅ supported | `ref-prefix`, `symrefs`, `peel`, `unborn` all honoured. HEAD advertised as a symref when requested. Annotated tags peel to a recursive target (depth capped at 10). |
| `fetch=shallow` | ✅ supported | `deepen <n>`, `deepen-since <ts>`, `deepen-not <ref>`, `deepen-relative`, `shallow <sha>` all honoured. `shallow-info` section emitted when the walker surfaces any new / unshallow boundary. |
| `fetch=wait-for-done` | ✅ supported | Client `--negotiate-only` works end-to-end. |
| `fetch=filter` (partial) | ✅ supported | `blob:none`, `blob:limit=<n>[k\|m\|g]`, `tree:<n>`, `object:type=<t>`, `sparse:oid=<oid>`, `combine:a+b+…`. Invalid specs are rejected with a band-3 `ERR` reply. Lazy promisor fetches (blob/tree wants) bypass the filter. |
| `server-option` | ✅ advertised | Server accepts `server-option <value>` lines in fetch requests; currently a no-op (parsed but not acted on). |
| `object-format=sha1` | ✅ supported | Only SHA-1 is supported. SHA-256 is out of scope. |
| Multi-round negotiation | ✅ supported | Client may send haves across multiple rounds without `done`; stateful session persists until packfile is sent. |
| `packfile-uris` | ❌ not supported | No CDN-offloaded pack URIs. |
| `wanted-refs` | ❌ not supported | Server never emits a `wanted-refs` section. |
| Protocol v0 / v1 | ❌ not supported | Clients must request v2 (git 2.26+ does by default). |
### ReceivePack
| Capability | Status | Notes |
|-------------------------|--------------|-------|
| `report-status` | ✅ supported | Per-ref `ok` / `ng` lines returned. |
| `delete-refs` | ✅ supported | Push of `zero-sha → ref` deletes. |
| `atomic` | ✅ supported | Two-phase validate-then-commit with rollback on mid-batch failure. Atomicity is best-effort at the storage layer — see `Documentation/atomic.md`. |
| `report-status-v2` | ❌ not supported | Only v1 status lines are emitted. |
| `side-band-64k` | ❌ not supported | Status goes in-band. |
| `quiet` | ❌ not supported | Ignored; hooks still fire. |
| `ofs-delta` (write-side)| ❌ not supported | Pack writer stores full objects only; thin-pack *reading* via REF_DELTA is fully supported. |
## Known Limitations
- **ofs-delta generation** — pack writer stores full objects only, no delta compression on the write side.
- Atomic ref updates are validation-based with rollback from a pre-flight snapshot, not a native multi-key storage transaction. A VM crash mid-rollback can leave refs partially applied. Failures are logged via `Logger.error` and emitted as a `[:ex_git_objectstore, :protocol, :receive_pack, :rollback_failed]` telemetry event.
## License