Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

server/web/releases.go +6−3

@@ -6,6 +6,7 @@ package web
6 6 // with the repository, and no stored tarball that can disagree with the tag.
7 7
8 8 import (
9 "bytes"
9 10 "fmt"
10 11 "html/template"
11 12 "mime"
@@ -261,13 +262,15 @@ func (w *Web) releaseArchive(rw http.ResponseWriter, r *http.Request, format str
261 262 // usesLFS reports whether the tree tracks anything through Git LFS. It reads the
262 263 // root .gitattributes, which is where `git lfs track` writes its patterns; a
263 264 // pattern declared in a subdirectory is not detected, and the notice is worded so
264 // that the omission cannot make it false.
265 // that the omission cannot make it false. Only the head of the file is read: a
266 // pattern lives on one line, and the file's length is not evidence of anything.
265 267 func usesLFS(g *git.Repo, commit string) bool {
266 data, err := g.BlobAt(commit, ".gitattributes")
268 const maxGitattributesBytes = 64 << 10
269 data, _, err := g.BlobPrefixAt(commit, ".gitattributes", maxGitattributesBytes)
267 270 if err != nil {
268 271 return false
269 272 }
270 return strings.Contains(string(data), "filter=lfs")
273 return bytes.Contains(data, []byte("filter=lfs"))
271 274 }
272 275
273 276 // releaseSafe reduces a repository or tag name to characters that are safe in a