Skip to content

Commit

Permalink
feat: Added new flags to support release notes header and footer. (#1212
Browse files Browse the repository at this point in the history
)

* Added new flags to support release notes header and footer.

Created two flags for release generation.
--release-footer
--release-header
These flags can help you to add custom changelog text before/after changes that are generated by git log.

* Fix changelog.go to avoid lint errors

* Fix test typo

* Added tests for main, fixed bug with no passing options to release ctx

* Add @caarlos0  suggestions
  • Loading branch information
jnovikov authored and caarlos0 committed Nov 15, 2019
1 parent fdfe048 commit 16cb4d8
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 39 deletions.
32 changes: 29 additions & 3 deletions internal/pipe/changelog/changelog.go
Expand Up @@ -35,6 +35,8 @@ func (Pipe) Run(ctx *context.Context) error {
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
}
if ctx.Config.Changelog.Skip {
Expand All @@ -46,9 +48,25 @@ 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
}
ctx.ReleaseHeader = header
}
if ctx.ReleaseFooter != "" {
footer, err := loadFromFile(ctx.ReleaseFooter)
if err != nil {
return err
}
ctx.ReleaseFooter = footer
}

if err := checkSortDirection(ctx.Config.Changelog.Sort); err != nil {
return err
}

entries, err := buildChangelog(ctx)
if err != nil {
return err
Expand All @@ -61,7 +79,17 @@ func (Pipe) Run(ctx *context.Context) error {
log.Debug("is gitlab or gitea changelog")
changelogStringJoiner = " \n"
}
ctx.ReleaseNotes = fmt.Sprintf("## Changelog\n\n%v\n", strings.Join(entries, changelogStringJoiner))

ctx.ReleaseNotes = strings.Join(
[]string{
ctx.ReleaseHeader,
"## Changelog",
strings.Join(entries, changelogStringJoiner),
ctx.ReleaseFooter,
},
"\n\n",
)

var path = filepath.Join(ctx.Config.Dist, "CHANGELOG.md")
log.WithField("changelog", path).Info("writing")
return ioutil.WriteFile(path, []byte(ctx.ReleaseNotes), 0644)
Expand All @@ -72,8 +100,6 @@ func loadFromFile(file string) (string, error) {
if err != nil {
return "", err
}
log.WithField("file", file).Info("loaded custom release notes")
log.WithField("file", file).Debugf("custom release notes: \n%s", string(bts))
return string(bts), nil
}

Expand Down
65 changes: 65 additions & 0 deletions internal/pipe/changelog/changelog_test.go
Expand Up @@ -2,6 +2,7 @@ package changelog

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

Expand Down Expand Up @@ -46,6 +47,18 @@ func TestChangelogSkip(t *testing.T) {
testlib.AssertSkipped(t, Pipe{}.Run(ctx))
}

func TestReleaseHeaderProvidedViaFlagDoesntExist(t *testing.T) {
var ctx = context.New(config.Project{})
ctx.ReleaseHeader = "testdata/header.nope"
require.EqualError(t, Pipe{}.Run(ctx), "open testdata/header.nope: no such file or directory")
}

func TestReleaseFooterProvidedViaFlagDoesntExist(t *testing.T) {
var ctx = context.New(config.Project{})
ctx.ReleaseFooter = "testdata/footer.nope"
require.EqualError(t, Pipe{}.Run(ctx), "open testdata/footer.nope: no such file or directory")
}

func TestSnapshot(t *testing.T) {
var ctx = context.New(config.Project{})
ctx.Snapshot = true
Expand Down Expand Up @@ -285,3 +298,55 @@ func TestChangelogOnBranchWithSameNameAsTag(t *testing.T) {
require.Contains(t, ctx.ReleaseNotes, msg)
}
}

func TestChangeLogWithReleaseHeader(t *testing.T) {
current, err := os.Getwd()
require.NoError(t, err)
tmpdir, back := testlib.Mktmp(t)
defer back()
require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
testlib.GitInit(t)
var msgs = []string{
"initial commit",
"another one",
"one more",
"and finally this one",
}
for _, msg := range msgs {
testlib.GitCommit(t, msg)
}
testlib.GitTag(t, "v0.0.1")
testlib.GitCheckoutBranch(t, "v0.0.1")
var ctx = context.New(config.Project{})
ctx.Git.CurrentTag = "v0.0.1"
ctx.ReleaseHeader = "testdata/release-header.md"
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.Contains(t, ctx.ReleaseNotes, "test header")
}

func TestChangeLogWithReleaseFooter(t *testing.T) {
current, err := os.Getwd()
require.NoError(t, err)
tmpdir, back := testlib.Mktmp(t)
defer back()
require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
testlib.GitInit(t)
var msgs = []string{
"initial commit",
"another one",
"one more",
"and finally this one",
}
for _, msg := range msgs {
testlib.GitCommit(t, msg)
}
testlib.GitTag(t, "v0.0.1")
testlib.GitCheckoutBranch(t, "v0.0.1")
var ctx = context.New(config.Project{})
ctx.Git.CurrentTag = "v0.0.1"
ctx.ReleaseFooter = "testdata/release-footer.md"
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.Contains(t, ctx.ReleaseNotes, "test footer")
}
1 change: 1 addition & 0 deletions internal/pipe/changelog/testdata/release-footer.md
@@ -0,0 +1 @@
test footer
1 change: 1 addition & 0 deletions internal/pipe/changelog/testdata/release-header.md
@@ -0,0 +1 @@
test header
46 changes: 27 additions & 19 deletions main.go
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/goreleaser/goreleaser/pkg/defaults"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/alecthomas/kingpin.v2"
)

// nolint: gochecknoglobals
Expand All @@ -28,15 +28,17 @@ var (
)

type releaseOptions struct {
Config string
ReleaseNotes string
Snapshot bool
SkipPublish bool
SkipSign bool
SkipValidate bool
RmDist bool
Parallelism int
Timeout time.Duration
Config string
ReleaseNotes string
ReleaseHeader string
ReleaseFooter string
Snapshot bool
SkipPublish bool
SkipSign bool
SkipValidate bool
RmDist bool
Parallelism int
Timeout time.Duration
}

func main() {
Expand All @@ -56,6 +58,8 @@ func main() {
var checkCmd = app.Command("check", "Checks if configuration is valid").Alias("c")
var releaseCmd = app.Command("release", "Releases the current project").Alias("r").Default()
var releaseNotes = releaseCmd.Flag("release-notes", "Load custom release notes from a markdown file").PlaceHolder("notes.md").String()
var releaseHeader = releaseCmd.Flag("release-header", "Load custom release notes header from a markdown file").PlaceHolder("notes-header.md").String()
var releaseFooter = releaseCmd.Flag("release-footer", "Load custom release notes footer from a markdown file").PlaceHolder("notes-footer.md").String()
var snapshot = releaseCmd.Flag("snapshot", "Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts").Bool()
var skipPublish = releaseCmd.Flag("skip-publish", "Skips publishing artifacts").Bool()
var skipSign = releaseCmd.Flag("skip-sign", "Skips signing the artifacts").Bool()
Expand Down Expand Up @@ -96,15 +100,17 @@ func main() {
start := time.Now()
log.Infof(color.New(color.Bold).Sprintf("releasing using goreleaser %s...", version))
var options = releaseOptions{
Config: *config,
ReleaseNotes: *releaseNotes,
Snapshot: *snapshot,
SkipPublish: *skipPublish,
SkipValidate: *skipValidate,
SkipSign: *skipSign,
RmDist: *rmDist,
Parallelism: *parallelism,
Timeout: *timeout,
Config: *config,
ReleaseNotes: *releaseNotes,
ReleaseHeader: *releaseHeader,
ReleaseFooter: *releaseFooter,
Snapshot: *snapshot,
SkipPublish: *skipPublish,
SkipValidate: *skipValidate,
SkipSign: *skipSign,
RmDist: *rmDist,
Parallelism: *parallelism,
Timeout: *timeout,
}
if err := releaseProject(options); err != nil {
log.WithError(err).Errorf(color.New(color.Bold).Sprintf("release failed after %0.2fs", time.Since(start).Seconds()))
Expand Down Expand Up @@ -141,6 +147,8 @@ func releaseProject(options releaseOptions) error {
ctx.Parallelism = options.Parallelism
log.Debugf("parallelism: %v", ctx.Parallelism)
ctx.ReleaseNotes = options.ReleaseNotes
ctx.ReleaseHeader = options.ReleaseHeader
ctx.ReleaseFooter = options.ReleaseFooter
ctx.Snapshot = options.Snapshot
ctx.SkipPublish = ctx.Snapshot || options.SkipPublish
ctx.SkipValidate = ctx.Snapshot || options.SkipValidate
Expand Down
46 changes: 45 additions & 1 deletion main_test.go
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)

func init() {
Expand Down Expand Up @@ -107,6 +107,50 @@ func TestCustomReleaseNotesFile(t *testing.T) {
assert.NoError(t, releaseProject(params))
}

func TestCustomReleaseHeaderFileDontExist(t *testing.T) {
_, back := setup(t)
defer back()
params := testParams()
params.ReleaseHeader = "/header/that/dont/exist"
params.Snapshot = false
assert.Error(t, releaseProject(params))
}

func TestCustomReleaseHeaderFile(t *testing.T) {
_, back := setup(t)
defer back()
releaseHeader, err := ioutil.TempFile("", "")
assert.NoError(t, err)
createFile(t, releaseHeader.Name(), "some release header")
params := testParams()
params.ReleaseHeader = releaseHeader.Name()
params.Snapshot = false
params.SkipPublish = true
assert.NoError(t, releaseProject(params))
}

func TestCustomReleaseFooterFileDontExist(t *testing.T) {
_, back := setup(t)
defer back()
params := testParams()
params.ReleaseFooter = "/footer/that/dont/exist"
params.Snapshot = false
assert.Error(t, releaseProject(params))
}

func TestCustomReleaseFooterFile(t *testing.T) {
_, back := setup(t)
defer back()
releaseFooter, err := ioutil.TempFile("", "")
assert.NoError(t, err)
createFile(t, releaseFooter.Name(), "some release footer")
params := testParams()
params.ReleaseFooter = releaseFooter.Name()
params.Snapshot = false
params.SkipPublish = true
assert.NoError(t, releaseProject(params))
}

func TestBrokenPipe(t *testing.T) {
_, back := setup(t)
defer back()
Expand Down
34 changes: 18 additions & 16 deletions pkg/context/context.go
Expand Up @@ -53,22 +53,24 @@ const (
// Context carries along some data through the pipes
type Context struct {
ctx.Context
Config config.Project
Env Env
Token string
TokenType TokenType
Git GitInfo
Artifacts artifact.Artifacts
ReleaseNotes string
Version string
Snapshot bool
SkipPublish bool
SkipSign bool
SkipValidate bool
RmDist bool
PreRelease bool
Parallelism int
Semver Semver
Config config.Project
Env Env
Token string
TokenType TokenType
Git GitInfo
Artifacts artifact.Artifacts
ReleaseNotes string
ReleaseHeader string
ReleaseFooter string
Version string
Snapshot bool
SkipPublish bool
SkipSign bool
SkipValidate bool
RmDist bool
PreRelease bool
Parallelism int
Semver Semver
}

// Semver represents a semantic version
Expand Down

0 comments on commit 16cb4d8

Please sign in to comment.