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.

Record the default branch summary when a push lands

8dc56e4e258dd56e2c20776f80d61dbed4993f31 · Keenan Nemetz · 10d ago · parent 20442b59

Record the default branch summary when a push lands

The landing card showed the default branch tip and the language it is
mostly made of by running `symbolic-ref`, `rev-parse`, `log -1` and
`ls-tree -r` per repository on every home page read. Those facts change
only when a push moves the branch, so the push now records them.

- `repos` carries head_sha, head_subject, head_author, head_when,
  language and lang_class, and `Index.Entry` returns them with the rest of
  the row.
- `Store.RefreshHead` recomputes the summary and skips the tree walk when
  the recorded tip is still the branch tip, so a metadata or tag push
  costs one rev-parse. It runs from post-receive, from the rebuild, and
  after the default branch changes in settings.
- The language mapping moved to `internal/lang`, shared by the web pages
  and the summary; `fileClass` and `languageFor` are now one table.
- The home cards read the head, language, counts, ACL and newest event
  time from the index. The home page starts no git process at all; 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 243
to 749 req/s, p50 from 16 ms to 5 ms, and the server runs no git child
process for those requests.

internal/lang/lang.go +158

@@ -0,0 +1,158 @@
1 // Package lang names the language a filename suggests and picks the language a
2 // tree is mostly made of.
3 //
4 // It is shared by the web pages and by the index's head summary: the home card
5 // shows the same answer the repository overview groups by, and the summary is
6 // computed when a push lands rather than on every page read.
7
8 package lang
9
10 import (
11 "path"
12 "strings"
13
14 "github.com/rodent-software/vole/core/git"
15 )
16
17 // For names the language a filename suggests. Unknown types are "Other"; the
18 // map is a hint, not a classifier.
19 func For(name string) string {
20 switch strings.ToLower(path.Ext(name)) {
21 case ".go":
22 return "Go"
23 case ".js", ".mjs", ".cjs":
24 return "JavaScript"
25 case ".ts", ".tsx":
26 return "TypeScript"
27 case ".jsx":
28 return "JavaScript"
29 case ".py":
30 return "Python"
31 case ".rs":
32 return "Rust"
33 case ".rb":
34 return "Ruby"
35 case ".java":
36 return "Java"
37 case ".kt", ".kts":
38 return "Kotlin"
39 case ".c", ".h":
40 return "C"
41 case ".cc", ".cpp", ".hpp":
42 return "C++"
43 case ".cs":
44 return "C#"
45 case ".sh", ".bash", ".zsh":
46 return "Shell"
47 case ".md", ".markdown":
48 return "Markdown"
49 case ".html", ".htm":
50 return "HTML"
51 case ".css", ".scss", ".sass":
52 return "CSS"
53 case ".json":
54 return "JSON"
55 case ".yml", ".yaml":
56 return "YAML"
57 case ".toml":
58 return "TOML"
59 case ".sql":
60 return "SQL"
61 case ".proto":
62 return "Protocol Buffers"
63 case ".lua":
64 return "Lua"
65 case ".php":
66 return "PHP"
67 case ".swift":
68 return "Swift"
69 case ".ex", ".exs":
70 return "Elixir"
71 case ".zig":
72 return "Zig"
73 }
74 return "Other"
75 }
76
77 // Class is the CSS class a filename's language is drawn with. It is empty for a
78 // name the table does not know.
79 func Class(name string) string {
80 switch strings.ToLower(path.Ext(name)) {
81 case ".go":
82 return "lang-go"
83 case ".rs":
84 return "lang-rust"
85 case ".js", ".mjs", ".cjs", ".jsx":
86 return "lang-js"
87 case ".ts", ".tsx":
88 return "lang-ts"
89 case ".py":
90 return "lang-py"
91 case ".rb":
92 return "lang-rb"
93 case ".java", ".kt", ".scala":
94 return "lang-jvm"
95 case ".c", ".h":
96 return "lang-c"
97 case ".cc", ".cpp", ".cxx", ".hpp":
98 return "lang-cpp"
99 case ".cs":
100 return "lang-cs"
101 case ".php":
102 return "lang-php"
103 case ".sh", ".bash", ".zsh", ".fish":
104 return "lang-sh"
105 case ".html", ".htm":
106 return "lang-html"
107 case ".css", ".scss", ".sass":
108 return "lang-css"
109 case ".json", ".yaml", ".yml", ".toml", ".ini", ".xml", ".conf":
110 return "lang-config"
111 case ".md", ".markdown", ".mdown", ".mkd", ".txt", ".rst":
112 return "lang-text"
113 case ".sql":
114 return "lang-sql"
115 case ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico":
116 return "lang-image"
117 case ".lock":
118 return "lang-lock"
119 }
120 switch strings.ToLower(name) {
121 case "dockerfile", "containerfile", "makefile", "makefile.am", "cmakelists.txt":
122 return "lang-build"
123 case "go.mod", "go.sum":
124 return "lang-go"
125 }
126 return ""
127 }
128
129 // Top picks the language with the most files in a tree. Counting files rather
130 // than bytes is deliberate: one generated binary or lock file should not decide
131 // the label. Unknown types are ignored unless the tree contains nothing else.
132 func Top(tree []git.Entry) (name, class string) {
133 counts := map[string]int{}
134 sample := map[string]string{}
135 for _, e := range tree {
136 if e.Type != "blob" {
137 continue
138 }
139 l := For(e.Name)
140 counts[l]++
141 if _, ok := sample[l]; !ok {
142 sample[l] = e.Name
143 }
144 }
145 best := ""
146 for l, n := range counts {
147 if l == "Other" {
148 continue
149 }
150 if best == "" || n > counts[best] || (n == counts[best] && l < best) {
151 best = l
152 }
153 }
154 if best == "" {
155 return "", ""
156 }
157 return best, Class(sample[best])
158 }