vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 1h ago
4b8c9e1d
| 1 | package index |
| 2 | |
| 3 | // The head summary: a repository's default branch tip, the language it is mostly |
| 4 | // made of, and its newest commits. |
| 5 | // |
| 6 | // The landing and overview pages showed these from git on every request, and the |
| 7 | // history and tree walks are the expensive part. They change only when a push |
| 8 | // moves the default branch, so the push records them and a page reads rows. |
| 9 | |
| 10 | import ( |
| 11 | "rodent.software/vole/forge/repo" |
| 12 | "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, its newest |
| 25 | // commits and its language breakdown. It runs when a push lands (post-receive), |
| 26 | // on a rebuild, and after the default branch changes. If the recorded SHA is |
| 27 | // still the branch tip, nothing is written and changed is false: a tag or |
| 28 | // metadata push then costs one rev-parse, not a tree walk. |
| 29 | func (s *Store) RefreshHead(m *repo.Meta) (changed bool, err error) { |
| 30 | branch := m.DefaultBranch |
| 31 | if branch == "" { |
| 32 | // The hook only knows the directory and the id; the branch is the row's. |
| 33 | _ = s.db.QueryRow(`SELECT default_branch FROM repos WHERE id=?`, m.ID).Scan(&branch) |
| 34 | } |
| 35 | if branch == "" { |
| 36 | branch = m.Git().Symref() |
| 37 | } |
| 38 | sha := "" |
| 39 | if branch != "" { |
| 40 | if tip, err := m.Git().RevParse("refs/heads/" + branch); err == nil { |
| 41 | sha = tip |
| 42 | } |
| 43 | } |
| 44 | var stored string |
| 45 | if err := s.db.QueryRow(`SELECT head_sha FROM repos WHERE id=?`, m.ID).Scan(&stored); err != nil { |
| 46 | return false, err |
| 47 | } |
| 48 | if stored == sha { |
| 49 | return false, nil |
| 50 | } |
| 51 | |
| 52 | var h headSummary |
| 53 | var commits []CommitRow |
| 54 | var shares []lang.Share |
| 55 | if sha != "" { |
| 56 | h.SHA = sha |
| 57 | if list, err := m.Git().Log(sha, 5); err == nil { |
| 58 | for i, c := range list { |
| 59 | if i == 0 { |
| 60 | h.Subject, h.Author, h.When = c.Subject, c.Author, c.When |
| 61 | } |
| 62 | commits = append(commits, CommitRow{SHA: c.SHA, Subject: c.Subject, Author: c.Author, When: c.When}) |
| 63 | } |
| 64 | } |
| 65 | if sizes, err := m.Git().LsTreeSizes(sha); err == nil { |
| 66 | shares = lang.Breakdown(sizes) |
| 67 | h.Language, h.LangClass = lang.TopSizes(sizes) |
| 68 | } |
| 69 | } |
| 70 | return true, s.writeHead(m.ID, h, shares, commits) |
| 71 | } |
| 72 | |
| 73 | // writeHead replaces the head row and the summaries that belong to it in one |
| 74 | // transaction, so a reader never sees a new tip with an old tree. |
| 75 | func (s *Store) writeHead(repoID string, h headSummary, shares []lang.Share, commits []CommitRow) error { |
| 76 | tx, err := s.db.Begin() |
| 77 | if err != nil { |
| 78 | return err |
| 79 | } |
| 80 | defer tx.Rollback() |
| 81 | if _, err := tx.Exec( |
| 82 | `UPDATE repos SET head_sha=?, head_subject=?, head_author=?, head_when=?, language=?, lang_class=? WHERE id=?`, |
| 83 | h.SHA, h.Subject, h.Author, h.When, h.Language, h.LangClass, repoID); err != nil { |
| 84 | return err |
| 85 | } |
| 86 | // The old summaries describe the old tip. Clearing them here means a |
| 87 | // failure to record the new ones shows a missing section, not yesterday's |
| 88 | // languages and commits under today's tip. |
| 89 | for _, table := range []string{"repo_languages", "repo_commits"} { |
| 90 | if _, err := tx.Exec(`DELETE FROM `+table+` WHERE repo_id=?`, repoID); err != nil { |
| 91 | return err |
| 92 | } |
| 93 | } |
| 94 | for _, sh := range shares { |
| 95 | if _, err := tx.Exec( |
| 96 | `INSERT INTO repo_languages(repo_id, name, bytes) VALUES(?,?,?)`, |
| 97 | repoID, sh.Name, sh.Bytes); err != nil { |
| 98 | return err |
| 99 | } |
| 100 | } |
| 101 | for i, c := range commits { |
| 102 | if _, err := tx.Exec( |
| 103 | `INSERT INTO repo_commits(repo_id, pos, sha, subject, author, when_at) VALUES(?,?,?,?,?,?)`, |
| 104 | repoID, i, c.SHA, c.Subject, c.Author, c.When); err != nil { |
| 105 | return err |
| 106 | } |
| 107 | } |
| 108 | return tx.Commit() |
| 109 | } |
| 110 |