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