1package git
2
3import (
4 "errors"
5 "log"
6 "os"
7 "sort"
8 "sync"
9 "time"
10
11 "github.com/go-git/go-git/v5"
12 "github.com/go-git/go-git/v5/plumbing/object"
13)
14
15var ErrMissingRepo = errors.New("missing repo")
16
17type Repo struct {
18 Name string
19 Repository *git.Repository
20 Readme string
21 LastUpdated *time.Time
22}
23
24type RepoCommit struct {
25 Name string
26 Commit *object.Commit
27}
28
29type CommitLog []RepoCommit
30
31type ReadmeTransform func(string) string
32
33func (cl CommitLog) Len() int { return len(cl) }
34func (cl CommitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
35func (cl CommitLog) Less(i, j int) bool {
36 return cl[i].Commit.Author.When.After(cl[j].Commit.Author.When)
37}
38
39type RepoSource struct {
40 mtx sync.Mutex
41 path string
42 repos []*Repo
43 commits CommitLog
44 readmeTransform ReadmeTransform
45}
46
47func NewRepoSource(repoPath string, poll time.Duration, rf ReadmeTransform) *RepoSource {
48 rs := &RepoSource{path: repoPath, readmeTransform: rf}
49 go func() {
50 for {
51 rs.loadRepos()
52 time.Sleep(poll)
53 }
54 }()
55 return rs
56}
57
58func (rs *RepoSource) AllRepos() []*Repo {
59 rs.mtx.Lock()
60 defer rs.mtx.Unlock()
61 return rs.repos
62}
63
64func (rs *RepoSource) GetRepo(name string) (*Repo, error) {
65 rs.mtx.Lock()
66 defer rs.mtx.Unlock()
67 for _, r := range rs.repos {
68 if r.Name == name {
69 return r, nil
70 }
71 }
72 return nil, ErrMissingRepo
73}
74
75func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
76 rs.mtx.Lock()
77 defer rs.mtx.Unlock()
78 if limit > len(rs.commits) {
79 limit = len(rs.commits)
80 }
81 return rs.commits[:limit]
82}
83
84func (rs *RepoSource) loadRepos() {
85 rs.mtx.Lock()
86 defer rs.mtx.Unlock()
87 rd, err := os.ReadDir(rs.path)
88 if err != nil {
89 return
90 }
91 rs.repos = make([]*Repo, 0)
92 rs.commits = make([]RepoCommit, 0)
93 for _, de := range rd {
94 rn := de.Name()
95 r := &Repo{Name: rn}
96 rg, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rn)
97 if err != nil {
98 log.Fatal(err)
99 }
100 r.Repository = rg
101 l, err := rg.Log(&git.LogOptions{All: true})
102 if err != nil {
103 log.Fatal(err)
104 }
105 l.ForEach(func(c *object.Commit) error {
106 if r.LastUpdated == nil {
107 r.LastUpdated = &c.Author.When
108 rf, err := c.File("README.md")
109 if err == nil {
110 rmd, err := rf.Contents()
111 if err == nil {
112 r.Readme = rs.readmeTransform(rmd)
113 }
114 }
115 }
116 rs.commits = append(rs.commits, RepoCommit{Name: rn, Commit: c})
117 return nil
118 })
119 sort.Sort(rs.commits)
120 rs.repos = append(rs.repos, r)
121 }
122}