git.go

  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}