releaser-pleaser/git.go

133 lines
2.6 KiB
Go

package rp
import (
"context"
"errors"
"fmt"
"io"
"os"
"slices"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
)
const (
CommitSearchDepth = 50 // TODO: Increase
GitRemoteName = "origin"
)
type Commit struct {
Hash string
Message string
}
type Tag struct {
Hash string
Name string
}
func CloneRepo(ctx context.Context, cloneURL, branch string, auth transport.AuthMethod) (*git.Repository, error) {
dir, err := os.MkdirTemp("", "releaser-pleaser.*")
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory for repo clone: %w", err)
}
// TODO: Log tmpdir
fmt.Printf("Clone tmpdir: %s\n", dir)
repo, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
URL: cloneURL,
RemoteName: GitRemoteName,
ReferenceName: plumbing.NewBranchReferenceName(branch),
SingleBranch: false,
Auth: auth,
})
if err != nil {
return nil, fmt.Errorf("failed to clone repository: %w", err)
}
return repo, nil
}
func ReleasableCommits(repo *git.Repository) ([]Commit, *Tag, error) {
ref, err := repo.Head()
if err != nil {
return nil, nil, err
}
iter, err := repo.Log(&git.LogOptions{From: ref.Hash()})
if err != nil {
return nil, nil, err
}
tags, err := buildTagRefMap(repo)
if err != nil {
return nil, nil, err
}
commits := make([]Commit, 0)
var tag *Tag
for {
commit, err := iter.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, nil, err
}
if tagRef, exists := tags[commit.Hash]; exists {
// We found the nearest tag, return results
tagName, _ := strings.CutPrefix(tagRef.Name().String(), "refs/tags/")
tag = &Tag{
Hash: tagRef.Hash().String(),
Name: tagName,
}
break
}
commits = append(commits, Commit{
Hash: commit.Hash.String(),
Message: commit.Message,
})
}
// We discover the commits from HEAD, but want to process them in "chronological" order
slices.Reverse(commits)
return commits, tag, nil
}
// From go-git PR
func buildTagRefMap(r *git.Repository) (map[plumbing.Hash]*plumbing.Reference, error) {
iter, err := r.Tags()
if err != nil {
return nil, err
}
tags := map[plumbing.Hash]*plumbing.Reference{}
for {
t, err := iter.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
to, err := r.TagObject(t.Hash())
if errors.Is(err, plumbing.ErrObjectNotFound) {
// t is a lightweight tag
tags[t.Hash()] = t
} else if err != nil {
return nil, err
} else {
// t is an annotated tag
tags[to.Target] = t
}
}
return tags, nil
}