2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-07-14 09:45:04 -04:00
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"regexp"
|
2022-08-03 10:01:36 -04:00
|
|
|
"strings"
|
2022-07-14 09:45:04 -04:00
|
|
|
"time"
|
|
|
|
|
|
|
|
git "github.com/go-git/go-git/v5"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing/storer"
|
|
|
|
)
|
|
|
|
|
2022-07-14 11:16:38 -04:00
|
|
|
var (
|
2022-09-21 08:32:53 -04:00
|
|
|
versionRegex = regexp.MustCompile(`^v\d+\.\d+\.\d+(?:-pre)?$`)
|
2022-07-14 11:16:38 -04:00
|
|
|
tagReference = regexp.MustCompile(`^refs/tags/([^/]+)$`)
|
|
|
|
)
|
2022-07-14 09:45:04 -04:00
|
|
|
|
|
|
|
type Git struct {
|
|
|
|
repo *git.Repository
|
|
|
|
}
|
|
|
|
|
|
|
|
func New() (*Git, error) {
|
|
|
|
repo, err := git.PlainOpenWithOptions("", &git.PlainOpenOptions{DetectDotGit: true})
|
|
|
|
return &Git{repo: repo}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Revision returns the current revision (HEAD) of the repository in the format used by go pseudo versions.
|
|
|
|
func (g *Git) Revision() (string, time.Time, error) {
|
|
|
|
commitRef, err := g.repo.Head()
|
|
|
|
if err != nil {
|
|
|
|
return "", time.Time{}, err
|
|
|
|
}
|
|
|
|
commit, err := g.repo.CommitObject(commitRef.Hash())
|
|
|
|
if err != nil {
|
|
|
|
return "", time.Time{}, err
|
|
|
|
}
|
2022-08-02 04:47:04 -04:00
|
|
|
return commitRef.Hash().String()[:12], commit.Author.When, nil
|
2022-07-14 09:45:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// FirstParentWithVersionTag returns the first parent of the HEAD commit (or HEAD itself) that has a version tag.
|
|
|
|
func (g *Git) FirstParentWithVersionTag() (revision string, versionTag string, err error) {
|
|
|
|
commitRef, err := g.repo.Head()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
commit, err := g.repo.CommitObject(commitRef.Hash())
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
commitToHash, err := g.tagsByRevisionHash()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
iter := object.NewCommitIterCTime(commit, nil, nil)
|
|
|
|
if err := iter.ForEach(
|
|
|
|
func(c *object.Commit) error {
|
|
|
|
tags, ok := commitToHash[c.Hash.String()]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
version := g.findVersionTag(tags)
|
|
|
|
if version == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
versionTag = *version
|
|
|
|
revision = c.Hash.String()
|
|
|
|
return storer.ErrStop
|
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
if revision == "" || versionTag == "" {
|
|
|
|
return "", "", errors.New("no version tag found")
|
|
|
|
}
|
|
|
|
return revision, versionTag, nil
|
|
|
|
}
|
|
|
|
|
2022-08-03 10:01:36 -04:00
|
|
|
// ParsedBranchName returns the name of the current branch.
|
|
|
|
// Special characters are replaced with "-", and the name is lowercased and trimmed to 49 characters.
|
|
|
|
// This makes sure that the branch name is usable as a GCP image name.
|
|
|
|
func (g *Git) ParsedBranchName() (string, error) {
|
|
|
|
commitRef, err := g.repo.Head()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
rxp, err := regexp.Compile("[^a-zA-Z0-9-]+")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
branch := strings.ToLower(rxp.ReplaceAllString(commitRef.Name().Short(), "-"))
|
|
|
|
if len(branch) > 49 {
|
|
|
|
branch = branch[:49]
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSuffix(branch, "-"), nil
|
|
|
|
}
|
|
|
|
|
2022-08-19 09:55:44 -04:00
|
|
|
func (g *Git) BranchName() (string, error) {
|
|
|
|
commitRef, err := g.repo.Head()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return commitRef.Name().Short(), nil
|
|
|
|
}
|
|
|
|
|
2022-07-14 09:45:04 -04:00
|
|
|
// tagsByRevisionHash returns a map from revision hash to a list of associated tags.
|
|
|
|
func (g *Git) tagsByRevisionHash() (map[string][]string, error) {
|
|
|
|
tags := make(map[string][]string)
|
|
|
|
refs, err := g.repo.Tags()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := refs.ForEach(
|
|
|
|
func(ref *plumbing.Reference) error {
|
2022-09-21 08:32:53 -04:00
|
|
|
hash, err := g.tagRefToHash(ref)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-07-15 03:04:37 -04:00
|
|
|
message, err := tagRefToName(ref)
|
|
|
|
if err != nil {
|
2022-07-14 09:45:04 -04:00
|
|
|
return err
|
|
|
|
}
|
2022-09-21 08:32:53 -04:00
|
|
|
tags[hash] = append(tags[hash], message)
|
2022-07-14 09:45:04 -04:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return tags, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// findVersionTag tries to find a tag for a semantic version (e.g.: v1.0.0).
|
|
|
|
func (g *Git) findVersionTag(tags []string) *string {
|
|
|
|
for _, tag := range tags {
|
|
|
|
if versionRegex.MatchString(tag) {
|
|
|
|
return &tag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2022-07-14 11:16:38 -04:00
|
|
|
|
2022-09-21 08:32:53 -04:00
|
|
|
func (g *Git) tagRefToHash(tagRef *plumbing.Reference) (string, error) {
|
|
|
|
tagObject, err := g.repo.TagObject(tagRef.Hash())
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
return tagObject.Target.String(), nil
|
|
|
|
case plumbing.ErrObjectNotFound:
|
|
|
|
return tagRef.Hash().String(), nil
|
|
|
|
default:
|
|
|
|
// Some other error
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-15 03:04:37 -04:00
|
|
|
// tagRefToName extracts the name of a tag reference.
|
|
|
|
func tagRefToName(tagRef *plumbing.Reference) (string, error) {
|
2022-07-14 11:16:38 -04:00
|
|
|
matches := tagReference.FindStringSubmatch(tagRef.Name().String())
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return "", errors.New("invalid tag reference")
|
|
|
|
}
|
|
|
|
return matches[1], nil
|
|
|
|
}
|