Skip to content

Commit

The git guide says g1t asks for each push whole, what a thin one costs and how thin;desc= shows it, and its stray repos row is back in the steps table; the Artifacts and performance notes mark step 1 of the push speed-up built, with what read should fall to and why the 437-object push answered 503: every base asked for at once, failures retried and counted against the store's breaker until every read after them was turned away as busy.

syntaqxcommitted Parente9b14b1Browse files
4 files+60−160/4 viewed
+14−6
293293 | `upload` | Only for a push: handing it to the git store and its answer |
294294 | `refs` | Only for a push: recording that the repository's refs changed |
295295 | `total` | Everything g1t did |
296+| `repos` | The same, measured where your request arrived |
296297
297298 A push's checks run side by side, so each also has its own entry, after
298−the steps and not counted in the total: `read` (reading the push's objects
299−and fetching what they build on from the repository), `rules`, `scan`
300−(secrets and email addresses) and, for an access token, `gate` (workflow
301−files).
302−| `repos` | The same, measured where your request arrived |
299+the steps and not counted in the total: `read` (reading the push's
300+objects), `rules`, `scan` (secrets and email addresses) and, for an access
301+token, `gate` (workflow files).
302+
303+g1t asks git to send each push whole: every object a delta in it builds on
304+is in the push too (the `no-thin` capability), so checking a push reads
305+nothing from the repository. A push that changes a large file a little
306+uploads more than it would otherwise, and stays within the
307+[size limits](#size-limits). If your git sends a push that builds on
308+objects outside it anyway, g1t reads up to 200 of those from the
309+repository to check it, and says `thin;desc=yes`.
303310
304−Two entries say how a step went rather than how long it took:
311+Some entries say how a step went rather than how long it took:
305312
306313 | Entry | Values |
307314 | --- | --- |
308315 | `refs;desc=` | `hit-colo` or `hit-shared` when the ref listing came from g1t's cache, `miss` when the git store was asked |
309316 | `pack;desc=` | Only for a fresh clone: `hit` when its pack came from g1t's cache, `miss` when the git store built it |
310317 | `cred;desc=` | `isolate` or `shared` for a store credential made a moment ago, `mint` for a new one |
318+| `thin;desc=` | Only for a push: `no` when it came whole, `yes` when it built on objects outside it |
311319
312320 The ref listing git asks for first on every clone and fetch is kept for up
313321 to a minute, and only the same question about the same refs gets the same
+28−7
169169 | Commit one file | `commit_file.rs` | mint, `log` | `info/refs` 1, `receive-pack` 1 |
170170 | Mirror sync or import | `mirror.rs`, `import.rs` | mint | `info/refs` 1–2, `upload-pack` and `receive-pack` 1 each; packs capped at 40 MB |
171171 | Search indexing | `listing.rs` | trees by level, blobs in groups | 0 |
172−| Push protection | `secret_scan.rs` | trees and blobs for bases, up to 24 MB scanned | 0 |
172+| Push protection | `secret_scan.rs` | none for bases (`no-thin`); for a client that sends a thin pack anyway, up to 200 bases, 16 at a time; up to 24 MB scanned | 0 |
173173 | Delete (purge) | `lifecycle.rs` | `delete` per key, including forks | 0 |
174174
175175 Every sandbox job clones in full (`crates/runner/src/{main,checks,review,plan,queue,reply,update,deploy,mergecheck}.rs`:
238238
239239 **Next step, in this order:**
240240
241−1. **Ask for packs without outside bases.** Add `no-thin` to the receive-pack advertisement g1t
242− forwards, so git sends every delta's base in the pack (git's `send-pack` turns thin packs off
243− when it sees it). `read` goes to 0 for every client that honours it, and big pushes no longer
244− make hundreds of reads. The cost is a larger upload when a push changes a big file a little;
245− pushes stay capped by `MAX_SCANNED_PUSH`. `supply_bases` stays for clients that send thin packs
246− anyway.
241+1. **Ask for packs without outside bases. Built, not yet deployed.** `git_http.rs` `with_no_thin`
242+ adds `no-thin` to the receive-pack advertisement g1t forwards (the first ref line's capabilities,
243+ or the `capabilities^{}` line of an empty repository; v0 and v1; never upload-pack; an answer it
244+ cannot read goes through as it came), so git sends every delta's base in the pack (git's
245+ `send-pack` turns thin packs off when it sees it). `read` goes to 0 for every client that
246+ honours it, and big pushes no longer make hundreds of reads. The cost is a larger upload when a
247+ push changes a big file a little; pushes stay capped by `MAX_SCANNED_PUSH`.
248+ `services/repos/dev/push-check.mjs` shows it with git 2.45 against a local store: through g1t,
249+ `pack-objects` runs without `--thin`; straight to the store, with it.
250+ `supply_bases` stays for clients that send thin packs anyway, now bounded: 200 bases in all
251+ (was 500 a round, three rounds), 16 at a time (was all at once), and it stops when the store
252+ says it is busy. A push that arrives thin says `thin;desc=yes` in `Server-Timing` and logs the
253+ client's user agent with how many bases it lacked, so clients ignoring `no-thin` show up in the
254+ tail.
255+
256+ **Why the 437-object push answered 503**, as the code explains it (that push's logs were not
257+ read, so which reads failed, and how, is not known). Its bases were all asked for at once: up to 500 a
258+ round, two binding reads each when the pack's trees do not name the base (old versions of
259+ changed files never are), each read with a Cache API look before it, for up to three rounds.
260+ Hundreds of reads in flight on one handle, and the reads that failed (dropped or rate limited)
261+ were each tried three times with backoff and then counted against the namespace's breaker
262+ (`store.rs` `invoke`, `resilience.rs`: five failures in a row open it for 10 s, for the whole
263+ isolate). `supply_bases` swallowed its own failures, but once the breaker was open every store
264+ read after them, the scan's parent trees and blobs and the rules' reads, was turned away as
265+ busy, and `lib.rs` answers busy with 503 and `Retry-After`. The retries and backoff are where
266+ the 23 s most likely went. Subrequests (10,000 per invocation) and memory (24 MB pack, small
267+ bases) would not have been the limit at that size; concurrency against the store would.
247268 2. **Upload while checking.** Stream the pack to Artifacts while the checks run, holding back the
248269 last 20 bytes (the checksum) until they pass, so a declined push is never stored. A push then
249270 takes about the larger of `checks` and `upload`, not their sum. Whether Artifacts waits for a
+5−3
147147 Anything holding a write token can push past every policy.
148148 - **What we built.** `services/repos/src/git_http.rs` `forward` reads the whole push body
149149 (`request.bytes()`), checks protected branches (`refusal`), then `secret_scan.rs` `scan_push` parses
150− the pack in WebAssembly, resolves delta bases by reading objects back through the binding
151− (`supply_bases`, up to 500 bases), walks trees and scans up to 24 MB (`MAX_SCANNED_PUSH`) before the
150+ the pack in WebAssembly, walks trees and scans up to 24 MB (`MAX_SCANNED_PUSH`) before the
152151 body is copied again and forwarded. Pushes larger than that are let through unscanned, and we say so in
153− the logs. Write tokens handed to sandboxes (`run_access.rs`) bypass all of it, so we keep their life
152+ the logs. To check a pack without reading its delta bases back through the binding, g1t adds
153+ `no-thin` to the receive-pack advertisement it forwards, so git sends every base in the pack; a
154+ client that sends a thin pack anyway has up to 200 bases read for it (`supply_bases`). Write tokens
155+ handed to sandboxes (`run_access.rs`) bypass all of it, so we keep their life
154156 minimal.
155157 - **What it costs.** A second implementation of git's pack format in our code, memory pressure on every
156158 push, and a policy that only holds for pushes that come through our proxy.
+13−0
489489 2026-10-09 answered 503 twice and took 23 s the third time. The next step
490490 is to stop needing bases: see "Pushes" in docs/ARTIFACTS.md.
491491
492+Step 1 of that, built and not yet deployed: the receive-pack advertisement
493+g1t forwards says `no-thin` (`git_http.rs` `with_no_thin`), so git sends
494+every delta's base in the pack and `read` has nothing to fetch: it should
495+fall from 267–318 ms to the few milliseconds it takes to parse the pack,
496+for every push from a client that honours it (git does; see
497+`services/repos/dev/push-check.mjs`), and stop growing with the push. A
498+push that changes a large file a little uploads more, so `upload` may grow
499+a little for those. A push that arrives thin anyway says `thin;desc=yes` in
500+`Server-Timing`, and is logged with its user agent; its bases are read 16
501+at a time, at most 200, and not at all once the store says it is busy
502+(the 503 above came from all of them at once tripping the store's
503+breaker; see docs/ARTIFACTS.md).
504+
492505 ## Client navigation
493506
494507 - `<Link prefetch="intent">` on the sidebar, project tabs, breadcrumbs,