github.com/git-chglog/git-chglog@v0.15.5-0.20240126074033-6a6993d52d69/tag_reader.go (about)

     1  package chglog
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/coreos/go-semver/semver"
    11  	gitcmd "github.com/tsuyoshiwada/go-gitcmd"
    12  )
    13  
    14  type tagReader struct {
    15  	client    gitcmd.Client
    16  	separator string
    17  	reFilter  *regexp.Regexp
    18  	sortBy    string
    19  }
    20  
    21  func newTagReader(client gitcmd.Client, filterPattern string, sort string) *tagReader {
    22  	return &tagReader{
    23  		client:    client,
    24  		separator: "@@__CHGLOG__@@",
    25  		reFilter:  regexp.MustCompile(filterPattern),
    26  		sortBy:    sort,
    27  	}
    28  }
    29  
    30  func (r *tagReader) ReadAll() ([]*Tag, error) {
    31  	out, err := r.client.Exec(
    32  		"for-each-ref",
    33  		"--format",
    34  		"%(refname)"+r.separator+"%(subject)"+r.separator+"%(taggerdate)"+r.separator+"%(authordate)",
    35  		"refs/tags",
    36  	)
    37  
    38  	tags := []*Tag{}
    39  
    40  	if err != nil {
    41  		return tags, fmt.Errorf("failed to get git-tag: %w", err)
    42  	}
    43  
    44  	lines := strings.Split(out, "\n")
    45  
    46  	for _, line := range lines {
    47  		tokens := strings.Split(line, r.separator)
    48  
    49  		if len(tokens) != 4 {
    50  			continue
    51  		}
    52  
    53  		name := r.parseRefname(tokens[0])
    54  		subject := r.parseSubject(tokens[1])
    55  		date, err := r.parseDate(tokens[2])
    56  		if err != nil {
    57  			t, err2 := r.parseDate(tokens[3])
    58  			if err2 != nil {
    59  				return nil, err2
    60  			}
    61  			date = t
    62  		}
    63  
    64  		if r.reFilter != nil {
    65  			if !r.reFilter.MatchString(name) {
    66  				continue
    67  			}
    68  		}
    69  
    70  		tags = append(tags, &Tag{
    71  			Name:    name,
    72  			Subject: subject,
    73  			Date:    date,
    74  		})
    75  	}
    76  
    77  	switch r.sortBy {
    78  	case "date":
    79  		r.sortTags(tags)
    80  	case "semver":
    81  		r.filterSemVerTags(&tags)
    82  		r.sortTagsBySemver(tags)
    83  	}
    84  	r.assignPreviousAndNextTag(tags)
    85  
    86  	return tags, nil
    87  }
    88  
    89  func (*tagReader) filterSemVerTags(tags *[]*Tag) {
    90  	// filter out any non-semver tags
    91  	for i, t := range *tags {
    92  		// remove leading v, since its so
    93  		// common.
    94  		name := strings.TrimPrefix(t.Name, "v")
    95  
    96  		// attempt semver parse, if not successful
    97  		// remove it from tags slice.
    98  		if _, err := semver.NewVersion(name); err != nil {
    99  			*tags = append((*tags)[:i], (*tags)[i+1:]...)
   100  		}
   101  	}
   102  }
   103  
   104  func (*tagReader) parseRefname(input string) string {
   105  	return strings.Replace(input, "refs/tags/", "", 1)
   106  }
   107  
   108  func (*tagReader) parseSubject(input string) string {
   109  	return strings.TrimSpace(input)
   110  }
   111  
   112  func (*tagReader) parseDate(input string) (time.Time, error) {
   113  	return time.Parse("Mon Jan 2 15:04:05 2006 -0700", input)
   114  }
   115  
   116  func (*tagReader) assignPreviousAndNextTag(tags []*Tag) {
   117  	total := len(tags)
   118  
   119  	for i, tag := range tags {
   120  		var (
   121  			next *RelateTag
   122  			prev *RelateTag
   123  		)
   124  
   125  		if i > 0 {
   126  			next = &RelateTag{
   127  				Name:    tags[i-1].Name,
   128  				Subject: tags[i-1].Subject,
   129  				Date:    tags[i-1].Date,
   130  			}
   131  		}
   132  
   133  		if i+1 < total {
   134  			prev = &RelateTag{
   135  				Name:    tags[i+1].Name,
   136  				Subject: tags[i+1].Subject,
   137  				Date:    tags[i+1].Date,
   138  			}
   139  		}
   140  
   141  		tag.Next = next
   142  		tag.Previous = prev
   143  	}
   144  }
   145  
   146  func (*tagReader) sortTags(tags []*Tag) {
   147  	sort.Slice(tags, func(i, j int) bool {
   148  		return !tags[i].Date.Before(tags[j].Date)
   149  	})
   150  }
   151  
   152  func (*tagReader) sortTagsBySemver(tags []*Tag) {
   153  	sort.Slice(tags, func(i, j int) bool {
   154  		semver1 := strings.TrimPrefix(tags[i].Name, "v")
   155  		semver2 := strings.TrimPrefix(tags[j].Name, "v")
   156  		v1 := semver.New(semver1)
   157  		v2 := semver.New(semver2)
   158  		return v2.LessThan(*v1)
   159  	})
   160  }