diff --git a/.gitignore b/.gitignore index 7c8f013..068d872 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.dylib go-semantic-release +!cmd/go-semantic-release # Test binary, build with `go test -c` *.test diff --git a/.golangci.yml b/.golangci.yml index 0e24022..51f24bf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ linters-settings: golint: min-confidence: 0 issues: - exclude-use-default: false + exclude-use-default: true linters: enable: - golint \ No newline at end of file diff --git a/.release.yml b/.release.yml index 4d30862..c26bd48 100644 --- a/.release.yml +++ b/.release.yml @@ -5,6 +5,7 @@ branch: rc: rc beta: beta alpha: alpha + add_git_releases: alpha changelog: printAll: false template: '' @@ -13,8 +14,7 @@ release: 'github' assets: - name: compress: -provider: - name: "GitHub" - repo: "" - user: "" - customURL: "" +github: + repo: "go-semantic-release" + user: "nightapes" + customUrl: "" diff --git a/.travis.yml b/.travis.yml index d078e60..8de50b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ dist: xenial language: go go: - 1.12.x - +go_import_path: github.com/nightapes/go-semantic-release cache: directories: - $HOME/.cache/go-build diff --git a/cmd/go-semantic-release/commands/changelog.go b/cmd/go-semantic-release/commands/changelog.go new file mode 100644 index 0000000..98968f1 --- /dev/null +++ b/cmd/go-semantic-release/commands/changelog.go @@ -0,0 +1,59 @@ +package commands + +import ( + "github.com/Nightapes/go-semantic-release/pkg/semanticrelease" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func init() { + changelogCmd.Flags().StringP("out", "o", "CHANGELOG.md", "Name of the file") + rootCmd.AddCommand(changelogCmd) +} + +var changelogCmd = &cobra.Command{ + Use: "changelog", + Short: "Generate changelog and save to file", + RunE: func(cmd *cobra.Command, args []string) error { + config, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + return err + } + + force, err := cmd.Flags().GetBool("no-cache") + if err != nil { + return err + } + + file, err := cmd.Flags().GetString("out") + if err != nil { + return err + } + + s, err := semanticrelease.New(readConfig(config), repository) + if err != nil { + return err + } + + releaseVersion, err := s.GetNextVersion(force) + if err != nil { + return err + } + + generatedChangelog, err := s.GetChangelog(releaseVersion) + if err != nil { + return err + } + + if err = s.WriteChangeLog(generatedChangelog.Content, file); err != nil { + log.Fatal(err) + } + + return nil + }, +} diff --git a/cmd/go-semantic-release/commands/next.go b/cmd/go-semantic-release/commands/next.go new file mode 100644 index 0000000..0f64259 --- /dev/null +++ b/cmd/go-semantic-release/commands/next.go @@ -0,0 +1,45 @@ +package commands + +import ( + "fmt" + + "github.com/Nightapes/go-semantic-release/pkg/semanticrelease" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(nextCmd) +} + +var nextCmd = &cobra.Command{ + Use: "next", + Short: "Get next release version", + RunE: func(cmd *cobra.Command, args []string) error { + config, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + return err + } + + force, err := cmd.Flags().GetBool("no-cache") + if err != nil { + return err + } + + s, err := semanticrelease.New(readConfig(config), repository) + if err != nil { + return err + } + + releaseVersion, err := s.GetNextVersion(force) + if err != nil { + return err + } + fmt.Println(releaseVersion.Next.Version.String()) + return nil + }, +} diff --git a/cmd/go-semantic-release/commands/release.go b/cmd/go-semantic-release/commands/release.go new file mode 100644 index 0000000..5b9d8be --- /dev/null +++ b/cmd/go-semantic-release/commands/release.go @@ -0,0 +1,37 @@ +package commands + +import ( + "github.com/Nightapes/go-semantic-release/pkg/semanticrelease" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(releaseCmd) +} + +var releaseCmd = &cobra.Command{ + Use: "release", + Short: "Make a release", + RunE: func(cmd *cobra.Command, args []string) error { + config, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + return err + } + + force, err := cmd.Flags().GetBool("force") + if err != nil { + return err + } + + s, err := semanticrelease.New(readConfig(config), repository) + if err != nil { + return err + } + return s.Release(force) + }, +} diff --git a/cmd/go-semantic-release/commands/root.go b/cmd/go-semantic-release/commands/root.go new file mode 100644 index 0000000..5c4ccd0 --- /dev/null +++ b/cmd/go-semantic-release/commands/root.go @@ -0,0 +1,56 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/Nightapes/go-semantic-release/pkg/config" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "go-semantic-release", + Short: "Make simple releases", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + level, err := cmd.Flags().GetString("loglevel") + if err != nil { + return err + } + setLoglevel(level) + return nil + }, +} + +//Execute rootCmd +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + rootCmd.PersistentFlags().StringP("repository", "r", "", "Path to repository") + rootCmd.PersistentFlags().StringP("loglevel", "l", "error", "Set loglevel") + rootCmd.PersistentFlags().StringP("config", "c", ".release.yml", "Path to config file") + rootCmd.PersistentFlags().Bool("no-cache", false, "Ignore cache, don't use in ci build") +} + +func readConfig(file string) *config.ReleaseConfig { + releaseConfig, err := config.Read(file) + if err != nil { + log.Fatal(err) + } + return releaseConfig +} + +func setLoglevel(level string) { + parsed, err := log.ParseLevel(level) + if err != nil { + log.Errorf("Invalid loglevel %s", level) + } else { + log.SetLevel(parsed) + } + +} diff --git a/cmd/go-semantic-release/commands/set.go b/cmd/go-semantic-release/commands/set.go new file mode 100644 index 0000000..2f580fe --- /dev/null +++ b/cmd/go-semantic-release/commands/set.go @@ -0,0 +1,36 @@ +package commands + +import ( + "github.com/Nightapes/go-semantic-release/pkg/semanticrelease" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(setCmd) +} + +var setCmd = &cobra.Command{ + Use: "set [version]", + Args: cobra.ExactArgs(1), + Short: "Set next release version", + RunE: func(cmd *cobra.Command, args []string) error { + + config, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + + repository, err := cmd.Flags().GetString("repository") + if err != nil { + return err + } + + s, err := semanticrelease.New(readConfig(config), repository) + if err != nil { + return err + } + + return s.SetVersion(args[0]) + }, +} diff --git a/cmd/go-semantic-release/main.go b/cmd/go-semantic-release/main.go index 5f5ef94..a31920a 100644 --- a/cmd/go-semantic-release/main.go +++ b/cmd/go-semantic-release/main.go @@ -1,91 +1,9 @@ -// Package main as start point for go build package main import ( - "fmt" - "os" - - "github.com/Nightapes/go-semantic-release/pkg/config" - "github.com/Nightapes/go-semantic-release/pkg/semanticrelease" - - log "github.com/sirupsen/logrus" - "gopkg.in/alecthomas/kingpin.v2" -) - -var ( - app = kingpin.New("go-semantic-release", "A command-line for releasing software") - loglevel = app.Flag("loglevel", "Set loglevel.").Default("error").HintOptions("error", "warning", "info", "debug").Short('l').String() - - nextCommand = app.Command("next", "Print next version") - nextRepository = nextCommand.Flag("repository", "Path to repository").String() - nextConfigPath = nextCommand.Flag("config", "Path to config file").Default(".release.yml").String() - nextForce = nextCommand.Flag("force", "Ignore cache, don't use in ci build").Bool() - - setCommand = app.Command("set", "Set version for current build.") - setRepository = setCommand.Flag("repository", "Path to repository").String() - setConfigPath = setCommand.Flag("config", "Path to config file").Default(".release.yml").String() - setVersion = setCommand.Arg("version", "semver version").Required().String() - - getChangelog = app.Command("changelog", "Print changelog.") - getChangelogRepository = getChangelog.Flag("repository", "Path to repository").String() - getChangelogConfigPath = getChangelog.Flag("config", "Path to config file").Default(".release.yml").String() - getChangelogFile = getChangelog.Flag("file", "save changelog to file").Default("CHANGELOG.md").String() + "github.com/Nightapes/go-semantic-release/cmd/go-semantic-release/commands" ) func main() { - - switch kingpin.MustParse(app.Parse(os.Args[1:])) { - case nextCommand.FullCommand(): - setLoglevel(*loglevel) - s := semanticrelease.New(readConfig(nextConfigPath)) - version, err := s.GetNextVersion(*nextRepository, *nextForce) - if err != nil { - log.Fatal(err) - } - fmt.Println(version) - if err = s.Release(*nextRepository); err != nil { - log.Fatal(err) - } - - case setCommand.FullCommand(): - setLoglevel(*loglevel) - log.Infof("Version %s", *setVersion) - s := semanticrelease.New(readConfig(setConfigPath)) - err := s.SetVersion(*setVersion, *setRepository) - if err != nil { - log.Fatal(err) - } - if err = s.Release(*setRepository); err != nil { - log.Fatal(err) - } - case getChangelog.FullCommand(): - setLoglevel(*loglevel) - s := semanticrelease.New(readConfig(getChangelogConfigPath)) - changelog, err := s.GetChangelog(*getChangelogRepository) - if err != nil { - log.Fatal(err) - } - if err = s.WriteChangeLog(changelog, *getChangelogFile); err != nil { - log.Fatal(err) - } - } - -} - -func readConfig(path *string) *config.ReleaseConfig { - releaseConfig, err := config.Read(*path) - if err != nil { - log.Fatal(err) - } - return releaseConfig -} - -func setLoglevel(level string) { - parsed, err := log.ParseLevel(level) - if err != nil { - log.Errorf("Invalid loglevel %s", level) - } else { - log.SetLevel(parsed) - } - + commands.Execute() } diff --git a/go.mod b/go.mod index 9a92235..eff09c3 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,18 @@ go 1.12 require ( github.com/Masterminds/semver v1.4.2 - github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect + github.com/google/go-cmp v0.3.0 // indirect github.com/google/go-github/v25 v25.1.1 - github.com/sirupsen/logrus v1.4.1 - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be - gopkg.in/alecthomas/kingpin.v2 v2.2.6 + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/sirupsen/logrus v1.4.2 + github.com/spf13/cobra v0.0.5 + github.com/stretchr/testify v1.3.0 // indirect + golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect + golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/sys v0.0.0-20190614160838-b47fdc937951 // indirect + google.golang.org/appengine v1.6.1 // indirect gopkg.in/src-d/go-git.v4 v4.11.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 39e2eec..7ea57ff 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,43 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github/v25 v25.1.1 h1:6eW++i/CXcR5GKfYaaJT7oJJtHNU+/iiw55noEPNVao= github.com/google/go-github/v25 v25.1.1/go.mod h1:6z5pC69qHtrPJ0sXPsj4BLnd82b+r6sLB7qcBoRZqpw= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -31,53 +45,102 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 h1:lkiLiLBHGoH3XnqSLUIaBsilGMUjI+Uy2Xu2JLUtTas= golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190614160838-b47fdc937951 h1:ZUgGZ7PSkne6oY+VgAvayrB16owfm9/DKAtgWubzgzU= +golang.org/x/sys v0.0.0-20190614160838-b47fdc937951/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/analyzer/analyzer.go b/internal/analyzer/analyzer.go index 60e7e56..7f1f21f 100644 --- a/internal/analyzer/analyzer.go +++ b/internal/analyzer/analyzer.go @@ -50,7 +50,7 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) { log.Debugf("Commit format set to angular") analyzer.analyzeCommit = newAngular() default: - return nil, fmt.Errorf("Invalid commit format: %s", format) + return nil, fmt.Errorf("invalid commit format: %s", format) } return analyzer, nil diff --git a/internal/analyzer/angular.go b/internal/analyzer/angular.go index d05cff5..e36499a 100644 --- a/internal/analyzer/angular.go +++ b/internal/analyzer/angular.go @@ -107,6 +107,6 @@ func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, boo } } log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag) - return analyzed, false, fmt.Errorf("Not found") + return analyzed, false, fmt.Errorf("not found") } diff --git a/internal/cache/cache.go b/internal/cache/cache.go index e64a061..7bc027d 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -3,40 +3,49 @@ package cache import ( "io/ioutil" + "path" "gopkg.in/yaml.v2" ) -// VersionFileContent struct -type VersionFileContent struct { - Version string `yaml:"version"` - NextVersion string `yaml:"nextVersion"` - Commit string `yaml:"commit"` - Branch string `yaml:"branch"` +// ReleaseVersion struct +type ReleaseVersion struct { + Last ReleaseVersionEntry `yaml:"last"` + Next ReleaseVersionEntry `yaml:"next"` + Branch string `yaml:"branch"` +} + +//ReleaseVersionEntry struct +type ReleaseVersionEntry struct { + Commit string `yaml:"commit"` + Version string `yaml:"version"` } // Write version into .version -func Write(versionFileContent VersionFileContent) error { +func Write(repository string, versionFileContent ReleaseVersion) error { + completePath := path.Join(path.Dir(repository), ".version") + data, err := yaml.Marshal(&versionFileContent) if err != nil { return err } - return ioutil.WriteFile(".version", data, 0644) + return ioutil.WriteFile(completePath, data, 0644) } // Read version into .version -func Read() (*VersionFileContent, error) { +func Read(repository string) (*ReleaseVersion, error) { + completePath := path.Join(path.Dir(repository), ".version") - content, err := ioutil.ReadFile(".version") + content, err := ioutil.ReadFile(completePath) if err != nil { - return &VersionFileContent{}, err + return &ReleaseVersion{}, err } - var versionFileContent VersionFileContent + var versionFileContent ReleaseVersion err = yaml.Unmarshal(content, &versionFileContent) if err != nil { - return &VersionFileContent{}, err + return &ReleaseVersion{}, err } return &versionFileContent, nil diff --git a/internal/changelog/changelog.go b/internal/changelog/changelog.go index eed9d11..f1a9319 100644 --- a/internal/changelog/changelog.go +++ b/internal/changelog/changelog.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Nightapes/go-semantic-release/internal/analyzer" + "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" log "github.com/sirupsen/logrus" @@ -49,7 +50,7 @@ func New(config *config.ReleaseConfig, rules []analyzer.Rule) *Changelog { } // GenerateChanglog from given commits -func (c *Changelog) GenerateChanglog(version, url string, analyzedCommits map[string][]analyzer.AnalyzedCommit) (string, string, error) { +func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[string][]analyzer.AnalyzedCommit) (*shared.GeneratedChangelog, error) { commitsPerScope := map[string][]analyzer.AnalyzedCommit{} order := make([]string, 0) @@ -73,22 +74,22 @@ func (c *Changelog) GenerateChanglog(version, url string, analyzedCommits map[st } changelogContent := changelogContent{ - Version: version, + Version: templateConfig.Version, Commits: commitsPerScope, Now: time.Now(), Backtick: "`", Order: order, - HasURL: url != "", - URL: url, + HasURL: templateConfig.CommitURL != "", + URL: templateConfig.CommitURL, } title, err := generateTemplate(defaultChangelogTitle, changelogContent) if err != nil { - return "", "", err + return nil, err } content, err := generateTemplate(defaultChangelog, changelogContent) - return title, content, err + return &shared.GeneratedChangelog{Title: title, Content: content}, err } func generateTemplate(text string, values changelogContent) (string, error) { diff --git a/internal/gitutil/gitutil.go b/internal/gitutil/gitutil.go index 028d35a..a721f4c 100644 --- a/internal/gitutil/gitutil.go +++ b/internal/gitutil/gitutil.go @@ -136,7 +136,7 @@ func (g *GitUtil) GetCommits(lastTagHash string) ([]Commit, error) { log.Debugf("Found commit with hash %s, will stop here", c.Hash.String()) foundEnd = true } - + log.Tracef("Found commit with hash %s", c.Hash.String()) if !foundEnd { commit := Commit{ Message: c.Message, diff --git a/internal/releaser/github.go b/internal/releaser/github.go deleted file mode 100644 index 64d2497..0000000 --- a/internal/releaser/github.go +++ /dev/null @@ -1,83 +0,0 @@ -package releaser - -import ( - "context" - "fmt" - "github.com/Nightapes/go-semantic-release/pkg/config" - "github.com/google/go-github/v25/github" - "net/http" - "os" -) - -// GITHUB identifer for github interface -const GITHUB = "GitHub" - -// GitHubReleaser type struct -type GitHubReleaser struct { - config *config.ReleaseConfig - client *github.Client - context context.Context - release *github.RepositoryRelease -} - -type gitHubCreateReleaseResponse struct { - ReleaseURL string `json:url` - AssetUploadURL string `json:upload_url` -} - -// NewGitHubReleaser initialize a new GitHubRelease -func NewGitHubReleaser(c *config.ReleaseConfig) *GitHubReleaser { - ctx := context.Background() - httpClient := createHTTPClient(ctx, c.GitProvider.AccessToken) - - return &GitHubReleaser{ - config: c, - client: github.NewClient(httpClient), - context: ctx, - } -} - -// CreateRelease creates release on remote -func (g GitHubReleaser) CreateRelease(tag, releaseName, releaseMessage, targetBranch string) error { - - release, resp, err := g.client.Repositories.CreateRelease(g.context, g.config.GitProvider.User, g.config.GitProvider.Repo, &github.RepositoryRelease{ - TagName: &tag, - TargetCommitish: &targetBranch, - Name: &releaseName, - Body: &releaseMessage, - Draft: &g.config.IsDraft, - Prerelease: &g.config.IsPreRelease, - }) - - if err != nil { - return fmt.Errorf("releaser: github: Could not create release: %v", err) - } - - if resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("releaser: github: Could not create release: response statuscode: %s", resp.Status) - } - - g.release = release - return nil - -} - -// UploadAssets uploads specified assets -func (g GitHubReleaser) UploadAssets(assets []config.Asset) error { - for _, asset := range assets { - file, err := os.Open(asset.Name) - if err != nil { - return err - } - - _, resp, err := g.client.Repositories.UploadReleaseAsset(g.context, g.config.GitProvider.User, g.config.GitProvider.Repo, *g.release.ID, &github.UploadOptions{Name: asset.Name}, file) - if err != nil { - return err - } - - if resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("releaser: github: Could not create release: response statuscode: %s", resp.Status) - } - } - return nil -} diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go new file mode 100644 index 0000000..66a7043 --- /dev/null +++ b/internal/releaser/github/github.go @@ -0,0 +1,128 @@ +package github + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + + "github.com/Nightapes/go-semantic-release/internal/releaser/util" + "github.com/Nightapes/go-semantic-release/internal/shared" + "github.com/Nightapes/go-semantic-release/pkg/config" + + "github.com/google/go-github/v25/github" +) + +// GITHUB identifer for github interface +const GITHUB = "github" + +// Client type struct +type Client struct { + config *config.GitHubProvider + client *github.Client + context context.Context + release *github.RepositoryRelease + baseURL string + token string +} + +// New initialize a new GitHubRelease +func New(c *config.GitHubProvider) (*Client, error) { + ctx := context.Background() + httpClient := util.CreateBearerHTTPClient(ctx, c.AccessToken) + + var client *github.Client + var err error + baseURL := "https://github.com" + if c.CustomURL == "" { + client = github.NewClient(httpClient) + } else { + client, err = github.NewEnterpriseClient(c.CustomURL, c.CustomURL+"/api/v3/", httpClient) + baseURL = c.CustomURL + } + return &Client{ + config: c, + client: client, + context: ctx, + baseURL: baseURL, + }, err +} + +//GetCommitURL for github +func (g Client) GetCommitURL() string { + return fmt.Sprintf("%s/%s/%s/commit/{{hash}}", g.baseURL, g.config.User, g.config.Repo) +} + +//GetCompareURL for github +func (g Client) GetCompareURL(oldVersion, newVersion string) string { + return fmt.Sprintf("%s/%s/%s/compare/%s...%s", g.baseURL, g.config.User, g.config.Repo, oldVersion, newVersion) +} + +//ValidateConfig for github +func (g Client) ValidateConfig() error { + + if g.config.Repo == "" { + return fmt.Errorf("github Repro is not set") + } + + if g.config.User == "" { + return fmt.Errorf("github User is not set") + } + + envName := fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(GITHUB)) + token, isSet := os.LookupEnv(envName) + if !isSet { + return fmt.Errorf("can not find environment variable %s", envName) + } + g.token = token + return nil + +} + +// CreateRelease creates release on remote +func (g Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { + + tag := releaseVersion.Next.Version.String() + prerelease := releaseVersion.Next.Version.Prerelease() != "" + release, resp, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{ + TagName: &tag, + TargetCommitish: &releaseVersion.Branch, + Name: &generatedChangelog.Title, + Body: &generatedChangelog.Content, + Draft: &prerelease, + Prerelease: &prerelease, + }) + + if err != nil { + return fmt.Errorf("could not create release: %v", err) + } + + if resp.StatusCode >= http.StatusBadRequest { + return fmt.Errorf("could not create release: response statuscode: %s", resp.Status) + } + + g.release = release + return nil + +} + +// UploadAssets uploads specified assets +func (g Client) UploadAssets(assets []config.Asset) error { + for _, asset := range assets { + file, err := os.Open(asset.Name) + if err != nil { + return err + } + + _, resp, err := g.client.Repositories.UploadReleaseAsset(g.context, g.config.User, g.config.Repo, *g.release.ID, &github.UploadOptions{Name: asset.Name}, file) + if err != nil { + return err + } + + if resp.StatusCode >= http.StatusBadRequest { + return fmt.Errorf("releaser: github: Could not create release: response statuscode: %s", resp.Status) + } + } + return nil +} diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go index 3e095bb..e57c461 100644 --- a/internal/releaser/releaser.go +++ b/internal/releaser/releaser.go @@ -1,12 +1,12 @@ package releaser import ( - "context" "fmt" + + "github.com/Nightapes/go-semantic-release/internal/releaser/github" + "github.com/Nightapes/go-semantic-release/internal/shared" + "github.com/Nightapes/go-semantic-release/pkg/config" - "golang.org/x/oauth2" - "net/http" - "os" ) // Releasers struct type @@ -16,8 +16,11 @@ type Releasers struct { // Releaser interface for providers type Releaser interface { - CreateRelease(tag, releaseName, releaseMessage, targetBranch string) error + ValidateConfig() error + CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog) error UploadAssets(assets []config.Asset) error + GetCommitURL() string + GetCompareURL(oldVersion, newVersion string) string } // New initialize a Relerser @@ -29,38 +32,26 @@ func New(c *config.ReleaseConfig) *Releasers { //GetReleaser returns an initialized releaser func (r *Releasers) GetReleaser() (Releaser, error) { - switch r.config.GitProvider.Name { - case GITHUB: - return NewGitHubReleaser(r.config), nil + switch r.config.Release { + case github.GITHUB: + return github.New(&r.config.GitHubProvider) } - return nil, fmt.Errorf("Could not initialize a releaser from this type: %s", r.config.GitProvider.Name) + return nil, fmt.Errorf("could not initialize a releaser from this type: %s", r.config.Release) } -// tbd. http helper function +// func checkIfAssetsExists(assets []config.Asset) error { +// var missingAssets []string +// for _, asset := range assets { -func createHTTPClient(ctx context.Context, token string) *http.Client { - tokenSource := oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: token}, - ) +// if _, err := os.Stat(asset.Name); err != nil { +// missingAssets = append(missingAssets, asset.Name) +// } +// } - client := oauth2.NewClient(ctx, tokenSource) +// if len(missingAssets) != 0 { +// return fmt.Errorf("could not find specified Asset: %+v ", assets) +// } - return client -} +// return nil -func checkIfAssetsExists(assets []config.Asset) error { - var missingAssets []string - for _, asset := range assets { - - if _, err := os.Stat(asset.Name); err != nil { - missingAssets = append(missingAssets, asset.Name) - } - } - - if len(missingAssets) != 0 { - return fmt.Errorf("Could not find specified Asset: %+v ", assets) - } - - return nil - -} +// } diff --git a/internal/releaser/util/util.go b/internal/releaser/util/util.go new file mode 100644 index 0000000..6479ace --- /dev/null +++ b/internal/releaser/util/util.go @@ -0,0 +1,19 @@ +package util + +import ( + "context" + "net/http" + + "golang.org/x/oauth2" +) + +//CreateBearerHTTPClient with given token +func CreateBearerHTTPClient(ctx context.Context, token string) *http.Client { + tokenSource := oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: token}, + ) + + client := oauth2.NewClient(ctx, tokenSource) + + return client +} diff --git a/internal/shared/shared.go b/internal/shared/shared.go new file mode 100644 index 0000000..3d0154a --- /dev/null +++ b/internal/shared/shared.go @@ -0,0 +1,32 @@ +package shared + +import ( + "github.com/Masterminds/semver" +) + +//ReleaseVersion struct +type ReleaseVersion struct { + Last ReleaseVersionEntry + Next ReleaseVersionEntry + Branch string +} + +//ReleaseVersionEntry struct +type ReleaseVersionEntry struct { + Commit string + Version *semver.Version +} + +//GeneratedChangelog struct +type GeneratedChangelog struct { + Title string + Content string +} + +//GenerateChangelogConfig struct +type ChangelogTemplateConfig struct { + CommitURL string + CompareURL string + Hash string + Version string +} diff --git a/pkg/config/config.go b/pkg/config/config.go index f06b0e2..62a2a4d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -2,18 +2,12 @@ package config import ( - "fmt" "io/ioutil" - "os" - "strings" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" ) -// List of all supported git providers -var gitProviders = map[string]string{"GitHub": "https://github.com/", "GitLab": "https://gitlab.com/"} - // ChangelogConfig struct type ChangelogConfig struct { PrintAll bool `yaml:"printAll,omitempty"` @@ -21,28 +15,27 @@ type ChangelogConfig struct { TemplatePath string `yaml:"templatePath,omitempty"` } -// GitProvider struct -type GitProvider struct { - Name string `yaml:"name"` - Repo string `yaml:"repo"` - User string `yaml:"user"` - customProviderURL string `yaml:"customURL"` - AccessToken string -} - //Asset type struct type Asset struct { Name string `yaml:"name"` Compress bool `yaml:"compress"` } +// GitHubProvider struct +type GitHubProvider struct { + Repo string `yaml:"repo"` + User string `yaml:"user"` + CustomURL string `yaml:"customUrl,omitempty"` + AccessToken string +} + // ReleaseConfig struct type ReleaseConfig struct { CommitFormat string `yaml:"commitFormat"` Branch map[string]string `yaml:"branch"` Changelog ChangelogConfig `yaml:"changelog,omitempty"` Release string `yaml:"release,omitempty"` - GitProvider GitProvider `yaml:"provider,omitempty"` + GitHubProvider GitHubProvider `yaml:"github,omitempty"` Assets []Asset `yaml:"assets"` ReleaseTitle string `yaml:"title"` IsPreRelease, IsDraft bool @@ -64,36 +57,5 @@ func Read(configPath string) (*ReleaseConfig, error) { log.Debugf("Found config %+v", releaseConfig) - releaseConfig, err = checkProvider(releaseConfig) - if err != nil { - return &ReleaseConfig{}, err - } return &releaseConfig, nil } - -func checkProvider(config ReleaseConfig) (ReleaseConfig, error) { - if config.GitProvider != (GitProvider{}) { - if _, ok := gitProviders[config.GitProvider.Name]; !ok { - return ReleaseConfig{}, fmt.Errorf("config: provider: configured provider %s is not supported", config.GitProvider.Name) - } - envName := fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(config.GitProvider.Name)) - - token, isSet := os.LookupEnv(envName) - if !isSet { - return ReleaseConfig{}, fmt.Errorf("config: Can not find environment variable %s", envName) - } - config.GitProvider.AccessToken = token - } else { - log.Debugln("No provider is set, will continue") - } - return config, nil -} - -// GetRepositoryURL returns the repo FQDN -func (c *ReleaseConfig) GetRepositoryURL() string { - if c.GitProvider.customProviderURL != "" { - return fmt.Sprintf("%s/%s/%s/", c.GitProvider.customProviderURL, c.GitProvider.User, c.GitProvider.Repo) - } else { - return fmt.Sprintf("%s/%s/%s/", gitProviders[c.GitProvider.Name], c.GitProvider.User, c.GitProvider.Repo) - } -} diff --git a/pkg/semanticrelease/helper.go b/pkg/semanticrelease/helper.go new file mode 100644 index 0000000..990f57e --- /dev/null +++ b/pkg/semanticrelease/helper.go @@ -0,0 +1,87 @@ +package semanticrelease + +import ( + "strconv" + "strings" + + "github.com/Masterminds/semver" + "github.com/Nightapes/go-semantic-release/internal/cache" + "github.com/Nightapes/go-semantic-release/internal/shared" + log "github.com/sirupsen/logrus" +) + +func (s *SemanticRelease) incPrerelease(preReleaseType string, version semver.Version) semver.Version { + defaultPrerelease := preReleaseType + ".0" + if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) { + version, _ = version.SetPrerelease(defaultPrerelease) + } else { + parts := strings.Split(version.Prerelease(), ".") + if len(parts) == 2 { + i, err := strconv.Atoi(parts[1]) + if err != nil { + version, _ = version.SetPrerelease(defaultPrerelease) + log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String()) + } else { + version, _ = version.SetPrerelease(preReleaseType + "." + strconv.Itoa((i + 1))) + } + } else { + version, _ = version.SetPrerelease(defaultPrerelease) + log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String()) + } + } + + return version +} + +func (s *SemanticRelease) saveToCache(releaseVersion shared.ReleaseVersion) error { + + toCache := cache.ReleaseVersion{ + Next: cache.ReleaseVersionEntry{ + Commit: releaseVersion.Next.Commit, + Version: releaseVersion.Next.Version.String(), + }, + Last: cache.ReleaseVersionEntry{ + Commit: releaseVersion.Last.Commit, + Version: releaseVersion.Last.Version.String(), + }, + Branch: releaseVersion.Branch, + } + + log.Debugf("Save %s with hash %s to cache", releaseVersion.Next.Version.String(), releaseVersion.Next.Commit) + return cache.Write(s.repository, toCache) +} + +func (s *SemanticRelease) readFromCache(currentHash string) (*shared.ReleaseVersion, error) { + content, err := cache.Read(s.repository) + + if err == nil && content.Next.Commit == currentHash { + + nextVersion, err := semver.NewVersion(content.Next.Version) + if err != nil { + return nil, err + } + + lastVersion, err := semver.NewVersion(content.Last.Version) + if err != nil { + return nil, err + } + + releaseVersion := &shared.ReleaseVersion{ + Next: shared.ReleaseVersionEntry{ + Commit: content.Next.Commit, + Version: nextVersion, + }, + Last: shared.ReleaseVersionEntry{ + Commit: content.Last.Commit, + Version: lastVersion, + }, + Branch: content.Branch, + } + + log.Infof("Found cache, will return cached version %s", content.Next.Version) + return releaseVersion, nil + + } + log.Debugf("Mismatch git and version file %s - %s", content.Next.Commit, currentHash) + return nil, nil +} diff --git a/pkg/semanticrelease/semantic-release.go b/pkg/semanticrelease/semantic-release.go index c92a020..52c6f25 100644 --- a/pkg/semanticrelease/semantic-release.go +++ b/pkg/semanticrelease/semantic-release.go @@ -1,61 +1,83 @@ -// Package semanticrelease provides public methods to include in own code package semanticrelease import ( - "fmt" "io/ioutil" - "strconv" "strings" "github.com/Masterminds/semver" "github.com/Nightapes/go-semantic-release/internal/analyzer" - "github.com/Nightapes/go-semantic-release/internal/cache" "github.com/Nightapes/go-semantic-release/internal/changelog" "github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/Nightapes/go-semantic-release/internal/releaser" + "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" log "github.com/sirupsen/logrus" ) // SemanticRelease struct type SemanticRelease struct { - config *config.ReleaseConfig + config *config.ReleaseConfig + gitutil *gitutil.GitUtil + analyzer *analyzer.Analyzer + releaser releaser.Releaser + repository string } // New SemanticRelease struct -func New(c *config.ReleaseConfig) *SemanticRelease { - return &SemanticRelease{ - config: c, +func New(c *config.ReleaseConfig, repository string) (*SemanticRelease, error) { + util, err := gitutil.New(repository) + if err != nil { + return nil, err } + + analyzer, err := analyzer.New(c.CommitFormat, c.Changelog) + if err != nil { + return nil, err + } + + releaser, err := releaser.New(c).GetReleaser() + if err != nil { + return nil, err + } + + return &SemanticRelease{ + config: c, + gitutil: util, + releaser: releaser, + analyzer: analyzer, + repository: repository, + }, nil } // GetNextVersion from .version or calculate new from commits -func (s *SemanticRelease) GetNextVersion(repo string, force bool) (string, error) { - util, err := gitutil.New(repo) +func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, error) { + hash, err := s.gitutil.GetHash() if err != nil { - return "", err - } - - hash, err := util.GetHash() - if err != nil { - return "", err + return nil, err } log.Debugf("Ignore .version file if exits, %t", force) if !force { - content, err := cache.Read() - - if err == nil && content.Commit == hash { - log.Infof("Found cache, will return cached version %s", content.NextVersion) - return content.NextVersion, err + releaseVersion, err := s.readFromCache(hash) + if err != nil { + return nil, err + } + + if releaseVersion != nil { + return releaseVersion, nil } - log.Debugf("Mismatch git and version file %s - %s", content.Commit, hash) } - lastVersion, lastVersionHash, err := util.GetLastVersion() + currentBranch, err := s.gitutil.GetBranch() if err != nil { - return "", err + return nil, err } + + lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion() + if err != nil { + return nil, err + } + var newVersion semver.Version if lastVersion == nil { @@ -66,30 +88,25 @@ func (s *SemanticRelease) GetNextVersion(repo string, force bool) (string, error newVersion = *lastVersion } - commits, err := util.GetCommits(lastVersionHash) + commits, err := s.gitutil.GetCommits(lastVersionHash) if err != nil { - return "", err + return nil, err } log.Debugf("Found %d commits till last release", len(commits)) a, err := analyzer.New(s.config.CommitFormat, s.config.Changelog) if err != nil { - return "", err + return nil, err } result := a.Analyze(commits) - currentBranch, err := util.GetBranch() - if err != nil { - return "", err - } - for branch, releaseType := range s.config.Branch { if currentBranch == branch || strings.HasPrefix(currentBranch, branch) { log.Debugf("Found branch config for branch %s with release type %s", currentBranch, releaseType) switch releaseType { case "rc", "beta", "alpha": - newVersion = incPrerelease(releaseType, newVersion) + newVersion = s.incPrerelease(releaseType, newVersion) case "release": if len(result["major"]) > 0 { newVersion = newVersion.IncMajor() @@ -102,121 +119,83 @@ func (s *SemanticRelease) GetNextVersion(repo string, force bool) (string, error } } - log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String()) - err = saveToCache(util, lastVersion, &newVersion) - if err != nil { - return "", err + releaseVersion := shared.ReleaseVersion{ + Next: shared.ReleaseVersionEntry{ + Commit: hash, + Version: &newVersion, + }, + Last: shared.ReleaseVersionEntry{ + Commit: lastVersionHash, + Version: lastVersion, + }, + Branch: currentBranch, } - return newVersion.String(), err + + log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String()) + err = s.saveToCache(releaseVersion) + if err != nil { + return nil, err + } + return &releaseVersion, err } //SetVersion for git repository -func (s *SemanticRelease) SetVersion(version string, repo string) error { - - util, err := gitutil.New(repo) - if err != nil { - return err - } +func (s *SemanticRelease) SetVersion(version string) error { newVersion, err := semver.NewVersion(version) if err != nil { return err } - lastVersion, _, err := util.GetLastVersion() + lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion() + if err != nil { + return err + } + if lastVersion == nil { + lastVersion, _ = semver.NewVersion("1.0.0") + } + + hash, err := s.gitutil.GetHash() if err != nil { return err } - return saveToCache(util, lastVersion, newVersion) -} - -func saveToCache(util *gitutil.GitUtil, lastVersion *semver.Version, nextVersion *semver.Version) error { - - hash, err := util.GetHash() + currentBranch, err := s.gitutil.GetBranch() if err != nil { return err } - branch, err := util.GetBranch() - if err != nil { - return err - } - - newVersionContent := cache.VersionFileContent{ - Commit: hash, - NextVersion: nextVersion.String(), - Branch: branch, - } - - if lastVersion != nil { - newVersionContent.Version = lastVersion.String() - } - - log.Debugf("Save %s with hash %s to cache", nextVersion.String(), hash) - return cache.Write(newVersionContent) -} - -func incPrerelease(preReleaseType string, version semver.Version) semver.Version { - defaultPrerelease := preReleaseType + ".0" - if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) { - version, _ = version.SetPrerelease(defaultPrerelease) - } else { - parts := strings.Split(version.Prerelease(), ".") - if len(parts) == 2 { - i, err := strconv.Atoi(parts[1]) - if err != nil { - version, _ = version.SetPrerelease(defaultPrerelease) - log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String()) - } else { - version, _ = version.SetPrerelease(preReleaseType + "." + strconv.Itoa((i + 1))) - } - } else { - version, _ = version.SetPrerelease(defaultPrerelease) - log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String()) - } - } - - return version + return s.saveToCache(shared.ReleaseVersion{ + Next: shared.ReleaseVersionEntry{ + Commit: hash, + Version: newVersion, + }, + Last: shared.ReleaseVersionEntry{ + Commit: lastVersionHash, + Version: lastVersion, + }, + Branch: currentBranch, + }) } // GetChangelog from last version till now -func (s *SemanticRelease) GetChangelog(repo string) (string, error) { - nextVersion, err := s.GetNextVersion(repo, false) +func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) { + commits, err := s.gitutil.GetCommits(releaseVersion.Last.Commit) if err != nil { - log.Debugf("Could not get next version") - return "", err + return nil, err } - util, err := gitutil.New(repo) - if err != nil { - return "", err - } - - _, lastVersionHash, err := util.GetLastVersion() - if err != nil { - return "", err - } - - commits, err := util.GetCommits(lastVersionHash) - if err != nil { - return "", err - } + result := s.analyzer.Analyze(commits) log.Debugf("Found %d commits till last release", len(commits)) - a, err := analyzer.New(s.config.CommitFormat, s.config.Changelog) - if err != nil { - return "", err - } - result := a.Analyze(commits) - - c := changelog.New(s.config, a.GetRules()) - _, content, err := c.GenerateChanglog(nextVersion, s.config.GetRepositoryURL()+"/commit/{{hash}}", result) - if err != nil { - return "", err - } - return content, nil + c := changelog.New(s.config, s.analyzer.GetRules()) + return c.GenerateChanglog(shared.ChangelogTemplateConfig{ + Version: releaseVersion.Next.Version.String(), + Hash: releaseVersion.Last.Commit, + CommitURL: s.releaser.GetCommitURL(), + CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()), + }, result) } @@ -226,38 +205,40 @@ func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error { } // Release pusblish release to provider -func (s *SemanticRelease) Release(repo string) error { - util, err := gitutil.New(repo) +func (s *SemanticRelease) Release(force bool) error { + currentBranch, err := s.gitutil.GetBranch() if err != nil { return err } - currentBranch, err := util.GetBranch() if _, ok := s.config.Branch[currentBranch]; !ok { log.Debugf("Will not perform a new release. Current %s branch is not configured in release config", currentBranch) return nil } - nextVersion, err := s.GetNextVersion(repo, false) + releaseVersion, err := s.GetNextVersion(force) if err != nil { log.Debugf("Could not get next version") return err } - changelog, err := s.GetChangelog(repo) + generatedChanglog, err := s.GetChangelog(releaseVersion) if err != nil { log.Debugf("Could not get changelog") return err } - releaseTitle := fmt.Sprintf("%s v%s", s.config.ReleaseTitle, nextVersion) - releaser, err := releaser.New(s.config).GetReleaser() if err != nil { return err } - if err = releaser.CreateRelease(nextVersion, releaseTitle, changelog, "master"); err != nil { + err = releaser.ValidateConfig() + if err != nil { + return err + } + + if err = releaser.CreateRelease(releaseVersion, generatedChanglog); err != nil { return err }