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.

Drive the home rail from the events table

20442b5948ea72c5e84b6cf2ff922f3cc987ac13 · Keenan Nemetz · 10d ago · parent bca9ec24

Drive the home rail from the events table

The landing page still built every visible repository's whole feed in
memory to show the newest ten events across the instance. It now asks the
events table for one bounded page, with the repository names attached, so
the rail costs a page instead of the history.

The cards are separated from the feed:

- counts come from the index (issue and change totals);
- the newest event time per card is one grouped query (`LastEventTimes`)
  instead of deriving it from the feed that was built anyway;
- the release chip comes from the newest release event (`LastReleases`)
  instead of listing tags from git per card;
- the head commit and language still come from git, which is the
  remaining per-repository work on the page.

`visibleRepos` now builds handles with `Meta` rather than `Open`, which
removes a `symbolic-ref` per repository from the landing page and the
instance activity page. The all-git card builder stays as the fallback
for a Web without an index.

Load harness at concurrency 4 with three repositories: home goes from 178
to 243 req/s, p50 from 21 ms to 16 ms.

forge/index/events_test.go +32

@@ -304,3 +304,35 @@ func TestEventsOrderAcrossRepositories(t *testing.T) {
304 304 t.Fatalf("second page = %+v, %v", page, err)
305 305 }
306 306 }
307
308 func TestLastEventTimesIsPerRepository(t *testing.T) {
309 t.Parallel()
310 s, m, g := incrementalRepo(t)
311 other, og := newSecondRepo(t, s, "other")
312 now := time.Date(2026, 10, 30, 12, 0, 0, 0, time.UTC)
313 createIndexedIssue(t, g, "demo old", now, nil, nil)
314 createIndexedIssue(t, g, "demo new", now.Add(5*time.Minute), nil, nil)
315 iss, err := metadata.NewIssue("other", "body", "keenan", nil, nil, now.Add(time.Minute))
316 if err != nil {
317 t.Fatal(err)
318 }
319 if err := metadata.CreateIssue(og, iss, "keenan"); err != nil {
320 t.Fatal(err)
321 }
322 if err := s.Ensure(m); err != nil {
323 t.Fatal(err)
324 }
325 if err := s.Ensure(other); err != nil {
326 t.Fatal(err)
327 }
328 times, err := s.LastEventTimes([]string{m.ID, other.ID})
329 if err != nil {
330 t.Fatal(err)
331 }
332 if !times[m.ID].Equal(now.Add(5 * time.Minute)) {
333 t.Fatalf("demo last event = %s, want %s", times[m.ID], now.Add(5*time.Minute))
334 }
335 if !times[other.ID].Equal(now.Add(time.Minute)) {
336 t.Fatalf("other last event = %s, want %s", times[other.ID], now.Add(time.Minute))
337 }
338 }