Repository: goreleaser/goreleaser
Pull request #3203 (2022-06-25 20:31:06)

feat: changelog group improvements
This changes the changelog grouping feature a bit: it evaluates the
groups in the order they are declared in the config file, but renders
them following their `order` attribute.

This allows to have better filters, for instance, you might want to have
dependabot commits in another section, which does not need a high
priority in the changelog output, but does when evaluating, as it might
share the words with other groups: in goreleaser's case, the `feat`
prefix.

Giving that Go's regex seems to don't allow negative look-aheads (which
would help in this particular case), this seems like a good quality of
life improvement.


codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/3203?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) Report
> Merging [#3203](https://codecov.io/gh/goreleaser/goreleaser/pull/3203?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (62582ec) into [main](https://codecov.io/gh/goreleaser/goreleaser/commit/ad2986a2e1334b19f3f33522db8fdeefcd5b1c58?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (ad2986a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##             main    #3203   +/-   ##
=======================================
  Coverage   84.20%   84.20%           
=======================================
  Files         113      113           
  Lines        9267     9271    +4     
=======================================
+ Hits         7803     7807    +4     
  Misses       1183     1183           
  Partials      281      281           
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/3203?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) | Coverage Δ | |
|---|---|---|
| [internal/pipe/changelog/changelog.go](https://codecov.io/gh/goreleaser/goreleaser/pull/3203/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9jaGFuZ2Vsb2cvY2hhbmdlbG9nLmdv) | `91.01% <100.00%> (+0.13%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/3203?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/3203?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Last update [ad2986a...62582ec](https://codecov.io/gh/goreleaser/goreleaser/pull/3203?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/.goreleaser.yaml b/.goreleaser.yaml
index f8a3b015..6a659514 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -52,17 +52,21 @@ changelog:
     - Merge branch
     - go mod tidy
   groups:
-    - title: 'New Features and updates'
+    - title: Dependency updates
+      regexp: "^.*feat\\(deps\\)*:+.*$"
+      order: 300
+    - title: 'New Features'
       regexp: "^.*feat[(\\w)]*:+.*$"
-      order: 0
+      order: 100
     - title: 'Bug fixes'
       regexp: "^.*fix[(\\w)]*:+.*$"
-      order: 10
+      order: 200
     - title: 'Documentation updates'
       regexp: "^.*docs[(\\w)]*:+.*$"
-      order: 20
+      order: 400
     - title: Other work
-      order: 999
+      order: 9999
+
 
 
 dockers:
diff --git a/internal/pipe/changelog/changelog.go b/internal/pipe/changelog/changelog.go
index fb12c893..059f6394 100644
--- a/internal/pipe/changelog/changelog.go
+++ b/internal/pipe/changelog/changelog.go
@@ -95,6 +95,12 @@ func (Pipe) Run(ctx *context.Context) error {
 	return os.WriteFile(path, []byte(ctx.ReleaseNotes), 0o644) //nolint: gosec
 }
 
+type changelogGroup struct {
+	title   string
+	entries []string
+	order   int
+}
+
 func formatChangelog(ctx *context.Context, entries []string) (string, error) {
 	newLine := "\n"
 	if ctx.TokenType == context.TokenTypeGitLab || ctx.TokenType == context.TokenTypeGitea {
@@ -115,14 +121,15 @@ func formatChangelog(ctx *context.Context, entries []string) (string, error) {
 	}
 
 	log.Debug("grouping entries")
-	groups := ctx.Config.Changelog.Groups
-
-	sort.Slice(groups, func(i, j int) bool { return groups[i].Order < groups[j].Order })
-	for _, group := range groups {
-		items := make([]string, 0)
+	var groups []changelogGroup
+	for _, group := range ctx.Config.Changelog.Groups {
+		item := changelogGroup{
+			title: group.Title,
+			order: group.Order,
+		}
 		if group.Regexp == "" {
 			// If no regexp is provided, we purge all strikethrough entries and add remaining entries to the list
-			items = filterAndPrefixItems(entries)
+			item.entries = filterAndPrefixItems(entries)
 			// clear array
 			entries = nil
 		} else {
@@ -133,18 +140,22 @@ func formatChangelog(ctx *context.Context, entries []string) (string, error) {
 			for i, entry := range entries {
 				match := regex.MatchString(entry)
 				if match {
-					items = append(items, li+entry)
+					item.entries = append(item.entries, li+entry)
 					// Striking out the matched entry
 					entries[i] = ""
 				}
 			}
 		}
-		if len(items) > 0 {
-			result = append(result, fmt.Sprintf("### %s", group.Title))
-			result = append(result, items...)
-		}
+		groups = append(groups, item)
 	}
 
+	sort.Slice(groups, func(i, j int) bool { return groups[i].order < groups[j].order })
+	for _, group := range groups {
+		if len(group.entries) > 0 {
+			result = append(result, fmt.Sprintf("### %s", group.title))
+			result = append(result, group.entries...)
+		}
+	}
 	return strings.Join(result, newLine), nil
 }
 
diff --git a/internal/pipe/release/release.go b/internal/pipe/release/release.go
index 47318e26..960556c3 100644
--- a/internal/pipe/release/release.go
+++ b/internal/pipe/release/release.go
@@ -48,51 +48,18 @@ func (Pipe) Default(ctx *context.Context) error {
 
 	switch ctx.TokenType {
 	case context.TokenTypeGitLab:
-		if ctx.Config.Release.GitLab.Name == "" {
-			repo, err := getRepository(ctx)
-			if err != nil {
-				return err
-			}
-			ctx.Config.Release.GitLab = repo
+		if err := setupGitLab(ctx); err != nil {
+			return err
 		}
-		ctx.ReleaseURL = fmt.Sprintf(
-			"%s/%s/%s/-/releases/%s",
-			ctx.Config.GitLabURLs.Download,
-			ctx.Config.Release.GitLab.Owner,
-			ctx.Config.Release.GitLab.Name,
-			ctx.Git.CurrentTag,
-		)
 	case context.TokenTypeGitea:
-		if ctx.Config.Release.Gitea.Name == "" {
-			repo, err := getRepository(ctx)
-			if err != nil {
-				return err
-			}
-			ctx.Config.Release.Gitea = repo
+		if err := setupGitea(ctx); err != nil {
+			return err
 		}
-		ctx.ReleaseURL = fmt.Sprintf(
-			"%s/%s/%s/releases/tag/%s",
-			ctx.Config.GiteaURLs.Download,
-			ctx.Config.Release.Gitea.Owner,
-			ctx.Config.Release.Gitea.Name,
-			ctx.Git.CurrentTag,
-		)
 	default:
 		// We keep github as default for now
-		if ctx.Config.Release.GitHub.Name == "" {
-			repo, err := getRepository(ctx)
-			if err != nil && !ctx.Snapshot {
-				return err
-			}
-			ctx.Config.Release.GitHub = repo
+		if err := setupGitHub(ctx); err != nil {
+			return err
 		}
-		ctx.ReleaseURL = fmt.Sprintf(
-			"%s/%s/%s/releases/tag/%s",
-			ctx.Config.GitHubURLs.Download,
-			ctx.Config.Release.GitHub.Owner,
-			ctx.Config.Release.GitHub.Name,
-			ctx.Git.CurrentTag,
-		)
 	}
 
 	// Check if we have to check the git tag for an indicator to mark as pre release
diff --git a/internal/pipe/release/scm.go b/internal/pipe/release/scm.go
new file mode 100644
index 00000000..c2c4f794
--- /dev/null
+++ b/internal/pipe/release/scm.go
@@ -0,0 +1,104 @@
+package release
+
+import (
+	"fmt"
+
+	"github.com/goreleaser/goreleaser/internal/tmpl"
+	"github.com/goreleaser/goreleaser/pkg/context"
+)
+
+func setupGitHub(ctx *context.Context) error {
+	if ctx.Config.Release.GitHub.Name == "" {
+		repo, err := getRepository(ctx)
+		if err != nil && !ctx.Snapshot {
+			return err
+		}
+		ctx.Config.Release.GitHub = repo
+	}
+
+	owner, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitHub.Owner)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.GitHub.Owner = owner
+
+	name, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitHub.Name)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.GitHub.Name = name
+
+	ctx.ReleaseURL = fmt.Sprintf(
+		"%s/%s/%s/releases/tag/%s",
+		ctx.Config.GitHubURLs.Download,
+		ctx.Config.Release.GitHub.Owner,
+		ctx.Config.Release.GitHub.Name,
+		ctx.Git.CurrentTag,
+	)
+
+	return nil
+}
+
+func setupGitLab(ctx *context.Context) error {
+	if ctx.Config.Release.GitLab.Name == "" {
+		repo, err := getRepository(ctx)
+		if err != nil {
+			return err
+		}
+		ctx.Config.Release.GitLab = repo
+	}
+
+	owner, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitLab.Owner)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.GitLab.Owner = owner
+
+	name, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitLab.Name)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.GitLab.Name = name
+
+	ctx.ReleaseURL = fmt.Sprintf(
+		"%s/%s/%s/-/releases/%s",
+		ctx.Config.GitLabURLs.Download,
+		ctx.Config.Release.GitLab.Owner,
+		ctx.Config.Release.GitLab.Name,
+		ctx.Git.CurrentTag,
+	)
+
+	return nil
+}
+
+func setupGitea(ctx *context.Context) error {
+	if ctx.Config.Release.Gitea.Name == "" {
+		repo, err := getRepository(ctx)
+		if err != nil {
+			return err
+		}
+		ctx.Config.Release.Gitea = repo
+	}
+
+	owner, err := tmpl.New(ctx).Apply(ctx.Config.Release.Gitea.Owner)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.Gitea.Owner = owner
+
+	name, err := tmpl.New(ctx).Apply(ctx.Config.Release.Gitea.Name)
+	if err != nil {
+		return err
+	}
+	ctx.Config.Release.Gitea.Name = name
+
+	ctx.ReleaseURL = fmt.Sprintf(
+		"%s/%s/%s/releases/tag/%s",
+		ctx.Config.GiteaURLs.Download,
+		ctx.Config.Release.Gitea.Owner,
+		ctx.Config.Release.Gitea.Name,
+		ctx.Git.CurrentTag,
+	)
+
+	return nil
+}
diff --git a/www/docs/ci/actions.md b/www/docs/ci/actions.md
index 325bf1de..2b50350a 100644
--- a/www/docs/ci/actions.md
+++ b/www/docs/ci/actions.md
@@ -147,10 +147,10 @@ Following environment variables can be used as `step.env` keys
 
 The following [permissions](https://docs.github.com/en/actions/reference/authentication-in-a-workflow#permissions-for-the-github_token) are required by GoReleaser:
 
- - `content: write` if you wish to
+ - `contents: write` if you wish to
     - [upload archives as GitHub Releases](/customization/release/), or
     - publish to [Homebrew](/customization/homebrew/), or [Scoop](/customization/scoop/) (assuming it's part of the same repository)
- - or just `content: read` if you don't need any of the above
+ - or just `contents: read` if you don't need any of the above
  - `packages: write` if you [push Docker images](/customization/docker/) to GitHub
  - `issues: write` if you use [milestone closing capability](/customization/milestone/)
 
diff --git a/www/docs/customization/nfpm.md b/www/docs/customization/nfpm.md
index e8aec3ce..dc1b0d43 100644
--- a/www/docs/customization/nfpm.md
+++ b/www/docs/customization/nfpm.md
@@ -166,7 +166,7 @@ nfpms:
       - dst: /var/log/boo.log
         type: ghost
 
-      # You can user the packager field to add files that are unique to a specific packager
+      # You can use the packager field to add files that are unique to a specific packager
       - src: path/to/rpm/file.conf
         dst: /etc/file.conf
         type: "config|noreplace"
@@ -301,7 +301,7 @@ nfpms:
 
       # Custom deb triggers
       triggers:
-        # register interrest on a trigger activated by another package
+        # register interest on a trigger activated by another package
         # (also available: interest_await, interest_noawait)
         interest:
           - some-trigger-name
diff --git a/www/docs/customization/release.md b/www/docs/customization/release.md
index bb904a1a..9b523dad 100644
--- a/www/docs/customization/release.md
+++ b/www/docs/customization/release.md
@@ -71,7 +71,7 @@ release:
   name_template: "{{.ProjectName}}-v{{.Version}} {{.Env.USER}}"
 
   # You can disable this pipe in order to not create the release on any SCM.
-  # Keep in mind that this might also break things that depens on the release URL, for instance, homebrew taps.
+  # Keep in mind that this might also break things that depend on the release URL, for instance, homebrew taps.
   #
   # Defaults to false.
   disable: true
@@ -91,7 +91,7 @@ release:
 ```
 
 !!! tip
-    [Learn how to setup an API token, GitHub enteprise and etc](/scm/github/).
+    [Learn how to setup an API token, GitHub Enterprise and etc](/scm/github/).
 
 ## GitLab
 


Test patch:
diff --git a/internal/pipe/changelog/changelog_test.go b/internal/pipe/changelog/changelog_test.go
index 776747dd..3f55f4e0 100644
--- a/internal/pipe/changelog/changelog_test.go
+++ b/internal/pipe/changelog/changelog_test.go
@@ -619,6 +619,7 @@ func TestGroup(t *testing.T) {
 	testlib.GitCommit(t, "added feature 1")
 	testlib.GitCommit(t, "fixed bug 2")
 	testlib.GitCommit(t, "ignored: whatever")
+	testlib.GitCommit(t, "feat(deps): update foobar [bot]")
 	testlib.GitCommit(t, "fix: whatever")
 	testlib.GitCommit(t, "docs: whatever")
 	testlib.GitCommit(t, "chore: something about cArs we dont need")
@@ -630,6 +631,11 @@ func TestGroup(t *testing.T) {
 		Dist: folder,
 		Changelog: config.Changelog{
 			Groups: []config.ChangeLogGroup{
+				{
+					Title:  "Bots",
+					Regexp: ".*bot.*",
+					Order:  900,
+				},
 				{
 					Title:  "Features",
 					Regexp: "^.*feat[(\\w)]*:+.*$",
@@ -655,6 +661,7 @@ func TestGroup(t *testing.T) {
 	ctx.Git.CurrentTag = "v0.0.2"
 	require.NoError(t, Pipe{}.Run(ctx))
 	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
+	require.Contains(t, ctx.ReleaseNotes, "### Bots")
 	require.Contains(t, ctx.ReleaseNotes, "### Features")
 	require.Contains(t, ctx.ReleaseNotes, "### Bug Fixes")
 	require.NotContains(t, ctx.ReleaseNotes, "### Catch nothing")
diff --git a/internal/pipe/release/scm_test.go b/internal/pipe/release/scm_test.go
new file mode 100644
index 00000000..8e6598f4
--- /dev/null
+++ b/internal/pipe/release/scm_test.go
@@ -0,0 +1,168 @@
+package release
+
+import (
+	"testing"
+
+	"github.com/goreleaser/goreleaser/pkg/config"
+	"github.com/goreleaser/goreleaser/pkg/context"
+	"github.com/stretchr/testify/require"
+)
+
+func TestSetupGitLab(t *testing.T) {
+	t.Run("no repo", func(t *testing.T) {
+		ctx := context.New(config.Project{})
+
+		require.NoError(t, setupGitLab(ctx))
+		require.Equal(t, "goreleaser", ctx.Config.Release.GitLab.Owner)
+		require.Equal(t, "goreleaser", ctx.Config.Release.GitLab.Name)
+	})
+
+	t.Run("with templates", func(t *testing.T) {
+		ctx := context.New(config.Project{
+			Env: []string{"NAME=foo", "OWNER=bar"},
+			Release: config.Release{
+				GitLab: config.Repo{
+					Owner: "{{.Env.OWNER}}",
+					Name:  "{{.Env.NAME}}",
+				},
+			},
+		})
+
+		require.NoError(t, setupGitLab(ctx))
+		require.Equal(t, "bar", ctx.Config.Release.GitLab.Owner)
+		require.Equal(t, "foo", ctx.Config.Release.GitLab.Name)
+	})
+
+	t.Run("with invalid templates", func(t *testing.T) {
+		t.Run("owner", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					GitLab: config.Repo{
+						Name:  "foo",
+						Owner: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitLab(ctx))
+		})
+
+		t.Run("name", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					GitLab: config.Repo{
+						Name: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitLab(ctx))
+		})
+	})
+}
+
+func TestSetupGitea(t *testing.T) {
+	t.Run("no repo", func(t *testing.T) {
+		ctx := context.New(config.Project{})
+
+		require.NoError(t, setupGitea(ctx))
+		require.Equal(t, "goreleaser", ctx.Config.Release.Gitea.Owner)
+		require.Equal(t, "goreleaser", ctx.Config.Release.Gitea.Name)
+	})
+
+	t.Run("with templates", func(t *testing.T) {
+		ctx := context.New(config.Project{
+			Env: []string{"NAME=foo", "OWNER=bar"},
+			Release: config.Release{
+				Gitea: config.Repo{
+					Owner: "{{.Env.OWNER}}",
+					Name:  "{{.Env.NAME}}",
+				},
+			},
+		})
+
+		require.NoError(t, setupGitea(ctx))
+		require.Equal(t, "bar", ctx.Config.Release.Gitea.Owner)
+		require.Equal(t, "foo", ctx.Config.Release.Gitea.Name)
+	})
+
+	t.Run("with invalid templates", func(t *testing.T) {
+		t.Run("owner", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					Gitea: config.Repo{
+						Name:  "foo",
+						Owner: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitea(ctx))
+		})
+
+		t.Run("name", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					Gitea: config.Repo{
+						Name: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitea(ctx))
+		})
+	})
+}
+
+func TestSetupGitHub(t *testing.T) {
+	t.Run("no repo", func(t *testing.T) {
+		ctx := context.New(config.Project{})
+
+		require.NoError(t, setupGitHub(ctx))
+		require.Equal(t, "goreleaser", ctx.Config.Release.GitHub.Owner)
+		require.Equal(t, "goreleaser", ctx.Config.Release.GitHub.Name)
+	})
+
+	t.Run("with templates", func(t *testing.T) {
+		ctx := context.New(config.Project{
+			Env: []string{"NAME=foo", "OWNER=bar"},
+			Release: config.Release{
+				GitHub: config.Repo{
+					Owner: "{{.Env.OWNER}}",
+					Name:  "{{.Env.NAME}}",
+				},
+			},
+		})
+
+		require.NoError(t, setupGitHub(ctx))
+		require.Equal(t, "bar", ctx.Config.Release.GitHub.Owner)
+		require.Equal(t, "foo", ctx.Config.Release.GitHub.Name)
+	})
+
+	t.Run("with invalid templates", func(t *testing.T) {
+		t.Run("owner", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					GitHub: config.Repo{
+						Name:  "foo",
+						Owner: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitHub(ctx))
+		})
+
+		t.Run("name", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Release: config.Release{
+					GitHub: config.Repo{
+						Name: "{{.Env.NOPE}}",
+					},
+				},
+			})
+
+			require.Error(t, setupGitHub(ctx))
+		})
+	})
+}