Repository: goreleaser/goreleaser
Pull request #2242 (2021-05-21 21:11:05)

feat: template release notes
closes https://github.com/goreleaser/goreleaser/issues/2196

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/goreleaser/goreleaser/2pMuWcWuts2KUzXcJsTf5qNmXRxd](https://vercel.com/goreleaser/goreleaser/2pMuWcWuts2KUzXcJsTf5qNmXRxd)  
✅ Preview: [https://goreleaser-git-notes-goreleaser.vercel.app](https://goreleaser-git-notes-goreleaser.vercel.app)


codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) Report
> Merging [#2242](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (58ae529) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/2d136c4c90bb7ffb538325868c3d1632db24513d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (2d136c4) will **increase** coverage by `0.09%`.
> The diff coverage is `94.44%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/2242/graphs/tree.svg?width=650&height=150&src=pr&token=3bJvv6b7hN&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)

```diff
@@            Coverage Diff             @@
##           master    #2242      +/-   ##
==========================================
+ Coverage   82.64%   82.74%   +0.09%     
==========================================
  Files          76       76              
  Lines        4190     4190              
==========================================
+ Hits         3463     3467       +4     
+ Misses        589      587       -2     
+ Partials      138      136       -2     
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) | Coverage Δ | |
|---|---|---|
| [pkg/context/context.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2242/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-cGtnL2NvbnRleHQvY29udGV4dC5nbw==) | `100.00% <ø> (ø)` | |
| [internal/pipe/changelog/changelog.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2242/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9jaGFuZ2Vsb2cvY2hhbmdlbG9nLmdv) | `96.22% <91.66%> (+3.36%)` | :arrow_up: |
| [cmd/release.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2242/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-Y21kL3JlbGVhc2UuZ28=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Last update [2d136c4...58ae529](https://codecov.io/gh/goreleaser/goreleaser/pull/2242?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser).


Patch:
diff --git a/cmd/release.go b/cmd/release.go
index 9c21c934..34ddbc41 100644
--- a/cmd/release.go
+++ b/cmd/release.go
@@ -19,18 +19,21 @@ type releaseCmd struct {
 }
 
 type releaseOpts struct {
-	config        string
-	releaseNotes  string
-	releaseHeader string
-	releaseFooter string
-	snapshot      bool
-	skipPublish   bool
-	skipSign      bool
-	skipValidate  bool
-	rmDist        bool
-	deprecated    bool
-	parallelism   int
-	timeout       time.Duration
+	config            string
+	releaseNotesFile  string
+	releaseNotesTmpl  string
+	releaseHeaderFile string
+	releaseHeaderTmpl string
+	releaseFooterFile string
+	releaseFooterTmpl string
+	snapshot          bool
+	skipPublish       bool
+	skipSign          bool
+	skipValidate      bool
+	rmDist            bool
+	deprecated        bool
+	parallelism       int
+	timeout           time.Duration
 }
 
 func newReleaseCmd() *releaseCmd {
@@ -63,9 +66,12 @@ func newReleaseCmd() *releaseCmd {
 	}
 
 	cmd.Flags().StringVarP(&root.opts.config, "config", "f", "", "Load configuration from file")
-	cmd.Flags().StringVar(&root.opts.releaseNotes, "release-notes", "", "Load custom release notes from a markdown file")
-	cmd.Flags().StringVar(&root.opts.releaseHeader, "release-header", "", "Load custom release notes header from a markdown file")
-	cmd.Flags().StringVar(&root.opts.releaseFooter, "release-footer", "", "Load custom release notes footer from a markdown file")
+	cmd.Flags().StringVar(&root.opts.releaseNotesFile, "release-notes", "", "Load custom release notes from a markdown file")
+	cmd.Flags().StringVar(&root.opts.releaseHeaderFile, "release-header", "", "Load custom release notes header from a markdown file")
+	cmd.Flags().StringVar(&root.opts.releaseFooterFile, "release-footer", "", "Load custom release notes footer from a markdown file")
+	cmd.Flags().StringVar(&root.opts.releaseNotesTmpl, "release-notes-tmpl", "", "Load custom release notes from a templated markdown file (overrides --release-notes)")
+	cmd.Flags().StringVar(&root.opts.releaseHeaderTmpl, "release-header-tmpl", "", "Load custom release notes header from a templated markdown file (overrides --release-header)")
+	cmd.Flags().StringVar(&root.opts.releaseFooterTmpl, "release-footer-tmpl", "", "Load custom release notes footer from a templated markdown file (overrides --release-footer)")
 	cmd.Flags().BoolVar(&root.opts.snapshot, "snapshot", false, "Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts")
 	cmd.Flags().BoolVar(&root.opts.skipPublish, "skip-publish", false, "Skips publishing artifacts")
 	cmd.Flags().BoolVar(&root.opts.skipSign, "skip-sign", false, "Skips signing the artifacts")
@@ -108,9 +114,12 @@ func setupReleaseContext(ctx *context.Context, options releaseOpts) *context.Con
 		ctx.Parallelism = options.parallelism
 	}
 	log.Debugf("parallelism: %v", ctx.Parallelism)
-	ctx.ReleaseNotes = options.releaseNotes
-	ctx.ReleaseHeader = options.releaseHeader
-	ctx.ReleaseFooter = options.releaseFooter
+	ctx.ReleaseNotesFile = options.releaseNotesFile
+	ctx.ReleaseNotesTmpl = options.releaseNotesTmpl
+	ctx.ReleaseHeaderFile = options.releaseHeaderFile
+	ctx.ReleaseHeaderTmpl = options.releaseHeaderTmpl
+	ctx.ReleaseFooterFile = options.releaseFooterFile
+	ctx.ReleaseFooterTmpl = options.releaseFooterTmpl
 	ctx.Snapshot = options.snapshot
 	ctx.SkipPublish = ctx.Snapshot || options.skipPublish
 	ctx.SkipValidate = ctx.Snapshot || options.skipValidate
diff --git a/internal/pipe/changelog/changelog.go b/internal/pipe/changelog/changelog.go
index 09d212d1..6d5205ff 100644
--- a/internal/pipe/changelog/changelog.go
+++ b/internal/pipe/changelog/changelog.go
@@ -29,21 +29,12 @@ func (Pipe) String() string {
 
 // Run the pipe.
 func (Pipe) Run(ctx *context.Context) error {
-	// TODO: should probably have a different field for the filename and its
-	// contents.
-	if ctx.ReleaseNotes != "" {
-		notes, err := loadFromFile(ctx.ReleaseNotes)
-		if err != nil {
-			return err
-		}
-		notes, err = tmpl.New(ctx).Apply(notes)
-		if err != nil {
-			return err
-		}
-		log.WithField("file", ctx.ReleaseNotes).Info("loaded custom release notes")
-		log.WithField("file", ctx.ReleaseNotes).Debugf("custom release notes: \n%s", notes)
-		ctx.ReleaseNotes = notes
+	notes, err := loadContent(ctx, ctx.ReleaseNotesFile, ctx.ReleaseNotesTmpl)
+	if err != nil {
+		return err
 	}
+	ctx.ReleaseNotes = notes
+
 	if ctx.Config.Changelog.Skip {
 		return pipe.ErrSkipDisabledPipe
 	}
@@ -53,27 +44,15 @@ func (Pipe) Run(ctx *context.Context) error {
 	if ctx.ReleaseNotes != "" {
 		return nil
 	}
-	if ctx.ReleaseHeader != "" {
-		header, err := loadFromFile(ctx.ReleaseHeader)
-		if err != nil {
-			return err
-		}
-		header, err = tmpl.New(ctx).Apply(header)
-		if err != nil {
-			return err
-		}
-		ctx.ReleaseHeader = header
+
+	footer, err := loadContent(ctx, ctx.ReleaseFooterFile, ctx.ReleaseFooterTmpl)
+	if err != nil {
+		return err
 	}
-	if ctx.ReleaseFooter != "" {
-		footer, err := loadFromFile(ctx.ReleaseFooter)
-		if err != nil {
-			return err
-		}
-		footer, err = tmpl.New(ctx).Apply(footer)
-		if err != nil {
-			return err
-		}
-		ctx.ReleaseFooter = footer
+
+	header, err := loadContent(ctx, ctx.ReleaseHeaderFile, ctx.ReleaseHeaderTmpl)
+	if err != nil {
+		return err
 	}
 
 	if err := checkSortDirection(ctx.Config.Changelog.Sort); err != nil {
@@ -97,11 +76,11 @@ func (Pipe) Run(ctx *context.Context) error {
 		"## Changelog",
 		strings.Join(entries, changelogStringJoiner),
 	}
-	if len(ctx.ReleaseHeader) > 0 {
-		changelogElements = append([]string{ctx.ReleaseHeader}, changelogElements...)
+	if header != "" {
+		changelogElements = append([]string{header}, changelogElements...)
 	}
-	if len(ctx.ReleaseFooter) > 0 {
-		changelogElements = append(changelogElements, ctx.ReleaseFooter)
+	if footer != "" {
+		changelogElements = append(changelogElements, footer)
 	}
 
 	ctx.ReleaseNotes = strings.Join(changelogElements, "\n\n")
@@ -225,3 +204,21 @@ var validSHA1 = regexp.MustCompile(`^[a-fA-F0-9]{40}$`)
 func isSHA1(ref string) bool {
 	return validSHA1.MatchString(ref)
 }
+
+func loadContent(ctx *context.Context, fileName, tmplName string) (string, error) {
+	if tmplName != "" {
+		log.Debugf("loading template %s", tmplName)
+		content, err := loadFromFile(tmplName)
+		if err != nil {
+			return "", err
+		}
+		return tmpl.New(ctx).Apply(content)
+	}
+
+	if fileName != "" {
+		log.Debugf("loading file %s", fileName)
+		return loadFromFile(fileName)
+	}
+
+	return "", nil
+}
diff --git a/pkg/context/context.go b/pkg/context/context.go
index ffeeca39..29f34f94 100644
--- a/pkg/context/context.go
+++ b/pkg/context/context.go
@@ -73,8 +73,12 @@ type Context struct {
 	Date               time.Time
 	Artifacts          artifact.Artifacts
 	ReleaseNotes       string
-	ReleaseHeader      string
-	ReleaseFooter      string
+	ReleaseNotesFile   string
+	ReleaseNotesTmpl   string
+	ReleaseHeaderFile  string
+	ReleaseHeaderTmpl  string
+	ReleaseFooterFile  string
+	ReleaseFooterTmpl  string
 	Version            string
 	ModulePath         string
 	Snapshot           bool
diff --git a/www/docs/cmd/goreleaser_release.md b/www/docs/cmd/goreleaser_release.md
index d01d854b..38faec81 100644
--- a/www/docs/cmd/goreleaser_release.md
+++ b/www/docs/cmd/goreleaser_release.md
@@ -9,18 +9,21 @@ goreleaser release [flags]
 ## Options
 
 ```
-  -f, --config string           Load configuration from file
-  -h, --help                    help for release
-  -p, --parallelism int         Amount tasks to run concurrently (default: number of CPUs)
-      --release-footer string   Load custom release notes footer from a markdown file
-      --release-header string   Load custom release notes header from a markdown file
-      --release-notes string    Load custom release notes from a markdown file
-      --rm-dist                 Remove the dist folder before building
-      --skip-publish            Skips publishing artifacts
-      --skip-sign               Skips signing the artifacts
-      --skip-validate           Skips several sanity checks
-      --snapshot                Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts
-      --timeout duration        Timeout to the entire release process (default 30m0s)
+  -f, --config string                Load configuration from file
+  -h, --help                         help for release
+  -p, --parallelism int              Amount tasks to run concurrently (default: number of CPUs)
+      --release-footer string        Load custom release notes footer from a markdown file
+      --release-footer-tmpl string   Load custom release notes footer from a templated markdown file (overrides --release-footer)
+      --release-header string        Load custom release notes header from a markdown file
+      --release-header-tmpl string   Load custom release notes header from a templated markdown file (overrides --release-header)
+      --release-notes string         Load custom release notes from a markdown file
+      --release-notes-tmpl string    Load custom release notes from a templated markdown file (overrides --release-notes)
+      --rm-dist                      Remove the dist folder before building
+      --skip-publish                 Skips publishing artifacts
+      --skip-sign                    Skips signing the artifacts
+      --skip-validate                Skips several sanity checks
+      --snapshot                     Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts
+      --timeout duration             Timeout to the entire release process (default 30m0s)
 ```
 
 ## Options inherited from parent commands


Test patch:
diff --git a/cmd/release_test.go b/cmd/release_test.go
index 90e5adb5..bcf19ec1 100644
--- a/cmd/release_test.go
+++ b/cmd/release_test.go
@@ -67,13 +67,27 @@ func TestReleaseFlags(t *testing.T) {
 		header := "header.md"
 		footer := "footer.md"
 		ctx := setup(releaseOpts{
-			releaseNotes:  notes,
-			releaseHeader: header,
-			releaseFooter: footer,
+			releaseNotesFile:  notes,
+			releaseHeaderFile: header,
+			releaseFooterFile: footer,
 		})
-		require.Equal(t, notes, ctx.ReleaseNotes)
-		require.Equal(t, header, ctx.ReleaseHeader)
-		require.Equal(t, footer, ctx.ReleaseFooter)
+		require.Equal(t, notes, ctx.ReleaseNotesFile)
+		require.Equal(t, header, ctx.ReleaseHeaderFile)
+		require.Equal(t, footer, ctx.ReleaseFooterFile)
+	})
+
+	t.Run("templated notes", func(t *testing.T) {
+		notes := "foo.md"
+		header := "header.md"
+		footer := "footer.md"
+		ctx := setup(releaseOpts{
+			releaseNotesTmpl:  notes,
+			releaseHeaderTmpl: header,
+			releaseFooterTmpl: footer,
+		})
+		require.Equal(t, notes, ctx.ReleaseNotesTmpl)
+		require.Equal(t, header, ctx.ReleaseHeaderTmpl)
+		require.Equal(t, footer, ctx.ReleaseFooterTmpl)
 	})
 
 	t.Run("rm dist", func(t *testing.T) {
diff --git a/internal/pipe/changelog/changelog_test.go b/internal/pipe/changelog/changelog_test.go
index aa5182f3..39dad118 100644
--- a/internal/pipe/changelog/changelog_test.go
+++ b/internal/pipe/changelog/changelog_test.go
@@ -18,14 +18,15 @@ func TestDescription(t *testing.T) {
 
 func TestChangelogProvidedViaFlag(t *testing.T) {
 	ctx := context.New(config.Project{})
-	ctx.ReleaseNotes = "testdata/changes.md"
+	ctx.ReleaseNotesFile = "testdata/changes.md"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes)
 }
 
 func TestTemplatedChangelogProvidedViaFlag(t *testing.T) {
 	ctx := context.New(config.Project{})
-	ctx.ReleaseNotes = "testdata/changes-templated.md"
+	ctx.ReleaseNotesFile = "testdata/changes.md"
+	ctx.ReleaseNotesTmpl = "testdata/changes-templated.md"
 	ctx.Git.CurrentTag = "v0.0.1"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Equal(t, "c0ff33 coffeee v0.0.1\n", ctx.ReleaseNotes)
@@ -37,14 +38,14 @@ func TestChangelogProvidedViaFlagAndSkipEnabled(t *testing.T) {
 			Skip: true,
 		},
 	})
-	ctx.ReleaseNotes = "testdata/changes.md"
+	ctx.ReleaseNotesFile = "testdata/changes.md"
 	testlib.AssertSkipped(t, Pipe{}.Run(ctx))
 	require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes)
 }
 
 func TestChangelogProvidedViaFlagDoesntExist(t *testing.T) {
 	ctx := context.New(config.Project{})
-	ctx.ReleaseNotes = "testdata/changes.nope"
+	ctx.ReleaseNotesFile = "testdata/changes.nope"
 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/changes.nope: no such file or directory")
 }
 
@@ -56,13 +57,13 @@ func TestChangelogSkip(t *testing.T) {
 
 func TestReleaseHeaderProvidedViaFlagDoesntExist(t *testing.T) {
 	ctx := context.New(config.Project{})
-	ctx.ReleaseHeader = "testdata/header.nope"
+	ctx.ReleaseHeaderFile = "testdata/header.nope"
 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/header.nope: no such file or directory")
 }
 
 func TestReleaseFooterProvidedViaFlagDoesntExist(t *testing.T) {
 	ctx := context.New(config.Project{})
-	ctx.ReleaseFooter = "testdata/footer.nope"
+	ctx.ReleaseFooterFile = "testdata/footer.nope"
 	require.EqualError(t, Pipe{}.Run(ctx), "open testdata/footer.nope: no such file or directory")
 }
 
@@ -341,7 +342,7 @@ func TestChangeLogWithReleaseHeader(t *testing.T) {
 	testlib.GitCheckoutBranch(t, "v0.0.1")
 	ctx := context.New(config.Project{})
 	ctx.Git.CurrentTag = "v0.0.1"
-	ctx.ReleaseHeader = "testdata/release-header.md"
+	ctx.ReleaseHeaderFile = "testdata/release-header.md"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
 	require.Contains(t, ctx.ReleaseNotes, "test header")
@@ -366,7 +367,7 @@ func TestChangeLogWithTemplatedReleaseHeader(t *testing.T) {
 	testlib.GitCheckoutBranch(t, "v0.0.1")
 	ctx := context.New(config.Project{})
 	ctx.Git.CurrentTag = "v0.0.1"
-	ctx.ReleaseHeader = "testdata/release-header-templated.md"
+	ctx.ReleaseHeaderTmpl = "testdata/release-header-templated.md"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
 	require.Contains(t, ctx.ReleaseNotes, "test header with tag v0.0.1")
@@ -391,7 +392,7 @@ func TestChangeLogWithReleaseFooter(t *testing.T) {
 	testlib.GitCheckoutBranch(t, "v0.0.1")
 	ctx := context.New(config.Project{})
 	ctx.Git.CurrentTag = "v0.0.1"
-	ctx.ReleaseFooter = "testdata/release-footer.md"
+	ctx.ReleaseFooterFile = "testdata/release-footer.md"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
 	require.Contains(t, ctx.ReleaseNotes, "test footer")
@@ -417,7 +418,7 @@ func TestChangeLogWithTemplatedReleaseFooter(t *testing.T) {
 	testlib.GitCheckoutBranch(t, "v0.0.1")
 	ctx := context.New(config.Project{})
 	ctx.Git.CurrentTag = "v0.0.1"
-	ctx.ReleaseFooter = "testdata/release-footer-templated.md"
+	ctx.ReleaseFooterTmpl = "testdata/release-footer-templated.md"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
 	require.Contains(t, ctx.ReleaseNotes, "test footer with tag v0.0.1")