| | @@ -107,3 +107,75 @@ What survives unchanged is the part that mattered: several narrow ports rather t |
| 107 | 107 | git service, and one infrastructure-side invoker owning how git is actually run. |
| 108 | 108 | Authorization still happens in the use case, before the subprocess exists — the CGI |
| 109 | 109 | shape moves *how* git is called, not *who decides* it may be. |
| 110 | + |
| 111 | +## Amendment — 2026-08-29, the Milestone 5 read path |
| 112 | + |
| 113 | +0006 deferred one question to this point: whether `gix` should take over the read path, |
| 114 | +"where browsing is read-only, hot, and the place `gix` is strongest". Answered here, with |
| 115 | +the measurement it asked for. |
| 116 | + |
| 117 | +**The read path stays on the `git` binary, one process per query, for now.** |
| 118 | + |
| 119 | +### What was measured |
| 120 | + |
| 121 | +On a 201-commit repository, 50 runs averaged per command: `rev-parse` 11.2ms, `ls-tree` |
| 122 | +11.7ms, `cat-file` 11.6ms, `log -20` 11.8ms, `for-each-ref` 14.4ms. **The cost is |
| 123 | +starting the process, not answering the question** — every command lands in the same |
| 124 | +band regardless of the work it does, matching the ~13ms `git init --bare` from Milestone |
| 125 | +3. A three-call tree page is therefore ~35ms of pure overhead. |
| 126 | + |
| 127 | +### Why not `gix` |
| 128 | + |
| 129 | +Checked against gitoxide's own crate status rather than recalled. The read path is |
| 130 | +comprehensively covered — `gix-ref`, `gix-odb`, `gix-pack`, `gix-object`, `gix-traverse` |
| 131 | +and `gix-revwalk` all do what browsing needs. Capability is not the obstacle. |
| 132 | + |
| 133 | +**Server-side protocol support is.** `gix-protocol` offers `upload-pack` / |
| 134 | +`receive-pack` plumbing *for in-process transports* only — not a network server. Since |
| 135 | +[0001](0001-git-over-http-not-ssh.md) commits to `git http-backend`, the binary is a |
| 136 | +permanent runtime dependency, so adopting `gix` would not replace an implementation. It |
| 137 | +would **add** one: two libraries reading the same bytes on disk, forever, needing to |
| 138 | +agree. That is the cost this ADR priced originally, now confirmed rather than assumed — |
| 139 | +and the price of avoiding it is 35ms a page for one user. |
| 140 | + |
| 141 | +### Why not fork gitoxide and write the server side |
| 142 | + |
| 143 | +Considered and rejected. A fork only pays off if it lets Steid delete the binary, and |
| 144 | +that requires server-side `receive-pack`: pack verification and fsck, quarantine, atomic |
| 145 | +ref updates, hooks, report-status — executed against untrusted packs from the internet, |
| 146 | +a surface with a real CVE history. Months of work, no user-visible capability, and |
| 147 | +[ROADMAP.md](../ROADMAP.md#why-this-order) names disappearing into protocol work as what |
| 148 | +ended both previous attempts. If the work is wanted for its own sake it belongs upstream |
| 149 | +in gitoxide, whose stated goal already includes server support, on its own timeline — |
| 150 | +not on Steid's critical path. |
| 151 | + |
| 152 | +### The upgrade path, which is the point |
| 153 | + |
| 154 | +Staged, and each stage keeps `GitQuery` unchanged: |
| 155 | + |
| 156 | +1. **One-shot commands.** ~35ms a page. Where Milestone 5 starts. |
| 157 | +2. **A `cat-file --batch` process held open per repository.** Deletes the fork cost for |
| 158 | + object reads; what Gitaly and Gitea both do. Projected at ~1–3ms a page — projected, |
| 159 | + not measured, and to be benchmarked before it is claimed. |
| 160 | +3. **`gix-object` decoding bytes fetched by stage 2**, if tree and commit parsing ever |
| 161 | + need to happen in-process. Worth distinguishing from adopting `gix`: a decoder for a |
| 162 | + documented format is not a second implementation of repository *access* — no ref |
| 163 | + resolution, no pack access, no protocol. Full `gix` and the fork stay rejected; |
| 164 | + neither is on this ladder. |
| 165 | + |
| 166 | +**One thing must be got right now for stage 2 to stay cheap.** A batch adapter owns live |
| 167 | +subprocesses, so it has to be a shared handle in app context rather than constructed per |
| 168 | +request as the SQLite adapters are. Built per request, the pool would spawn and kill a |
| 169 | +process per request — the fork cost back, plus a pool that never pools. The port gives us |
| 170 | +the seam; it does not give us the lifetime, and the lifetime is a composition-root |
| 171 | +decision that has to be made before the first adapter is wired. |
| 172 | + |
| 173 | +### Reopening conditions |
| 174 | + |
| 175 | +Evidence, not intuition: |
| 176 | + |
| 177 | +- A per-file last-commit column becomes a must-have — ~230ms for a twenty-file directory |
| 178 | + at one fork per entry. Go to stage 2 first, not to `gix`. |
| 179 | +- Browsing takes real public traffic, making the read path hot rather than occasional. |
| 180 | +- gitoxide ships usable server-side `upload-pack` and `receive-pack`, at which point it |
| 181 | + stops being additive and becomes a candidate to replace the binary outright. |