diff --git a/.travis.yml b/.travis.yml index 7e2dca16a..a148f767b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: go -go: 1.8 +go: 1.8.1 install: - make setup - gem install fpm diff --git a/Gopkg.toml b/Gopkg.toml index e550874ce..b0bb335d2 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,4 +1,56 @@ +## Gopkg.toml example (these lines may be deleted) + +## "required" lists a set of packages (not projects) that must be included in +## Gopkg.lock. This list is merged with the set of packages imported by the current +## project. Use it when your project needs a package it doesn't explicitly import - +## including "main" packages. +# required = ["github.com/user/thing/cmd/thing"] + +## "ignored" lists a set of packages (not projects) that are ignored when +## dep statically analyzes source code. Ignored packages can be in this project, +## or in a dependency. +# ignored = ["github.com/user/project/badpkg"] + +## Dependencies define constraints on dependent projects. They are respected by +## dep whether coming from the Gopkg.toml of the current project or a dependency. +# [[dependencies]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Recommended: the version constraint to enforce for the project. +## Only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: an alternate location (URL or import path) for the project's source. +# source = "https://github.com/myfork/package.git" + +## Overrides have the same structure as [[dependencies]], but supercede all +## [[dependencies]] declarations from all projects. Only the current project's +## [[overrides]] are applied. +## +## Overrides are a sledgehammer. Use them only as a last resort. +# [[overrides]] +## Required: the root import path of the project being constrained. +# name = "github.com/user/project" +# +## Optional: specifying a version constraint override will cause all other +## constraints on this project to be ignored; only the overriden constraint +## need be satisfied. +## Again, only one of "branch", "version" or "revision" can be specified. +# version = "1.0.0" +# branch = "master" +# revision = "abc123" +# +## Optional: specifying an alternate source location as an override will +## enforce that the alternate location is used for that project, regardless of +## what source location any dependent projects specify. +# source = "https://github.com/myfork/package.git" + + + [[dependencies]] name = "github.com/stretchr/testify" version = "^1.1.4" diff --git a/README.md b/README.md index 3bb113d49..d63884d38 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Codecov branch Go Report Card Go Doc + SayThanks.io Powered By: GoReleaser

@@ -56,7 +57,7 @@ func main() { } ``` -By default GoReleaser will build the **main.go** file located in your current directory, but you can change the build package path in the GoReleaser configuration file. +By default GoReleaser will build the your current directory, but you can change the build package path in the GoReleaser configuration file. ```yml # goreleaser.yml @@ -94,9 +95,6 @@ build: # Archive customization archive: format: tar.gz - format_overrides: - - goos: windows - format: zip replacements: amd64: 64-bit darwin: macOS @@ -129,6 +127,8 @@ are not enforcing it though. We do remove the `v` prefix and then enforce that the next character is a number. So, `v0.1.0` and `0.1.0` are virtually the same and are both accepted, while `version0.1.0` is not. +If you don't want to create a tag yet but instead simply create a package based on the latest commit, then you can also use the `--snapshot` flag. + Now you can run GoReleaser at the root of your repository: ```console @@ -163,11 +163,15 @@ func main() { } ``` -`version` will always be the name of the current Git tag. +`version` will be the current Git tag (with `v` prefix stripped) or the name of the snapshot if you're using the `--snapshot` flag. -## Release customization +## GoReleaser customization -GoReleaser provides multiple customizations. We will cover them with the help of `goreleaser.yml`: +GoReleaser provides multiple customizations via the `goreleaser.yml` file. +You can generate it by running `goreleaser init` or start from scratch. The +defaults are sensible and fit for most projects. + +We'll cover all customizations available bellow: ### Build customization @@ -189,9 +193,10 @@ build: # Custom ldflags template. # This is parsed with Golang template engine and the following variables # are available: - # - Version # - Date # - Commit + # - Tag + # - Version (Tag with the `v` prefix stripped) # The default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}` # Date format is `2006-01-02_15:04:05` ldflags: -s -w -X main.build={{.Version}} @@ -256,6 +261,13 @@ archive: # Default is `tar.gz` format: zip + # Can be used to archive on different formats for specific GOOSs. + # Most common use case is to archive as zip on Windows. + # Default is empty + format_overrides: + - goos: windows + format: zip + # Replacements for GOOS and GOARCH on the archive name. # The keys should be valid GOOS or GOARCH values followed by your custom # replacements. @@ -295,6 +307,20 @@ release: You can also specify a release notes file in markdown format using the `--release-notes` flag. +### Snapshot customization + +```yml +# goreleaser.yml +snapshot: + # Allows you to change the name of the generated snapshot + # releases. The following variables are available: + # - Commit + # - Tag + # - Timestamp + # Default: SNAPSHOT-{{.Commit}} + name_template: SNAPSHOT-{{.Commit}} +``` + ### Homebrew tap customization The brew section specifies how the formula should be created. @@ -412,6 +438,11 @@ fpm: Note that GoReleaser will not install `fpm` nor any of its dependencies for you. +### Custom release notes + +You can have a markdown file previously created with the release notes, and +pass it down to goreleaser with the `--release-notes=FILE` flag. + ## Integration with CI You may want to wire this to auto-deploy your new tags on [Travis](https://travis-ci.org), for example: diff --git a/config/config.go b/config/config.go index 64c47761d..4b738d159 100644 --- a/config/config.go +++ b/config/config.go @@ -10,8 +10,8 @@ import ( // Repo represents any kind of repo (github, gitlab, etc) type Repo struct { - Owner string - Name string + Owner string `yaml:",omitempty"` + Name string `yaml:",omitempty"` } // String of the repo, e.g. owner/name @@ -21,21 +21,21 @@ func (r Repo) String() string { // Homebrew contains the brew section type Homebrew struct { - GitHub Repo - Folder string - Caveats string - Plist string - Install string - Dependencies []string - Conflicts []string - Description string - Homepage string + GitHub Repo `yaml:",omitempty"` + Folder string `yaml:",omitempty"` + Caveats string `yaml:",omitempty"` + Plist string `yaml:",omitempty"` + Install string `yaml:",omitempty"` + Dependencies []string `yaml:",omitempty"` + Conflicts []string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Homepage string `yaml:",omitempty"` } // Hooks define actions to run before and/or after something type Hooks struct { - Pre string - Post string + Pre string `yaml:",omitempty"` + Post string `yaml:",omitempty"` } // IgnoredBuild represents a build ignored by the user @@ -45,57 +45,63 @@ type IgnoredBuild struct { // Build contains the build configuration section type Build struct { - Goos []string - Goarch []string - Goarm []string - Ignore []IgnoredBuild - Main string - Ldflags string - Flags string - Binary string - Hooks Hooks + Goos []string `yaml:",omitempty"` + Goarch []string `yaml:",omitempty"` + Goarm []string `yaml:",omitempty"` + Ignore []IgnoredBuild `yaml:",omitempty"` + Main string `yaml:",omitempty"` + Ldflags string `yaml:",omitempty"` + Flags string `yaml:",omitempty"` + Binary string `yaml:",omitempty"` + Hooks Hooks `yaml:",omitempty"` } // FormatOverride is used to specify a custom format for a specific GOOS. type FormatOverride struct { - Goos string - Format string + Goos string `yaml:",omitempty"` + Format string `yaml:",omitempty"` } // Archive config used for the archive type Archive struct { - Format string - FormatOverrides []FormatOverride `yaml:"format_overrides"` - NameTemplate string `yaml:"name_template"` - Replacements map[string]string - Files []string + Format string `yaml:",omitempty"` + FormatOverrides []FormatOverride `yaml:"format_overrides,omitempty"` + NameTemplate string `yaml:"name_template,omitempty"` + Replacements map[string]string `yaml:",omitempty"` + Files []string `yaml:",omitempty"` } // Release config used for the GitHub release type Release struct { - GitHub Repo - Draft bool + GitHub Repo `yaml:",omitempty"` + Draft bool `yaml:",omitempty"` } // FPM config type FPM struct { - Formats []string - Dependencies []string - Conflicts []string - Vendor string - Homepage string - Maintainer string - Description string - License string + Formats []string `yaml:",omitempty"` + Dependencies []string `yaml:",omitempty"` + Conflicts []string `yaml:",omitempty"` + Vendor string `yaml:",omitempty"` + Homepage string `yaml:",omitempty"` + Maintainer string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + License string `yaml:",omitempty"` +} + +// Snapshot config +type Snapshot struct { + NameTemplate string `yaml:"name_template,omitempty"` } // Project includes all project configuration type Project struct { - Release Release - Brew Homebrew - Build Build - Archive Archive - FPM FPM `yaml:"fpm"` + Release Release `yaml:",omitempty"` + Brew Homebrew `yaml:",omitempty"` + Build Build `yaml:",omitempty"` + Archive Archive `yaml:",omitempty"` + FPM FPM `yaml:",omitempty"` + Snapshot Snapshot `yaml:",omitempty"` // test only property indicating the path to the dist folder Dist string `yaml:"-"` diff --git a/context/context.go b/context/context.go index 46bbda57e..dd3412d23 100644 --- a/context/context.go +++ b/context/context.go @@ -33,6 +33,7 @@ type Context struct { Version string Validate bool Publish bool + Snapshot bool } var lock sync.Mutex diff --git a/goreleaserlib/goreleaser.go b/goreleaserlib/goreleaser.go index 0aeeaa18b..bd2025c46 100644 --- a/goreleaserlib/goreleaser.go +++ b/goreleaserlib/goreleaser.go @@ -5,6 +5,10 @@ import ( "log" "os" + yaml "gopkg.in/yaml.v1" + + "fmt" + "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/pipeline" @@ -66,6 +70,11 @@ func Release(flags Flags) error { log.Println("Loaded custom release notes from", notes) ctx.ReleaseNotes = string(bts) } + ctx.Snapshot = flags.Bool("snapshot") + if ctx.Snapshot { + log.Println("Publishing disabled in snapshot mode") + ctx.Publish = false + } for _, pipe := range pipes { log.Println(pipe.Description()) log.SetPrefix(" -> ") @@ -77,3 +86,25 @@ func Release(flags Flags) error { log.Println("Done!") return nil } + +// InitProject creates an example goreleaser.yml in the current directory +func InitProject(filename string) error { + if _, err := os.Stat(filename); !os.IsNotExist(err) { + if err != nil { + return err + } + return fmt.Errorf("%s already exists", filename) + } + + var ctx = context.New(config.Project{}) + var pipe = defaults.Pipe{} + if err := pipe.Run(ctx); err != nil { + return err + } + out, err := yaml.Marshal(ctx.Config) + if err != nil { + return err + } + + return ioutil.WriteFile(filename, out, 0644) +} diff --git a/goreleaserlib/goreleaser_test.go b/goreleaserlib/goreleaser_test.go index 34286fa16..cf79efc70 100644 --- a/goreleaserlib/goreleaser_test.go +++ b/goreleaserlib/goreleaser_test.go @@ -8,6 +8,9 @@ import ( "path/filepath" "testing" + yaml "gopkg.in/yaml.v1" + + "github.com/goreleaser/goreleaser/config" "github.com/stretchr/testify/assert" ) @@ -28,6 +31,18 @@ func TestRelease(t *testing.T) { assert.NoError(Release(flags)) } +func TestSnapshotRelease(t *testing.T) { + var assert = assert.New(t) + _, back := setup(t) + defer back() + var flags = fakeFlags{ + flags: map[string]string{ + "snapshot": "true", + }, + } + assert.NoError(Release(flags)) +} + func TestConfigFileIsSetAndDontExist(t *testing.T) { var assert = assert.New(t) var flags = fakeFlags{ @@ -78,6 +93,40 @@ func TestBrokenPipe(t *testing.T) { assert.Error(Release(flags)) } +func TestInitProject(t *testing.T) { + var assert = assert.New(t) + _, back := setup(t) + defer back() + var filename = "test_goreleaser.yml" + assert.NoError(InitProject(filename)) + + file, err := os.Open(filename) + assert.NoError(err) + out, err := ioutil.ReadAll(file) + assert.NoError(err) + + var config = config.Project{} + assert.NoError(yaml.Unmarshal(out, &config)) +} + +func TestInitProjectFileExist(t *testing.T) { + var assert = assert.New(t) + _, back := setup(t) + defer back() + var filename = "test_goreleaser.yml" + createFile(t, filename, "") + assert.Error(InitProject(filename)) +} + +func TestInitProjectDefaultPipeFails(t *testing.T) { + var assert = assert.New(t) + _, back := setup(t) + defer back() + var filename = "test_goreleaser.yml" + assert.NoError(os.RemoveAll(".git")) + assert.Error(InitProject(filename)) +} + // fakeFlags is a mock of the cli flags type fakeFlags struct { flags map[string]string diff --git a/main.go b/main.go index dabbe1deb..0af8bb37f 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,10 @@ func main() { Name: "skip-publish", Usage: "Skip all publishing pipes of the release", }, + cli.BoolFlag{ + Name: "snapshot", + Usage: "Generate an unversioned snapshot release", + }, } app.Action = func(c *cli.Context) error { log.Printf("Running goreleaser %v\n", version) @@ -46,6 +50,22 @@ func main() { } return nil } + app.Commands = []cli.Command{ + { + Name: "init", + Aliases: []string{"i"}, + Usage: "generate goreleaser.yml", + Action: func(c *cli.Context) error { + var filename = "goreleaser.yml" + if err := goreleaserlib.InitProject(filename); err != nil { + return cli.NewExitError(err.Error(), 1) + } + + log.Printf("%s created. Please edit accordingly to your needs.", filename) + return nil + }, + }, + } if err := app.Run(os.Args); err != nil { log.Fatalln(err) } diff --git a/pipeline/build/ldflags.go b/pipeline/build/ldflags.go index 51921b98e..2dacfb3ed 100644 --- a/pipeline/build/ldflags.go +++ b/pipeline/build/ldflags.go @@ -10,6 +10,7 @@ import ( type ldflagsData struct { Date string + Tag string Commit string Version string } @@ -17,7 +18,8 @@ type ldflagsData struct { func ldflags(ctx *context.Context) (string, error) { var data = ldflagsData{ Commit: ctx.Git.Commit, - Version: ctx.Git.CurrentTag, + Tag: ctx.Git.CurrentTag, + Version: ctx.Version, Date: time.Now().UTC().Format(time.RFC3339), } var out bytes.Buffer diff --git a/pipeline/build/ldflags_test.go b/pipeline/build/ldflags_test.go index bfb049bd8..4db97cc7a 100644 --- a/pipeline/build/ldflags_test.go +++ b/pipeline/build/ldflags_test.go @@ -12,7 +12,7 @@ func TestLdFlagsFullTemplate(t *testing.T) { assert := assert.New(t) var config = config.Project{ Build: config.Build{ - Ldflags: "-s -w -X main.version={{.Version}} -X main.date={{.Date}} -X main.commit={{.Commit}}", + Ldflags: "-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}}", }, } var ctx = &context.Context{ @@ -20,12 +20,14 @@ func TestLdFlagsFullTemplate(t *testing.T) { CurrentTag: "v1.2.3", Commit: "123", }, - Config: config, + Version: "1.2.3", + Config: config, } flags, err := ldflags(ctx) assert.NoError(err) assert.Contains(flags, "-s -w") - assert.Contains(flags, "-X main.version=v1.2.3") + assert.Contains(flags, "-X main.version=1.2.3") + assert.Contains(flags, "-X main.tag=v1.2.3") assert.Contains(flags, "-X main.commit=123") assert.Contains(flags, "-X main.date=") } diff --git a/pipeline/build/name_test.go b/pipeline/build/name_test.go index 4b4213124..fef1e287e 100644 --- a/pipeline/build/name_test.go +++ b/pipeline/build/name_test.go @@ -81,9 +81,9 @@ func TestNameDefaltTemplate(t *testing.T) { Version: "1.2.3", } for key, target := range map[string]buildTarget{ - "test_darwin_amd64": buildTarget{"darwin", "amd64", ""}, - "test_linux_arm64": buildTarget{"linux", "arm64", ""}, - "test_linux_armv7": buildTarget{"linux", "arm", "7"}, + "test_darwin_amd64": {"darwin", "amd64", ""}, + "test_linux_arm64": {"linux", "arm64", ""}, + "test_linux_armv7": {"linux", "arm", "7"}, } { t.Run(key, func(t *testing.T) { name, err := nameFor(ctx, target) diff --git a/pipeline/defaults/defaults.go b/pipeline/defaults/defaults.go index 2ad854c98..4c4a414e4 100644 --- a/pipeline/defaults/defaults.go +++ b/pipeline/defaults/defaults.go @@ -3,7 +3,7 @@ package defaults import ( - "errors" + "fmt" "io/ioutil" "strings" @@ -15,6 +15,9 @@ var defaultFiles = []string{"licence", "license", "readme", "changelog"} // NameTemplate default name_template for the archive. const NameTemplate = "{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" +// SnapshotNameTemplate represents the default format for snapshot release names. +const SnapshotNameTemplate = "SNAPSHOT-{{ .Commit }}" + // Pipe for brew deployment type Pipe struct{} @@ -25,13 +28,36 @@ func (Pipe) Description() string { // Run the pipe func (Pipe) Run(ctx *context.Context) error { - if ctx.Config.Release.GitHub.Name == "" { - repo, err := remoteRepo() - ctx.Config.Release.GitHub = repo - if err != nil { - return errors.New("failed reading repo from git: " + err.Error()) - } + ctx.Config.Dist = "dist" + if ctx.Config.Snapshot.NameTemplate == "" { + ctx.Config.Snapshot.NameTemplate = SnapshotNameTemplate } + if err := setReleaseDefaults(ctx); err != nil { + return err + } + setBuildDefaults(ctx) + if ctx.Config.Brew.Install == "" { + ctx.Config.Brew.Install = fmt.Sprintf( + `bin.install "%s"`, + ctx.Config.Build.Binary, + ) + } + return setArchiveDefaults(ctx) +} + +func setReleaseDefaults(ctx *context.Context) error { + if ctx.Config.Release.GitHub.Name != "" { + return nil + } + repo, err := remoteRepo() + if err != nil { + return fmt.Errorf("failed reading repo from git: %v", err.Error()) + } + ctx.Config.Release.GitHub = repo + return nil +} + +func setBuildDefaults(ctx *context.Context) { if ctx.Config.Build.Binary == "" { ctx.Config.Build.Binary = ctx.Config.Release.GitHub.Name } @@ -50,7 +76,9 @@ func (Pipe) Run(ctx *context.Context) error { if ctx.Config.Build.Ldflags == "" { ctx.Config.Build.Ldflags = "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" } +} +func setArchiveDefaults(ctx *context.Context) error { if ctx.Config.Archive.NameTemplate == "" { ctx.Config.Archive.NameTemplate = NameTemplate } @@ -76,10 +104,6 @@ func (Pipe) Run(ctx *context.Context) error { } ctx.Config.Archive.Files = files } - if ctx.Config.Brew.Install == "" { - ctx.Config.Brew.Install = "bin.install \"" + ctx.Config.Build.Binary + "\"" - } - ctx.Config.Dist = "dist" return nil } diff --git a/pipeline/defaults/defaults_test.go b/pipeline/defaults/defaults_test.go index 1deb79d42..e851ebf3d 100644 --- a/pipeline/defaults/defaults_test.go +++ b/pipeline/defaults/defaults_test.go @@ -41,6 +41,22 @@ func TestFillBasicData(t *testing.T) { ) } +func TestFillPartial(t *testing.T) { + assert := assert.New(t) + + var ctx = &context.Context{ + Config: config.Project{ + Release: config.Release{ + GitHub: config.Repo{ + Owner: "goreleaser", + Name: "test", + }, + }, + }, + } + assert.NoError(Pipe{}.Run(ctx)) +} + func TestFilesFilled(t *testing.T) { assert := assert.New(t) diff --git a/pipeline/env/env.go b/pipeline/env/env.go index 67f6e5c71..4beb2780f 100644 --- a/pipeline/env/env.go +++ b/pipeline/env/env.go @@ -24,6 +24,10 @@ func (Pipe) Description() string { // Run the pipe func (Pipe) Run(ctx *context.Context) (err error) { ctx.Token = os.Getenv("GITHUB_TOKEN") + if !ctx.Publish { + log.Println("GITHUB_TOKEN not validated because publishing has been disabled") + return nil + } if !ctx.Validate { log.Println("Skipped validations because --skip-validate is set") return nil diff --git a/pipeline/env/env_test.go b/pipeline/env/env_test.go index 810bb5bd8..fe8d6f732 100644 --- a/pipeline/env/env_test.go +++ b/pipeline/env/env_test.go @@ -1,6 +1,7 @@ package env import ( + "fmt" "os" "testing" @@ -19,6 +20,7 @@ func TestValidEnv(t *testing.T) { var ctx = &context.Context{ Config: config.Project{}, Validate: true, + Publish: true, } assert.NoError(Pipe{}.Run(ctx)) } @@ -29,15 +31,37 @@ func TestInvalidEnv(t *testing.T) { var ctx = &context.Context{ Config: config.Project{}, Validate: true, + Publish: true, } assert.Error(Pipe{}.Run(ctx)) } -func TestSkipValidate(t *testing.T) { - assert := assert.New(t) - var ctx = &context.Context{ - Config: config.Project{}, - Validate: false, - } - assert.NoError(Pipe{}.Run(ctx)) +type flags struct { + Validate, Publish, Snapshot bool +} + +func TestInvalidEnvChecksSkipped(t *testing.T) { + for _, flag := range []flags{ + { + Validate: false, + Publish: true, + }, { + Validate: true, + Publish: false, + }, { + Validate: true, + }, + } { + t.Run(fmt.Sprintf("%v", flag), func(t *testing.T) { + var assert = assert.New(t) + assert.NoError(os.Unsetenv("GITHUB_TOKEN")) + var ctx = &context.Context{ + Config: config.Project{}, + Validate: flag.Validate, + Publish: flag.Publish, + Snapshot: flag.Snapshot, + } + assert.NoError(Pipe{}.Run(ctx)) + }) + } } diff --git a/pipeline/git/git.go b/pipeline/git/git.go index 663ee85eb..1f50b2a4d 100644 --- a/pipeline/git/git.go +++ b/pipeline/git/git.go @@ -3,10 +3,14 @@ package git import ( + "bytes" "fmt" "log" "regexp" "strings" + "time" + + "text/template" "github.com/goreleaser/goreleaser/context" ) @@ -38,6 +42,10 @@ func (e ErrWrongRef) Error() string { return fmt.Sprintf("git tag %v was not made against commit %v", e.tag, e.commit) } +// ErrNoTag happens if the underlying git repository doesn't contain any tags +// but no snapshot-release was requested. +var ErrNoTag = fmt.Errorf("git doesn't contain any tags. Either add a tag or use --snapshot") + // Pipe for brew deployment type Pipe struct{} @@ -52,35 +60,89 @@ func (Pipe) Run(ctx *context.Context) (err error) { if err != nil { return } + if tag == "" && !ctx.Snapshot { + return ErrNoTag + } ctx.Git = context.GitInfo{ CurrentTag: tag, Commit: commit, } - if ctx.ReleaseNotes == "" { - log, err := getChangelog(tag) - if err != nil { - return err - } - ctx.ReleaseNotes = fmt.Sprintf("## Changelog\n\n%v", log) + if err = setLog(ctx, tag, commit); err != nil { + return + } + if err = setVersion(ctx, tag, commit); err != nil { + return } - // removes usual `v` prefix - ctx.Version = strings.TrimPrefix(tag, "v") if !ctx.Validate { log.Println("Skipped validations because --skip-validate is set") return nil } - return validate(commit, tag, ctx.Version) + return validate(ctx, commit, tag) } -func validate(commit, tag, version string) error { - matches, err := regexp.MatchString("^[0-9.]+", version) - if err != nil || !matches { - return ErrInvalidVersionFormat{version} +func setVersion(ctx *context.Context, tag, commit string) (err error) { + if ctx.Snapshot { + snapshotName, err := getSnapshotName(ctx, tag, commit) + if err != nil { + return fmt.Errorf("failed to generate snapshot name: %s", err.Error()) + } + ctx.Version = snapshotName + return nil } + // removes usual `v` prefix + ctx.Version = strings.TrimPrefix(tag, "v") + return +} + +func setLog(ctx *context.Context, tag, commit string) (err error) { + if ctx.ReleaseNotes != "" { + return + } + var log string + if tag == "" { + log, err = getChangelog(commit) + } else { + log, err = getChangelog(tag) + } + if err != nil { + return err + } + ctx.ReleaseNotes = fmt.Sprintf("## Changelog\n\n%v", log) + return nil +} + +type snapshotNameData struct { + Commit string + Tag string + Timestamp int64 +} + +func getSnapshotName(ctx *context.Context, tag, commit string) (string, error) { + tmpl, err := template.New("snapshot").Parse(ctx.Config.Snapshot.NameTemplate) + var out bytes.Buffer + if err != nil { + return "", err + } + var data = snapshotNameData{ + Commit: commit, + Tag: tag, + Timestamp: time.Now().Unix(), + } + err = tmpl.Execute(&out, data) + return out.String(), err +} + +func validate(ctx *context.Context, commit, tag string) error { out, err := git("status", "-s") if strings.TrimSpace(out) != "" || err != nil { return ErrDirty{out} } + if ctx.Snapshot { + return nil + } + if !regexp.MustCompile("^[0-9.]+").MatchString(ctx.Version) { + return ErrInvalidVersionFormat{ctx.Version} + } _, err = cleanGit("describe", "--exact-match", "--tags", "--match", tag) if err != nil { return ErrWrongRef{commit, tag} @@ -108,7 +170,7 @@ func gitLog(refs ...string) (string, error) { func getInfo() (tag, commit string, err error) { tag, err = cleanGit("describe", "--tags", "--abbrev=0") if err != nil { - return + log.Printf("Failed to retrieve current tag: %s", err.Error()) } commit, err = cleanGit("show", "--format='%H'", "HEAD") return diff --git a/pipeline/git/git_test.go b/pipeline/git/git_test.go index 35e8da3ab..015a43903 100644 --- a/pipeline/git/git_test.go +++ b/pipeline/git/git_test.go @@ -8,6 +8,7 @@ import ( "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" + "github.com/goreleaser/goreleaser/pipeline/defaults" "github.com/stretchr/testify/assert" ) @@ -50,6 +51,64 @@ func TestNewRepository(t *testing.T) { assert.Error(Pipe{}.Run(ctx)) } +func TestNoTagsSnapshot(t *testing.T) { + assert := assert.New(t) + _, back := createAndChdir(t) + defer back() + gitInit(t) + gitCommit(t, "first") + var ctx = &context.Context{ + Config: config.Project{ + Snapshot: config.Snapshot{ + NameTemplate: defaults.SnapshotNameTemplate, + }, + }, + Snapshot: true, + Publish: false, + } + assert.NoError(Pipe{}.Run(ctx)) + assert.Contains(ctx.Version, "SNAPSHOT-") +} + +func TestNoTagsSnapshotInvalidTemplate(t *testing.T) { + assert := assert.New(t) + _, back := createAndChdir(t) + defer back() + gitInit(t) + gitCommit(t, "first") + var ctx = &context.Context{ + Config: config.Project{ + Snapshot: config.Snapshot{ + NameTemplate: "{{", + }, + }, + Snapshot: true, + Publish: false, + } + assert.Error(Pipe{}.Run(ctx)) +} + +// TestNoTagsNoSnapshot covers the situation where a repository +// only contains simple commits and no tags. In this case you have +// to set the --snapshot flag otherwise an error is returned. +func TestNoTagsNoSnapshot(t *testing.T) { + assert := assert.New(t) + _, back := createAndChdir(t) + defer back() + gitInit(t) + gitCommit(t, "first") + var ctx = &context.Context{ + Config: config.Project{ + Snapshot: config.Snapshot{ + NameTemplate: defaults.SnapshotNameTemplate, + }, + }, + Snapshot: false, + Publish: false, + } + assert.Error(Pipe{}.Run(ctx)) +} + func TestInvalidTagFormat(t *testing.T) { var assert = assert.New(t) _, back := createAndChdir(t)