puregit 0.0.2

A pure-Rust implementation of git, in the spirit of libgit2, built on purecrypto. Object model, packfiles, refs, and the smart transport over HTTP and SSH — client and server.
Documentation
# puregit roadmap

The goal: a pure-Rust git that can **maintain a repository**, **fetch/clone and
push** as a client, and **serve** fetch and push as a server, over both **HTTP**
and **SSH** — keeping the defining constraints intact.

**Invariants (never traded away):**

- **No C toolchain / no `*-sys` crates.** Pure-Rust dependencies only
  (`purecrypto`, `compcol`, `puressh`, `rsurl`). This is the whole point of the
  stack and gates every dependency decision.
- **`no_std + alloc` core.** The object/pack/ref/protocol engine never names
  `std`. Filesystem and network live behind the `std` feature and the [`Vfs`]
  trait; the wire protocols stay sans-IO.
- **Correctness verified against real git.** Object ids, pack/index decoding,
  index checksums, and ref formats are checked against git's own output and
  byte-for-byte round-trips. CI gate: build (default, `no_std`, `full`),
  `clippy -D warnings`, `fmt`, doc, and tests.

---

## Status

The core arc — clone / fetch / push / serve over HTTP, SSH, and `git://`, the
full local workflow (`add`/`rm`/`commit`/`status`/`branch`/`checkout`/`merge`/
`diff`/`gc`), and Git LFS — is implemented and validated against canonical git
(puregit-created history passes `git fsck`; a real `git clone` from GitHub over
pure-Rust TLS matches git byte-for-byte).

**The tracked milestones below are now complete** (M3 smart-protocol core, M8
repository maintenance, M9 compatibility & polish) — see each section for what
landed and how it was verified against real git. The single remaining item is a
deliberate **non-goal**: OpenPGP keyring management (puregit manages no keyring;
the caller supplies the key to verify/sign), documented under M9.

---

## Milestone 3 — Smart protocol core (sans-IO)

- **v0/v1 negotiation — done.**

  **v0/v1 multi-round negotiation is done** — both ACK modes (`multi_ack`,
  `multi_ack_detailed`). The server advertises them and answers a negotiation
  round (a `have` batch with no `done`) with `ACK <oid> common`/`ready` (detailed)
  or `ACK <oid> continue` (multi_ack), or `NAK` when nothing is common, deferring
  the pack to the `done` request — which now acknowledges the last common object
  (`ACK <oid>`) so a negotiating peer records the common base. The client drives
  it (`client::fetch_negotiated`): it offers local history in `have` batches until
  the server signals `ready`, then finishes with `done`. Verified end to end:
  real `git fetch` (incremental, over both the `git://` daemon and smart-HTTP)
  finds the common commits and pulls only the delta, `git fsck` clean; a puregit
  loopback fetch truncates the pack to the new objects; a wire-format test pins
  the `ACK`/`NAK` output. The plain single-round `fetch` (all ref tips as `have`
  + `done`) stays the default — already optimal when the haves are just the tips.

  `side-band-64k` demuxing and `shallow`/`deepen` **serving**
  are done — the client negotiates sideband and reassembles a multiplexed pack,
  and the server answers a `deepen` request (v0/v1 and v2) with the shallow
  boundary and a depth-truncated pack: a real `git clone --depth N` from puregit
  (both protocols) produces a correct shallow repository that passes `git fsck`.
  Client-side shallow (puregit issuing `--depth` and writing `.git/shallow`) is
  done (see M8).
- **Protocol v2 advanced features — done.** `filter` (partial clone) and
  `ref-in-want` were already done; `packfile-uris` and v2 negotiation now round
  it out. The v2 server advertises `fetch=shallow filter ref-in-want
  packfile-uris`, resolves each `want-ref <name>` to its id, and echoes a
  `wanted-refs` section before the pack.

  **`packfile-uris` (client offload download) is done.** When a v2 server
  advertises `packfile-uris`, the client offers `packfile-uris https` and, if the
  response carries a `packfile-uris` section (`<hash> <uri>` lines), downloads
  each pack **over https only** (a non-https/`file:` uri is refused before any
  network access), verifies it — the pack must be self-consistent (its trailing
  checksum matches its body) and the announced hash must name it (by pack
  checksum or whole-file digest) — and ingests it alongside the inline pack
  (`explode_pack_prefix` decodes the concatenated packfiles in sequence). Object
  integrity is additionally guaranteed at ingest by content-addressing. The
  server advertises the capability but always sends its pack inline (dynamic
  packs have no static URL to offload to). Covered by protocol round-trip,
  hash-verification, https-refusal, and concatenated-ingest tests.

  **Multi-round v2 negotiation is done** — the server answers a `fetch` round
  with an `acknowledgments` section (`ACK <oid>` per common object, or `NAK`,
  then `ready`), completing in one round (optimal — the pack already excludes the
  haves' closure), and the client demuxes the `packfile` section. Verified with
  real git both ways: `git -c protocol.version=2 clone` **and** an incremental
  `git -c protocol.version=2 fetch` from puregit find the common commits, pull
  only the delta, and pass `git fsck`.

  **Protocol v2 is implemented, client and server** (`protocol::v2`). Server:
  `serve_v2_upload_pack` handles `ls-refs` (with `symrefs`/`peel`/`ref-prefix`)
  and `fetch` (single-round `done`, plus an `acknowledgments` round), framing the
  pack in the v2 `packfile` section over sideband; the HTTP handler serves the v2
  capability advertisement and dispatch on `Git-Protocol: version=2`. Client: the
  HTTP transport offers `version=2` at discovery, and on a v2 server drives
  `ls-refs` then `fetch`, demuxing the `packfile` section — transparently, so
  `clone`/`fetch` are unchanged. Verified end to end: a real
  `git -c protocol.version=2 clone` from puregit passes `git fsck`, the puregit
  client clones a puregit v2 server over HTTP, and v0/v1 peers still work.


## Milestone 8 — Repository maintenance

  **Transparent promisor fetch is done**: `client::backfill_tree` scans a tree
  for the objects a partial clone is missing
  ([`walk::missing_tree_objects`](src/walk.rs) — descending only as far as the
  present objects allow, so a newly-fetched subtree's children surface on the
  next round), fetches them in batched rounds via `fetch_objects`, and repeats
  until the closure is complete; `client::checkout_tree` wires that ahead of the
  worktree materialization, so a `--filter`ed clone checks out with no explicit
  backfill call. The transport stays a caller-supplied parameter — the sans-IO
  core holds no network handle. Verified by a loopback test: a `blob:none`
  partial clone (spanning a subdirectory) checks out to the correct working tree
  with every blob auto-fetched, plus a `no_std` unit test of the missing-object
  scan across nested subtrees.

  The client half of partial clone is otherwise done: `fetch_with_filter` issues
  a `--filter` fetch (the server omits the filtered objects), and `fetch_objects`
  backfills specific objects on demand — the server sends an explicitly-wanted
  object even when it is reachable from a `have`. Verified by a loopback test: a
  `blob:none` fetch omits the blob, and `fetch_objects` retrieves it with the
  correct content.

  **Deepen / unshallow is done** (client and server, v0/v1 and v2). The server
  plans a deepen against the client's current shallow boundary — sending only the
  newly-reached objects, announcing the new boundary, and `unshallow`ing commits
  whose ancestors now arrive — and never offers a thin-pack base a shallow client
  cannot resolve. The client applies the `shallow`/`unshallow` updates to
  `.git/shallow` and re-requests the tips when deepening. Verified: a real
  `git fetch --unshallow` of a puregit `--depth 1` clone restores full history
  (`is-shallow-repository` false, `git fsck` clean), and a hermetic loopback test
  covers a puregit client deepening depth 1 → 2.

  **Serving partial (`filter`) clones is done**: the server parses `filter`
  (`blob:none`, `blob:limit=<n>`), advertises it (v0/v1 `filter`, v2
  `fetch=filter`), and omits the filtered blobs from the pack — while always
  sending an *explicitly-wanted* object, so a promisor client's lazy blob
  re-fetch (which re-sends the filter yet names the blob) is served. Verified end
  to end: a real `git clone --filter=blob:none` from puregit produces a promisor
  clone whose checkout lazily fetches the blobs and passes `git fsck`.

  **Client-side shallow clone is done**: `clone_with_depth` / `fetch_with_depth`
  send `deepen <n>`, record the server's boundary in `.git/shallow`
  ([`Repository::write_shallow`]/[`shallow`](src/repository.rs)), and ingest the
  truncated pack — over v0/v1 and v2 alike. Verified: a puregit `--depth 1` clone
  of a puregit server produces a repository real `git` recognises as shallow
  (`is-shallow-repository` true, one commit, `git fsck` clean); a hermetic
  loopback test confirms the older history is truncated. Serving shallow clones
  is done (M3); alternates and worktrees are done.

  **Reachability bitmaps are done**: `pack::ewah` (an EWAH codec validated
  byte-identical to git) and `pack::bitmap` write a git-compatible reachability
  `.bitmap` and its `.rev`, and `Repository::repack_with_bitmap` packs the full
  reachable closure and emits them. Verified end to end: `git rev-list
  --test-bitmap HEAD` reports `OK!` — verifying every entry — on a repository
  puregit built and bitmapped, across linear and merge histories, `git fsck`
  clean.

## Milestone 9 — Compatibility & ecosystem polish

- **PGP — done** (verification + signing). OpenPGP **EdDSA**, **RSA**, and
  **ECDSA** *verification* and OpenPGP EdDSA *signature creation* are implemented
  (details below). Keyring management is a deliberate **non-goal**: puregit
  manages no keyring — the caller supplies the key to [`verify_pgp`] and
  [`sign_pgp_ed25519`], exactly as the SSH signing path takes an explicit key
  (matching the sans-IO, bring-your-own-store design; a keyring belongs in the
  embedding application, like the credential and known-hosts callbacks).

  **Signing/verification are otherwise done** (`signing` feature):
  - *OpenPGP EdDSA, RSA & ECDSA verify* — `verify_pgp` de-armors the block,
    parses the v4 OpenPGP signature packet and its MPIs, rebuilds the signed
    digest, and checks it with `purecrypto` — Ed25519 for EdDSA, PKCS#1 v1.5
    (SHA-256/512) for RSA, and NIST P-256/384/521 for ECDSA. Verified against
    real `git commit -S` GnuPG Ed25519, RSA-2048, **and** ECDSA (nistp256)
    signatures (tamper and wrong-key rejected); committed fixtures keep all
    three green.
  - *OpenPGP EdDSA sign* — `sign_pgp_ed25519` / `sign_commit_pgp_ed25519` build
    a v4 binary-document signature packet (creation-time + issuer subpackets,
    MPI-encoded R/S, CRC-24 armor) a private key never leaves puregit for.
    Verified end to end: a puregit-signed commit passes real `git verify-commit`
    (`Good signature`), and a hermetic round-trip test signs then re-verifies
    through `verify_pgp`.
  - *SSH sign & verify* — both Ed25519:
  - *Verify* — `verify_ssh` parses the `SSHSIG` structure, reconstructs the exact
    signed bytes ([`Commit::signed_payload`] strips the `gpgsig` header;
    [`Tag::signed_payload`] drops the trailing block), and checks it with
    `purecrypto` Ed25519 over SHA-256/512. Verified against real
    `git commit -S` / `git tag -s` signatures for commits and tags.
  - *Sign* — `sign_ssh_ed25519` / `sign_commit_ssh` produce a `gpgsig` armor a
    private key never leaves puregit for. Verified end to end: a puregit-created
    signature is accepted by real `ssh-keygen -Y verify`, and a puregit-signed
    commit passes real `git verify-commit` (`Good "git" signature`).

  The **C ABI** (`ffi` feature) is done: a `libgit2`-shaped `extern "C"` surface
  (`git_repository_open`/`init`/`free`, `git_repository_head_id`,
  `git_reference_name_to_id`, `git_object_lookup`/`type`/`id`/`free`,
  `git_blob_rawcontent`/`rawsize`, `git_commit_message`/`tree_id`/`parentcount`/
  `parent_id`, thread-local `git_error_last`), panic-guarded at the boundary and
  built explicitly as a staticlib/cdylib (`cargo rustc --features ffi
  --crate-type staticlib`). The default `crate-type` stays `["lib"]` and the
  crate keeps `forbid(unsafe_code)` unless `ffi` is enabled, which confines the
  `unsafe` to that one audited module. Verified by linking a C program against
  the staticlib and reading a real-git repository through it.

---

## Completed ordering (dependency-aware)

All of the milestones below landed in roughly this dependency order:

1. **M1** delta compression on write — thin packs and smaller pushes; feeds
   M6/M7 pack building and M8 repack.
2. **M3** multi-round `have` negotiation + sideband-64k, then **protocol v2**
   (incl. `filter`, `ref-in-want`, `packfile-uris`) — sans-IO.
3. **M6/M7** thin packs + server-side sideband progress.
4. **M2** ref lockfiles + reflogs, `read-tree`/`reset`/`mv`, gitignore, and
   symlink/gitlink materialization.
5. **M4/M5** HTTP auth + dumb fallback; SSH ssh-agent + `known_hosts`.
6. **M8 maintenance** (`commit-graph`, MIDX, fsck, reachability bitmaps,
   transparent promisor fetch) and **M9 polish** (`object-format` negotiation,
   `show`/`cat-file --batch`, C ABI, SSH/PGP signing).

## Out of scope / caveats (under the no-C invariant)

- **libgit2/git C plug-ins, credential helpers as C libraries** — not
  applicable; credential callbacks are pure-Rust.
- **GSSAPI/Kerberos SSH auth** — no pure-Rust GSSAPI today (same caveat as
  `rsurl`/`puressh`); key, password, **and ssh-agent** auth work.
- **OpenPGP keyring management** — a deliberate non-goal; the caller supplies the
  key (see M9).

[`Vfs`]: src/vfs/mod.rs
[`ObjectDatabase`]: src/odb/mod.rs
[`transport::http`]: src/transport/http.rs
[`transport::ssh`]: src/transport/ssh.rs
[`server`]: src/server/mod.rs