Merge pull request #7 from Nightapes/add_git_releases

Add git releases
This commit is contained in:
Felix Wiedmann
2019-07-28 21:56:06 +02:00
committed by GitHub
40 changed files with 2657 additions and 327 deletions

4
.gitignore vendored
View File

@@ -4,7 +4,9 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
go-semantic-release
!cmd/go-semantic-release
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.test
@@ -12,3 +14,5 @@
*.out *.out
.version .version
.vscode/settings.json .vscode/settings.json
CHANGELOG.md
cover.html

11
.golangci.yml Normal file
View File

@@ -0,0 +1,11 @@
run:
tests: true
linters-settings:
golint:
min-confidence: 0
issues:
exclude-use-default: true
linters:
enable:
- golint

22
.release.yml Normal file
View File

@@ -0,0 +1,22 @@
commitFormat: angular
title: "go-semantic-release release"
branch:
master: beta
rc: rc
beta: beta
alpha: alpha
add_git_releases: alpha
changelog:
printAll: false
template: ''
templatePath: ''
release: 'github'
assets:
- name: ./build/go-semantic-release
compress: false
- name: ./build/go-semantic-release.exe
compress: false
github:
repo: "go-semantic-release"
user: "nightapes"
customUrl: ""

40
.travis.yml Normal file
View File

@@ -0,0 +1,40 @@
dist: xenial
git:
depth: false
language: go
go:
- 1.12.x
go_import_path: github.com/nightapes/go-semantic-release
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
services:
- docker
notifications:
email: false
env:
- GO111MODULE=on
before_script:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0
script:
- golangci-lint run ./...
- go test -v ./...
- go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/
- echo "Building version `./build/go-semantic-release-temp next --loglevel debug --no-cache`"
- go build -o build/go-semantic-release -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
- GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
after_success:
- ./build/go-semantic-release-temp release --loglevel debug
branches:
except:
- /^v\d+\.\d+\.\d+$/

View File

@@ -1,5 +1,15 @@
# go-semantic-release # go-semantic-release
## Release Types
| Type | Git tag | Changelog | Release | Write access git | Api token |
|--- |:---: |:---: |:---: |:---: |:---: |
| `git` | :white_check_mark: | | | :white_check_mark:| |
| `github` | :white_check_mark: | :white_check_mark: | :white_check_mark:| | :white_check_mark: |
| `gitlab` | :white_check_mark: | :white_check_mark: | :white_check_mark:| | :white_check_mark: |
## Build ## Build
`go build ./cmd/go-semantic-release/` `go build ./cmd/go-semantic-release/`

2
README2.md Normal file
View File

@@ -0,0 +1,2 @@
go build ./cmd/main.go && ./main.exe version next --path /f/Repro/ambassador/
go build ./cmd/main.go && ./main.exe --loglevel debug version set v1.1.1 --path /f/Repro/ambassador/

View File

@@ -0,0 +1,65 @@
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
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
releaseVersion, commits, err := s.GetNextVersion(provider, force)
if err != nil {
return err
}
log.Debugf("Found %d commits till last release", len(commits))
generatedChangelog, err := s.GetChangelog(commits, releaseVersion)
if err != nil {
return err
}
if err = s.WriteChangeLog(generatedChangelog.Content, file); err != nil {
log.Fatal(err)
}
return nil
},
}

View File

@@ -0,0 +1,53 @@
package commands
import (
"fmt"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
log "github.com/sirupsen/logrus"
"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
}
provider, err := s.GetCIProvider()
if err != nil {
log.Infof("Will not calculate version, set fake version. Could not find CI Provider, if running locally, set env CI=true")
fmt.Println("0.0.0-fake.0")
return nil
}
releaseVersion, _, err := s.GetNextVersion(provider, force)
if err != nil {
return err
}
fmt.Println(releaseVersion.Next.Version.String())
return nil
},
}

View File

@@ -0,0 +1,43 @@
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("no-cache")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository)
if err != nil {
return err
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
return s.Release(provider, force)
},
}

View File

@@ -0,0 +1,60 @@
package commands
import (
"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)
cmd.SilenceUsage = true
return nil
},
}
//Execute rootCmd
func Execute(version string) {
rootCmd.Version = version
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
currentDir, err := os.Getwd()
if err != nil {
panic(err)
}
rootCmd.PersistentFlags().StringP("repository", "r", currentDir, "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)
}
}

View File

@@ -0,0 +1,41 @@
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
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
return s.SetVersion(provider, args[0])
},
}

View File

@@ -0,0 +1,37 @@
package commands
import (
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(zipCmd)
}
var zipCmd = &cobra.Command{
Use: "zip",
Short: "Zip configured artifact from release config",
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
}
if err = s.ZipFiles(); err != nil {
return err
}
return nil
},
}

View File

@@ -1,112 +1,11 @@
package main package main
import ( import (
"os" "github.com/Nightapes/go-semantic-release/cmd/go-semantic-release/commands"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
log "github.com/sirupsen/logrus"
"gopkg.in/urfave/cli.v1" // imports as package "cli"
) )
var version string
func main() { func main() {
app := cli.NewApp() commands.Execute(version)
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "loglevel",
Value: "error",
Usage: "Set loglevel 'LEVEL",
},
}
app.Commands = []cli.Command{
{
Name: "version",
Aliases: []string{"v"},
Usage: "version commands",
Subcommands: []cli.Command{
{
Name: "set",
Usage: "set version `VERSION`",
Action: func(c *cli.Context) error {
setLoglevel(c.GlobalString("loglevel"))
path := c.String("path")
version := c.Args().First()
log.Infof("Version %s", version)
return semanticrelease.SetVersion(version, path)
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "release-config.json",
Usage: "Load release configuration from `FILE`",
},
cli.StringFlag{
Name: "path, p",
Usage: "`PATH` to repro ",
},
},
},
{
Name: "next",
Usage: "get next `VERSION` or the set one ",
Action: func(c *cli.Context) error {
setLoglevel(c.GlobalString("loglevel"))
path := c.String("path")
return semanticrelease.GetNextVersion(path)
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "release-config.json",
Usage: "Load release configuration from `FILE`",
},
cli.StringFlag{
Name: "path, p",
Usage: "`PATH` to repro ",
},
},
},
},
},
{
Name: "release",
Aliases: []string{},
Usage: "make release",
Action: func(c *cli.Context) error {
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "release-config.json",
Usage: "Load release configuration from `FILE`",
},
},
},
{
Name: "init",
Aliases: []string{},
Usage: "create config",
Action: func(c *cli.Context) error {
return nil
},
},
}
//gitutil.GetCommits(folder)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func setLoglevel(level string) {
parsed, err := log.ParseLevel(level)
if err != nil {
log.Errorf("Invalid loglevel %s", level)
} else {
log.SetLevel(parsed)
}
} }

20
go.mod
View File

@@ -4,10 +4,20 @@ go 1.12
require ( require (
github.com/Masterminds/semver v1.4.2 github.com/Masterminds/semver v1.4.2
github.com/sirupsen/logrus v1.4.1 github.com/coreos/etcd v3.3.10+incompatible
github.com/urfave/cli v1.20.0 // indirect github.com/gliderlabs/ssh v0.2.2 // indirect
golang.org/x/tools v0.0.0-20190508150211-cf84161cff3f // indirect github.com/google/go-cmp v0.3.0 // indirect
gopkg.in/src-d/go-git.v4 v4.11.0 github.com/google/go-github/v25 v25.1.3
gopkg.in/urfave/cli.v1 v1.20.0 github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
google.golang.org/appengine v1.6.1 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.1
gopkg.in/src-d/go-git.v4 v4.12.0
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
) )

135
go.sum
View File

@@ -1,77 +1,154 @@
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 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 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 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
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/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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.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/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 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.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/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.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.3 h1:Ht4YIQgUh4l4lc80fvGnw60khXysXvlgPxPP8uJG3EA=
github.com/google/go-github/v25 v25.1.3/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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 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= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 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/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
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.1/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 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 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 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/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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/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-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/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/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 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-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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/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-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190508150211-cf84161cff3f h1:sjxqKRXfnzgJFg/igBXeLZoBVAKXuAAljgr+PcNr7u8= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/tools v0.0.0-20190508150211-cf84161cff3f/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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=
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 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-billy.v4 v4.3.1 h1:OkK1DmefDy1Z6Veu82wdNj/cLpYORhdX4qdaYCPwc7s=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= gopkg.in/src-d/go-billy.v4 v4.3.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
gopkg.in/src-d/go-git.v4 v4.11.0 h1:cJwWgJ0DXifrNrXM6RGN1Y2yR60Rr1zQ9Q5DX5S9qgU= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.11.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/src-d/go-git.v4 v4.12.0 h1:CKgvBCJCcdfNnyXPYI4Cp8PaDDAmAPEN0CtfEdEAbd8=
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

View File

@@ -1,63 +1,102 @@
// Package analyzer provides different commit analyzer
package analyzer package analyzer
import ( import (
"fmt"
"github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
//Analyzer struct
type Analyzer struct { type Analyzer struct {
CommitFormat string analyzeCommit analyzeCommit
Config config.ChangelogConfig
} }
type Rules struct { //Release types, like major
type Release string
//Scope of the commit, like feat, fix,..
type Scope string
//Rule for commits
type Rule struct {
Tag string Tag string
Release string TagString string
Release Release
Changelog bool
} }
type AnalyzeCommit interface { type analyzeCommit interface {
Analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bool) analyze(commit gitutil.Commit, tag Rule) (AnalyzedCommit, bool, error)
GetRules() []Rules getRules() []Rule
} }
//AnalyzedCommit struct
type AnalyzedCommit struct { type AnalyzedCommit struct {
Commit gitutil.Commit Commit gitutil.Commit
ParsedMessage string ParsedMessage string
Scope string Scope Scope
ParsedBreakingChangeMessage string ParsedBreakingChangeMessage string
Tag string
TagString string
Print bool
} }
func New(format string) *Analyzer { //New Analyzer struct for given commit format
return &Analyzer{ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
CommitFormat: format, analyzer := &Analyzer{
Config: config,
} }
} switch format {
func (a *Analyzer) Analyze(commits []gitutil.Commit) map[string][]AnalyzedCommit {
var commitAnalayzer AnalyzeCommit
switch a.CommitFormat {
case "angular": case "angular":
log.Infof("analyze angular format") log.Debugf("Commit format set to angular")
commitAnalayzer = NewAngular() analyzer.analyzeCommit = newAngular()
default:
return nil, fmt.Errorf("invalid commit format: %s", format)
} }
return analyzer, nil
analyzedCommits := make(map[string][]AnalyzedCommit) }
// GetRules from current mode
func (a *Analyzer) GetRules() []Rule {
return a.analyzeCommit.getRules()
}
// Analyze commits and return commits splitted by major,minor,patch
func (a *Analyzer) Analyze(commits []gitutil.Commit) map[Release][]AnalyzedCommit {
analyzedCommits := make(map[Release][]AnalyzedCommit)
analyzedCommits["major"] = make([]AnalyzedCommit, 0) analyzedCommits["major"] = make([]AnalyzedCommit, 0)
analyzedCommits["minor"] = make([]AnalyzedCommit, 0) analyzedCommits["minor"] = make([]AnalyzedCommit, 0)
analyzedCommits["patch"] = make([]AnalyzedCommit, 0) analyzedCommits["patch"] = make([]AnalyzedCommit, 0)
analyzedCommits["none"] = make([]AnalyzedCommit, 0)
for _, commit := range commits { for _, commit := range commits {
for _, rule := range commitAnalayzer.GetRules() { for _, rule := range a.analyzeCommit.getRules() {
analyzedCommit, hasBreakingChange := commitAnalayzer.Analyze(commit, rule.Tag) analyzedCommit, hasBreakingChange, err := a.analyzeCommit.analyze(commit, rule)
if err == nil {
if a.Config.PrintAll {
analyzedCommit.Print = true
} else {
analyzedCommit.Print = rule.Changelog
}
if hasBreakingChange { if hasBreakingChange {
analyzedCommits["major"] = append(analyzedCommits["major"], analyzedCommit) analyzedCommits["major"] = append(analyzedCommits["major"], analyzedCommit)
} else { } else {
analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], analyzedCommit) analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], analyzedCommit)
} }
break
}
} }
} }
log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"]))
return analyzedCommits return analyzedCommits
} }

View File

@@ -0,0 +1,16 @@
package analyzer_test
import (
"testing"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestAnalyzer(t *testing.T) {
_, err := analyzer.New("unknown", config.ChangelogConfig{})
assert.Error(t, err)
}

View File

@@ -1,65 +1,113 @@
// Package analyzer provides different commit analyzer
package analyzer package analyzer
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/Nightapes/go-semantic-release/internal/gitutil"
) )
type Angular struct { type angular struct {
rules []Rules rules []Rule
regex string regex string
} }
func NewAngular() *Angular { func newAngular() *angular {
return &Angular{ return &angular{
regex: `(TAG)(?:\((.*)\))?: (.*)`, regex: `(TAG)(?:\((.*)\))?: (.*)`,
rules: []Rules{ rules: []Rule{
{ {
Tag: "feat", Tag: "feat",
TagString: "Features",
Release: "minor", Release: "minor",
Changelog: true,
}, },
{ {
Tag: "fix", Tag: "fix",
TagString: "Bug fixes",
Release: "patch", Release: "patch",
Changelog: true,
}, { }, {
Tag: "perf", Tag: "perf",
TagString: "Performance improvments",
Release: "patch", Release: "patch",
Changelog: true,
}, {
Tag: "docs",
TagString: "Documentation changes",
Release: "none",
Changelog: false,
},
{
Tag: "style",
TagString: "Style",
Release: "none",
Changelog: false,
}, {
Tag: "refactor",
TagString: "Code refactor",
Release: "none",
Changelog: false,
}, {
Tag: "test",
TagString: "Testing",
Release: "none",
Changelog: false,
}, {
Tag: "chore",
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
Release: "none",
Changelog: false,
}, {
Tag: "build",
TagString: "Changes to CI/CD",
Release: "none",
Changelog: false,
}, },
}, },
} }
} }
func (a *Angular) GetRules() []Rules { func (a *angular) getRules() []Rule {
return a.rules return a.rules
} }
func (a *Angular) Analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bool) { func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, bool, error) {
analyzed := AnalyzedCommit{ analyzed := AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
} }
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", tag, -1)) re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
matches := re.FindAllStringSubmatch(commit.Message+" "+commit.Message, -1) matches := re.FindAllStringSubmatch(commit.Message, -1)
if len(matches) >= 1 { if len(matches) >= 1 {
if len(matches[0]) >= 3 { if len(matches[0]) >= 3 {
analyzed.Scope = matches[0][2]
analyzed.Scope = Scope(matches[0][2])
message := strings.Join(matches[0][3:], "") message := strings.Join(matches[0][3:], "")
splitted := strings.SplitN(message, "BREAKING CHANGE:", 1) if !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ")
if len(splitted) == 1 { log.Tracef("%s: found %s", commit.Message, rule.Tag)
analyzed.ParsedMessage = splitted[0] return analyzed, false, nil
return analyzed, false
} }
analyzed.ParsedMessage = splitted[0] breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
analyzed.ParsedBreakingChangeMessage = splitted[1]
return analyzed, true
analyzed.ParsedMessage = strings.Trim(breakingChange[0], " ")
analyzed.ParsedBreakingChangeMessage = strings.Trim(breakingChange[1], " ")
log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil
} }
} }
return analyzed, false log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return analyzed, false, fmt.Errorf("not found")
} }

View File

@@ -0,0 +1,173 @@
package analyzer_test
import (
"testing"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestAngular(t *testing.T) {
testConfigs := []struct {
testCase string
commits []gitutil.Commit
analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit
}{
{
testCase: "feat",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
},
},
"major": []analyzer.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
commits: []gitutil.Commit{
gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
},
},
{
testCase: "feat breaking change",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
},
},
"major": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
},
Scope: "internal/changelog",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "change api to v2",
},
},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
commits: []gitutil.Commit{
gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
gitutil.Commit{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
},
},
},
{
testCase: "invalid",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{},
"major": []analyzer.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
commits: []gitutil.Commit{
gitutil.Commit{
Message: "internal/changelog: my first commit",
Author: "me",
Hash: "12345667",
},
},
},
{
testCase: "feat and build",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
},
},
"none": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "build(internal/changelog): my first build",
Author: "me",
Hash: "12345668",
},
Scope: "internal/changelog",
ParsedMessage: "my first build",
Tag: "build",
TagString: "Changes to CI/CD",
Print: false,
ParsedBreakingChangeMessage: "",
},
},
"patch": []analyzer.AnalyzedCommit{},
"major": []analyzer.AnalyzedCommit{},
},
commits: []gitutil.Commit{
gitutil.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
gitutil.Commit{
Message: "build(internal/changelog): my first build",
Author: "me",
Hash: "12345668",
},
},
},
}
angular, err := analyzer.New("angular", config.ChangelogConfig{})
assert.NoError(t, err)
for _, test := range testConfigs {
analyzedCommits := angular.Analyze(test.commits)
assert.Equalf(t, test.analyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
}
}

93
internal/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,93 @@
// Package cache helper for cache version
package cache
import (
log "github.com/sirupsen/logrus"
"io/ioutil"
"path"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/shared"
"gopkg.in/yaml.v2"
)
// ReleaseVersion struct
type ReleaseVersion struct {
Last ReleaseVersionEntry `yaml:"last"`
Next ReleaseVersionEntry `yaml:"next"`
Branch string `yaml:"branch"`
Draft bool `yaml:"draft"`
}
//ReleaseVersionEntry struct
type ReleaseVersionEntry struct {
Commit string `yaml:"commit"`
Version string `yaml:"version"`
}
// Write version into .version
func Write(repository string, releaseVersion shared.ReleaseVersion) error {
completePath := path.Join(path.Dir(repository), ".version")
toCache := &ReleaseVersion{
Next: ReleaseVersionEntry{
Commit: releaseVersion.Next.Commit,
Version: releaseVersion.Next.Version.String(),
},
Last: ReleaseVersionEntry{
Commit: releaseVersion.Last.Commit,
Version: releaseVersion.Last.Version.String(),
},
Branch: releaseVersion.Branch,
Draft: releaseVersion.Draft,
}
data, err := yaml.Marshal(toCache)
if err != nil {
return err
}
log.Infof("Save %s with hash %s to cache", releaseVersion.Next.Version.String(), releaseVersion.Next.Commit)
return ioutil.WriteFile(completePath, data, 0644)
}
// Read version into .version
func Read(repository string) (*shared.ReleaseVersion, error) {
completePath := path.Join(path.Dir(repository), ".version")
content, err := ioutil.ReadFile(completePath)
if err != nil {
return &shared.ReleaseVersion{}, err
}
var parsedContent ReleaseVersion
err = yaml.Unmarshal(content, &parsedContent)
if err != nil {
return &shared.ReleaseVersion{}, err
}
nextVersion, err := semver.NewVersion(parsedContent.Next.Version)
if err != nil {
return nil, err
}
lastVersion, err := semver.NewVersion(parsedContent.Last.Version)
if err != nil {
return nil, err
}
releaseVersion := &shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Commit: parsedContent.Next.Commit,
Version: nextVersion,
},
Last: shared.ReleaseVersionEntry{
Commit: parsedContent.Last.Commit,
Version: lastVersion,
},
Branch: parsedContent.Branch,
Draft: parsedContent.Draft,
}
log.Infof("Found cache, will return cached version %s", parsedContent.Next.Version)
return releaseVersion, nil
}

89
internal/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,89 @@
package cache_test
import (
"testing"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/cache"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path"
)
func TestReadCacheNotFound(t *testing.T) {
_, err := cache.Read("notfound/dir")
assert.Errorf(t, err, "Read non exsiting file")
}
func TestReadCacheInvalidContent(t *testing.T) {
dir, err := ioutil.TempDir("", "prefix")
assert.NoError(t, err)
defer os.RemoveAll(dir)
completePath := path.Join(path.Dir(dir), ".version")
brokenContent := []byte("hello broken\ngo: lang\n")
err = ioutil.WriteFile(completePath, brokenContent, 0644)
assert.NoError(t, err)
_, readError := cache.Read(dir)
assert.Errorf(t, readError, "Should give error, when broken content")
}
func TestWriteAndReadCache(t *testing.T) {
dir, err := ioutil.TempDir("", "prefix")
assert.NoError(t, err)
content := shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Commit: "12345",
Version: createVersion("1.0.0"),
},
Next: shared.ReleaseVersionEntry{
Commit: "12346",
Version: createVersion("1.1.0"),
},
Branch: "master",
Draft: true,
}
defer os.RemoveAll(dir)
writeError := cache.Write(dir, content)
assert.NoErrorf(t, writeError, "Should write file")
result, readError := cache.Read(dir)
assert.NoErrorf(t, readError, "Should read file")
assert.EqualValues(t, &content, result)
}
func TestWriteNotFound(t *testing.T) {
err := cache.Write("notfound/dir", shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Commit: "12345",
Version: createVersion("1.0.0"),
},
Next: shared.ReleaseVersionEntry{
Commit: "12346",
Version: createVersion("1.1.0"),
},
Branch: "master",
Draft: true,
})
assert.Errorf(t, err, "Write non exsiting file")
}
func createVersion(version string) *semver.Version {
ver, _ := semver.NewVersion(version)
return ver
}

View File

@@ -0,0 +1,68 @@
package calculator
import (
"strconv"
"strings"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
log "github.com/sirupsen/logrus"
)
// Calculator struct
type Calculator struct{}
// New Calculator struct
func New() *Calculator {
return &Calculator{}
}
//IncPrerelease increase prerelease by one
func (c *Calculator) IncPrerelease(preReleaseType string, version *semver.Version) (semver.Version, error) {
defaultPrerelease := preReleaseType + ".0"
if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) {
return version.SetPrerelease(defaultPrerelease)
}
parts := strings.Split(version.Prerelease(), ".")
if len(parts) == 2 {
i, err := strconv.Atoi(parts[1])
if err != nil {
log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String())
return version.SetPrerelease(defaultPrerelease)
}
return version.SetPrerelease(preReleaseType + "." + strconv.Itoa((i + 1)))
}
log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String())
return version.SetPrerelease(defaultPrerelease)
}
//CalculateNewVersion from given commits and lastversion
func (c *Calculator) CalculateNewVersion(commits map[analyzer.Release][]analyzer.AnalyzedCommit, lastVersion *semver.Version, releaseType string, firstRelease bool) (semver.Version, bool) {
switch releaseType {
case "beta", "alpha":
if len(commits["major"]) > 0 || len(commits["minor"]) > 0 || len(commits["patch"]) > 0 {
version, _ := c.IncPrerelease(releaseType, lastVersion)
return version, true
}
case "rc":
if len(commits["major"]) > 0 || len(commits["minor"]) > 0 || len(commits["patch"]) > 0 {
version, _ := c.IncPrerelease(releaseType, lastVersion)
return version, false
}
case "release":
if !firstRelease {
if len(commits["major"]) > 0 {
return lastVersion.IncMajor(), false
} else if len(commits["minor"]) > 0 {
return lastVersion.IncMinor(), false
} else if len(commits["patch"]) > 0 {
return lastVersion.IncPatch(), false
}
}
}
return *lastVersion, false
}

View File

@@ -0,0 +1,219 @@
package calculator_test
import (
"testing"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/calculator"
"github.com/stretchr/testify/assert"
)
func createVersion(version string) *semver.Version {
ver, _ := semver.NewVersion(version)
return ver
}
func TestCalculator_IncPrerelease(t *testing.T) {
testConfigs := []struct {
testCase string
preReleaseType string
lastVersion *semver.Version
nextVersion string
hasError bool
}{
{
testCase: "version without preRelease",
preReleaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-alpha.0",
},
{
testCase: "version with preRelease",
preReleaseType: "alpha",
lastVersion: createVersion("1.0.0-alpha.0"),
nextVersion: "1.0.0-alpha.1",
},
{
testCase: "version with preRelease, change type",
preReleaseType: "beta",
lastVersion: createVersion("1.0.0-alpha.0"),
nextVersion: "1.0.0-beta.0",
},
{
testCase: "version with preRelease but broken",
preReleaseType: "alpha",
lastVersion: createVersion("1.0.0-alpha.br0ken"),
nextVersion: "1.0.0-alpha.0",
},
{
testCase: "version with preRelease but broken 2",
preReleaseType: "alpha",
lastVersion: createVersion("1.0.0-alphabr0ken"),
nextVersion: "1.0.0-alpha.0",
},
}
c := calculator.New()
for _, test := range testConfigs {
next, err := c.IncPrerelease(test.preReleaseType, test.lastVersion)
assert.Equalf(t, test.hasError, err != nil, "Testcase %s should have error: %t -> %s", test.testCase, test.hasError, err)
assert.Equal(t, test.nextVersion, next.String())
}
}
func TestCalculator_CalculateNewVersion(t *testing.T) {
testConfigs := []struct {
testCase string
releaseType string
lastVersion *semver.Version
nextVersion string
isDraft bool
isFirst bool
analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit
}{
{
testCase: "version with preRelease alpha",
releaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-alpha.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{},
},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: true,
},
{
testCase: "version with preRelease beta",
releaseType: "beta",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-beta.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{},
},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: true,
},
{
testCase: "version without commits",
releaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
{
testCase: "version with commits and first release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"minor": []analyzer.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: true,
isDraft: false,
},
{
testCase: "version with commits and rc release",
releaseType: "rc",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-rc.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"minor": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"patch": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
{
testCase: "version with commits and rc release",
releaseType: "rc",
lastVersion: createVersion("1.0.0-rc.0"),
nextVersion: "1.0.0-rc.1",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"patch": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
{
testCase: "version with commits and major release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "2.0.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"minor": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"patch": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
{
testCase: "version with commits and minor release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.1.0",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"patch": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
{
testCase: "version with commits and minor patch",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.1",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"major": []analyzer.AnalyzedCommit{},
"minor": []analyzer.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{analyzer.AnalyzedCommit{}},
"none": []analyzer.AnalyzedCommit{},
},
isFirst: false,
isDraft: false,
},
}
c := calculator.New()
for _, test := range testConfigs {
next, draft := c.CalculateNewVersion(test.analyzedCommits, test.lastVersion, test.releaseType, test.isFirst)
assert.Equalf(t, test.isDraft, draft, "Should have draft %t for testcase %s", test.isDraft, test.testCase)
assert.Equalf(t, test.nextVersion, next.String(), "Should have version %s for testcase %s", test.nextVersion, test.testCase)
}
}

View File

@@ -0,0 +1,132 @@
package changelog
import (
"bytes"
"strings"
"text/template"
"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"
)
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ range $index,$commit := .BreakingChanges -}}
{{ if eq $index 0 }}
## BREAKING CHANGES
{{ end}}
* **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedBreakingChangeMessage}}
introduced by commit:
{{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}}
{{ end -}}
{{ range $key := .Order }}
{{ $commits := index $.Commits $key}} {{if $commits -}}
### {{ $key }}
{{ range $index,$commit := $commits -}}
* **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}}
{{ end -}}
{{ end -}}
{{ end -}}
`
type changelogContent struct {
Commits map[string][]analyzer.AnalyzedCommit
BreakingChanges []analyzer.AnalyzedCommit
Order []string
Version string
Now time.Time
Backtick string
HasURL bool
URL string
}
//Changelog struct
type Changelog struct {
config *config.ReleaseConfig
rules []analyzer.Rule
releaseTime time.Time
}
//New Changelog struct for generating changelog from commits
func New(config *config.ReleaseConfig, rules []analyzer.Rule, releaseTime time.Time) *Changelog {
return &Changelog{
config: config,
rules: rules,
releaseTime: releaseTime,
}
}
// GenerateChanglog from given commits
func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
commitsPerScope := map[string][]analyzer.AnalyzedCommit{}
commitsBreakingChange := []analyzer.AnalyzedCommit{}
order := make([]string, 0)
for _, rule := range c.rules {
log.Debugf("Add %s to list", rule.TagString)
if rule.Changelog || c.config.Changelog.PrintAll {
order = append(order, rule.TagString)
}
}
for _, commits := range analyzedCommits {
for _, commit := range commits {
if commit.Print {
if commit.ParsedBreakingChangeMessage != "" {
commitsBreakingChange = append(commitsBreakingChange, commit)
continue
}
if _, ok := commitsPerScope[commit.TagString]; !ok {
commitsPerScope[commit.TagString] = make([]analyzer.AnalyzedCommit, 0)
}
commitsPerScope[commit.TagString] = append(commitsPerScope[commit.TagString], commit)
}
}
}
changelogContent := changelogContent{
Version: templateConfig.Version,
Commits: commitsPerScope,
Now: c.releaseTime,
BreakingChanges: commitsBreakingChange,
Backtick: "`",
Order: order,
HasURL: templateConfig.CommitURL != "",
URL: templateConfig.CommitURL,
}
title, err := generateTemplate(defaultChangelogTitle, changelogContent)
if err != nil {
return nil, err
}
content, err := generateTemplate(defaultChangelog, changelogContent)
return &shared.GeneratedChangelog{Title: title, Content: content}, err
}
func generateTemplate(text string, values changelogContent) (string, error) {
funcMap := template.FuncMap{
"replace": replace,
}
var tpl bytes.Buffer
tmpl, err := template.New("template").Funcs(funcMap).Parse(text)
if err != nil {
return "", err
}
err = tmpl.Execute(&tpl, values)
if err != nil {
return "", err
}
return tpl.String(), nil
}
func replace(input, from, to string) string {
return strings.Replace(input, from, to, -1)
}

View File

@@ -0,0 +1,120 @@
package changelog_test
import (
"testing"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/changelog"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
"time"
)
func TestChangelog(t *testing.T) {
templateConfig := shared.ChangelogTemplateConfig{
CommitURL: "https://commit.url",
CompareURL: "https://compare.url",
Hash: "hash",
Version: "1.0.0",
}
testConfigs := []struct {
testCase string
analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit
result *shared.GeneratedChangelog
hasError bool
}{
{
testCase: "feat",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(test): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ",
},
hasError: false,
},
{
testCase: "feat breaking change",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(test): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
},
analyzer.AnalyzedCommit{
Commit: gitutil.Commit{
Message: "feat(test): my first break: BREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
},
Scope: "internal/changelog",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "change api to v2",
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n\n## BREAKING CHANGES\n\n* **`internal/changelog`** change api to v2 \nintroduced by commit: \nmy first break ([1234566](https://commit.url)) \n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ",
},
hasError: false,
},
}
cl := changelog.New(&config.ReleaseConfig{}, []analyzer.Rule{
{
Tag: "feat",
TagString: "Features",
Release: "minor",
Changelog: true,
},
{
Tag: "fix",
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
},
{
Tag: "build",
TagString: "Build",
Release: "none",
Changelog: false,
},
}, time.Date(2019, 7, 19, 0, 0, 0, 0, time.UTC))
for _, config := range testConfigs {
generatedChangelog, err := cl.GenerateChanglog(templateConfig, config.analyzedCommits)
assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err)
assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase)
}
}

57
internal/ci/ci.go Normal file
View File

@@ -0,0 +1,57 @@
package ci
import (
"fmt"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
log "github.com/sirupsen/logrus"
"os"
"strings"
)
//ProviderConfig struct
type ProviderConfig struct {
IsPR bool
PR string
PRBranch string
Branch string
Tag string
Commit string
BuildURL string
Service string
Name string
}
//Service interface
type Service interface {
detect(envs map[string]string) (*ProviderConfig, error)
}
//ReadAllEnvs as a map
func ReadAllEnvs() map[string]string {
envs := map[string]string{}
for _, pair := range os.Environ() {
splitted := strings.SplitN(pair, "=", 2)
envs[splitted[0]] = splitted[1]
}
return envs
}
//GetCIProvider get provider
func GetCIProvider(gitUtil *gitutil.GitUtil, envs map[string]string) (*ProviderConfig, error) {
services := []Service{
Travis{},
Git{gitUtil: gitUtil}, // GIt must be the last option to check
}
for _, service := range services {
config, err := service.detect(envs)
if err == nil {
log.Infof("Found CI: %s", config.Name)
return config, nil
}
log.Debugf("%s", err.Error())
}
return nil, fmt.Errorf("could not find any CI, if running locally set env CI=true")
}

100
internal/ci/ci_test.go Normal file
View File

@@ -0,0 +1,100 @@
package ci_test
import (
"testing"
"time"
"github.com/Nightapes/go-semantic-release/internal/ci"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/stretchr/testify/assert"
"gopkg.in/src-d/go-billy.v4/memfs"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/storage/memory"
)
func TestCi(t *testing.T) {
fs := memfs.New()
repository, err := git.Init(memory.NewStorage(), fs)
assert.NoError(t, err, "should open git repository")
file, err := fs.Create("README.md")
assert.NoError(t, err, "should create file")
w, err := repository.Worktree()
assert.NoError(t, err, "should get worktree")
_, err = w.Add(file.Name())
assert.NoError(t, err, "should add file")
gitUtilInMemory := &gitutil.GitUtil{
Repository: repository,
}
newCommit, err := w.Commit("fix(test): add a commit", &git.CommitOptions{
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
assert.NoError(t, err, "should commit")
testConfigs := []struct {
service string
envs map[string]string
result *ci.ProviderConfig
hasError bool
}{
{
service: "none",
envs: map[string]string{},
result: nil,
hasError: true,
},
{
service: "Git",
envs: map[string]string{
"CI": "true",
},
result: &ci.ProviderConfig{IsPR: false, PR: "", PRBranch: "", Branch: "master", Tag: "", Commit: newCommit.String(), BuildURL: "", Service: "git", Name: "Git only"},
hasError: false,
},
{
service: "Travis PR",
envs: map[string]string{
"TRAVIS": "true",
"TRAVIS_PULL_REQUEST": "10",
"TRAVIS_COMMIT": "190bfd6aa60022afd0ef830342cfb07e33c45f37",
"TRAVIS_TAG": "TAG",
"TRAVIS_BUILD_WEB_URL": "https://travis-ci.com/owner/repo/builds/1234",
"TRAVIS_BRANCH": "master",
"TRAVIS_PULL_REQUEST_BRANCH": "pr",
},
result: &ci.ProviderConfig{IsPR: true, PR: "10", PRBranch: "pr", Branch: "master", Tag: "TAG", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "https://travis-ci.com/owner/repo/builds/1234", Service: "travis", Name: "Travis CI"},
hasError: false,
},
{
service: "Travis Push",
envs: map[string]string{
"TRAVIS": "true",
"TRAVIS_PULL_REQUEST": "false",
"TRAVIS_COMMIT": "190bfd6aa60022afd0ef830342cfb07e33c45f37",
"TRAVIS_TAG": "TAG",
"TRAVIS_BUILD_WEB_URL": "https://travis-ci.com/owner/repo/builds/1234",
"TRAVIS_BRANCH": "master",
},
result: &ci.ProviderConfig{IsPR: false, PR: "", PRBranch: "", Branch: "master", Tag: "TAG", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "https://travis-ci.com/owner/repo/builds/1234", Service: "travis", Name: "Travis CI"},
hasError: false,
},
}
for _, config := range testConfigs {
provider, err := ci.GetCIProvider(gitUtilInMemory, config.envs)
assert.Equalf(t, config.hasError, err != nil, "Service %s should have error: %t -> %s", config.service, config.hasError, err)
assert.Equalf(t, config.result, provider, "Service %s should have provider", config.service)
}
}

37
internal/ci/git.go Normal file
View File

@@ -0,0 +1,37 @@
package ci
import (
"fmt"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
)
//Git struct
type Git struct {
gitUtil *gitutil.GitUtil
}
//Detect if on Git
func (t Git) detect(envs map[string]string) (*ProviderConfig, error) {
if _, exists := envs["CI"]; !exists {
return nil, fmt.Errorf("running not git only")
}
hash, err := t.gitUtil.GetHash()
if err != nil {
return nil, err
}
currentBranch, err := t.gitUtil.GetBranch()
if err != nil {
return nil, err
}
return &ProviderConfig{
Service: "git",
Name: "Git only",
Commit: hash,
Branch: currentBranch,
IsPR: false,
}, nil
}

41
internal/ci/travis.go Normal file
View File

@@ -0,0 +1,41 @@
package ci
import (
"fmt"
log "github.com/sirupsen/logrus"
)
//Travis struct
type Travis struct{}
//Detect if on travis
func (t Travis) detect(envs map[string]string) (*ProviderConfig, error) {
if _, exists := envs["TRAVIS"]; !exists {
return nil, fmt.Errorf("not running on travis")
}
isPR := false
value := envs["TRAVIS_PULL_REQUEST"]
pr := ""
if value == "false" {
log.Debugf("TRAVIS_PULL_REQUEST=%s, not running on pr", value)
} else {
isPR = true
pr = value
}
return &ProviderConfig{
Service: "travis",
Name: "Travis CI",
Commit: envs["TRAVIS_COMMIT"],
Tag: envs["TRAVIS_TAG"],
BuildURL: envs["TRAVIS_BUILD_WEB_URL"],
Branch: envs["TRAVIS_BRANCH"],
IsPR: isPR,
PR: pr,
PRBranch: envs["TRAVIS_PULL_REQUEST_BRANCH"],
}, nil
}

View File

@@ -1,3 +1,4 @@
// Package gitutil provides helper methods for git
package gitutil package gitutil
import ( import (
@@ -7,32 +8,37 @@ import (
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/object"
) )
// Commit struct
type Commit struct { type Commit struct {
Message string Message string
Author string Author string
Hash string Hash string
} }
type GitUtils struct { // GitUtil struct
type GitUtil struct {
Repository *git.Repository Repository *git.Repository
} }
func New(folder string) (*GitUtils, error) { // New GitUtil struct and open git repository
func New(folder string) (*GitUtil, error) {
r, err := git.PlainOpen(folder) r, err := git.PlainOpen(folder)
if err != nil { if err != nil {
return nil, err return nil, err
} }
utils := &GitUtils{ utils := &GitUtil{
Repository: r, Repository: r,
} }
return utils, nil return utils, nil
} }
func (g *GitUtils) GetHash() (string, error) { // GetHash from git HEAD
func (g *GitUtil) GetHash() (string, error) {
ref, err := g.Repository.Head() ref, err := g.Repository.Head()
if err != nil { if err != nil {
return "", err return "", err
@@ -40,38 +46,59 @@ func (g *GitUtils) GetHash() (string, error) {
return ref.Hash().String(), nil return ref.Hash().String(), nil
} }
func (g *GitUtils) GetBranch() (string, error) { // GetBranch from git HEAD
func (g *GitUtil) GetBranch() (string, error) {
ref, err := g.Repository.Head() ref, err := g.Repository.Head()
if err != nil { if err != nil {
return "", err return "", err
} }
if !ref.Name().IsBranch() { if !ref.Name().IsBranch() {
return "", fmt.Errorf("No branch found, found %s, please checkout a branch (git checkout <BRANCH>)", ref.Name().String()) branches, err := g.Repository.Branches()
if err != nil {
return "", err
} }
var currentBranch string
found := branches.ForEach(func(p *plumbing.Reference) error {
if p.Name().IsBranch() && p.Name().Short() != "origin" {
currentBranch = p.Name().Short()
return fmt.Errorf("break")
}
return nil
})
if found != nil {
log.Debugf("Found branch from HEAD %s", currentBranch)
return currentBranch, nil
}
return "", fmt.Errorf("no branch found, found %s, please checkout a branch (git checkout -b <BRANCH>)", ref.Name().String())
}
log.Debugf("Found branch %s", ref.Name().Short())
return ref.Name().Short(), nil return ref.Name().Short(), nil
} }
func (g *GitUtils) GetLastVersion() (*semver.Version, string, error) { // GetLastVersion from git tags
func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
log.Debugf("GetLastVersion") var tags []*semver.Version
gitTags, err := g.Repository.Tags()
tagObjects, err := g.Repository.TagObjects()
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
var tags []*semver.Version err = gitTags.ForEach(func(p *plumbing.Reference) error {
v, err := semver.NewVersion(p.Name().Short())
log.Tracef("%+v with hash: %s", p.Target(), p.Hash())
err = tagObjects.ForEach(func(t *object.Tag) error { if err == nil {
v, err := semver.NewVersion(t.Name)
if err != nil {
log.Debugf("Tag %s is not a valid version, skip", t.Name)
} else {
log.Debugf("Add tag %s", t.Name)
tags = append(tags, v) tags = append(tags, v)
} else {
log.Debugf("Tag %s is not a valid version, skip", p.Name().Short())
} }
return nil return nil
}) })
@@ -94,18 +121,13 @@ func (g *GitUtils) GetLastVersion() (*semver.Version, string, error) {
return nil, "", err return nil, "", err
} }
tagObject, err := g.Repository.TagObject(tag.Hash()) log.Debugf("Found old hash %s", tag.Hash().String())
if err != nil { return tags[0], tag.Hash().String(), nil
return nil, "", err
}
log.Debugf("Found old hash %s", tagObject.Target.String())
return tags[0], tagObject.Target.String(), nil
} }
func (g *GitUtils) GetCommits(lastTagHash string) ([]Commit, error) { // GetCommits from git hash to HEAD
func (g *GitUtil) GetCommits(lastTagHash string) ([]Commit, error) {
log.Printf("Read head")
ref, err := g.Repository.Head() ref, err := g.Repository.Head()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -121,11 +143,12 @@ func (g *GitUtils) GetCommits(lastTagHash string) ([]Commit, error) {
err = cIter.ForEach(func(c *object.Commit) error { err = cIter.ForEach(func(c *object.Commit) error {
if c.Hash.String() == lastTagHash { if c.Hash.String() == lastTagHash {
log.Infof("%s == %s", c.Hash.String(), lastTagHash) log.Debugf("Found commit with hash %s, will stop here", c.Hash.String())
foundEnd = true foundEnd = true
}
}
if !foundEnd { if !foundEnd {
log.Tracef("Found commit with hash %s", c.Hash.String())
commit := Commit{ commit := Commit{
Message: c.Message, Message: c.Message,
Author: c.Committer.Name, Author: c.Committer.Name,
@@ -136,5 +159,5 @@ func (g *GitUtils) GetCommits(lastTagHash string) ([]Commit, error) {
return nil return nil
}) })
return commits, nil return commits, err
} }

View File

@@ -0,0 +1,141 @@
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"
log "github.com/sirupsen/logrus"
)
// 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
}
// New initialize a new GitHubRelease
func New(c *config.GitHubProvider) (*Client, error) {
var err error
if c.AccessToken, err = util.GetAccessToken(GITHUB); err != nil {
return &Client{}, err
}
ctx := context.Background()
httpClient := util.CreateBearerHTTPClient(ctx, c.AccessToken)
var client *github.Client
baseURL := "https://github.com"
if c.CustomURL == "" {
client = github.NewClient(httpClient)
} else {
if client, err = github.NewEnterpriseClient(c.CustomURL, c.CustomURL+"/api/v3/", httpClient); err != nil {
return &Client{}, err
}
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 {
log.Debugf("validate GitHub provider config")
if g.config.Repo == "" {
return fmt.Errorf("github Repro is not set")
}
if g.config.User == "" {
return fmt.Errorf("github User is not set")
}
return nil
}
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := releaseVersion.Next.Version.String()
log.Debugf("create release with version %s", tag)
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: &releaseVersion.Draft,
Prerelease: &prerelease,
})
if err != nil {
if !strings.Contains(err.Error(), "already_exists") && resp.StatusCode >= http.StatusUnprocessableEntity {
return fmt.Errorf("could not create release: %v", err)
}
log.Infof("A release with tag %s already exits, will not perform a release or update", tag)
} else {
g.release = release
log.Debugf("Release repsone: %+v", *release)
log.Infof("Crated release")
}
return nil
}
// UploadAssets uploads specified assets
func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
if g.release != nil {
filesToUpload, err := util.PrepareAssets(repoDir, assets)
if err != nil {
return err
}
for _, f := range filesToUpload {
file, err := os.Open(*f)
if err != nil {
return err
}
fileInfo, _ := file.Stat()
_, resp, err := g.client.Repositories.UploadReleaseAsset(g.context, g.config.User, g.config.Repo, g.release.GetID(), &github.UploadOptions{Name: fileInfo.Name()}, file)
if err != nil {
return err
}
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("releaser: github: Could not upload asset %s: %s", file.Name(), resp.Status)
}
}
}
return nil
}

View File

@@ -0,0 +1,42 @@
package releaser
import (
"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"
log "github.com/sirupsen/logrus"
)
// Releasers struct type
type Releasers struct {
config *config.ReleaseConfig
}
// Releaser interface for providers
type Releaser interface {
ValidateConfig() error
CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog) error
UploadAssets(repoDir string, assets []config.Asset) error
GetCommitURL() string
GetCompareURL(oldVersion, newVersion string) string
}
// New initialize a Relerser
func New(c *config.ReleaseConfig) *Releasers {
return &Releasers{
config: c,
}
}
//GetReleaser returns an initialized releaser
func (r *Releasers) GetReleaser() (Releaser, error) {
switch r.config.Release {
case github.GITHUB:
log.Debugf("initialize new %s-provider", github.GITHUB)
return github.New(&r.config.GitHubProvider)
}
return nil, fmt.Errorf("could not initialize a releaser from this type: %s", r.config.Release)
}

View File

@@ -0,0 +1,111 @@
package util
import (
"archive/zip"
"context"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus"
"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
}
// GetAccessToken lookup for the providers accesstoken
func GetAccessToken(providerName string) (string, error) {
var token string
var exists bool
envName := fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(providerName))
log.Debugf("check if %s environment variable is set", envName)
if token, exists = os.LookupEnv(envName); !exists {
return "", fmt.Errorf("could not find %s in the enviroment variables. Please check if it is set", envName)
} else if token == "" {
return "", fmt.Errorf("token %s is set in environment variables but is empty", envName)
}
return token, nil
}
// PrepareAssets prepare all files before uploading
func PrepareAssets(repository string, assets []config.Asset) ([]*string, error) {
filesToUpload := []*string{}
for _, asset := range assets {
if asset.Name == "" {
return nil, fmt.Errorf("asset name declaration is empty, please check your configuration file")
} else if asset.Compress {
log.Debugf("Asset %s will now be compressed", asset.Name)
log.Debugf("Repo url %s", repository)
zipNameWithPath, err := zipFile(repository, asset.Name)
if err != nil {
return filesToUpload, err
}
filesToUpload = append(filesToUpload, &zipNameWithPath)
} else {
tmpFileName := fmt.Sprintf("%s/%s", repository, asset.Name)
filesToUpload = append(filesToUpload, &tmpFileName)
}
log.Debugf("Add asset %s to files to upload", asset.Name)
}
return filesToUpload, nil
}
// ZipFile compress given file in zip format
func zipFile(repository string, file string) (string, error) {
fileToZip, err := os.Open(repository + "/" + file)
if err != nil {
return "", err
}
defer fileToZip.Close()
zipFileName := fmt.Sprintf("%s/%s.zip", strings.TrimSuffix(repository, "/"), file)
zipFile, err := os.Create(zipFileName)
if err != nil {
return "", err
}
log.Debugf("Created zipfile %s", zipFile.Name())
defer zipFile.Close()
fileToZipInfo, err := fileToZip.Stat()
if err != nil {
return "", err
}
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
fileToZipHeader, err := zip.FileInfoHeader(fileToZipInfo)
if err != nil {
return "", err
}
fileToZipHeader.Name = fileToZipInfo.Name()
fileToZipWriter, err := zipWriter.CreateHeader(fileToZipHeader)
if err != nil {
return "", err
}
if _, err = io.Copy(fileToZipWriter, fileToZip); err != nil {
return "", err
}
return zipFileName, nil
}

View File

@@ -0,0 +1,119 @@
package util_test
import (
"context"
"fmt"
"os"
"strings"
"testing"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/Nightapes/go-semantic-release/internal/releaser/util"
)
func TestCreateBearerHTTPClient(t *testing.T) {
client := util.CreateBearerHTTPClient(context.Background(), "")
assert.True(t, client != nil, "Client is empty")
}
type testDoubleToken struct {
providerName, token string
valid bool
}
var testDoubles = []testDoubleToken{
testDoubleToken{providerName: "test0", token: "foo", valid: true},
testDoubleToken{providerName: "test1", token: "", valid: false},
}
func TestGetAccessToken(t *testing.T) {
for _, testObject := range testDoubles {
envName := fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(testObject.providerName))
if err := os.Setenv(envName, testObject.token); err != nil {
fmt.Println(err.Error())
}
_, err := util.GetAccessToken(testObject.providerName)
assert.Equal(t, testObject.valid, err == nil)
os.Unsetenv(envName)
}
}
type testDoubleFiles struct {
testFiles []config.Asset
valid bool
}
var files = []testDoubleFiles{
testDoubleFiles{
testFiles: []config.Asset{
config.Asset{
Name: "file0",
Compress: true,
},
config.Asset{
Name: "file1",
Compress: true,
},
},
valid: true,
},
testDoubleFiles{
testFiles: []config.Asset{
config.Asset{
Name: "",
Compress: true,
},
config.Asset{
Name: "",
Compress: false,
},
},
valid: false,
},
}
func TestPrepareAssets(t *testing.T) {
for _, testObject := range files {
workDir, _ := os.Getwd()
filesToDelete := []string{}
for _, testFile := range testObject.testFiles {
if testFile.Name != "" {
filesToDelete = append(filesToDelete, testFile.Name)
file, err := os.Create(testFile.Name)
if err != nil {
fmt.Print(err.Error())
}
defer file.Close()
if testFile.Compress {
filesToDelete = append(filesToDelete, testFile.Name+".zip")
}
}
}
preparedFiles, err := util.PrepareAssets(workDir, testObject.testFiles)
if err == nil {
assert.Equal(t, 2, len(preparedFiles))
}
assert.Equal(t, testObject.valid, err == nil)
for _, file := range filesToDelete {
if err := os.Remove(file); err != nil {
fmt.Println(err.Error())
}
}
}
}

View File

33
internal/shared/shared.go Normal file
View File

@@ -0,0 +1,33 @@
package shared
import (
"github.com/Masterminds/semver"
)
//ReleaseVersion struct
type ReleaseVersion struct {
Last ReleaseVersionEntry
Next ReleaseVersionEntry
Branch string
Draft bool
}
//ReleaseVersionEntry struct
type ReleaseVersionEntry struct {
Commit string
Version *semver.Version
}
//GeneratedChangelog struct
type GeneratedChangelog struct {
Title string
Content string
}
//ChangelogTemplateConfig struct
type ChangelogTemplateConfig struct {
CommitURL string
CompareURL string
Hash string
Version string
}

View File

@@ -1,42 +0,0 @@
package storage
import (
"io/ioutil"
"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"`
}
// Write version into .version
func Write(versionFileContent VersionFileContent) error {
data, err := yaml.Marshal(&versionFileContent)
if err != nil {
return err
}
return ioutil.WriteFile(".version", data, 0644)
}
// Read version into .version
func Read() (*VersionFileContent, error) {
content, err := ioutil.ReadFile(".version")
if err != nil {
return &VersionFileContent{}, err
}
var versionFileContent VersionFileContent
err = yaml.Unmarshal(content, &versionFileContent)
if err != nil {
return &VersionFileContent{}, err
}
return &versionFileContent, nil
}

61
pkg/config/config.go Normal file
View File

@@ -0,0 +1,61 @@
// Package config provides defimition of .release.yml and read method
package config
import (
"io/ioutil"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
// ChangelogConfig struct
type ChangelogConfig struct {
PrintAll bool `yaml:"printAll,omitempty"`
Template string `yaml:"template,omitempty"`
TemplatePath string `yaml:"templatePath,omitempty"`
}
//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"`
GitHubProvider GitHubProvider `yaml:"github,omitempty"`
Assets []Asset `yaml:"assets"`
ReleaseTitle string `yaml:"title"`
IsPreRelease, IsDraft bool
}
// Read ReleaseConfig
func Read(configPath string) (*ReleaseConfig, error) {
content, err := ioutil.ReadFile(configPath)
if err != nil {
return &ReleaseConfig{}, err
}
var releaseConfig ReleaseConfig
err = yaml.Unmarshal(content, &releaseConfig)
if err != nil {
return &ReleaseConfig{}, err
}
log.Tracef("Found config %+v", releaseConfig)
return &releaseConfig, nil
}

112
pkg/config/config_test.go Normal file
View File

@@ -0,0 +1,112 @@
package config_test
import (
"testing"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path"
)
func TestReadCacheNotFound(t *testing.T) {
_, err := config.Read("notfound/dir")
assert.Errorf(t, err, "Read non exsiting file")
}
func TestReadCacheInvalidContent(t *testing.T) {
dir, err := ioutil.TempDir("", "prefix")
assert.NoError(t, err)
defer os.RemoveAll(dir)
completePath := path.Join(path.Dir(dir), ".release.yml")
brokenContent := []byte("hello broken\ngo: lang\n")
err = ioutil.WriteFile(completePath, brokenContent, 0644)
assert.NoError(t, err)
_, readError := config.Read(completePath)
assert.Errorf(t, readError, "Should give error, when broken content")
}
func TestWriteAndReadCache(t *testing.T) {
dir, err := ioutil.TempDir("", "prefix")
assert.NoError(t, err)
defer os.RemoveAll(dir)
completePath := path.Join(path.Dir(dir), ".release.yml")
content := []byte(`
commitFormat: angular
title: "go-semantic-release release"
branch:
master: release
rc: rc
beta: beta
alpha: alpha
add_git_releases: alpha
changelog:
printAll: false
template: ''
templatePath: ''
release: 'github'
assets:
- name: ./build/go-semantic-release
compress: false
github:
repo: "go-semantic-release"
user: "nightapes"
customUrl: ""
`)
err = ioutil.WriteFile(completePath, content, 0644)
assert.NoError(t, err)
result, readError := config.Read(completePath)
assert.NoErrorf(t, readError, "Should read file")
assert.EqualValues(t, &config.ReleaseConfig{
CommitFormat: "angular",
Branch: map[string]string{"add_git_releases": "alpha", "alpha": "alpha", "beta": "beta", "master": "release", "rc": "rc"},
Changelog: config.ChangelogConfig{
PrintAll: false,
Template: "",
TemplatePath: ""},
Release: "github",
GitHubProvider: config.GitHubProvider{
Repo: "go-semantic-release",
User: "nightapes",
CustomURL: "",
AccessToken: ""},
Assets: []config.Asset{
config.Asset{
Name: "./build/go-semantic-release",
Compress: false}},
ReleaseTitle: "go-semantic-release release",
IsPreRelease: false,
IsDraft: false,
}, result)
}
// func TestWriteNotFound(t *testing.T) {
// err := cache.Write("notfound/dir", shared.ReleaseVersion{
// Last: shared.ReleaseVersionEntry{
// Commit: "12345",
// Version: createVersion("1.0.0"),
// },
// Next: shared.ReleaseVersionEntry{
// Commit: "12346",
// Version: createVersion("1.1.0"),
// },
// Branch: "master",
// Draft: true,
// })
// assert.Errorf(t, err, "Write non exsiting file")
// }

View File

@@ -1,114 +1,238 @@
package semanticrelease package semanticrelease
import ( import (
"fmt" "io/ioutil"
"strings"
"time"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/analyzer" "github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/cache"
"github.com/Nightapes/go-semantic-release/internal/calculator"
"github.com/Nightapes/go-semantic-release/internal/changelog"
"github.com/Nightapes/go-semantic-release/internal/ci"
"github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/internal/storage" "github.com/Nightapes/go-semantic-release/internal/releaser"
"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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// GetNextVersion from .version or calculate new // SemanticRelease struct
func GetNextVersion(repro string) error { type SemanticRelease struct {
util, err := gitutil.New(repro) config *config.ReleaseConfig
gitutil *gitutil.GitUtil
analyzer *analyzer.Analyzer
calculator *calculator.Calculator
releaser releaser.Releaser
repository string
}
// New SemanticRelease struct
func New(c *config.ReleaseConfig, repository string) (*SemanticRelease, error) {
util, err := gitutil.New(repository)
if err != nil { if err != nil {
return err return nil, err
} }
hash, err := util.GetHash() analyzer, err := analyzer.New(c.CommitFormat, c.Changelog)
if err != nil { if err != nil {
return err return nil, err
} }
content, err := storage.Read() releaser, err := releaser.New(c).GetReleaser()
if err == nil && content.Commit == hash {
fmt.Printf(content.NextVersion)
return nil
}
log.Debugf("Mismatch git and version file %s - %s", content.Commit, hash)
lastVersion, lastVersionHash, err := util.GetLastVersion()
if err != nil { if err != nil {
return err return nil, err
} }
return &SemanticRelease{
config: c,
gitutil: util,
releaser: releaser,
analyzer: analyzer,
repository: repository,
calculator: calculator.New(),
}, nil
}
//GetCIProvider result with ci config
func (s *SemanticRelease) GetCIProvider() (*ci.ProviderConfig, error) {
return ci.GetCIProvider(s.gitutil, ci.ReadAllEnvs())
}
// GetNextVersion from .version or calculate new from commits
func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool) (*shared.ReleaseVersion, map[analyzer.Release][]analyzer.AnalyzedCommit, error) {
log.Debugf("Ignore .version file if exits, %t", force)
if !force {
releaseVersion, err := cache.Read(s.repository)
if err != nil {
return nil, nil, err
}
if releaseVersion.Next.Commit == provider.Commit && releaseVersion != nil {
return releaseVersion, nil, nil
}
}
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion()
if err != nil {
return nil, nil, err
}
firstRelease := false
if lastVersion == nil { if lastVersion == nil {
defaultVersion, _ := semver.NewVersion("1.0.0") defaultVersion, _ := semver.NewVersion("1.0.0")
SetVersion(defaultVersion.String(), repro) lastVersion = defaultVersion
fmt.Printf(defaultVersion.String()) firstRelease = true
return nil
} }
commits, err := util.GetCommits(lastVersionHash) commits, err := s.gitutil.GetCommits(lastVersionHash)
if err != nil { if err != nil {
return err return nil, nil, err
} }
log.Debugf("Found %d commits till last release", len(commits)) log.Debugf("Found %d commits till last release", len(commits))
a := analyzer.New("angular") analyzedCommits := s.analyzer.Analyze(commits)
result := a.Analyze(commits)
isDraft := false
var newVersion semver.Version var newVersion semver.Version
for branch, releaseType := range s.config.Branch {
if len(result["major"]) > 0 { if provider.Branch == branch || strings.HasPrefix(provider.Branch, branch) {
newVersion = lastVersion.IncMajor() log.Debugf("Found branch config for branch %s with release type %s", provider.Branch, releaseType)
return nil newVersion, isDraft = s.calculator.CalculateNewVersion(analyzedCommits, lastVersion, releaseType, firstRelease)
} else if len(result["minor"]) > 0 { break
newVersion = lastVersion.IncMinor() }
} else if len(result["patch"]) > 0 {
newVersion = lastVersion.IncPatch()
} }
SetVersion(newVersion.String(), repro) releaseVersion := shared.ReleaseVersion{
fmt.Printf(newVersion.String()) Next: shared.ReleaseVersionEntry{
Commit: provider.Commit,
Version: &newVersion,
},
Last: shared.ReleaseVersionEntry{
Commit: lastVersionHash,
Version: lastVersion,
},
Branch: provider.Branch,
Draft: isDraft,
}
return err log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String())
err = cache.Write(s.repository, releaseVersion)
if err != nil {
return nil, nil, err
}
return &releaseVersion, analyzedCommits, err
} }
func SetVersion(version string, repro string) error { //SetVersion for git repository
func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string) error {
util, err := gitutil.New(repro)
if err != nil {
return err
}
newVersion, err := semver.NewVersion(version) newVersion, err := semver.NewVersion(version)
if err != nil { if err != nil {
return err return err
} }
hash, err := util.GetHash() lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion()
if err != nil { if err != nil {
return err return err
} }
if lastVersion == nil {
branch, err := util.GetBranch() lastVersion, _ = semver.NewVersion("1.0.0")
if err != nil {
return err
} }
newVersionContent := storage.VersionFileContent{ return cache.Write(s.repository, shared.ReleaseVersion{
Commit: hash, Next: shared.ReleaseVersionEntry{
NextVersion: newVersion.String(), Commit: provider.Commit,
Branch: branch, Version: newVersion,
} },
Last: shared.ReleaseVersionEntry{
lastVersion, _, err := util.GetLastVersion() Commit: lastVersionHash,
if err != nil { Version: lastVersion,
return err },
} Branch: provider.Branch,
})
if lastVersion != nil {
newVersionContent.Version = lastVersion.String()
}
return storage.Write(newVersionContent)
} }
func Release() { // GetChangelog from last version till now
func (s *SemanticRelease) GetChangelog(analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit, releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) {
c := changelog.New(s.config, s.analyzer.GetRules(), time.Now())
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()),
}, analyzedCommits)
}
// WriteChangeLog wirtes changelog content to the given file
func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error {
return ioutil.WriteFile(file, []byte(changelogContent), 0644)
}
// Release pusblish release to provider
func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error {
if provider.IsPR {
log.Debugf("Will not perform a new release. This is a pull request")
return nil
}
if _, ok := s.config.Branch[provider.Branch]; !ok {
log.Debugf("Will not perform a new release. Current %s branch is not configured in release config", provider.Branch)
return nil
}
releaseVersion, analyzedCommits, err := s.GetNextVersion(provider, force)
if err != nil {
log.Debugf("Could not get next version")
return err
}
if releaseVersion.Next.Version.Equal(releaseVersion.Next.Version) {
log.Infof("No new version, no release needed")
return nil
}
generatedChanglog, err := s.GetChangelog(analyzedCommits, releaseVersion)
if err != nil {
log.Debugf("Could not get changelog")
return err
}
releaser, err := releaser.New(s.config).GetReleaser()
if err != nil {
return err
}
err = releaser.ValidateConfig()
if err != nil {
return err
}
if err = releaser.CreateRelease(releaseVersion, generatedChanglog); err != nil {
return err
}
if err = releaser.UploadAssets(s.repository, s.config.Assets); err != nil {
return err
}
return nil
}
// ZipFiles zip files configured in release config
func (s *SemanticRelease) ZipFiles() error {
for _, file := range s.config.Assets {
if file.Compress {
if _, err := util.PrepareAssets(s.repository, s.config.Assets); err != nil {
return err
}
}
}
return nil
} }