package git import ( "errors" "fmt" "io" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) var () var ( MissingRefErr = errors.New("Reference not found") TreeForFileErr = errors.New("Trying to get tree of a file") ) type ( GitRepository struct { path string repository *git.Repository ref plumbing.Hash // this is setRef when ref is setRef setRef bool } ) func OpenRepository(dir string) (*GitRepository, error) { g := &GitRepository{ path: dir, } repo, err := git.PlainOpen(dir) if err != nil { return nil, err } g.repository = repo return g, nil } func (g *GitRepository) SetRef(ref string) error { if ref == "" { head, err := g.repository.Head() if err != nil { return errors.Join(MissingRefErr, err) } g.ref = head.Hash() } else { hash, err := g.repository.ResolveRevision(plumbing.Revision(ref)) if err != nil { return errors.Join(MissingRefErr, err) } g.ref = *hash } g.setRef = true return nil } func (g *GitRepository) Path() string { return g.path } func (g *GitRepository) LastCommit() (*object.Commit, error) { err := g.validateRef() if err != nil { return nil, err } c, err := g.repository.CommitObject(g.ref) if err != nil { return nil, err } return c, nil } func (g *GitRepository) Commits() ([]*object.Commit, error) { err := g.validateRef() if err != nil { return nil, err } ci, err := g.repository.Log(&git.LogOptions{From: g.ref}) if err != nil { return nil, fmt.Errorf("commits from ref: %w", err) } commits := []*object.Commit{} // TODO: for now only load first 1000 for x := 0; x < 1000; x++ { c, err := ci.Next() if err != nil && errors.Is(err, io.EOF) { break } else if err != nil { return nil, err } commits = append(commits, c) } return commits, nil } func (g *GitRepository) Head() (*plumbing.Reference, error) { return g.repository.Head() } func (g *GitRepository) Tags() ([]*plumbing.Reference, error) { ti, err := g.repository.Tags() if err != nil { return nil, err } tags := []*plumbing.Reference{} err = ti.ForEach(func(t *plumbing.Reference) error { tags = append(tags, t) return nil }) if err != nil { return nil, err } return tags, nil } func (g *GitRepository) Branches() ([]*plumbing.Reference, error) { bs, err := g.repository.Branches() if err != nil { return nil, err } branches := []*plumbing.Reference{} err = bs.ForEach(func(ref *plumbing.Reference) error { branches = append(branches, ref) return nil }) if err != nil { return nil, err } return branches, nil } func (g *GitRepository) Tree(path string) (*object.Tree, error) { err := g.validateRef() if err != nil { return nil, err } c, err := g.repository.CommitObject(g.ref) if err != nil { return nil, err } tree, err := c.Tree() if err != nil { return nil, err } if path == "" { return tree, nil } else { o, err := tree.FindEntry(path) if err != nil { return nil, err } if !o.Mode.IsFile() { subtree, err := tree.Tree(path) if err != nil { return nil, err } return subtree, nil } else { return nil, TreeForFileErr } } } func (g *GitRepository) validateRef() error { if !g.setRef { return g.SetRef("") } return nil } func (g *GitRepository) FileContent(path string) (string, error) { c, err := g.repository.CommitObject(g.ref) if err != nil { return "", err } tree, err := c.Tree() if err != nil { return "", err } file, err := tree.File(path) if err != nil { return "", err } isbin, err := file.IsBinary() if err != nil { return "", err } if !isbin { return file.Contents() } else { return "Binary file", nil } }