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 }