github.com/wesleimp/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  }