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.

forge/index/index.go +65−3

@@ -83,6 +83,45 @@ CREATE TABLE IF NOT EXISTS repos (
83 83 } {
84 84 _, _ = db.Exec(`ALTER TABLE repos ADD COLUMN ` + col)
85 85 }
86 // The overview's git-derived summaries, recorded at push time. They are
87 // keyed by repo id and replaced together with the head they describe.
88 if _, err := db.Exec(`
89 CREATE TABLE IF NOT EXISTS repo_languages (
90 repo_id TEXT NOT NULL,
91 name TEXT NOT NULL,
92 bytes INTEGER NOT NULL,
93 PRIMARY KEY (repo_id, name)
94 );
95 CREATE TABLE IF NOT EXISTS repo_commits (
96 repo_id TEXT NOT NULL,
97 pos INTEGER NOT NULL,
98 sha TEXT NOT NULL,
99 subject TEXT NOT NULL,
100 author TEXT NOT NULL,
101 when_at INTEGER NOT NULL,
102 PRIMARY KEY (repo_id, pos)
103 );
104 CREATE TABLE IF NOT EXISTS repo_contributors (
105 repo_id TEXT NOT NULL,
106 pos INTEGER NOT NULL,
107 name TEXT NOT NULL,
108 commits INTEGER NOT NULL,
109 PRIMARY KEY (repo_id, pos)
110 );
111 CREATE TABLE IF NOT EXISTS repo_tags (
112 repo_id TEXT NOT NULL,
113 name TEXT NOT NULL,
114 oid TEXT NOT NULL,
115 commit_sha TEXT NOT NULL,
116 annotated INTEGER NOT NULL,
117 tagger TEXT NOT NULL,
118 when_at INTEGER NOT NULL,
119 subject TEXT NOT NULL,
120 PRIMARY KEY (repo_id, name)
121 );`); err != nil {
122 db.Close()
123 return nil, err
124 }
86 125 if _, err := db.Exec(`
87 126 CREATE TABLE IF NOT EXISTS issues (
88 127 repo_id TEXT NOT NULL,
@@ -289,7 +328,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(
289 328 // and leaves every repo with an empty issues_sha, which makes the next read
290 329 // rebuild that repo from git. A stale row is worse than a rebuild, and a rebuild is the
291 330 // operation the index already has to survive.
292 const schemaVersion = 9
331 const schemaVersion = 10
293 332
294 333 func (s *Store) migrate() error {
295 334 var v int
@@ -311,6 +350,10 @@ DELETE FROM comments;
311 350 DELETE FROM artifacts;
312 351 DELETE FROM state_changes;
313 352 DELETE FROM events;
353 DELETE FROM repo_languages;
354 DELETE FROM repo_commits;
355 DELETE FROM repo_contributors;
356 DELETE FROM repo_tags;
314 357 DELETE FROM search;
315 358 UPDATE repos SET issues_sha = '', events_count = 0,
316 359 head_sha = '', head_subject = '', head_author = '', head_when = 0,
@@ -359,6 +402,7 @@ func (s *Store) Rebuild(st *repo.Store) (int, error) {
359 402 for _, table := range []string{
360 403 "issues", "issue_labels", "issue_assignees", "requests", "request_closes",
361 404 "request_approvals", "comments", "artifacts", "state_changes", "events", "search", "entity_numbers",
405 "repo_languages", "repo_commits", "repo_contributors", "repo_tags",
362 406 } {
363 407 if _, err := tx.Exec(`DELETE FROM ` + table); err != nil {
364 408 return 0, err
@@ -396,8 +440,26 @@ func (s *Store) Upsert(m *repo.Meta) error {
396 440 }
397 441
398 442 func (s *Store) Remove(id string) error {
399 _, err := s.db.Exec(`DELETE FROM repos WHERE id=?`, id)
400 return err
443 // A removed repository must leave no rows behind: every table is keyed by
444 // repo id and nothing else would ever delete them.
445 tx, err := s.db.Begin()
446 if err != nil {
447 return err
448 }
449 defer tx.Rollback()
450 for _, table := range []string{
451 "issues", "issue_labels", "issue_assignees", "requests", "request_closes",
452 "request_approvals", "comments", "artifacts", "state_changes", "events", "search",
453 "entity_numbers", "repo_languages", "repo_commits", "repo_contributors", "repo_tags",
454 } {
455 if _, err := tx.Exec(`DELETE FROM `+table+` WHERE repo_id=?`, id); err != nil {
456 return err
457 }
458 }
459 if _, err := tx.Exec(`DELETE FROM repos WHERE id=?`, id); err != nil {
460 return err
461 }
462 return tx.Commit()
401 463 }
402 464
403 465 func (s *Store) List() ([]Entry, error) {