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.
Stop repeating the index check and the history summaries
73ec800beafe53eea186fbc1cebec3306cc49da0 · Keenan Nemetz · 10d ago · parent 5d440e37
Stop repeating the index check and the history summaries The request memo covered the repository, the open counts and the change list, but two more reads were repeated: - The index refresh (issue rows, filters, `#42` numbers) ran on every surface that shows one, and each run started by resolving the metadata ref. A page calls it from the header, the number lookup and its own view, so it now happens once per request, and the resolved commit is shared with the change-request read instead of being resolved twice. - The overview walked the repository's whole history on every load: `shortlog` for contributors, `rev-list --count` for the code page's commit count, and the tree grouping for languages. Those are properties of a commit, and a commit id is content-addressed, so they cannot go stale: they are remembered by commit id in a bounded per-process cache that evicts oldest-first. On the fixture (30 change requests, 30 issues) the overview drops from 19 git calls to 9 on a repeated load, the code page from 12 to 11, and every page loses one `rev-parse` for the metadata ref.
server/web/cache_test.go +47
| @@ -0,0 +1,47 @@ | |||
| 1 | package web | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | "testing" | ||
| 6 | ) | ||
| 7 | |||
| 8 | // TestCommitCacheRemembersAndEvicts covers the bound that keeps a repository | ||
| 9 | // page's history summaries from growing without limit as people browse older | ||
| 10 | // commits. | ||
| 11 | func TestCommitCacheRemembersAndEvicts(t *testing.T) { | ||
| 12 | var c commitCache[int] | ||
| 13 | if _, ok := c.get("missing"); ok { | ||
| 14 | t.Fatal("empty cache returned a value") | ||
| 15 | } | ||
| 16 | c.put("a", 1) | ||
| 17 | if v, ok := c.get("a"); !ok || v != 1 { | ||
| 18 | t.Fatalf("get(a) = %d, %t; want 1, true", v, ok) | ||
| 19 | } | ||
| 20 | // A repeated put is not a new entry, so it cannot evict a real one. | ||
| 21 | c.put("a", 2) | ||
| 22 | if v, _ := c.get("a"); v != 1 { | ||
| 23 | t.Fatalf("re-put changed the value to %d", v) | ||
| 24 | } | ||
| 25 | |||
| 26 | for i := 0; i < commitCacheEntries; i++ { | ||
| 27 | c.put(fmt.Sprintf("k%d", i), i) | ||
| 28 | } | ||
| 29 | if _, ok := c.get("a"); ok { | ||
| 30 | t.Fatal("the oldest entry survived a full cache") | ||
| 31 | } | ||
| 32 | if got := len(c.order); got != commitCacheEntries { | ||
| 33 | t.Fatalf("order holds %d keys, want %d", got, commitCacheEntries) | ||
| 34 | } | ||
| 35 | if v, ok := c.get(fmt.Sprintf("k%d", commitCacheEntries-1)); !ok || v != commitCacheEntries-1 { | ||
| 36 | t.Fatalf("newest entry missing: %d, %t", v, ok) | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | // TestCommitKeySeparatesRepositories pins the key: two repositories can hold | ||
| 41 | // the same commit id, and one's summary must not answer for the other's. | ||
| 42 | func TestCommitKeySeparatesRepositories(t *testing.T) { | ||
| 43 | const sha = "0123456789abcdef0123456789abcdef01234567" | ||
| 44 | if commitKey("/repos/a.git", sha) == commitKey("/repos/b.git", sha) { | ||
| 45 | t.Fatal("the same commit id in two repositories shares a key") | ||
| 46 | } | ||
| 47 | } | ||