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/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.

Read the metadata tree in one git call

5cecc6183eadab8147d11438abc64d54c4b0a78f · Keenan Nemetz · 10d ago · parent 5fce878d

Read the metadata tree in one git call

The issue and change-request loaders read the metadata tree with one
`git cat-file` per file, so a page render cost as many processes as the
branch had issues and comments. On a repository with 30 change requests
the overview paid 114 git invocations and 98 ms; the requests list paid
72 and 90 ms.

Read every listed blob through a single `cat-file --batch`. The push
validators share the same helper, so pre-receive stops paying one
process per file as well.

The metadata branch is the whole source of truth for issues and changes,
so this is the read path that grows with use. Nothing about the format or
the size limits changes: an oversized file is still refused before any of
it is allocated.

metadata/limits_test.go +55

@@ -128,3 +128,58 @@ func TestParseRefusesOversizedBody(t *testing.T) {
128 128 t.Fatal("ParseComment accepted a body past MaxBodyBytes")
129 129 }
130 130 }
131
132 // TestLoadRequestsReadsEveryFile guards the batched read of the metadata tree:
133 // many requests and comments come back complete, and two files with identical
134 // bytes (one object in the pack, two paths in the tree) each get their content.
135 // The list page reads this tree on every render, so the loader is also the place
136 // where a per-file git process would show up as a page that costs as much as the
137 // repository has issues.
138 func TestLoadRequestsReadsEveryFile(t *testing.T) {
139 t.Parallel()
140 g := limitsRepo(t)
141
142 const n = 12
143 // One character distinguishes the ids, which keeps every id a well-formed
144 // ULID of the same length.
145 const ulidPrefix = "01M5JR2JK29KKMAA1T9MDNTRB"
146 id := func(i int) string {
147 return ulidPrefix + string("0123456789ABCDEFGHJKMNPQRSTVWXYZ"[i])
148 }
149 files := map[string][]byte{}
150 for i := 0; i < n; i++ {
151 // Odd and even requests share a body, so their request.md files are the
152 // same object under different paths.
153 body := requestBody(headSHA)
154 if i%2 == 1 {
155 body = requestBody(sideSHA)
156 }
157 files[RequestsDir+"/"+id(i)+"/request.md"] = []byte(body)
158 files[RequestsDir+"/"+id(i)+"/comments/"+id(i+n)+".md"] = []byte(
159 "---\nauthor: keenan\ncreated: 2026-10-22T22:00:00Z\n---\n\ncomment " + string('a'+rune(i)) + "\n")
160 }
161 if _, err := git.CommitFiles(g, Ref, func(f map[string][]byte) error {
162 for path, body := range files {
163 f[path] = body
164 }
165 return nil
166 }, "requests: batch", "keenan"); err != nil {
167 t.Fatal(err)
168 }
169
170 list, err := LoadRequests(g, Ref)
171 if err != nil {
172 t.Fatalf("LoadRequests: %v", err)
173 }
174 if len(list) != n {
175 t.Fatalf("got %d requests, want %d", len(list), n)
176 }
177 for _, req := range list {
178 if len(req.Comments) != 1 {
179 t.Fatalf("%s has %d comments, want 1", req.ID, len(req.Comments))
180 }
181 if req.Title != "Land the retry" {
182 t.Fatalf("%s title = %q", req.ID, req.Title)
183 }
184 }
185 }