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