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.

Show real accounts as repository contributors

a32a859a08276681497be390d6f7195098a97ab4 · Keenan Nemetz · 5d ago · parent 802e0e52

Show real accounts as repository contributors

Count contributors from the change and issue records instead of the author text in commits: changes merged, issues those changes fixed, issues the reporter opened that were fixed, and reviews that named the merged head. The overview lists accounts by that work with compact icon counts and full labels in tooltips. The shortlog-derived contributor table and its refresh path are gone.

forge/index/head.go +5−44

@@ -1,7 +1,7 @@
1 1 package index
2 2
3 3 // The head summary: a repository's default branch tip, the language it is mostly
4 // made of, its newest commits, and (in RefreshContributors) its authors.
4 // made of, and its newest commits.
5 5 //
6 6 // The landing and overview pages showed these from git on every request, and the
7 7 // history and tree walks are the expensive part. They change only when a push
@@ -70,45 +70,6 @@ func (s *Store) RefreshHead(m *repo.Meta) (changed bool, err error) {
70 70 return true, s.writeHead(m.ID, h, shares, commits)
71 71 }
72 72
73 // RefreshContributors records the authors of the default branch's history. It
74 // walks every commit, so it runs once per pushed tip and on a rebuild, never on
75 // a metadata refresh.
76 func (s *Store) RefreshContributors(m *repo.Meta) error {
77 var sha string
78 if err := s.db.QueryRow(`SELECT head_sha FROM repos WHERE id=?`, m.ID).Scan(&sha); err != nil {
79 return err
80 }
81 var list []ContributorRow
82 if sha != "" {
83 authors, err := m.Git().Contributors(sha)
84 if err != nil {
85 return err
86 }
87 for i, c := range authors {
88 if i == 8 {
89 break
90 }
91 list = append(list, ContributorRow{Name: c.Name, Commits: c.Commits})
92 }
93 }
94 tx, err := s.db.Begin()
95 if err != nil {
96 return err
97 }
98 defer tx.Rollback()
99 if _, err := tx.Exec(`DELETE FROM repo_contributors WHERE repo_id=?`, m.ID); err != nil {
100 return err
101 }
102 for i, c := range list {
103 if _, err := tx.Exec(
104 `INSERT INTO repo_contributors(repo_id, pos, name, commits) VALUES(?,?,?,?)`,
105 m.ID, i, c.Name, c.Commits); err != nil {
106 return err
107 }
108 }
109 return tx.Commit()
110 }
111
112 73 // writeHead replaces the head row and the summaries that belong to it in one
113 74 // transaction, so a reader never sees a new tip with an old tree.
114 75 func (s *Store) writeHead(repoID string, h headSummary, shares []lang.Share, commits []CommitRow) error {
@@ -122,10 +83,10 @@ func (s *Store) writeHead(repoID string, h headSummary, shares []lang.Share, com
122 83 h.SHA, h.Subject, h.Author, h.When, h.Language, h.LangClass, repoID); err != nil {
123 84 return err
124 85 }
125 // The old summaries describe the old tip. The authors are refilled by
126 // RefreshContributors; clearing them here means a failure there shows a
127 // missing section, not yesterday's authors under today's commit.
128 for _, table := range []string{"repo_languages", "repo_commits", "repo_contributors"} {
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"} {
129 90 if _, err := tx.Exec(`DELETE FROM `+table+` WHERE repo_id=?`, repoID); err != nil {
130 91 return err
131 92 }