vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Bound and stream the anonymous read path
4581690d1ae9d87c272428d101a0fbe3538d7f44 · Keenan Nemetz · 13d ago · parent e05f1c48
Bound and stream the anonymous read path Every anonymous read path read a whole git object before deciding what to do with it: the code browser truncated after allocating, the overview rendered the README it had just read in full, and the site pool allocated the entire object for every request. `max_blob_bytes` allows a 100 MiB blob, so one pushed file made every concurrent request for it a 100 MiB allocation, with no account needed to ask for it. Read the header first instead. `core/git` gains `BlobSize`, `BlobSizes` (one `cat-file --batch-check` for a whole tree) and `BlobStream`, which parses the batch header before touching the payload and stops the process when a caller wants only a prefix. Then use it: `/raw` streams with the object's own `Content-Length`, the inline viewer reads a one-MiB prefix and reports the file's real size, the README is capped and named instead of rendered when it is too large, site blobs are capped at 25 MiB with a 413, and the commit and tree objects read to resolve a site path are capped separately. Metadata gets the same treatment on both sides: 4 MiB per file, 2 MiB per body, 20,000 files and 64 MiB for `issues/` and `requests/` together. The loader asks git for every size in one call and refuses the tree before reading it; the push boundary refuses the same tree, so the author learns why instead of finding that their issue list stopped loading. Counting is now per subtree, so the machine written `runs/` tree no longer eats the issue file budget.
metadata/requests.go +10−19
| @@ -134,21 +134,10 @@ func LoadRequests(g *git.Repo, ref string) ([]*Request, error) { | |||
| 134 | 134 | if commit == "" { | |
| 135 | 135 | return nil, nil | |
| 136 | 136 | } | |
| 137 | blobs, err := g.LsTreeR(commit) | ||
| 137 | contents, err := readMetadata(g, commit, RequestsDir) | ||
| 138 | 138 | if err != nil { | |
| 139 | 139 | return nil, err | |
| 140 | 140 | } | |
| 141 | contents := map[string]string{} | ||
| 142 | for _, e := range blobs { | ||
| 143 | if !strings.HasPrefix(e.Name, RequestsDir+"/") { | ||
| 144 | continue | ||
| 145 | } | ||
| 146 | data, err := g.BlobBySHA(e.SHA) | ||
| 147 | if err != nil { | ||
| 148 | return nil, err | ||
| 149 | } | ||
| 150 | contents[e.Name] = string(data) | ||
| 151 | } | ||
| 152 | 141 | return ParseRequests(contents) | |
| 153 | 142 | } | |
| 154 | 143 | ||
| @@ -336,6 +325,7 @@ func applyRequest(req *Request, raw, path string, problems *[]string) { | |||
| 336 | 325 | "%s: merged_base, merged_head, merged_by and merged_at only belong on a merged request (state is %q)", path, req.State)) | |
| 337 | 326 | } | |
| 338 | 327 | req.Description = body | |
| 328 | *problems = append(*problems, bodyProblems(path, body)...) | ||
| 339 | 329 | } | |
| 340 | 330 | ||
| 341 | 331 | // validBranchName is the file-format check for base and head_ref: one line, no | |
| @@ -439,16 +429,17 @@ func ValidateRequests(g *git.Repo, commit string) []string { | |||
| 439 | 429 | if err != nil { | |
| 440 | 430 | return []string{fmt.Sprintf("cannot read the requests tree: %v", err)} | |
| 441 | 431 | } | |
| 432 | paths, shas := metadataPaths(blobs, RequestsDir) | ||
| 433 | if problems := metadataSizeProblems(paths, shas, g); len(problems) > 0 { | ||
| 434 | return problems | ||
| 435 | } | ||
| 442 | 436 | contents := map[string]string{} | |
| 443 | for _, e := range blobs { | ||
| 444 | if !strings.HasPrefix(e.Name, RequestsDir+"/") { | ||
| 445 | continue | ||
| 446 | } | ||
| 447 | data, err := g.BlobBySHA(e.SHA) | ||
| 437 | for i, path := range paths { | ||
| 438 | data, err := g.BlobBySHA(shas[i]) | ||
| 448 | 439 | if err != nil { | |
| 449 | return []string{fmt.Sprintf("cannot read %s: %v", e.Name, err)} | ||
| 440 | return []string{fmt.Sprintf("cannot read %s: %v", path, err)} | ||
| 450 | 441 | } | |
| 451 | contents[e.Name] = string(data) | ||
| 442 | contents[path] = string(data) | ||
| 452 | 443 | } | |
| 453 | 444 | if _, err := ParseRequests(contents); err != nil { | |
| 454 | 445 | if ve, ok := err.(*ValidationError); ok { | |