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 Path string
41 mtx sync.Mutex
42 repos []*Repo
43 commits CommitLog
44 readmeTransform ReadmeTransform
45}
46
47func NewRepoSource(repoPath string, rf ReadmeTransform) *RepoSource {
48 err := os.MkdirAll(repoPath, os.ModeDir|os.FileMode(0700))
49 if err != nil {
50 log.Fatal(err)
51 }
52 rs := &RepoSource{Path: repoPath, readmeTransform: rf}
53 return rs
54}
55
56func (rs *RepoSource) AllRepos() []*Repo {
57 rs.mtx.Lock()
58 defer rs.mtx.Unlock()
59 return rs.repos
60}
61
62func (rs *RepoSource) GetRepo(name string) (*Repo, error) {
63 rs.mtx.Lock()
64 defer rs.mtx.Unlock()
65 for _, r := range rs.repos {
66 if r.Name == name {
67 return r, nil
68 }
69 }
70 return nil, ErrMissingRepo
71}
72
73func (rs *RepoSource) InitRepo(name string, bare bool) (*Repo, error) {
74 rs.mtx.Lock()
75 defer rs.mtx.Unlock()
76 rg, err := git.PlainInit(rs.Path+string(os.PathSeparator)+name, bare)
77 if err != nil {
78 return nil, err
79 }
80 r := &Repo{
81 Name: name,
82 Repository: rg,
83 }
84 rs.repos = append(rs.repos, r)
85 return r, nil
86}
87
88func (rs *RepoSource) GetCommits(limit int) []RepoCommit {
89 rs.mtx.Lock()
90 defer rs.mtx.Unlock()
91 if limit > len(rs.commits) {
92 limit = len(rs.commits)
93 }
94 return rs.commits[:limit]
95}
96
97func (rs *RepoSource) LoadRepos() error {
98 rs.mtx.Lock()
99 defer rs.mtx.Unlock()
100 rd, err := os.ReadDir(rs.Path)
101 if err != nil {
102 return err
103 }
104 rs.repos = make([]*Repo, 0)
105 rs.commits = make([]RepoCommit, 0)
106 for _, de := range rd {
107 rn := de.Name()
108 rg, err := git.PlainOpen(rs.Path + string(os.PathSeparator) + rn)
109 if err != nil {
110 return err
111 }
112 r, err := rs.loadRepo(rn, rg)
113 rs.repos = append(rs.repos, r)
114 }
115 return nil
116}
117
118func (rs *RepoSource) loadRepo(name string, rg *git.Repository) (*Repo, error) {
119 r := &Repo{Name: name}
120 r.Repository = rg
121 l, err := rg.Log(&git.LogOptions{All: true})
122 if err != nil {
123 return nil, err
124 }
125 err = l.ForEach(func(c *object.Commit) error {
126 if r.LastUpdated == nil {
127 r.LastUpdated = &c.Author.When
128 rf, err := c.File("README.md")
129 if err == nil {
130 rmd, err := rf.Contents()
131 if err == nil {
132 r.Readme = rs.readmeTransform(rmd)
133 }
134 }
135 }
136 rs.commits = append(rs.commits, RepoCommit{Name: name, Commit: c})
137 return nil
138 })
139 if err != nil {
140 return nil, err
141 }
142 sort.Sort(rs.commits)
143 return r, nil
144}