internal/httpd/facts.go
92 lines · 2727 bytes
1package httpd
2
3import (
4 "fmt"
5 "sort"
6
7 "gitbay.org/gitbay/internal/control"
8 "gitbay.org/gitbay/internal/gitutil"
9)
10
11// repoFacts is the "what is this repository" summary on a repo home: the
12// counts a visitor uses to size up a project before reading any code.
13type repoFacts struct {
14 Commits int
15 Branches int
16 Tags int
17 Contributors []factContributor
18 Languages []gitutil.Language
19 License string
20 Release string // newest release tag, if any
21 Build string // latest build status on the default branch
22}
23
24type factContributor struct {
25 Name string
26 User string // account, when the email is verified here
27 Email string // the account's busiest address, when several collapsed
28 Commits int
29}
30
31// Title is the hover text: which address the commits carry, and how many.
32func (c factContributor) Title() string {
33 if c.Commits == 1 {
34 return c.Email + " · 1 commit"
35 }
36 return fmt.Sprintf("%s · %d commits", c.Email, c.Commits)
37}
38
39const maxContributors = 12
40
41// collapseContributors folds the addresses of one account into a single
42// row. .mailmap has already merged whatever the repository claims about
43// its own history; this merges what an account has proven here, which a
44// repository cannot know about. Addresses with no account stay distinct —
45// two people sharing a git name are not one contributor.
46func (s *Server) collapseContributors(cs []gitutil.Contributor) []factContributor {
47 names := s.authorNames()
48 var out []factContributor
49 byUser := map[string]int{}
50 for _, c := range cs {
51 user, known := names.account(c.Email)
52 if known {
53 if i, seen := byUser[user]; seen {
54 out[i].Commits += c.Commits
55 continue
56 }
57 byUser[user] = len(out)
58 }
59 out = append(out, factContributor{
60 Name: names.name(c.Email, c.Name), User: user, Email: c.Email, Commits: c.Commits,
61 })
62 }
63 sort.SliceStable(out, func(i, j int) bool { return out[i].Commits > out[j].Commits })
64 if len(out) > maxContributors {
65 out = out[:maxContributors]
66 }
67 return out
68}
69
70func (s *Server) factsFor(p repoPage) repoFacts {
71 f := repoFacts{
72 Commits: gitutil.CountCommits(p.Dir, p.Ref),
73 License: control.DetectLicense(p.Dir, p.Ref),
74 }
75 if heads, err := gitutil.Refs(p.Dir, "heads"); err == nil {
76 f.Branches = len(heads)
77 }
78 if tags, err := gitutil.Refs(p.Dir, "tags"); err == nil {
79 f.Tags = len(tags)
80 }
81 f.Languages = gitutil.Languages(p.Dir, p.Ref, langOf)
82
83 f.Contributors = s.collapseContributors(gitutil.Contributors(p.Dir, p.Ref))
84
85 if rels, err := s.st.ListReleases(p.Repo.ID); err == nil && len(rels) > 0 {
86 f.Release = rels[0].Tag
87 }
88 if builds, err := s.st.ListBuilds(p.Repo.ID, 1); err == nil && len(builds) > 0 {
89 f.Build = builds[0].Status
90 }
91 return f
92}