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  }