From 2f2f1227ec42ad84f4f1379948b18d50b4e49f2d Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Mon, 6 Jul 2026 23:52:02 +0200 Subject: [PATCH] docs(technical): rewrite git module section for libgit2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.1 technical doc still described the gitoxide/gix plan with an undecided transport and a 32 KB git_task stack. Bring it in line with what spike 7 proved: - gix → libgit2 via the git2 crate (ADR-004 kill-switch fired: no gix push) - transport settled: HTTPS + PAT over esp-idf mbedTLS, no custom transport or reqwest/rustls layer needed - fail-closed cert verification (embedded GitHub roots + PASSTHROUGH) - git_task stack 32 KB → 96 KB (measured ~67 KB depth); dedicated thread - persistent-clone + fast-forward is the product flow (spike used a fresh per-boot throwaway branch); add --all stages deletions - PAT via libgit2 credential callback, never logged/persisted Also mark the Spike 7 entry done in the spikes list. --- docs/v0.1-mvp-technical.md | 50 +++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/docs/v0.1-mvp-technical.md b/docs/v0.1-mvp-technical.md index 9037556..ff2d79b 100644 --- a/docs/v0.1-mvp-technical.md +++ b/docs/v0.1-mvp-technical.md @@ -48,7 +48,7 @@ blocked by I/O. | `wifi_task` | 0 | 8 KB | On-demand station: off by default, brought up by `Ctrl-G` | | `ui_task` | 1 | 16 KB | Consume `KeyEvent`s, mutate editor state, enqueue render | | `render_task` | 1 | 12 KB | Drain render queue, do partial/full refresh on EPD | -| `git_task` | 1 | 32 KB | Triggered by `Ctrl-G`; runs gitoxide commit + push | +| `git_task` | 1 | 96 KB | Triggered by `Ctrl-G`; runs libgit2 commit + push | All inter-task communication is via `crossbeam-channel` or `std::sync::mpsc` bounded queues. The editor state is `Arc>`; the lock is @@ -97,8 +97,10 @@ spike 4 is the gate for refresh per character, measure end-to-end latency. 6. **Spike 6 — Wi-Fi + TLS.** Connect to home Wi-Fi, do an HTTPS GET to `api.github.com`, validate cert chain. -7. **Spike 7 — gitoxide push.** Smoke test: from desktop-Rust first, then on - device, push a single commit to a test repo over HTTPS+PAT. +7. **Spike 7 — git push.** Smoke test: from desktop-Rust first, then on + device, push a single commit to a test repo over HTTPS+PAT. **Done** — the + gitoxide fall-back to **libgit2** (`git2`) fired here; push is proven on + hardware ([postmortem](postmortems/2026-07-05-spike7-gix-https-push.md)). Only after spike 7 do we start integration. Any spike that fails forces a stack decision (e.g. fall back to libgit2; switch to a separate SD SPI bus). @@ -196,26 +198,46 @@ to v0.9. v0.1 reads all config from the binary at build time — see ### `git` — commit + push -`gitoxide` choice and kill-switch: -[ADR-004](adr.md#adr-004-git-implementation--gitoxide-gix). -Auth model: [ADR-005](adr.md#adr-005-auth--https--github-personal-access-token). +Library + kill-switch: +[ADR-004](adr.md#adr-004-git-implementation--gitoxide-gix) — the gitoxide bet was +**retired in spike 7** (gix has no HTTP(S) push at all), so the module is built on +**libgit2 via the `git2` crate**. +Auth + transport are settled: **HTTPS + PAT over esp-idf's mbedTLS** +([ADR-005](adr.md#adr-005-auth--https--github-personal-access-token)) — no custom +transport and no `reqwest`/`rustls` layer are needed; libgit2's own smart-HTTP +client speaks TLS through the mbedTLS stream it is compiled against. The full +`init → commit → push` path is **proven on hardware** — see the +[spike 7 postmortem](postmortems/2026-07-05-spike7-gix-https-push.md). PSRAM heap during push is a top-3 watched metric — see [qfd.md §6](qfd.md#6-critical-performance-budget). -- `gix` with the smart-HTTP transport backed by `esp-idf` mbedtls (via a - custom transport impl, or `gix-transport` with `reqwest`+`rustls-mbedtls` - if that path is cleaner — decided in spike 7). +- libgit2 **1.9.4** is vendored as an esp-idf component + (`firmware/components/libgit2`) built with `GIT_HTTPS + GIT_MBEDTLS`; `git2` + binds it in system mode. A short `esp_stubs.c` supplies the POSIX calls + picolibc/FATFS lack (`utimes` existence-gate, `p_rename` replace, uid/symlink). +- The server cert chain is verified against an embedded GitHub root bundle + (`GIT_OPT_SET_SSL_CERT_LOCATIONS`); the `certificate_check` callback returns + PASSTHROUGH so libgit2's own verdict stands → **fail-closed** on an untrusted / + MITM cert. A product should refresh those roots (or reuse esp-idf's bundle) at + provisioning. +- Runs on a **dedicated ~96 KB thread**, not the shared UI/main stack: libgit2's + init→push chain nests ~10 `GIT_PATH_MAX` (4 KB) buffers (~67 KB measured). - Operations needed in v0.1 (the [`gct` shell function](../CONTEXT.md#user-facing-actions) is the reference): - - `gix::open` the existing working copy at `/sd/repo` - - stage everything under the working copy (`git add .` equivalent) + - open a **persistent clone** at `/sd/repo` (spike 7 proved the flow on a + fresh per-boot init pushing a throwaway `device/` branch, to isolate + the transport from history reconciliation; the product keeps one clone and + fast-forwards — storage is [ADR-007](adr.md), still SD-vs-flash-FAT while SD + is blocked) + - stage everything under the working copy (`git add --all`, incl. deletions) - short-circuit return if nothing is staged — status: "nothing to publish" - commit with author from config, message `""` (no `wip` prefix; the timestamp _is_ the message) - - push HEAD to `origin/` + - push HEAD to `origin/` (normally a clean fast-forward) - on push failure: `git pull --no-edit` (merge), then retry the push once. Only surface failure to the side panel's publish-state field if the pull conflicts or the second push also fails. -- The PAT is loaded into the Authorization header per request; never logged. +- The PAT is handed to libgit2's credential callback (`USER_PASS_PLAINTEXT`); + never logged, never persisted to the working copy. - The whole sequence is atomic from the user's view — see [`CONTEXT.md` → Publish](../CONTEXT.md#user-facing-actions). @@ -254,7 +276,7 @@ ESP32-S3-N16R8: 512 KB SRAM + 8 MB PSRAM. We budget conservatively. | Internal SRAM | ~80 KB | mbedtls runtime (TLS handshake working set) | | PSRAM | ~128 KB | EPD framebuffer (792×272×1 ≈ 27 KB) + shadow + glyph cache | | PSRAM | ~512 KB | rope buffer + edit history headroom | -| PSRAM | ~1.5 MB | gitoxide working set during push (pack delta, etc.) | +| PSRAM | ~1.5 MB | libgit2 working set during push (pack delta, etc.) | | PSRAM | rest | heap headroom | PSRAM is the default for `Box::new` via a custom allocator wrapper; DMA-able