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.

Store the overview's summaries when a push lands

2a8f7cef47240564179a45126863d1ccd8d9d362 · Keenan Nemetz · 10d ago · parent 8dc56e4e

Store the overview's summaries when a push lands

The repository overview walked git on every read: `rev-parse` for the
tip, `log` for the last five commits, `for-each-ref` for tags, `ls-tree`
plus `shortlog` for languages and contributors, and a `cat-file` for the
README. All of those describe a ref except the README, and a ref moves
only when a push lands, so the push now records them.

- Schema v10 adds `repo_languages`, `repo_commits`, `repo_contributors`
  and `repo_tags`.
- `RefreshHead` stores the tip's newest commits and its language
  breakdown from one `ls-tree -r -l`, and returns whether the tip moved.
- `RefreshContributors` stores the top authors; the hook calls it only
  when the tip changed, never on a metadata or tag push.
- `RefreshTags` stores the tag rows beside the release events it already
  rewrote, and `IndexRepo` now refreshes tags and contributors too, so a
  rebuild restores everything a push would have written.
- The overview reads those rows when the recorded head is the branch tip
  (`RefCommit` is a loose-ref file read) and falls back to git when it is
  not, so a missed hook shows the correct page rather than a stale one.
  If one stored section is empty on a repository with commits, that
  section alone is re-read from git.
- The sidebar asks SQL for five open issues and changes instead of
  sorting the whole open queue in memory.
- The rendered README is cached by repository, commit and ref under a
  byte budget, so the overview's last git spawn is gone after the first
  read of a commit.
- The language fallback reads one `ls-tree -r -l` instead of `ls-tree`
  followed by a `cat-file --batch-check`.
- `Remove` deletes every derived row for a repository, not only its row.

Load harness at concurrency 4: overview goes from 473 to about 740 req/s,
p50 from 8 ms to 5 ms, and the server runs no git child process for the
warm requests.

internal/lang/lang.go +42

@@ -9,6 +9,7 @@ package lang
9 9
10 10 import (
11 11 "path"
12 "sort"
12 13 "strings"
13 14
14 15 "github.com/rodent-software/vole/core/git"
@@ -126,6 +127,47 @@ func Class(name string) string {
126 127 return ""
127 128 }
128 129
130 // Share is how many bytes one language accounts for in a tree. The overview's
131 // bar is about size; a card's label is about file count, so the two use
132 // different counts on purpose.
133 type Share struct {
134 Name string
135 Bytes int64
136 }
137
138 // Breakdown groups a tree's blobs by language, largest first, by bytes. Unknown
139 // types stay in the list under "Other" so the percentages add up.
140 func Breakdown(tree []git.EntrySize) []Share {
141 byName := map[string]int64{}
142 for _, e := range tree {
143 if e.Type != "blob" {
144 continue
145 }
146 byName[For(e.Name)] += e.Size
147 }
148 list := make([]Share, 0, len(byName))
149 for name, n := range byName {
150 list = append(list, Share{Name: name, Bytes: n})
151 }
152 sort.Slice(list, func(i, j int) bool {
153 if list[i].Bytes != list[j].Bytes {
154 return list[i].Bytes > list[j].Bytes
155 }
156 return list[i].Name < list[j].Name
157 })
158 return list
159 }
160
161 // TopSizes is Top for a sized listing, which is what a push already reads for
162 // the language breakdown.
163 func TopSizes(tree []git.EntrySize) (name, class string) {
164 entries := make([]git.Entry, len(tree))
165 for i, e := range tree {
166 entries[i] = e.Entry
167 }
168 return Top(entries)
169 }
170
129 171 // Top picks the language with the most files in a tree. Counting files rather
130 172 // than bytes is deliberate: one generated binary or lock file should not decide
131 173 // the label. Unknown types are ignored unless the tree contains nothing else.