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.

core/git/stats.go −62

@@ -1,62 +0,0 @@
1 package git
2
3 // Repository statistics. These are read from git on demand and never stored:
4 // the same rule as every other page, and it means a stat cannot go stale after a
5 // push. The calls are bounded (one shortlog, one tree walk) because the overview
6 // is the first page a visitor sees.
7
8 import (
9 "fmt"
10 "strconv"
11 "strings"
12 )
13
14 // Contributor is one author in shortlog order (most commits first).
15 type Contributor struct {
16 Name string
17 Email string
18 Commits int
19 }
20
21 // Contributors summarises the authors reachable from rev. Merge commits are
22 // excluded: a maintainer who merges ten branches did not write those changes,
23 // and counting the merges would rank them above the people who did.
24 func (r *Repo) Contributors(rev string) ([]Contributor, error) {
25 if rev == "" {
26 return nil, nil
27 }
28 out, errs, err := r.run(nil, "shortlog", "-sne", "--no-merges", rev)
29 if err != nil {
30 return nil, fmt.Errorf("shortlog %s: %w: %s", rev, err, errs)
31 }
32 var list []Contributor
33 for _, line := range strings.Split(string(out), "\n") {
34 line = strings.TrimRight(line, "\r")
35 if strings.TrimSpace(line) == "" {
36 continue
37 }
38 count, identity, ok := strings.Cut(line, "\t")
39 if !ok {
40 // shortlog separates with a tab; a line without one is not a record.
41 continue
42 }
43 n, err := strconv.Atoi(strings.TrimSpace(count))
44 if err != nil || n == 0 {
45 continue
46 }
47 name, email := splitIdentity(identity)
48 list = append(list, Contributor{Name: name, Email: email, Commits: n})
49 }
50 return list, nil
51 }
52
53 // splitIdentity turns "Name <email>" into its parts. A malformed identity keeps
54 // the whole string as the name rather than dropping the author.
55 func splitIdentity(s string) (name, email string) {
56 s = strings.TrimSpace(s)
57 i := strings.LastIndex(s, " <")
58 if i < 0 || !strings.HasSuffix(s, ">") {
59 return s, ""
60 }
61 return strings.TrimSpace(s[:i]), s[i+2 : len(s)-1]
62 }