Files
go-semantic-release/internal/gitutil/gitutil.go
Sebastian Beisch 2cd24777b3 fix(internal): improve git log for commits on current branch since last release
At the moment when PRs where done in parallel it could happen that some commits where ignored in the release. This should be fixed now
2021-04-28 08:43:10 +02:00

181 lines
3.9 KiB
Go

// Package gitutil provides helper methods for git
package gitutil
import (
"fmt"
"github.com/pkg/errors"
"sort"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
log "github.com/sirupsen/logrus"
)
// GitUtil struct
type GitUtil struct {
Repository *git.Repository
}
// New GitUtil struct and open git repository
func New(folder string) (*GitUtil, error) {
r, err := git.PlainOpen(folder)
if err != nil {
return nil, err
}
utils := &GitUtil{
Repository: r,
}
return utils, nil
}
// GetHash from git HEAD
func (g *GitUtil) GetHash() (string, error) {
ref, err := g.Repository.Head()
if err != nil {
return "", err
}
return ref.Hash().String(), nil
}
// GetBranch from git HEAD
func (g *GitUtil) GetBranch() (string, error) {
ref, err := g.Repository.Head()
if err != nil {
return "", err
}
if !ref.Name().IsBranch() {
branches, err := g.Repository.Branches()
if err != nil {
return "", err
}
var currentBranch string
found := branches.ForEach(func(p *plumbing.Reference) error {
if p.Name().IsBranch() && p.Name().Short() != "origin" {
currentBranch = p.Name().Short()
return fmt.Errorf("break")
}
return nil
})
if found != nil {
log.Debugf("Found branch from HEAD %s", currentBranch)
return currentBranch, nil
}
return "", fmt.Errorf("no branch found, found %s, please checkout a branch (git checkout -b <BRANCH>)", ref.Name().String())
}
log.Debugf("Found branch %s", ref.Name().Short())
return ref.Name().Short(), nil
}
// GetLastVersion from git tags
func (g *GitUtil) GetLastVersion() (*semver.Version, *plumbing.Reference, error) {
var tags []*semver.Version
gitTags, err := g.Repository.Tags()
if err != nil {
return nil, nil, err
}
err = gitTags.ForEach(func(p *plumbing.Reference) error {
v, err := semver.NewVersion(p.Name().Short())
log.Tracef("Tag %+v with hash: %s", p.Name().Short(), p.Hash())
if err == nil {
tags = append(tags, v)
} else {
log.Debugf("Tag %s is not a valid version, skip", p.Name().Short())
}
return nil
})
if err != nil {
return nil, nil, err
}
sort.Sort(sort.Reverse(semver.Collection(tags)))
if len(tags) == 0 {
log.Debugf("Found no tags")
return nil, nil, nil
}
log.Debugf("Found old version %s", tags[0].String())
tag, err := g.Repository.Tag(tags[0].Original())
if err != nil {
return nil, nil, err
}
log.Debugf("Found old hash %s", tag.Hash().String())
return tags[0], tag, nil
}
// GetCommits from git hash to HEAD
func (g *GitUtil) GetCommits(lastTagHash *plumbing.Reference) ([]shared.Commit, error) {
ref, err := g.Repository.Head()
if err != nil {
return nil, err
}
excludeIter, err := g.Repository.Log(&git.LogOptions{From: lastTagHash.Hash()})
if err != nil {
return nil, err
}
startCommit, err := g.Repository.CommitObject(ref.Hash())
if err != nil {
return nil, err
}
seen := map[plumbing.Hash]struct{}{}
err = excludeIter.ForEach(func(c *object.Commit) error {
seen[c.Hash] = struct{}{}
return nil
})
if err != nil {
return nil, err
}
var isValid object.CommitFilter = func(commit *object.Commit) bool {
_, ok := seen[commit.Hash]
return !ok && len(commit.ParentHashes) < 2
}
cIter := object.NewFilterCommitIter(startCommit, &isValid, nil)
commits := make(map[string]shared.Commit)
err = cIter.ForEach(func(c *object.Commit) error {
log.Debugf("Found commit with hash %s", c.Hash.String())
commits[c.Hash.String()] = shared.Commit{
Message: c.Message,
Author: c.Committer.Name,
Hash: c.Hash.String(),
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "Could not read commits, check git clone depth in your ci")
}
l := make([]shared.Commit, 0)
for _, value := range commits {
l = append(l, value)
}
return l, nil
}