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 change list and its counts from the index
b739915c0941be0f97cef8b96fd28d8f4c4685ae · Keenan Nemetz · 10d ago · parent 7f547ca3
Read the change list and its counts from the index The header, the change list and the release artifact list now come from the derived rows instead of the metadata branch: - The header's open-changes badge is one COUNT, so a page that only shows the badge no longer reads `ls-tree`, `batch-check` and `batch` on every view. That is the read that grew with the repository's issues and comments. - The change list, its state chips and its approval counts are queries. An approval still only counts when it names the request's current head and base, and eligibility is still the ACL's decision, so a stale approval is shown as history exactly as before. - The release page's artifact list is the indexed pointer set. - A change page reads its one request rather than every request; the conversation page keeps reading git, so the record it renders is authoritative rather than a copy. On the fixture (40 files, 30 issues, 30 changes), git calls per page: issues list 5 -> 2 change list 6 -> 3 releases 6 -> 3 commits 7 -> 4 overview 19 -> 6 (repeat) code 12 -> 9 change view 14 -> 12 The remaining calls are the one `rev-parse` the refresh needs, the branch and tree reads the page itself shows, and the detail pages' own git read.
server/web/overview.go +10−5
| @@ -130,12 +130,8 @@ func (w *Web) Overview(rw http.ResponseWriter, r *http.Request) { | |||
| 130 | 130 | // home in their own lists; the overview is a map of what is still being worked | |
| 131 | 131 | // on. A failed read leaves an empty section rather than failing the README page. | |
| 132 | 132 | func (w *Web) overviewRecent(memo *requestMemo, m *repo.Meta) (issues, requests []overviewEntity) { | |
| 133 | var numbers map[string]int | ||
| 134 | 133 | if w.Index != nil { | |
| 135 | 134 | if err := w.ensureIndex(memo, m); err == nil { | |
| 136 | if nums, err := w.Index.Numbers(m.ID); err == nil { | ||
| 137 | numbers = nums | ||
| 138 | } | ||
| 139 | 135 | if rows, err := w.Index.Issues(m.ID, index.Filter{State: "open"}); err == nil { | |
| 140 | 136 | for _, row := range rows { | |
| 141 | 137 | issues = append(issues, overviewEntity{ | |
| @@ -144,6 +140,15 @@ func (w *Web) overviewRecent(memo *requestMemo, m *repo.Meta) (issues, requests | |||
| 144 | 140 | }) | |
| 145 | 141 | } | |
| 146 | 142 | } | |
| 143 | if rows, err := w.Index.Requests(m.ID, index.RequestFilter{State: "open"}); err == nil { | ||
| 144 | for _, row := range rows { | ||
| 145 | requests = append(requests, overviewEntity{ | ||
| 146 | ID: row.ID, Title: row.Title, Author: row.Author, | ||
| 147 | Created: row.Created, Number: row.Number, | ||
| 148 | }) | ||
| 149 | } | ||
| 150 | } | ||
| 151 | return newestOverview(issues), newestOverview(requests) | ||
| 147 | 152 | } | |
| 148 | 153 | } | |
| 149 | 154 | if list, err := w.requestsAt(memo, m); err == nil { | |
| @@ -151,7 +156,7 @@ func (w *Web) overviewRecent(memo *requestMemo, m *repo.Meta) (issues, requests | |||
| 151 | 156 | if req.Open() { | |
| 152 | 157 | requests = append(requests, overviewEntity{ | |
| 153 | 158 | ID: req.ID, Title: req.Title, Author: req.Author, | |
| 154 | Created: req.Created, Number: numbers[req.ID], | ||
| 159 | Created: req.Created, | ||
| 155 | 160 | }) | |
| 156 | 161 | } | |
| 157 | 162 | } | |