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.

forge/index/head.go +69

@@ -0,0 +1,69 @@
1 package index
2
3 // The head summary: a repository's default branch tip and the language it is
4 // mostly made of.
5 //
6 // The landing page showed these from git on every request: `symbolic-ref`,
7 // `rev-parse`, `log -1` and `ls-tree -r` per card. They change only when a push
8 // moves the default branch, so the push records them and a page reads them.
9
10 import (
11 "github.com/rodent-software/vole/forge/repo"
12 "github.com/rodent-software/vole/internal/lang"
13 )
14
15 type headSummary struct {
16 SHA string
17 Subject string
18 Author string
19 When int64
20 Language string
21 LangClass string
22 }
23
24 // RefreshHead recomputes a repository's default branch summary. It runs when a
25 // push lands (post-receive), on a rebuild, and after the default branch changes.
26 // If the recorded SHA is still the branch tip, nothing is written: a tag or
27 // metadata push then costs one rev-parse, not a tree walk.
28 func (s *Store) RefreshHead(m *repo.Meta) error {
29 branch := m.DefaultBranch
30 if branch == "" {
31 // The hook only knows the directory and the id; the branch is the row's.
32 _ = s.db.QueryRow(`SELECT default_branch FROM repos WHERE id=?`, m.ID).Scan(&branch)
33 }
34 if branch == "" {
35 branch = m.Git().Symref()
36 }
37 sha := ""
38 if branch != "" {
39 if tip, err := m.Git().RevParse("refs/heads/" + branch); err == nil {
40 sha = tip
41 }
42 }
43 var stored string
44 if err := s.db.QueryRow(`SELECT head_sha FROM repos WHERE id=?`, m.ID).Scan(&stored); err != nil {
45 return err
46 }
47 if stored == sha {
48 return nil
49 }
50 if sha == "" {
51 // The branch was deleted (or the repository still has no commits).
52 return s.setHead(m.ID, headSummary{})
53 }
54 h := headSummary{SHA: sha}
55 if commits, err := m.Git().Log(sha, 1); err == nil && len(commits) > 0 {
56 h.Subject, h.Author, h.When = commits[0].Subject, commits[0].Author, commits[0].When
57 }
58 if tree, err := m.Git().LsTreeR(sha); err == nil {
59 h.Language, h.LangClass = lang.Top(tree)
60 }
61 return s.setHead(m.ID, h)
62 }
63
64 func (s *Store) setHead(repoID string, h headSummary) error {
65 _, err := s.db.Exec(
66 `UPDATE repos SET head_sha=?, head_subject=?, head_author=?, head_when=?, language=?, lang_class=? WHERE id=?`,
67 h.SHA, h.Subject, h.Author, h.When, h.Language, h.LangClass, repoID)
68 return err
69 }