- substrate-recomposition.md: the four sibling reviews are closed — alkblobs C-1..C-4 at the wave-13 gate (ADR-018/019, publish hold lifted), alkcall 012 in 0.10.0 (ADR-048), alkhttp 008 in 0.8.0 (ADR-072/073), alkstore 005 the no-ask record; per-item landing pointers inline, version citations sync-passed - poco/pocr/pocl-findings.md: landing + re-run annotations (the git-object family on main, the gateway fix, Basic tolerance); the deleted prototype branch references re-pointed at ADR-018 - alk-stack.md + AGENTS.md: sibling version pins synced - gitoxide.md: version table re-pinned to the 0.89 family (2026-10-18 pass); the reference clone stays the reading aid All three POC suites re-ran green against the landed surfaces the same day (poco 8/8 incl. both gates vs alkblobs main, lfs-poc 14/14 with gateway_gap rewritten to pin the fix, pocr 12/12); no new asks surfaced — alkblobs' last pre-publish sweep is clean.
8.2 KiB
Research: gitoxide (gix) as the git engine
Status: initial pass complete; version table re-pinned 2026-10-18
Date: 2026-09-19 (bump pass 2026-10-18)
Sources: local clone at /workspace/gitoxide (repo HEAD 77c8cd956,
tag-described as gix-transport-v0.59.2-130-g77c8cd956), crates.io API.
TL;DR
gitoxide covers the storage layer (odb, packs, refs, objects, fsck) and gives
us the packetline codec, but it does not give us a git server. gix-protocol
and gix-transport are client-side (fetch/clone from the point of view of the
machine asking for objects). The server half of the smart protocol — capability
advertisement, ls-refs command dispatch, fetch negotiation on the serving side,
pack generation on demand, and receive-pack application with ref transaction and
update-hook semantics — has to be built by us. This is expected and acceptable:
it is exactly the narrow protocol-shim layer our security model wants to own
anyway (see git-protocol.md).
Version alignment (important)
The manifest pins were moved to the 0.89 family on 2026-10-18 (the
implementation has not begun, so the bump is a pure pin move; verified
across default/all/no-default-features — clippy and tests green):
| crate | crates.io max stable (2026-10-18) | alkgit pin | note |
|---|---|---|---|
| gix | 0.89.0 | — (facade not a dep) | current family |
| gix-packetline | 0.24.0 | 0.24 | async-io now in features2 |
| gix-transport | 0.61.0 | 0.61 | async-client unchanged |
| gix-protocol | 0.67.0 | 0.67 | async-client, sha features unchanged |
| gix-pack | 0.76.0 | 0.76 | sha1/sha256 features unchanged |
| gix-odb | 0.86.0 | 0.86 | parallel feature moved to a gix-parallel dep |
| gix-ref | 0.69.0 | 0.69 | sha features added in this family |
| gix-object | 0.66.0 | 0.66 | sha features unchanged |
| gix-hash | 0.28.0 | 0.28 | sha1/sha256 moved into features2 |
| gix-fsck | 0.27.0 | 0.27 | sha features unchanged |
The original 2026-09-19 table (0.87.1 family) matched the reference
clone component-for-component; the local clone still stands at that
family and remains the reading/reference aid it always was — never a
path dependency. If an unreleased fix turns out to be required, that
is an OQ for architecture (gitoxide git-patch or [patch] section is
the fallback).
Hash algorithm gotcha (encountered live)
gix with default-features = false and no hash feature fails to compile:
gix-hash emits compile_error!("Please set either the sha1 or the sha256 feature flag"). The workspace manifest now pins features = ["sha1"]
throughout (gix, gix-pack, gix-odb, gix-object, gix-hash) and our crates carry
a matching sha256 passthrough feature for the future. This is a
compile-time-rejected invariant, so the config can't drift silently.
What we can use off the shelf
Storage (strongest area — use as-is)
gix-odb— object database with loose + packed stores, dynamic multi-index loading,Handlewith caching; thedynamicstore refreshes on mtime changes, which suits a long-running server. API contract notes (POC-2, validated live): handles used for pack generation needprevent_pack_unload()andignore_replacements = true.Cacheis deliberately notSync(per-threadRefCellpack/object caches); shareArc<Store>across tasks and build a handle per session — generation belongs on blocking threads with the owned handle moved in.gix-pack— pack data reading (index file, multi-index, delta resolution) and pack writing (bundle::write→write_to_directoryfor index-from-stream). Pack writing produces index+pack into a directory; streaming a pack directly to a socket needs evaluation (POC candidate). Generation pipeline (POC-2, validated live):data::output(featuregenerate) is the on-demand generation path:count::objects(_unthreaded)(closure expansion;TreeContentsexpands each input commit's own tree but does NOT follow parents — feed the commit-ancestry walk through it) →entry::iter_from_counts(chunked deflate + pack-delta copy, back-pressured) →InOrderIter→bytes::FromEntriesIter(header/entries/trailer to anyio::Write, V2 only). Composition reference:gitoxide-core/src/pack/create.rs.bundle::write_to_directoryis the indexing side (consume an existing pack stream; needsstreaming-input) — receive-pack's tool, not fetch's. Deltas are copied from existing packs; no delta synthesis exists upstream (gix-deltais apply-only). Entry stats (finalize()) expose copied/recompressed/missing counts — usemissing_objectsto abort instead of emitting broken packs. Feature set for our lean build:sha1+generate+parallel(+streaming-inputfor bundle write).gix-ref— ref store withtransactionmodule (compare-and-swap semantics, reflog), which is what receive-pack needs for atomic ref updates.gix-object— object parsing/encoding (commit, tree, tag, blob).gix-fsck— connectivity checks for received packs.gix-discover— repository discovery /.gitdir resolution.gixfacade — repo opening, config, etc. For library-developer usage gitoxide recommendsdefault-features = false+ only the components needed; follow that to keep compile times down.
Wire format (use as-is)
gix-packetline— pkt-line encode/decode, both blocking andfutures-ioasync (async-iofeature). This is the one crate both the http and ssh interfaces need most; it is small and stable (0.24.xas of the 2026-10-18 re-pin). API contract notes (POC-1, validated live):async_iocodec is built onfutures_iotraits — bridging a tokio stream takestokio::io::split+tokio_util::compat::{compat, compat_write}(two one-liners).StreamingPeekableItertreatsFlushas a stop-delimiter: on flush,read_line()returnsNone(no line is yielded) and the iterator is inert until.reset()— a command loop must reset after every request or the next command is silently swallowed. Return type isOption<io::Result<Result<PacketLineRef, decode::Error>>>(three layers; wrap it in a session type).gix-transport— definesProtocol(V0/V1/V2),client::MessageKind, and the fetch-side abstractions. Only the types are reusable server-side; its transport implementations (client) are not what we need.
Client side (exists, mostly irrelevant for us)
gix-protocol— handshake,ls-refs, fetch negotiation from the client side (fetch::Response::from_line_readerparses server responses; the negotiate module builds request arguments). Useful as a reference for response shapes we must produce server-side, and possibly reused for alkgit-to-alkgit replication later.gix-transport/src/lib.rsexports onlypub mod client— confirming no server-side half exists upstream.
What gitoxide does NOT give us (we own this)
- Capability advertisement (V0/V1 first-want line and V2 capability handshake) — must produce ourselves.
- Server-side command dispatch —
ls-refs,fetchV2 command parsing and the request→response state machine. - Server-side negotiation — evaluating client haves against our refs (ack/NAK logic, shallow handling on the serve side).
- On-demand pack generation for a fetch —
pack writing exists (Resolved (POC-2): thegix-pack bundle::write) but "stream a pack computed from a want/have set to a socket" composition needs a POC; worst case we walk objects ourselves via odb and feedgix-pack::data::output::bytesentries-to-bytes writers.data::outputpipeline streams to anyio::Write; we own only the orchestration (peel tips → commit walk → count → entries → bytes) and the missing-objects check. Delta synthesis for loose objects remains an upstream gap (optimization backlog). - receive-pack — parsing client pack stream (
gix-pack::data::inputcan parse a pack from a reader), fsck, ref CAS updates viagix-reftransaction, reporting status (unpack ok/nglines).
License
MIT OR Apache-2.0 — same as ours. Clean to depend on and to read for inspiration (attribution not required under either license, though NOTICE-type courtesy is fine).