github.com/azunymous/cdx@v0.0.0-20201122180449-fbb46cc4d252/vcs/gogit/repo.go (about)

     1  // Package gogit interacts with git and git tags via the gogit git implementation
     2  package gogit
     3  
     4  import (
     5  	"github.com/azunymous/cdx/parse"
     6  	"github.com/blang/semver/v4"
     7  	"github.com/go-git/go-git/v5"
     8  	"github.com/go-git/go-git/v5/plumbing"
     9  	"github.com/sirupsen/logrus"
    10  	"regexp"
    11  	"sort"
    12  )
    13  
    14  // Repo is a VCS repository that can be manipulated
    15  type Repo struct {
    16  	gitRepo *git.Repository
    17  	log     logrus.StdLogger
    18  }
    19  
    20  // NewRepo returns a new repository, search recursively upwards for a git repository
    21  func NewRepo() (*Repo, error) {
    22  	gr, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
    23  
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	return &Repo{gitRepo: gr, log: logrus.New()}, nil
    28  }
    29  
    30  // TagsForHead returns sorted version tags at HEAD
    31  // If no promotion stage is provided, only unpromoted tags are returned.
    32  // Only the first provided promotion stage is used for filtering.
    33  func (r *Repo) TagsForHead(module string, stage ...string) ([]string, error) {
    34  	suffix := ""
    35  	if len(stage) > 0 && stage[0] != "" {
    36  		suffix = "\\+" + stage[0]
    37  	}
    38  
    39  	current, err := r.gitRepo.ResolveRevision("HEAD")
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	tags, err := r.gitRepo.Tags()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	regex, err := regexp.Compile("^" + module + "-[0-9]+\\.[0-9]+\\.[0-9]+" + suffix + "$")
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	var t []string
    54  	_ = tags.ForEach(func(reference *plumbing.Reference) error {
    55  		if (reference.Hash() == *current && regex.MatchString(reference.Name().Short())) || r.annotatedMatch(reference, current, regex) {
    56  			t = append(t, reference.Name().Short())
    57  		}
    58  		return nil
    59  	})
    60  	sort.Strings(t)
    61  	return t, nil
    62  }
    63  
    64  // TagsForModule returns sorted all semantic version tags for a module and a promotion stage.
    65  // If no promotion stage is provided, only unpromoted tags are returned.
    66  // Only the first provided promotion stage is used for filtering.
    67  func (r *Repo) TagsForModule(module string, stage ...string) ([]string, error) {
    68  	suffix := ""
    69  	if len(stage) > 0 && stage[0] != "" {
    70  		suffix = "\\+" + stage[0]
    71  	}
    72  
    73  	tags, err := r.gitRepo.Tags()
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	var t []string
    79  	regex, err := regexp.Compile("^" + module + "-[0-9]+\\.[0-9]+\\.[0-9]+" + suffix + "$")
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	_ = tags.ForEach(func(reference *plumbing.Reference) error {
    84  		if regex.MatchString(reference.Name().Short()) {
    85  			t = append(t, reference.Name().Short())
    86  		}
    87  		return nil
    88  	})
    89  	sortSemanticVers(t)
    90  	return t, nil
    91  }
    92  
    93  func sortSemanticVers(t []string) {
    94  	sort.Slice(t, func(i, j int) bool {
    95  		iVer := semver.MustParse(parse.Version(t[i]))
    96  		jVer := semver.MustParse(parse.Version(t[j]))
    97  		return iVer.LE(jVer)
    98  	})
    99  }
   100  
   101  func (r *Repo) annotatedMatch(reference *plumbing.Reference, hash *plumbing.Hash, regex *regexp.Regexp) bool {
   102  	obj, err := r.gitRepo.TagObject(reference.Hash())
   103  	if err != nil {
   104  		return false
   105  	}
   106  	commit, err := obj.Commit()
   107  	if err != nil {
   108  		return false
   109  	}
   110  	if commit.Hash == *hash && regex.MatchString(obj.Name) {
   111  		return true
   112  	}
   113  	return false
   114  }