vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0: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/issue_write.go +7
| @@ -41,6 +41,9 @@ func NewIssueComment(body, author string, now time.Time) (*Comment, error) { | |||
| 41 | 41 | if err != nil { | |
| 42 | 42 | return nil, err | |
| 43 | 43 | } | |
| 44 | if problems := bodyProblems("comment", body); len(problems) > 0 { | ||
| 45 | return nil, fmt.Errorf("issues: %s", problems[0]) | ||
| 46 | } | ||
| 44 | 47 | return &Comment{ID: id, Author: strings.TrimSpace(author), Created: now.UTC(), Body: body}, nil | |
| 45 | 48 | } | |
| 46 | 49 | ||
| @@ -188,6 +191,9 @@ func EditIssueComment(g *git.Repo, issueID, commentID, body, actor string) (*Com | |||
| 188 | 191 | if strings.TrimSpace(body) == "" { | |
| 189 | 192 | return nil, fmt.Errorf("issues: a comment needs a body") | |
| 190 | 193 | } | |
| 194 | if problems := bodyProblems("comment", body); len(problems) > 0 { | ||
| 195 | return nil, fmt.Errorf("issues: %s", problems[0]) | ||
| 196 | } | ||
| 191 | 197 | c.Body = body | |
| 192 | 198 | raw, err := RenderComment(c) | |
| 193 | 199 | if err != nil { | |
| @@ -309,6 +315,7 @@ func validateIssueForWrite(iss *Issue) error { | |||
| 309 | 315 | if len(iss.Title) > maxIssueTitleLen { | |
| 310 | 316 | problems = append(problems, fmt.Sprintf("title is %d bytes, limit %d", len(iss.Title), maxIssueTitleLen)) | |
| 311 | 317 | } | |
| 318 | problems = append(problems, bodyProblems("body", iss.Description)...) | ||
| 312 | 319 | if iss.State != "open" && iss.State != "closed" { | |
| 313 | 320 | problems = append(problems, fmt.Sprintf("state must be open or closed, got %q", iss.State)) | |
| 314 | 321 | } | |