github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/changelog/changelog_test.go (about)

     1  package changelog
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/goreleaser/goreleaser/internal/client"
    12  	"github.com/goreleaser/goreleaser/internal/git"
    13  	"github.com/goreleaser/goreleaser/internal/testctx"
    14  	"github.com/goreleaser/goreleaser/internal/testlib"
    15  	"github.com/goreleaser/goreleaser/pkg/config"
    16  	"github.com/goreleaser/goreleaser/pkg/context"
    17  )
    18  
    19  func TestDescription(t *testing.T) {
    20  	require.NotEmpty(t, Pipe{}.String())
    21  }
    22  
    23  func TestChangelogProvidedViaFlag(t *testing.T) {
    24  	ctx := testctx.New()
    25  	ctx.ReleaseNotesFile = "testdata/changes.md"
    26  	require.NoError(t, Pipe{}.Run(ctx))
    27  	require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes)
    28  }
    29  
    30  func TestChangelogProvidedViaFlagIsAWhitespaceOnlyFile(t *testing.T) {
    31  	ctx := testctx.New()
    32  	ctx.ReleaseNotesFile = "testdata/changes-empty.md"
    33  	require.NoError(t, Pipe{}.Run(ctx))
    34  	require.Equal(t, "\n", ctx.ReleaseNotes)
    35  }
    36  
    37  func TestChangelogProvidedViaFlagIsReallyEmpty(t *testing.T) {
    38  	ctx := testctx.New()
    39  	ctx.ReleaseNotesFile = "testdata/changes-really-empty.md"
    40  	require.NoError(t, Pipe{}.Run(ctx))
    41  	require.Equal(t, "", ctx.ReleaseNotes)
    42  }
    43  
    44  func TestChangelogTmplProvidedViaFlagIsReallyEmpty(t *testing.T) {
    45  	ctx := testctx.New()
    46  	ctx.ReleaseNotesTmpl = "testdata/changes-really-empty.md"
    47  	require.NoError(t, Pipe{}.Run(ctx))
    48  	require.Equal(t, "", ctx.ReleaseNotes)
    49  }
    50  
    51  func TestTemplatedChangelogProvidedViaFlag(t *testing.T) {
    52  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
    53  	ctx.ReleaseNotesFile = "testdata/changes.md"
    54  	ctx.ReleaseNotesTmpl = "testdata/changes-templated.md"
    55  	require.NoError(t, Pipe{}.Run(ctx))
    56  	require.Equal(t, "c0ff33 coffeee v0.0.1\n", ctx.ReleaseNotes)
    57  }
    58  
    59  func TestTemplatedChangelogProvidedViaFlagResultIsEmpty(t *testing.T) {
    60  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
    61  	ctx.ReleaseNotesTmpl = "testdata/changes-templated-empty.md"
    62  	require.NoError(t, Pipe{}.Run(ctx))
    63  	require.Equal(t, "\n\n", ctx.ReleaseNotes)
    64  }
    65  
    66  func TestChangelogProvidedViaFlagDoesntExist(t *testing.T) {
    67  	ctx := testctx.New()
    68  	ctx.ReleaseNotesFile = "testdata/changes.nope"
    69  	require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
    70  }
    71  
    72  func TestReleaseHeaderProvidedViaFlagDoesntExist(t *testing.T) {
    73  	ctx := testctx.New()
    74  	ctx.ReleaseHeaderFile = "testdata/header.nope"
    75  	require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
    76  }
    77  
    78  func TestReleaseFooterProvidedViaFlagDoesntExist(t *testing.T) {
    79  	ctx := testctx.New()
    80  	ctx.ReleaseFooterFile = "testdata/footer.nope"
    81  	require.ErrorIs(t, Pipe{}.Run(ctx), os.ErrNotExist)
    82  }
    83  
    84  func TestChangelog(t *testing.T) {
    85  	folder := testlib.Mktmp(t)
    86  	testlib.GitInit(t)
    87  	testlib.GitCommit(t, "first")
    88  	testlib.GitTag(t, "v0.0.1")
    89  	testlib.GitCommit(t, "added feature 1")
    90  	testlib.GitCommit(t, "fixed bug 2")
    91  	testlib.GitCommit(t, "ignored: whatever")
    92  	testlib.GitCommit(t, "docs: whatever")
    93  	testlib.GitCommit(t, "something about cArs we dont need")
    94  	testlib.GitCommit(t, "feat: added that thing")
    95  	testlib.GitCommit(t, "Merge pull request #999 from goreleaser/some-branch")
    96  	testlib.GitCommit(t, "this is not a Merge pull request")
    97  	testlib.GitTag(t, "v0.0.2")
    98  	ctx := testctx.NewWithCfg(config.Project{
    99  		Dist: folder,
   100  		Changelog: config.Changelog{
   101  			Use: "git",
   102  			Filters: config.Filters{
   103  				Exclude: []string{
   104  					"docs:",
   105  					"ignored:",
   106  					"(?i)cars",
   107  					"^Merge pull request",
   108  				},
   109  			},
   110  		},
   111  	}, testctx.WithCurrentTag("v0.0.2"), testctx.WithPreviousTag("v0.0.1"))
   112  	require.NoError(t, Pipe{}.Run(ctx))
   113  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   114  	require.NotContains(t, ctx.ReleaseNotes, "first")
   115  	require.Contains(t, ctx.ReleaseNotes, "added feature 1")
   116  	require.Contains(t, ctx.ReleaseNotes, "fixed bug 2")
   117  	require.NotContains(t, ctx.ReleaseNotes, "docs")
   118  	require.NotContains(t, ctx.ReleaseNotes, "ignored")
   119  	require.NotContains(t, ctx.ReleaseNotes, "cArs")
   120  	require.NotContains(t, ctx.ReleaseNotes, "from goreleaser/some-branch")
   121  
   122  	for _, line := range strings.Split(ctx.ReleaseNotes, "\n")[1:] {
   123  		if line == "" {
   124  			continue
   125  		}
   126  		require.Truef(t, strings.HasPrefix(line, "* "), "%q: changelog commit must be a list item", line)
   127  	}
   128  
   129  	bts, err := os.ReadFile(filepath.Join(folder, "CHANGELOG.md"))
   130  	require.NoError(t, err)
   131  	require.NotEmpty(t, string(bts))
   132  }
   133  
   134  func TestChangelogInclude(t *testing.T) {
   135  	folder := testlib.Mktmp(t)
   136  	testlib.GitInit(t)
   137  	testlib.GitCommit(t, "first")
   138  	testlib.GitTag(t, "v0.0.1")
   139  	testlib.GitCommit(t, "added feature 1")
   140  	testlib.GitCommit(t, "fixed bug 2")
   141  	testlib.GitCommit(t, "ignored: whatever")
   142  	testlib.GitCommit(t, "docs: whatever")
   143  	testlib.GitCommit(t, "something about cArs we dont need")
   144  	testlib.GitCommit(t, "feat: added that thing")
   145  	testlib.GitCommit(t, "Merge pull request #999 from goreleaser/some-branch")
   146  	testlib.GitCommit(t, "this is not a Merge pull request")
   147  	testlib.GitTag(t, "v0.0.2")
   148  	ctx := testctx.NewWithCfg(config.Project{
   149  		Dist: folder,
   150  		Changelog: config.Changelog{
   151  			Use: "git",
   152  			Filters: config.Filters{
   153  				Include: []string{
   154  					"docs:",
   155  					"ignored:",
   156  					"(?i)cars",
   157  					"^Merge pull request",
   158  				},
   159  			},
   160  		},
   161  	}, testctx.WithCurrentTag("v0.0.2"), testctx.WithPreviousTag("v0.0.1"))
   162  	require.NoError(t, Pipe{}.Run(ctx))
   163  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   164  	require.NotContains(t, ctx.ReleaseNotes, "first")
   165  	require.NotContains(t, ctx.ReleaseNotes, "added feature 1")
   166  	require.NotContains(t, ctx.ReleaseNotes, "fixed bug 2")
   167  	require.Contains(t, ctx.ReleaseNotes, "docs")
   168  	require.Contains(t, ctx.ReleaseNotes, "ignored")
   169  	require.Contains(t, ctx.ReleaseNotes, "cArs")
   170  	require.Contains(t, ctx.ReleaseNotes, "from goreleaser/some-branch")
   171  
   172  	for _, line := range strings.Split(ctx.ReleaseNotes, "\n")[1:] {
   173  		if line == "" {
   174  			continue
   175  		}
   176  		require.Truef(t, strings.HasPrefix(line, "* "), "%q: changelog commit must be a list item", line)
   177  	}
   178  
   179  	bts, err := os.ReadFile(filepath.Join(folder, "CHANGELOG.md"))
   180  	require.NoError(t, err)
   181  	require.NotEmpty(t, string(bts))
   182  }
   183  
   184  func TestChangelogForGitlab(t *testing.T) {
   185  	folder := testlib.Mktmp(t)
   186  	testlib.GitInit(t)
   187  	testlib.GitCommit(t, "first")
   188  	testlib.GitTag(t, "v0.0.1")
   189  	testlib.GitCommit(t, "added feature 1")
   190  	testlib.GitCommit(t, "fixed bug 2")
   191  	testlib.GitCommit(t, "ignored: whatever")
   192  	testlib.GitCommit(t, "docs: whatever")
   193  	testlib.GitCommit(t, "something about cArs we dont need")
   194  	testlib.GitCommit(t, "feat: added that thing")
   195  	testlib.GitCommit(t, "Merge pull request #999 from goreleaser/some-branch")
   196  	testlib.GitCommit(t, "this is not a Merge pull request")
   197  	testlib.GitTag(t, "v0.0.2")
   198  	ctx := testctx.NewWithCfg(
   199  		config.Project{
   200  			Dist: folder,
   201  			Changelog: config.Changelog{
   202  				Filters: config.Filters{
   203  					Exclude: []string{
   204  						"docs:",
   205  						"ignored:",
   206  						"(?i)cars",
   207  						"^Merge pull request",
   208  					},
   209  				},
   210  			},
   211  		},
   212  		testctx.GitLabTokenType,
   213  		testctx.WithCurrentTag("v0.0.2"),
   214  		testctx.WithPreviousTag("v0.0.1"),
   215  	)
   216  	require.NoError(t, Pipe{}.Run(ctx))
   217  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   218  	require.NotContains(t, ctx.ReleaseNotes, "first")
   219  	require.Contains(t, ctx.ReleaseNotes, "added feature 1") // no whitespace because its the last entry of the changelog
   220  	require.Contains(t, ctx.ReleaseNotes, "fixed bug 2   ")  // whitespaces are on purpose
   221  	require.NotContains(t, ctx.ReleaseNotes, "docs")
   222  	require.NotContains(t, ctx.ReleaseNotes, "ignored")
   223  	require.NotContains(t, ctx.ReleaseNotes, "cArs")
   224  	require.NotContains(t, ctx.ReleaseNotes, "from goreleaser/some-branch")
   225  
   226  	bts, err := os.ReadFile(filepath.Join(folder, "CHANGELOG.md"))
   227  	require.NoError(t, err)
   228  	require.NotEmpty(t, string(bts))
   229  }
   230  
   231  func TestChangelogSort(t *testing.T) {
   232  	testlib.Mktmp(t)
   233  	testlib.GitInit(t)
   234  	testlib.GitCommit(t, "whatever")
   235  	testlib.GitTag(t, "v0.9.9")
   236  	testlib.GitCommit(t, "c: commit")
   237  	testlib.GitCommit(t, "a: commit")
   238  	testlib.GitCommit(t, "b: commit")
   239  	testlib.GitTag(t, "v1.0.0")
   240  	ctx := testctx.New(
   241  
   242  		testctx.WithCurrentTag("v1.0.0"),
   243  		testctx.WithPreviousTag("v0.9.9"),
   244  	)
   245  
   246  	for _, cfg := range []struct {
   247  		Sort    string
   248  		Entries []string
   249  	}{
   250  		{
   251  			Sort: "",
   252  			Entries: []string{
   253  				"b: commit",
   254  				"a: commit",
   255  				"c: commit",
   256  			},
   257  		},
   258  		{
   259  			Sort: "asc",
   260  			Entries: []string{
   261  				"a: commit",
   262  				"b: commit",
   263  				"c: commit",
   264  			},
   265  		},
   266  		{
   267  			Sort: "desc",
   268  			Entries: []string{
   269  				"c: commit",
   270  				"b: commit",
   271  				"a: commit",
   272  			},
   273  		},
   274  	} {
   275  		t.Run("changelog sort='"+cfg.Sort+"'", func(t *testing.T) {
   276  			ctx.Config.Changelog.Sort = cfg.Sort
   277  			entries, err := buildChangelog(ctx)
   278  			require.NoError(t, err)
   279  			require.Len(t, entries, len(cfg.Entries))
   280  			var changes []string
   281  			for _, line := range entries {
   282  				changes = append(changes, extractCommitInfo(line))
   283  			}
   284  			require.EqualValues(t, cfg.Entries, changes)
   285  		})
   286  	}
   287  }
   288  
   289  func TestChangelogInvalidSort(t *testing.T) {
   290  	ctx := testctx.NewWithCfg(config.Project{
   291  		Changelog: config.Changelog{
   292  			Sort: "dope",
   293  		},
   294  	})
   295  	require.EqualError(t, Pipe{}.Run(ctx), ErrInvalidSortDirection.Error())
   296  }
   297  
   298  func TestChangelogOfFirstRelease(t *testing.T) {
   299  	testlib.Mktmp(t)
   300  	testlib.GitInit(t)
   301  	msgs := []string{
   302  		"initial commit",
   303  		"another one",
   304  		"one more",
   305  		"and finally this one",
   306  	}
   307  	for _, msg := range msgs {
   308  		testlib.GitCommit(t, msg)
   309  	}
   310  	testlib.GitTag(t, "v0.0.1")
   311  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   312  	require.NoError(t, Pipe{}.Run(ctx))
   313  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   314  	for _, msg := range msgs {
   315  		require.Contains(t, ctx.ReleaseNotes, msg)
   316  	}
   317  }
   318  
   319  func TestChangelogFilterInvalidRegex(t *testing.T) {
   320  	testlib.Mktmp(t)
   321  	testlib.GitInit(t)
   322  	testlib.GitCommit(t, "commitssss")
   323  	testlib.GitTag(t, "v0.0.3")
   324  	testlib.GitCommit(t, "commitzzz")
   325  	testlib.GitTag(t, "v0.0.4")
   326  	ctx := testctx.NewWithCfg(config.Project{
   327  		Changelog: config.Changelog{
   328  			Filters: config.Filters{
   329  				Exclude: []string{
   330  					"(?iasdr4qasd)not a valid regex i guess",
   331  				},
   332  			},
   333  		},
   334  	}, testctx.WithCurrentTag("v0.0.4"), testctx.WithPreviousTag("v0.0.3"))
   335  	require.EqualError(t, Pipe{}.Run(ctx), "error parsing regexp: invalid or unsupported Perl syntax: `(?ia`")
   336  }
   337  
   338  func TestChangelogFilterIncludeInvalidRegex(t *testing.T) {
   339  	testlib.Mktmp(t)
   340  	testlib.GitInit(t)
   341  	testlib.GitCommit(t, "commitssss")
   342  	testlib.GitTag(t, "v0.0.3")
   343  	testlib.GitCommit(t, "commitzzz")
   344  	testlib.GitTag(t, "v0.0.4")
   345  	ctx := testctx.NewWithCfg(config.Project{
   346  		Changelog: config.Changelog{
   347  			Filters: config.Filters{
   348  				Include: []string{
   349  					"(?iasdr4qasd)not a valid regex i guess",
   350  				},
   351  			},
   352  		},
   353  	}, testctx.WithCurrentTag("v0.0.4"), testctx.WithPreviousTag("v0.0.3"))
   354  	require.EqualError(t, Pipe{}.Run(ctx), "error parsing regexp: invalid or unsupported Perl syntax: `(?ia`")
   355  }
   356  
   357  func TestChangelogNoTags(t *testing.T) {
   358  	testlib.Mktmp(t)
   359  	testlib.GitInit(t)
   360  	testlib.GitCommit(t, "first")
   361  	ctx := testctx.New()
   362  	require.Error(t, Pipe{}.Run(ctx))
   363  	require.Empty(t, ctx.ReleaseNotes)
   364  }
   365  
   366  func TestChangelogOnBranchWithSameNameAsTag(t *testing.T) {
   367  	testlib.Mktmp(t)
   368  	testlib.GitInit(t)
   369  	msgs := []string{
   370  		"initial commit",
   371  		"another one",
   372  		"one more",
   373  		"and finally this one",
   374  	}
   375  	for _, msg := range msgs {
   376  		testlib.GitCommit(t, msg)
   377  	}
   378  	testlib.GitTag(t, "v0.0.1")
   379  	testlib.GitCheckoutBranch(t, "v0.0.1")
   380  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   381  	require.NoError(t, Pipe{}.Run(ctx))
   382  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   383  	for _, msg := range msgs {
   384  		require.Contains(t, ctx.ReleaseNotes, msg)
   385  	}
   386  }
   387  
   388  func TestChangeLogWithReleaseHeader(t *testing.T) {
   389  	current, err := os.Getwd()
   390  	require.NoError(t, err)
   391  	tmpdir := testlib.Mktmp(t)
   392  	require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
   393  	testlib.GitInit(t)
   394  	msgs := []string{
   395  		"initial commit",
   396  		"another one",
   397  		"one more",
   398  		"and finally this one",
   399  	}
   400  	for _, msg := range msgs {
   401  		testlib.GitCommit(t, msg)
   402  	}
   403  	testlib.GitTag(t, "v0.0.1")
   404  	testlib.GitCheckoutBranch(t, "v0.0.1")
   405  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   406  	ctx.ReleaseHeaderFile = "testdata/release-header.md"
   407  	require.NoError(t, Pipe{}.Run(ctx))
   408  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   409  	require.Contains(t, ctx.ReleaseNotes, "test header")
   410  }
   411  
   412  func TestChangeLogWithTemplatedReleaseHeader(t *testing.T) {
   413  	current, err := os.Getwd()
   414  	require.NoError(t, err)
   415  	tmpdir := testlib.Mktmp(t)
   416  	require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
   417  	testlib.GitInit(t)
   418  	msgs := []string{
   419  		"initial commit",
   420  		"another one",
   421  		"one more",
   422  		"and finally this one",
   423  	}
   424  	for _, msg := range msgs {
   425  		testlib.GitCommit(t, msg)
   426  	}
   427  	testlib.GitTag(t, "v0.0.1")
   428  	testlib.GitCheckoutBranch(t, "v0.0.1")
   429  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   430  	ctx.ReleaseHeaderTmpl = "testdata/release-header-templated.md"
   431  	require.NoError(t, Pipe{}.Run(ctx))
   432  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   433  	require.Contains(t, ctx.ReleaseNotes, "test header with tag v0.0.1")
   434  }
   435  
   436  func TestChangeLogWithReleaseFooter(t *testing.T) {
   437  	current, err := os.Getwd()
   438  	require.NoError(t, err)
   439  	tmpdir := testlib.Mktmp(t)
   440  	require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
   441  	testlib.GitInit(t)
   442  	msgs := []string{
   443  		"initial commit",
   444  		"another one",
   445  		"one more",
   446  		"and finally this one",
   447  	}
   448  	for _, msg := range msgs {
   449  		testlib.GitCommit(t, msg)
   450  	}
   451  	testlib.GitTag(t, "v0.0.1")
   452  	testlib.GitCheckoutBranch(t, "v0.0.1")
   453  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   454  	ctx.ReleaseFooterFile = "testdata/release-footer.md"
   455  	require.NoError(t, Pipe{}.Run(ctx))
   456  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   457  	require.Contains(t, ctx.ReleaseNotes, "test footer")
   458  	require.Equal(t, '\n', rune(ctx.ReleaseNotes[len(ctx.ReleaseNotes)-1]))
   459  }
   460  
   461  func TestChangeLogWithTemplatedReleaseFooter(t *testing.T) {
   462  	current, err := os.Getwd()
   463  	require.NoError(t, err)
   464  	tmpdir := testlib.Mktmp(t)
   465  	require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
   466  	testlib.GitInit(t)
   467  	msgs := []string{
   468  		"initial commit",
   469  		"another one",
   470  		"one more",
   471  		"and finally this one",
   472  	}
   473  	for _, msg := range msgs {
   474  		testlib.GitCommit(t, msg)
   475  	}
   476  	testlib.GitTag(t, "v0.0.1")
   477  	testlib.GitCheckoutBranch(t, "v0.0.1")
   478  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   479  	ctx.ReleaseFooterTmpl = "testdata/release-footer-templated.md"
   480  	require.NoError(t, Pipe{}.Run(ctx))
   481  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   482  	require.Contains(t, ctx.ReleaseNotes, "test footer with tag v0.0.1")
   483  	require.Equal(t, '\n', rune(ctx.ReleaseNotes[len(ctx.ReleaseNotes)-1]))
   484  }
   485  
   486  func TestChangeLogWithoutReleaseFooter(t *testing.T) {
   487  	current, err := os.Getwd()
   488  	require.NoError(t, err)
   489  	tmpdir := testlib.Mktmp(t)
   490  	require.NoError(t, os.Symlink(current+"/testdata", tmpdir+"/testdata"))
   491  	testlib.GitInit(t)
   492  	msgs := []string{
   493  		"initial commit",
   494  		"another one",
   495  		"one more",
   496  		"and finally this one",
   497  	}
   498  	for _, msg := range msgs {
   499  		testlib.GitCommit(t, msg)
   500  	}
   501  	testlib.GitTag(t, "v0.0.1")
   502  	testlib.GitCheckoutBranch(t, "v0.0.1")
   503  	ctx := testctx.New(testctx.WithCurrentTag("v0.0.1"), withFirstCommit(t))
   504  	require.NoError(t, Pipe{}.Run(ctx))
   505  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   506  	require.Equal(t, '\n', rune(ctx.ReleaseNotes[len(ctx.ReleaseNotes)-1]))
   507  }
   508  
   509  func TestGetChangelogGitHub(t *testing.T) {
   510  	ctx := testctx.NewWithCfg(config.Project{
   511  		Changelog: config.Changelog{
   512  			Use: useGitHub,
   513  		},
   514  	}, testctx.WithCurrentTag("v0.180.2"), testctx.WithPreviousTag("v0.180.1"))
   515  
   516  	expected := "c90f1085f255d0af0b055160bfff5ee40f47af79: fix: do not skip any defaults (#2521) (@caarlos0)"
   517  	mock := client.NewMock()
   518  	mock.Changes = expected
   519  	l := scmChangeloger{
   520  		client: mock,
   521  		repo: client.Repo{
   522  			Owner: "goreleaser",
   523  			Name:  "goreleaser",
   524  		},
   525  	}
   526  
   527  	log, err := l.Log(ctx)
   528  	require.NoError(t, err)
   529  	require.Equal(t, expected, log)
   530  }
   531  
   532  func TestGetChangelogGitHubNative(t *testing.T) {
   533  	ctx := testctx.NewWithCfg(config.Project{
   534  		Changelog: config.Changelog{
   535  			Use: useGitHubNative,
   536  		},
   537  	}, testctx.WithCurrentTag("v0.180.2"), testctx.WithPreviousTag("v0.180.1"))
   538  
   539  	expected := `## What's changed
   540  
   541  * Foo bar test
   542  
   543  **Full Changelog**: https://github.com/gorelease/goreleaser/compare/v0.180.1...v0.180.2
   544  `
   545  	mock := client.NewMock()
   546  	mock.ReleaseNotes = expected
   547  	l := githubNativeChangeloger{
   548  		client: mock,
   549  		repo: client.Repo{
   550  			Owner: "goreleaser",
   551  			Name:  "goreleaser",
   552  		},
   553  	}
   554  	log, err := l.Log(ctx)
   555  	require.NoError(t, err)
   556  	require.Equal(t, expected, log)
   557  	require.Equal(t, []string{"v0.180.1", "v0.180.2"}, mock.ReleaseNotesParams)
   558  }
   559  
   560  func TestGetChangelogGitHubNativeFirstRelease(t *testing.T) {
   561  	ctx := testctx.NewWithCfg(config.Project{
   562  		Changelog: config.Changelog{
   563  			Use: useGitHubNative,
   564  		},
   565  	}, testctx.WithCurrentTag("v0.1.0"))
   566  
   567  	expected := `## What's changed
   568  
   569  * Foo bar test
   570  
   571  **Full Changelog**: https://github.com/gorelease/goreleaser/commits/v0.1.0
   572  `
   573  	mock := client.NewMock()
   574  	mock.ReleaseNotes = expected
   575  	l := githubNativeChangeloger{
   576  		client: mock,
   577  		repo: client.Repo{
   578  			Owner: "goreleaser",
   579  			Name:  "goreleaser",
   580  		},
   581  	}
   582  	log, err := l.Log(ctx)
   583  	require.NoError(t, err)
   584  	require.Equal(t, expected, log)
   585  	require.Equal(t, []string{"", "v0.1.0"}, mock.ReleaseNotesParams)
   586  }
   587  
   588  func TestGetChangeloger(t *testing.T) {
   589  	t.Run("default", func(t *testing.T) {
   590  		c, err := getChangeloger(testctx.New())
   591  		require.NoError(t, err)
   592  		require.IsType(t, gitChangeloger{}, c)
   593  	})
   594  
   595  	t.Run(useGit, func(t *testing.T) {
   596  		c, err := getChangeloger(testctx.NewWithCfg(config.Project{
   597  			Changelog: config.Changelog{
   598  				Use: useGit,
   599  			},
   600  		}))
   601  		require.NoError(t, err)
   602  		require.IsType(t, gitChangeloger{}, c)
   603  	})
   604  
   605  	t.Run(useGitHub, func(t *testing.T) {
   606  		ctx := testctx.NewWithCfg(config.Project{
   607  			Changelog: config.Changelog{
   608  				Use: useGitHub,
   609  			},
   610  		}, testctx.GitHubTokenType)
   611  		c, err := getChangeloger(ctx)
   612  		require.NoError(t, err)
   613  		require.IsType(t, &scmChangeloger{}, c)
   614  	})
   615  
   616  	t.Run(useGitHubNative, func(t *testing.T) {
   617  		ctx := testctx.NewWithCfg(config.Project{
   618  			Changelog: config.Changelog{
   619  				Use: useGitHubNative,
   620  			},
   621  		}, testctx.GitHubTokenType)
   622  		c, err := getChangeloger(ctx)
   623  		require.NoError(t, err)
   624  		require.IsType(t, &githubNativeChangeloger{}, c)
   625  	})
   626  
   627  	t.Run(useGitHubNative+"-invalid-repo", func(t *testing.T) {
   628  		testlib.Mktmp(t)
   629  		testlib.GitInit(t)
   630  		testlib.GitRemoteAdd(t, "https://gist.github.com/")
   631  		ctx := testctx.NewWithCfg(config.Project{
   632  			Changelog: config.Changelog{
   633  				Use: useGitHubNative,
   634  			},
   635  		}, testctx.GitHubTokenType)
   636  		c, err := getChangeloger(ctx)
   637  		require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/")
   638  		require.Nil(t, c)
   639  	})
   640  
   641  	t.Run(useGitLab, func(t *testing.T) {
   642  		ctx := testctx.NewWithCfg(config.Project{
   643  			Changelog: config.Changelog{
   644  				Use: useGitLab,
   645  			},
   646  		}, testctx.GitHubTokenType)
   647  		c, err := getChangeloger(ctx)
   648  		require.NoError(t, err)
   649  		require.IsType(t, &scmChangeloger{}, c)
   650  	})
   651  
   652  	t.Run(useGitHub+"-invalid-repo", func(t *testing.T) {
   653  		testlib.Mktmp(t)
   654  		testlib.GitInit(t)
   655  		testlib.GitRemoteAdd(t, "https://gist.github.com/")
   656  		ctx := testctx.NewWithCfg(config.Project{
   657  			Changelog: config.Changelog{
   658  				Use: useGitHub,
   659  			},
   660  		}, testctx.GitHubTokenType)
   661  		c, err := getChangeloger(ctx)
   662  		require.EqualError(t, err, "unsupported repository URL: https://gist.github.com/")
   663  		require.Nil(t, c)
   664  	})
   665  
   666  	t.Run("invalid", func(t *testing.T) {
   667  		c, err := getChangeloger(testctx.NewWithCfg(config.Project{
   668  			Changelog: config.Changelog{
   669  				Use: "nope",
   670  			},
   671  		}))
   672  		require.EqualError(t, err, `invalid changelog.use: "nope"`)
   673  		require.Nil(t, c)
   674  	})
   675  }
   676  
   677  func TestSkip(t *testing.T) {
   678  	t.Run("skip", func(t *testing.T) {
   679  		ctx := testctx.New(testctx.Snapshot)
   680  		b, err := Pipe{}.Skip(ctx)
   681  		require.NoError(t, err)
   682  		require.True(t, b)
   683  	})
   684  
   685  	t.Run("skip/disable", func(t *testing.T) {
   686  		ctx := testctx.NewWithCfg(config.Project{
   687  			Changelog: config.Changelog{
   688  				Skip: "{{gt .Patch 0}}",
   689  			},
   690  		}, testctx.WithSemver(0, 0, 1, ""))
   691  		b, err := Pipe{}.Skip(ctx)
   692  		require.NoError(t, err)
   693  		require.True(t, b)
   694  		require.Equal(t, ctx.Config.Changelog.Skip, ctx.Config.Changelog.Disable)
   695  	})
   696  
   697  	t.Run("disable on patches", func(t *testing.T) {
   698  		ctx := testctx.NewWithCfg(config.Project{
   699  			Changelog: config.Changelog{
   700  				Disable: "{{gt .Patch 0}}",
   701  			},
   702  		}, testctx.WithSemver(0, 0, 1, ""))
   703  		b, err := Pipe{}.Skip(ctx)
   704  		require.NoError(t, err)
   705  		require.True(t, b)
   706  	})
   707  
   708  	t.Run("invalid template", func(t *testing.T) {
   709  		ctx := testctx.NewWithCfg(config.Project{
   710  			Changelog: config.Changelog{
   711  				Disable: "{{if eq .Patch 123}",
   712  			},
   713  		}, testctx.WithSemver(0, 0, 1, ""))
   714  		_, err := Pipe{}.Skip(ctx)
   715  		require.Error(t, err)
   716  	})
   717  
   718  	t.Run("dont skip", func(t *testing.T) {
   719  		b, err := Pipe{}.Skip(testctx.New())
   720  		require.NoError(t, err)
   721  		require.False(t, b)
   722  	})
   723  
   724  	t.Run("dont skip based on template", func(t *testing.T) {
   725  		ctx := testctx.NewWithCfg(config.Project{
   726  			Changelog: config.Changelog{
   727  				Disable: "{{gt .Patch 0}}",
   728  			},
   729  		})
   730  		b, err := Pipe{}.Skip(ctx)
   731  		require.NoError(t, err)
   732  		require.False(t, b)
   733  	})
   734  }
   735  
   736  func TestGroup(t *testing.T) {
   737  	folder := testlib.Mktmp(t)
   738  	testlib.GitInit(t)
   739  	testlib.GitCommit(t, "first")
   740  	testlib.GitTag(t, "v0.0.1")
   741  	testlib.GitCommit(t, "added feature 1")
   742  	testlib.GitCommit(t, "fixed bug 2")
   743  	testlib.GitCommit(t, "ignored: whatever")
   744  	testlib.GitCommit(t, "feat(deps): update foobar [bot]")
   745  	testlib.GitCommit(t, "fix: whatever")
   746  	testlib.GitCommit(t, "docs: whatever")
   747  	testlib.GitCommit(t, "chore: something about cArs we dont need")
   748  	testlib.GitCommit(t, "feat: added that thing")
   749  	testlib.GitCommit(t, "bug: Merge pull request #999 from goreleaser/some-branch")
   750  	testlib.GitCommit(t, "this is not a Merge pull request")
   751  	testlib.GitTag(t, "v0.0.2")
   752  	ctx := testctx.NewWithCfg(config.Project{
   753  		Dist: folder,
   754  		Changelog: config.Changelog{
   755  			Groups: []config.ChangelogGroup{
   756  				{
   757  					Title:  "Bots",
   758  					Regexp: ".*bot.*",
   759  					Order:  900,
   760  				},
   761  				{
   762  					Title:  "Features",
   763  					Regexp: `^.*?feat(\([[:word:]]+\))??!?:.+$`,
   764  					Order:  0,
   765  				},
   766  				{
   767  					Title:  "Bug Fixes",
   768  					Regexp: `^.*?bug(\([[:word:]]+\))??!?:.+$`,
   769  					Order:  1,
   770  				},
   771  				{
   772  					Title:  "Catch nothing",
   773  					Regexp: "yada yada yada honk the planet",
   774  					Order:  10,
   775  				},
   776  				{
   777  					Title: "Others",
   778  					Order: 999,
   779  				},
   780  			},
   781  		},
   782  	}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   783  	require.NoError(t, Pipe{}.Run(ctx))
   784  	require.Contains(t, ctx.ReleaseNotes, "## Changelog")
   785  	require.Contains(t, ctx.ReleaseNotes, "### Bots")
   786  	require.Contains(t, ctx.ReleaseNotes, "### Features")
   787  	require.Contains(t, ctx.ReleaseNotes, "### Bug Fixes")
   788  	require.NotContains(t, ctx.ReleaseNotes, "### Catch nothing")
   789  	require.Contains(t, ctx.ReleaseNotes, "### Others")
   790  }
   791  
   792  func TestGroupBadRegex(t *testing.T) {
   793  	folder := testlib.Mktmp(t)
   794  	testlib.GitInit(t)
   795  	testlib.GitCommit(t, "first")
   796  	testlib.GitTag(t, "v0.0.1")
   797  	testlib.GitTag(t, "v0.0.2")
   798  	ctx := testctx.NewWithCfg(config.Project{
   799  		Dist: folder,
   800  		Changelog: config.Changelog{
   801  			Groups: []config.ChangelogGroup{
   802  				{
   803  					Title:  "Something",
   804  					Regexp: "^.*feat[a-z", // unterminated regex
   805  				},
   806  			},
   807  		},
   808  	}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   809  	require.EqualError(t, Pipe{}.Run(ctx), "failed to group into \"Something\": error parsing regexp: missing closing ]: `[a-z`")
   810  }
   811  
   812  func TestChangelogFormat(t *testing.T) {
   813  	t.Run("without groups", func(t *testing.T) {
   814  		makeConf := func(u string) config.Project {
   815  			return config.Project{Changelog: config.Changelog{Use: u}}
   816  		}
   817  
   818  		for _, use := range []string{useGit, useGitHub, useGitLab} {
   819  			t.Run(use, func(t *testing.T) {
   820  				out, err := formatChangelog(
   821  					testctx.NewWithCfg(makeConf(use)),
   822  					[]string{
   823  						"aea123 foo",
   824  						"aef653 bar",
   825  					},
   826  				)
   827  				require.NoError(t, err)
   828  				require.Equal(t, `## Changelog
   829  * aea123 foo
   830  * aef653 bar`, out)
   831  			})
   832  		}
   833  
   834  		t.Run(useGitHubNative, func(t *testing.T) {
   835  			out, err := formatChangelog(
   836  				testctx.NewWithCfg(makeConf(useGitHubNative)),
   837  				[]string{
   838  					"# What's changed",
   839  					"* aea123 foo",
   840  					"* aef653 bar",
   841  				},
   842  			)
   843  			require.NoError(t, err)
   844  			require.Equal(t, `# What's changed
   845  * aea123 foo
   846  * aef653 bar`, out)
   847  		})
   848  	})
   849  
   850  	t.Run("with groups", func(t *testing.T) {
   851  		makeConf := func(u string) config.Project {
   852  			return config.Project{
   853  				Changelog: config.Changelog{
   854  					Use: u,
   855  					Groups: []config.ChangelogGroup{
   856  						{Title: "catch-all"},
   857  					},
   858  				},
   859  			}
   860  		}
   861  
   862  		t.Run(useGitHubNative, func(t *testing.T) {
   863  			out, err := formatChangelog(
   864  				testctx.NewWithCfg(makeConf(useGitHubNative)),
   865  				[]string{
   866  					"# What's changed",
   867  					"* aea123 foo",
   868  					"* aef653 bar",
   869  				},
   870  			)
   871  			require.NoError(t, err)
   872  			require.Equal(t, `# What's changed
   873  * aea123 foo
   874  * aef653 bar`, out)
   875  		})
   876  		for _, use := range []string{useGit, useGitHub, useGitLab} {
   877  			t.Run(use, func(t *testing.T) {
   878  				out, err := formatChangelog(
   879  					testctx.NewWithCfg(makeConf(use)),
   880  					[]string{
   881  						"aea123 foo",
   882  						"aef653 bar",
   883  					},
   884  				)
   885  				require.NoError(t, err)
   886  				require.Equal(t, `## Changelog
   887  ### catch-all
   888  * aea123 foo
   889  * aef653 bar`, out)
   890  			})
   891  		}
   892  	})
   893  }
   894  
   895  func TestAbbrev(t *testing.T) {
   896  	folder := testlib.Mktmp(t)
   897  	testlib.GitInit(t)
   898  	testlib.GitCommit(t, "first")
   899  	testlib.GitTag(t, "v0.0.1")
   900  	testlib.GitCommit(t, "added feature 1")
   901  	testlib.GitCommit(t, "fixed bug 2")
   902  	testlib.GitCommit(t, "ignored: whatever")
   903  	testlib.GitCommit(t, "feat(deps): update foobar [bot]")
   904  	testlib.GitCommit(t, "fix: whatever")
   905  	testlib.GitCommit(t, "docs: whatever")
   906  	testlib.GitCommit(t, "chore: something about cArs we dont need")
   907  	testlib.GitCommit(t, "feat: added that thing")
   908  	testlib.GitCommit(t, "bug: Merge pull request #999 from goreleaser/some-branch")
   909  	testlib.GitCommit(t, "this is not a Merge pull request")
   910  	testlib.GitTag(t, "v0.0.2")
   911  
   912  	t.Run("no abbrev", func(t *testing.T) {
   913  		ctx := testctx.NewWithCfg(config.Project{
   914  			Dist:      folder,
   915  			Changelog: config.Changelog{},
   916  		}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   917  
   918  		require.NoError(t, Pipe{}.Run(ctx))
   919  		ensureCommitHashLen(t, ctx.ReleaseNotes, 7)
   920  	})
   921  
   922  	t.Run("abbrev -1", func(t *testing.T) {
   923  		ctx := testctx.NewWithCfg(config.Project{
   924  			Dist: folder,
   925  			Changelog: config.Changelog{
   926  				Abbrev: -1,
   927  			},
   928  		}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   929  		require.NoError(t, Pipe{}.Run(ctx))
   930  	})
   931  
   932  	t.Run("abbrev 3", func(t *testing.T) {
   933  		ctx := testctx.NewWithCfg(config.Project{
   934  			Dist: folder,
   935  			Changelog: config.Changelog{
   936  				Abbrev: 3,
   937  			},
   938  		}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   939  		require.NoError(t, Pipe{}.Run(ctx))
   940  		ensureCommitHashLen(t, ctx.ReleaseNotes, 3)
   941  	})
   942  
   943  	t.Run("abbrev 7", func(t *testing.T) {
   944  		ctx := testctx.NewWithCfg(config.Project{
   945  			Dist: folder,
   946  			Changelog: config.Changelog{
   947  				Abbrev: 7,
   948  			},
   949  		}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   950  		require.NoError(t, Pipe{}.Run(ctx))
   951  		ensureCommitHashLen(t, ctx.ReleaseNotes, 7)
   952  	})
   953  
   954  	t.Run("abbrev 40", func(t *testing.T) {
   955  		ctx := testctx.NewWithCfg(config.Project{
   956  			Dist: folder,
   957  			Changelog: config.Changelog{
   958  				Abbrev: 40,
   959  			},
   960  		}, testctx.WithCurrentTag("v0.0.2"), withFirstCommit(t))
   961  		require.NoError(t, Pipe{}.Run(ctx))
   962  		ensureCommitHashLen(t, ctx.ReleaseNotes, 7)
   963  	})
   964  }
   965  
   966  func ensureCommitHashLen(tb testing.TB, log string, l int) {
   967  	tb.Helper()
   968  	for _, line := range strings.Split(log, "\n") {
   969  		if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
   970  			continue
   971  		}
   972  		parts := strings.SplitN(line, " ", 3)
   973  		commit := strings.TrimPrefix(parts[1], "* ")
   974  		require.Len(tb, commit, l)
   975  	}
   976  }
   977  
   978  func withFirstCommit(tb testing.TB) testctx.Opt {
   979  	tb.Helper()
   980  	return func(ctx *context.Context) {
   981  		s, err := git.Clean(git.Run(testctx.New(), "rev-list", "--max-parents=0", "HEAD"))
   982  		require.NoError(tb, err)
   983  		ctx.Git.FirstCommit = s
   984  	}
   985  }