···
+
"github.com/go-git/go-git/v5"
+
"github.com/go-git/go-git/v5/plumbing"
+
"github.com/go-git/go-git/v5/plumbing/object"
+
// TagReference is used to list both tag and non-annotated tags.
+
// Non-annotated tags should only contains a reference.
+
// Annotated tags should contain its reference and its tag information.
+
type TagReference struct {
+
ref *plumbing.Reference
+
// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
+
type infoWrapper struct {
+
func (self *TagList) Len() int {
+
func (self *TagList) Swap(i, j int) {
+
self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
+
// sorting tags in reverse chronological order
+
func (self *TagList) Less(i, j int) bool {
+
if self.refs[i].tag != nil {
+
dateI = self.refs[i].tag.Tagger.When
+
c, err := self.r.CommitObject(self.refs[i].ref.Hash())
+
dateI = c.Committer.When
+
if self.refs[j].tag != nil {
+
dateJ = self.refs[j].tag.Tagger.When
+
c, err := self.r.CommitObject(self.refs[j].ref.Hash())
+
dateJ = c.Committer.When
+
return dateI.After(dateJ)
+
func Open(path string, ref string) (*GitRepo, error) {
+
g.r, err = git.PlainOpen(path)
+
return nil, fmt.Errorf("opening %s: %w", path, err)
+
head, err := g.r.Head()
+
return nil, fmt.Errorf("getting head of %s: %w", path, err)
+
hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
+
return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
+
func (g *GitRepo) Commits() ([]*object.Commit, error) {
+
ci, err := g.r.Log(&git.LogOptions{From: g.h})
+
return nil, fmt.Errorf("commits from ref: %w", err)
+
commits := []*object.Commit{}
+
ci.ForEach(func(c *object.Commit) error {
+
commits = append(commits, c)
+
func (g *GitRepo) LastCommit() (*object.Commit, error) {
+
c, err := g.r.CommitObject(g.h)
+
return nil, fmt.Errorf("last commit: %w", err)
+
func (g *GitRepo) FileContent(path string) (string, error) {
+
c, err := g.r.CommitObject(g.h)
+
return "", fmt.Errorf("commit object: %w", err)
+
return "", fmt.Errorf("file tree: %w", err)
+
file, err := tree.File(path)
+
isbin, _ := file.IsBinary()
+
return "Not displaying binary file", nil
+
func (g *GitRepo) Tags() ([]*TagReference, error) {
+
iter, err := g.r.Tags()
+
return nil, fmt.Errorf("tag objects: %w", err)
+
tags := make([]*TagReference, 0)
+
if err := iter.ForEach(func(ref *plumbing.Reference) error {
+
obj, err := g.r.TagObject(ref.Hash())
+
tags = append(tags, &TagReference{
+
case plumbing.ErrObjectNotFound:
+
tags = append(tags, &TagReference{
+
tagList := &TagList{r: g.r, refs: tags}
+
func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
+
bi, err := g.r.Branches()
+
return nil, fmt.Errorf("branchs: %w", err)
+
branches := []*plumbing.Reference{}
+
_ = bi.ForEach(func(ref *plumbing.Reference) error {
+
branches = append(branches, ref)
+
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
+
for _, b := range branches {
+
_, err := g.r.ResolveRevision(plumbing.Revision(b))
+
return "", fmt.Errorf("unable to find main branch")
+
// WriteTar writes itself from a tree into a binary tar file format.
+
// prefix is root folder to be appended.
+
func (g *GitRepo) WriteTar(w io.Writer, prefix string) error {
+
c, err := g.r.CommitObject(g.h)
+
return fmt.Errorf("commit object: %w", err)
+
walker := object.NewTreeWalker(tree, true, nil)
+
name, entry, err := walker.Next()
+
for ; err == nil; name, entry, err = walker.Next() {
+
info, err := newInfoWrapper(name, prefix, &entry, tree)
+
header, err := tar.FileInfoHeader(info, "")
+
err = tw.WriteHeader(header)
+
file, err := tree.File(name)
+
reader, err := file.Blob.Reader()
+
_, err = io.Copy(tw, reader)
+
entry *object.TreeEntry,
+
) (*infoWrapper, error) {
+
if entry.Mode.IsFile() {
+
file, err := tree.TreeEntryFile(entry)
+
mode = fs.FileMode(file.Mode)
+
size, err = tree.Size(name)
+
mode = fs.ModeDir | fs.ModePerm
+
fullname := path.Join(prefix, name)
+
modTime: time.Unix(0, 0),
+
func (i *infoWrapper) Name() string {
+
func (i *infoWrapper) Size() int64 {
+
func (i *infoWrapper) Mode() fs.FileMode {
+
func (i *infoWrapper) ModTime() time.Time {
+
func (i *infoWrapper) IsDir() bool {
+
func (i *infoWrapper) Sys() any {
+
func (t *TagReference) Name() string {
+
return t.ref.Name().Short()
+
func (t *TagReference) Message() string {