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/index.go +41−7

@@ -25,6 +25,14 @@ type Entry struct {
25 25 ACLSHA string
26 26 ACLPrincipals int
27 27 ACLDefault string
28 // The default branch's summary, maintained at push time. A repository with no
29 // commits has an empty HeadSHA.
30 HeadSHA string
31 HeadSubject string
32 HeadAuthor string
33 HeadWhen int64
34 Language string
35 LangClass string
28 36 }
29 37
30 38 type Store struct{ db *sql.DB }
@@ -46,7 +54,13 @@ CREATE TABLE IF NOT EXISTS repos (
46 54 created INTEGER NOT NULL,
47 55 acl_sha TEXT NOT NULL DEFAULT '',
48 56 acl_principals INTEGER NOT NULL DEFAULT 0,
49 acl_default TEXT NOT NULL DEFAULT 'none'
57 acl_default TEXT NOT NULL DEFAULT 'none',
58 head_sha TEXT NOT NULL DEFAULT '',
59 head_subject TEXT NOT NULL DEFAULT '',
60 head_author TEXT NOT NULL DEFAULT '',
61 head_when INTEGER NOT NULL DEFAULT 0,
62 language TEXT NOT NULL DEFAULT '',
63 lang_class TEXT NOT NULL DEFAULT ''
50 64 );`); err != nil {
51 65 db.Close()
52 66 return nil, err
@@ -57,6 +71,18 @@ CREATE TABLE IF NOT EXISTS repos (
57 71 // events_count is the number of feed events a repository has, so an
58 72 // unfiltered pager gets its total without counting the table.
59 73 _, _ = db.Exec(`ALTER TABLE repos ADD COLUMN events_count INTEGER NOT NULL DEFAULT 0`)
74 // The head summary is the default branch's tip and the language it is mostly
75 // made of, refreshed when a push lands rather than on every page read.
76 for _, col := range []string{
77 "head_sha TEXT NOT NULL DEFAULT ''",
78 "head_subject TEXT NOT NULL DEFAULT ''",
79 "head_author TEXT NOT NULL DEFAULT ''",
80 "head_when INTEGER NOT NULL DEFAULT 0",
81 "language TEXT NOT NULL DEFAULT ''",
82 "lang_class TEXT NOT NULL DEFAULT ''",
83 } {
84 _, _ = db.Exec(`ALTER TABLE repos ADD COLUMN ` + col)
85 }
60 86 if _, err := db.Exec(`
61 87 CREATE TABLE IF NOT EXISTS issues (
62 88 repo_id TEXT NOT NULL,
@@ -263,7 +289,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(
263 289 // and leaves every repo with an empty issues_sha, which makes the next read
264 290 // rebuild that repo from git. A stale row is worse than a rebuild, and a rebuild is the
265 291 // operation the index already has to survive.
266 const schemaVersion = 8
292 const schemaVersion = 9
267 293
268 294 func (s *Store) migrate() error {
269 295 var v int
@@ -286,7 +312,9 @@ DELETE FROM artifacts;
286 312 DELETE FROM state_changes;
287 313 DELETE FROM events;
288 314 DELETE FROM search;
289 UPDATE repos SET issues_sha = '', events_count = 0;`); err != nil {
315 UPDATE repos SET issues_sha = '', events_count = 0,
316 head_sha = '', head_subject = '', head_author = '', head_when = 0,
317 language = '', lang_class = '';`); err != nil {
290 318 return err
291 319 }
292 320 _, err := s.db.Exec(fmt.Sprintf(`PRAGMA user_version = %d`, schemaVersion))
@@ -373,7 +401,9 @@ func (s *Store) Remove(id string) error {
373 401 }
374 402
375 403 func (s *Store) List() ([]Entry, error) {
376 rows, err := s.db.Query(`SELECT id, name, description, default_branch, created, acl_sha, acl_principals, acl_default FROM repos ORDER BY name`)
404 rows, err := s.db.Query(`SELECT id, name, description, default_branch, created, acl_sha, acl_principals, acl_default,
405 head_sha, head_subject, head_author, head_when, language, lang_class
406 FROM repos ORDER BY name`)
377 407 if err != nil {
378 408 return nil, err
379 409 }
@@ -382,7 +412,8 @@ func (s *Store) List() ([]Entry, error) {
382 412 for rows.Next() {
383 413 var e Entry
384 414 var created int64
385 if err := rows.Scan(&e.ID, &e.Name, &e.Description, &e.DefaultBranch, &created, &e.ACLSHA, &e.ACLPrincipals, &e.ACLDefault); err != nil {
415 if err := rows.Scan(&e.ID, &e.Name, &e.Description, &e.DefaultBranch, &created, &e.ACLSHA, &e.ACLPrincipals, &e.ACLDefault,
416 &e.HeadSHA, &e.HeadSubject, &e.HeadAuthor, &e.HeadWhen, &e.Language, &e.LangClass); err != nil {
386 417 return nil, err
387 418 }
388 419 e.Created = time.Unix(created, 0).UTC()
@@ -394,8 +425,11 @@ func (s *Store) List() ([]Entry, error) {
394 425 func (s *Store) Get(name string) (*Entry, error) {
395 426 var e Entry
396 427 var created int64
397 err := s.db.QueryRow(`SELECT id, name, description, default_branch, created, acl_sha, acl_principals, acl_default FROM repos WHERE name=?`, name).
398 Scan(&e.ID, &e.Name, &e.Description, &e.DefaultBranch, &created, &e.ACLSHA, &e.ACLPrincipals, &e.ACLDefault)
428 err := s.db.QueryRow(`SELECT id, name, description, default_branch, created, acl_sha, acl_principals, acl_default,
429 head_sha, head_subject, head_author, head_when, language, lang_class
430 FROM repos WHERE name=?`, name).
431 Scan(&e.ID, &e.Name, &e.Description, &e.DefaultBranch, &created, &e.ACLSHA, &e.ACLPrincipals, &e.ACLDefault,
432 &e.HeadSHA, &e.HeadSubject, &e.HeadAuthor, &e.HeadWhen, &e.Language, &e.LangClass)
399 433 if errors.Is(err, sql.ErrNoRows) {
400 434 return nil, osErrNotExist
401 435 }