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		if err != nil {
111			return err
112		}
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 = 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}
145
146func (r *Repo) LatestFile(path string) (string, error) {
147	lg, err := r.Repository.Log(&git.LogOptions{})
148	if err != nil {
149		return "", err
150	}
151	c, err := lg.Next()
152	if err != nil {
153		return "", err
154	}
155	f, err := c.File(path)
156	if err != nil {
157		return "", nil
158	}
159	content, err := f.Contents()
160	if err != nil {
161		return "", nil
162	}
163	return content, nil
164}