github.phpd.cn/goreleaser/goreleaser@v0.92.0/internal/pipe/changelog/changelog.go (about) 1 // Package changelog provides the release changelog to goreleaser. 2 package changelog 3 4 import ( 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "path/filepath" 9 "regexp" 10 "sort" 11 "strings" 12 13 "github.com/apex/log" 14 15 "github.com/goreleaser/goreleaser/internal/git" 16 "github.com/goreleaser/goreleaser/internal/pipe" 17 "github.com/goreleaser/goreleaser/pkg/context" 18 ) 19 20 // ErrInvalidSortDirection happens when the sort order is invalid 21 var ErrInvalidSortDirection = errors.New("invalid sort direction") 22 23 // Pipe for checksums 24 type Pipe struct{} 25 26 func (Pipe) String() string { 27 return "generating changelog" 28 } 29 30 // Run the pipe 31 func (Pipe) Run(ctx *context.Context) error { 32 if ctx.ReleaseNotes != "" { 33 return pipe.Skip("release notes already provided via --release-notes") 34 } 35 if ctx.Snapshot { 36 return pipe.Skip("not available for snapshots") 37 } 38 if err := checkSortDirection(ctx.Config.Changelog.Sort); err != nil { 39 return err 40 } 41 entries, err := buildChangelog(ctx) 42 if err != nil { 43 return err 44 } 45 ctx.ReleaseNotes = fmt.Sprintf("## Changelog\n\n%v\n", strings.Join(entries, "\n")) 46 var path = filepath.Join(ctx.Config.Dist, "CHANGELOG.md") 47 log.WithField("changelog", path).Info("writing") 48 return ioutil.WriteFile(path, []byte(ctx.ReleaseNotes), 0644) 49 } 50 51 func checkSortDirection(mode string) error { 52 switch mode { 53 case "": 54 fallthrough 55 case "asc": 56 fallthrough 57 case "desc": 58 return nil 59 } 60 return ErrInvalidSortDirection 61 } 62 63 func buildChangelog(ctx *context.Context) ([]string, error) { 64 log, err := getChangelog(ctx.Git.CurrentTag) 65 if err != nil { 66 return nil, err 67 } 68 var entries = strings.Split(log, "\n") 69 entries = entries[0 : len(entries)-1] 70 entries, err = filterEntries(ctx, entries) 71 if err != nil { 72 return entries, err 73 } 74 return sortEntries(ctx, entries), nil 75 } 76 77 func filterEntries(ctx *context.Context, entries []string) ([]string, error) { 78 for _, filter := range ctx.Config.Changelog.Filters.Exclude { 79 r, err := regexp.Compile(filter) 80 if err != nil { 81 return entries, err 82 } 83 entries = remove(r, entries) 84 } 85 return entries, nil 86 } 87 88 func sortEntries(ctx *context.Context, entries []string) []string { 89 var direction = ctx.Config.Changelog.Sort 90 if direction == "" { 91 return entries 92 } 93 var result = make([]string, len(entries)) 94 copy(result, entries) 95 sort.Slice(result, func(i, j int) bool { 96 _, imsg := extractCommitInfo(result[i]) 97 _, jmsg := extractCommitInfo(result[j]) 98 if direction == "asc" { 99 return strings.Compare(imsg, jmsg) < 0 100 } 101 return strings.Compare(imsg, jmsg) > 0 102 }) 103 return result 104 } 105 106 func remove(filter *regexp.Regexp, entries []string) (result []string) { 107 for _, entry := range entries { 108 _, msg := extractCommitInfo(entry) 109 if !filter.MatchString(msg) { 110 result = append(result, entry) 111 } 112 } 113 return result 114 } 115 116 func extractCommitInfo(line string) (hash, msg string) { 117 ss := strings.Split(line, " ") 118 return ss[0], strings.Join(ss[1:], " ") 119 } 120 121 func getChangelog(tag string) (string, error) { 122 prev, err := previous(tag) 123 if err != nil { 124 return "", err 125 } 126 if isSHA1(prev) { 127 return gitLog(prev, tag) 128 } 129 return gitLog(fmt.Sprintf("tags/%s..tags/%s", prev, tag)) 130 } 131 132 func gitLog(refs ...string) (string, error) { 133 var args = []string{"log", "--pretty=oneline", "--abbrev-commit", "--no-decorate", "--no-color"} 134 args = append(args, refs...) 135 return git.Run(args...) 136 } 137 138 func previous(tag string) (result string, err error) { 139 result, err = git.Clean(git.Run("describe", "--tags", "--abbrev=0", fmt.Sprintf("tags/%s^", tag))) 140 if err != nil { 141 result, err = git.Clean(git.Run("rev-list", "--max-parents=0", "HEAD")) 142 } 143 return 144 } 145 146 var validSHA1 = regexp.MustCompile(`^[a-fA-F0-9]{40}$`) 147 148 // isSHA1 te lets us know if the ref is a SHA1 or not 149 func isSHA1(ref string) bool { 150 return validSHA1.MatchString(ref) 151 }