Repository: goreleaser/goreleaser
Pull request #3102 (2022-05-12 01:57:26)

feat: add extra files to source archives
closes https://github.com/goreleaser/goreleaser/issues/2911

codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/3102?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) Report
> Merging [#3102](https://codecov.io/gh/goreleaser/goreleaser/pull/3102?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (0c03039) into [main](https://codecov.io/gh/goreleaser/goreleaser/commit/9c426a7b164ed9be667d9e7572f3e9ca340f33f4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (9c426a7) will **decrease** coverage by `0.18%`.
> The diff coverage is `39.47%`.

> :exclamation: Current head 0c03039 differs from pull request most recent head 2d937c3. Consider uploading reports for the commit 2d937c3 to get more accurate results

```diff
@@            Coverage Diff             @@
##             main    #3102      +/-   ##
==========================================
- Coverage   84.41%   84.23%   -0.19%     
==========================================
  Files         113      113              
  Lines        9295     9322      +27     
==========================================
+ Hits         7846     7852       +6     
- Misses       1176     1190      +14     
- Partials      273      280       +7     
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/3102?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) | Coverage Δ | |
|---|---|---|
| [pkg/config/config.go](https://codecov.io/gh/goreleaser/goreleaser/pull/3102/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-cGtnL2NvbmZpZy9jb25maWcuZ28=) | `88.83% <ø> (ø)` | |
| [internal/pipe/sourcearchive/source.go](https://codecov.io/gh/goreleaser/goreleaser/pull/3102/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9zb3VyY2VhcmNoaXZlL3NvdXJjZS5nbw==) | `65.71% <39.47%> (-27.31%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/3102?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/3102?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Last update [9c426a7...2d937c3](https://codecov.io/gh/goreleaser/goreleaser/pull/3102?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/go.mod b/go.mod
index 3338ccc6..6f0df3a4 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/Masterminds/semver/v3 v3.1.1
 	github.com/atc0005/go-teams-notify/v2 v2.6.1
 	github.com/caarlos0/ctrlc v1.2.0
-	github.com/caarlos0/env/v6 v6.9.3
+	github.com/caarlos0/env/v6 v6.10.0
 	github.com/caarlos0/go-reddit/v3 v3.0.1
 	github.com/caarlos0/go-shellwords v1.0.12
 	github.com/caarlos0/log v0.1.2
diff --git a/go.sum b/go.sum
index 13b9c13f..ad2135ad 100644
--- a/go.sum
+++ b/go.sum
@@ -208,8 +208,8 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk=
 github.com/caarlos0/ctrlc v1.2.0/go.mod h1:n3gDlSjsXZ7rbD9/RprIR040b7oaLfNStikPd4gFago=
-github.com/caarlos0/env/v6 v6.9.3 h1:Tyg69hoVXDnpO5Qvpsu8EoquarbPyQb+YwExWHP8wWU=
-github.com/caarlos0/env/v6 v6.9.3/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
+github.com/caarlos0/env/v6 v6.10.0 h1:lA7sxiGArZ2KkiqpOQNf8ERBRWI+v8MWIH+eGjSN22I=
+github.com/caarlos0/env/v6 v6.10.0/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
 github.com/caarlos0/go-reddit/v3 v3.0.1 h1:w8ugvsrHhaE/m4ez0BO/sTBOBWI9WZTjG7VTecHnql4=
 github.com/caarlos0/go-reddit/v3 v3.0.1/go.mod h1:QlwgmG5SAqxMeQvg/A2dD1x9cIZCO56BMnMdjXLoisI=
 github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
diff --git a/internal/pipe/sourcearchive/source.go b/internal/pipe/sourcearchive/source.go
index dfd433b5..339d718e 100644
--- a/internal/pipe/sourcearchive/source.go
+++ b/internal/pipe/sourcearchive/source.go
@@ -5,9 +5,11 @@ import (
 	"path/filepath"
 
 	"github.com/caarlos0/log"
+	"github.com/goreleaser/goreleaser/internal/archivefiles"
 	"github.com/goreleaser/goreleaser/internal/artifact"
 	"github.com/goreleaser/goreleaser/internal/git"
 	"github.com/goreleaser/goreleaser/internal/tmpl"
+	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 )
 
@@ -34,7 +36,9 @@ func (Pipe) Run(ctx *context.Context) (err error) {
 	args := []string{
 		"archive",
 		"-o", path,
+		"--format", ctx.Config.Source.Format,
 	}
+
 	if ctx.Config.Source.PrefixTemplate != "" {
 		prefix, err := tmpl.New(ctx).Apply(ctx.Config.Source.PrefixTemplate)
 		if err != nil {
@@ -42,9 +46,19 @@ func (Pipe) Run(ctx *context.Context) (err error) {
 		}
 		args = append(args, "--prefix", prefix)
 	}
+
+	files, err := evalFiles(ctx)
+	if err != nil {
+		return err
+	}
+	for _, f := range files {
+		args = append(args, "--add-file", f)
+	}
+
 	args = append(args, ctx.Git.FullCommit)
 	out, err := git.Clean(git.Run(ctx, args...))
 	log.Debug(out)
+
 	ctx.Artifacts.Add(&artifact.Artifact{
 		Type: artifact.UploadableSourceArchive,
 		Name: filename,
@@ -56,6 +70,41 @@ func (Pipe) Run(ctx *context.Context) (err error) {
 	return err
 }
 
+// to reuse the archivefiles packages, we do something funky:
+// - convert the []string to []config.File
+// - eval it in archivefiles
+// - convert it back to []string
+//
+// we also handle files already tracked, as if we add them again,
+// they'll get duplicated in the archive.
+func evalFiles(ctx *context.Context) ([]string, error) {
+	var files []config.File
+	for _, f := range ctx.Config.Source.Files {
+		files = append(files, config.File{
+			Source: f,
+		})
+	}
+	addFiles, err := archivefiles.Eval(tmpl.New(ctx), files)
+	if err != nil {
+		return nil, err
+	}
+
+	var result []string
+	for _, f := range addFiles {
+		if isTracked(ctx, f.Source) {
+			continue
+		}
+		result = append(result, f.Source)
+	}
+	return result, nil
+}
+
+// check if file is tracked, and, if it is we should not add it to the archive again.
+func isTracked(ctx *context.Context, path string) bool {
+	_, err := git.Run(ctx, "ls-files", "--error-unmatch", path)
+	return err == nil
+}
+
 // Default sets the pipe defaults.
 func (Pipe) Default(ctx *context.Context) error {
 	archive := &ctx.Config.Source
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 7df34ef6..6dcde36c 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -876,10 +876,11 @@ type Publisher struct {
 
 // Source configuration.
 type Source struct {
-	NameTemplate   string `yaml:"name_template,omitempty" json:"name_template,omitempty"`
-	Format         string `yaml:"format,omitempty" json:"format,omitempty"`
-	Enabled        bool   `yaml:"enabled,omitempty" json:"enabled,omitempty"`
-	PrefixTemplate string `yaml:"prefix_template,omitempty" json:"prefix_template,omitempty"`
+	NameTemplate   string   `yaml:"name_template,omitempty" json:"name_template,omitempty"`
+	Format         string   `yaml:"format,omitempty" json:"format,omitempty"`
+	Enabled        bool     `yaml:"enabled,omitempty" json:"enabled,omitempty"`
+	PrefixTemplate string   `yaml:"prefix_template,omitempty" json:"prefix_template,omitempty"`
+	Files          []string `yaml:"files,omitempty" json:"files,omitempty"`
 }
 
 // Project includes all project configuration.
diff --git a/www/docs/customization/source.md b/www/docs/customization/source.md
index 50b09b52..ef1f5181 100644
--- a/www/docs/customization/source.md
+++ b/www/docs/customization/source.md
@@ -23,6 +23,17 @@ source:
   # String to prepend to each filename in the archive.
   # Defaults to empty
   prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
+
+  # Additional files/template/globs you want to add to the source archive.
+  # Will use --add-file of git-archive.
+  # Defaults to empty.
+  files:
+    - LICENSE.txt
+    - README_{{.Os}}.md
+    - CHANGELOG.md
+    - docs/*
+    - design/*.png
+    - templates/**/*
 ```
 
 !!! tip


Test patch:
diff --git a/internal/pipe/discord/discord_test.go b/internal/pipe/discord/discord_test.go
index 97d564d3..f8e04923 100644
--- a/internal/pipe/discord/discord_test.go
+++ b/internal/pipe/discord/discord_test.go
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
 		},
 	})
 	require.NoError(t, Pipe{}.Default(ctx))
-	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to discord: env: environment variable "DISCORD_WEBHOOK_ID" should not be empty`)
+	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to discord: env: environment variable "DISCORD_WEBHOOK_ID" should not be empty; environment variable "DISCORD_WEBHOOK_TOKEN" should not be empty`)
 }
 
 func TestSkip(t *testing.T) {
diff --git a/internal/pipe/reddit/reddit_test.go b/internal/pipe/reddit/reddit_test.go
index e1d76187..b591d4c5 100644
--- a/internal/pipe/reddit/reddit_test.go
+++ b/internal/pipe/reddit/reddit_test.go
@@ -47,7 +47,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
 		},
 	})
 	require.NoError(t, Pipe{}.Default(ctx))
-	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to reddit: env: environment variable "REDDIT_SECRET" should not be empty`)
+	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to reddit: env: environment variable "REDDIT_SECRET" should not be empty; environment variable "REDDIT_PASSWORD" should not be empty`)
 }
 
 func TestSkip(t *testing.T) {
diff --git a/internal/pipe/sourcearchive/source_test.go b/internal/pipe/sourcearchive/source_test.go
index 33b7d621..b28926f7 100644
--- a/internal/pipe/sourcearchive/source_test.go
+++ b/internal/pipe/sourcearchive/source_test.go
@@ -1,6 +1,7 @@
 package sourcearchive
 
 import (
+	"archive/zip"
 	"os"
 	"path/filepath"
 	"testing"
@@ -20,9 +21,12 @@ func TestArchive(t *testing.T) {
 
 			testlib.GitInit(t)
 			require.NoError(t, os.WriteFile("code.txt", []byte("not really code"), 0o655))
+			require.NoError(t, os.WriteFile("code.py", []byte("print 1"), 0o655))
 			require.NoError(t, os.WriteFile("README.md", []byte("# my dope fake project"), 0o655))
 			testlib.GitAdd(t)
 			testlib.GitCommit(t, "feat: first")
+			require.NoError(t, os.WriteFile("added-later.txt", []byte("this file was added later"), 0o655))
+			require.NoError(t, os.WriteFile("ignored.md", []byte("never added"), 0o655))
 
 			ctx := context.New(config.Project{
 				ProjectName: "foo",
@@ -31,6 +35,9 @@ func TestArchive(t *testing.T) {
 					Format:         format,
 					Enabled:        true,
 					PrefixTemplate: "{{ .ProjectName }}-{{ .Version }}/",
+					Files: []string{
+						"*.txt",
+					},
 				},
 			})
 			ctx.Git.FullCommit = "HEAD"
@@ -49,13 +56,49 @@ func TestArchive(t *testing.T) {
 					artifact.ExtraFormat: format,
 				},
 			}, *artifacts[0])
-			stat, err := os.Stat(filepath.Join(tmp, "dist", "foo-1.0.0."+format))
+			path := filepath.Join(tmp, "dist", "foo-1.0.0."+format)
+			stat, err := os.Stat(path)
 			require.NoError(t, err)
 			require.Greater(t, stat.Size(), int64(100))
+
+			if format != "zip" {
+				return
+			}
+
+			f, err := os.Open(path)
+			require.NoError(t, err)
+			z, err := zip.NewReader(f, stat.Size())
+			require.NoError(t, err)
+
+			var paths []string
+			for _, zf := range z.File {
+				paths = append(paths, zf.Name)
+			}
+			require.Equal(t, []string{
+				"foo-1.0.0/",
+				"foo-1.0.0/README.md",
+				"foo-1.0.0/code.py",
+				"foo-1.0.0/code.txt",
+				"foo-1.0.0/added-later.txt",
+			}, paths)
 		})
 	}
 }
 
+func TestInvalidFormat(t *testing.T) {
+	ctx := context.New(config.Project{
+		Dist:        t.TempDir(),
+		ProjectName: "foo",
+		Source: config.Source{
+			Format:         "7z",
+			Enabled:        true,
+			PrefixTemplate: "{{ .ProjectName }}-{{ .Version }}/",
+		},
+	})
+	require.NoError(t, Pipe{}.Default(ctx))
+	require.EqualError(t, Pipe{}.Run(ctx), "fatal: Unknown archive format '7z'")
+}
+
 func TestDefault(t *testing.T) {
 	ctx := context.New(config.Project{})
 	require.NoError(t, Pipe{}.Default(ctx))
diff --git a/internal/pipe/twitter/twitter_test.go b/internal/pipe/twitter/twitter_test.go
index ee910b8b..f3e90fba 100644
--- a/internal/pipe/twitter/twitter_test.go
+++ b/internal/pipe/twitter/twitter_test.go
@@ -36,7 +36,7 @@ func TestAnnounceMissingEnv(t *testing.T) {
 		},
 	})
 	require.NoError(t, Pipe{}.Default(ctx))
-	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to twitter: env: environment variable "TWITTER_CONSUMER_KEY" should not be empty`)
+	require.EqualError(t, Pipe{}.Announce(ctx), `announce: failed to announce to twitter: env: environment variable "TWITTER_CONSUMER_KEY" should not be empty; environment variable "TWITTER_CONSUMER_SECRET" should not be empty; environment variable "TWITTER_ACCESS_TOKEN" should not be empty; environment variable "TWITTER_ACCESS_TOKEN_SECRET" should not be empty`)
 }
 
 func TestSkip(t *testing.T) {