mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
0d12e37c96
* Include EXC0014 and fix issues. * Include EXC0012 and fix issues. Signed-off-by: Fabian Kammel <fk@edgeless.systems> Co-authored-by: Otto Bittner <cobittner@posteo.net>
177 lines
4.3 KiB
Go
177 lines
4.3 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package git
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
var (
|
|
versionRegex = regexp.MustCompile(`^v\d+\.\d+\.\d+(?:-pre)?$`)
|
|
tagReference = regexp.MustCompile(`^refs/tags/([^/]+)$`)
|
|
)
|
|
|
|
// Git represents a git repository.
|
|
type Git struct {
|
|
repo *git.Repository
|
|
}
|
|
|
|
// New opens the git repository in current directory.
|
|
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
|
|
}
|
|
return commitRef.Hash().String()[:12], commit.Author.When, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// BranchName of current HEAD.
|
|
func (g *Git) BranchName() (string, error) {
|
|
commitRef, err := g.repo.Head()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return commitRef.Name().Short(), nil
|
|
}
|
|
|
|
// 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 {
|
|
hash, err := g.tagRefToHash(ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
message, err := tagRefToName(ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tags[hash] = append(tags[hash], message)
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// tagRefToName extracts the name of a tag reference.
|
|
func tagRefToName(tagRef *plumbing.Reference) (string, error) {
|
|
matches := tagReference.FindStringSubmatch(tagRef.Name().String())
|
|
if len(matches) != 2 {
|
|
return "", errors.New("invalid tag reference")
|
|
}
|
|
return matches[1], nil
|
|
}
|