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.
Read what a page needs once per request
5d440e37f16d06790ce87e2f8b7dcc7d9bf0532c · Keenan Nemetz · 10d ago · parent 34cbc99f
Read what a page needs once per request A page and its header are built from the same facts, and each of them was read separately: the repository (twice, each resolving HEAD's branch with a git call), the open change requests (once for the header's count, once for the page), and a change request looked up by reading every request. On a repository with 30 change requests the overview paid 21 git invocations after the metadata batching, and the requests list still paid 8. Give each read request one memo, installed by the middleware, and have the handlers and the header read through it. It lasts one request and covers reads only: a request that writes metadata gets no memo, so there is no TTL, no cross-request cache, and no invalidation protocol, and a push is still visible on the next page load. The header now takes the page it is rendering (`nav .Page "issues"`) instead of six fields the view already carries, which is also why the memo can reach it at all. Templates are unchanged in what they output.
server/web/home.go +9−9
| @@ -97,7 +97,7 @@ func (w *Web) IndexPage(rw http.ResponseWriter, r *http.Request) { | |||
| 97 | 97 | } | |
| 98 | 98 | ||
| 99 | 99 | var events []activityEvent | |
| 100 | data.Cards, events = w.repoCards(entries) | ||
| 100 | data.Cards, events = w.repoCards(memoOf(r), entries) | ||
| 101 | 101 | if query != "" { | |
| 102 | 102 | sort.SliceStable(data.Cards, func(i, j int) bool { return data.Cards[i].Name < data.Cards[j].Name }) | |
| 103 | 103 | } else { | |
| @@ -121,7 +121,7 @@ func (w *Web) InstanceActivity(rw http.ResponseWriter, r *http.Request) { | |||
| 121 | 121 | return | |
| 122 | 122 | } | |
| 123 | 123 | number, _ := strconv.Atoi(r.URL.Query().Get("page")) | |
| 124 | events, pager := paginate(w.instanceEvents(entries, kind), kind, number, instanceEventPage) | ||
| 124 | events, pager := paginate(w.instanceEvents(memoOf(r), entries, kind), kind, number, instanceEventPage) | ||
| 125 | 125 | w.render(rw, "instance_activity", instanceActivityData{ | |
| 126 | 126 | Page: w.page(r, "Activity"), Kind: kind, Events: events, Pager: pager, | |
| 127 | 127 | }, 0) | |
| @@ -138,7 +138,7 @@ func (w *Web) visibleRepos(r *http.Request, query string) ([]index.Entry, error) | |||
| 138 | 138 | needle := strings.ToLower(strings.TrimSpace(query)) | |
| 139 | 139 | var visible []index.Entry | |
| 140 | 140 | for _, e := range entries { | |
| 141 | m, err := w.Repos.Open(e.Name) | ||
| 141 | m, err := w.repo(memoOf(r), e.Name) | ||
| 142 | 142 | if err != nil { | |
| 143 | 143 | continue | |
| 144 | 144 | } | |
| @@ -158,15 +158,15 @@ func (w *Web) visibleRepos(r *http.Request, query string) ([]index.Entry, error) | |||
| 158 | 158 | // repoCards reads each repository once and returns its card plus its events with | |
| 159 | 159 | // the repository named on them. It replaces the separate card and feed passes so | |
| 160 | 160 | // the landing page does not walk each repository twice. | |
| 161 | func (w *Web) repoCards(entries []index.Entry) ([]repoCard, []activityEvent) { | ||
| 161 | func (w *Web) repoCards(memo *requestMemo, entries []index.Entry) ([]repoCard, []activityEvent) { | ||
| 162 | 162 | cards := make([]repoCard, 0, len(entries)) | |
| 163 | 163 | var events []activityEvent | |
| 164 | 164 | for _, e := range entries { | |
| 165 | m, err := w.Repos.Open(e.Name) | ||
| 165 | m, err := w.repo(memo, e.Name) | ||
| 166 | 166 | if err != nil { | |
| 167 | 167 | continue | |
| 168 | 168 | } | |
| 169 | feed := w.repoActivity(m, "", "", "") | ||
| 169 | feed := w.repoActivity(memo, m, "", "", "") | ||
| 170 | 170 | card := repoCard{ | |
| 171 | 171 | Name: e.Name, Description: e.Description, Branch: m.DefaultBranch, | |
| 172 | 172 | Private: true, Commit: feed.LastCommit, LastTag: feed.LastTag, | |
| @@ -201,14 +201,14 @@ func (w *Web) repoCards(entries []index.Entry) ([]repoCard, []activityEvent) { | |||
| 201 | 201 | // instanceEvents merges the visible repositories' feeds for one kind. It is | |
| 202 | 202 | // bounded after the merge: a per-repository cap would hide a quiet repository's | |
| 203 | 203 | // only event on an instance with a busy neighbour. | |
| 204 | func (w *Web) instanceEvents(entries []index.Entry, kind string) []activityEvent { | ||
| 204 | func (w *Web) instanceEvents(memo *requestMemo, entries []index.Entry, kind string) []activityEvent { | ||
| 205 | 205 | var events []activityEvent | |
| 206 | 206 | for _, e := range entries { | |
| 207 | m, err := w.Repos.Open(e.Name) | ||
| 207 | m, err := w.repo(memo, e.Name) | ||
| 208 | 208 | if err != nil { | |
| 209 | 209 | continue | |
| 210 | 210 | } | |
| 211 | for _, ev := range w.repoActivity(m, kind, "", "").Events { | ||
| 211 | for _, ev := range w.repoActivity(memo, m, kind, "", "").Events { | ||
| 212 | 212 | ev.Repo = m.Name | |
| 213 | 213 | events = append(events, ev) | |
| 214 | 214 | } | |