Compare commits

..

99 Commits

Author SHA1 Message Date
Felix Wiedmann
af2addbffd Merge pull request #29 from Nightapes/typo
fix(gitutil): fix log message for tags
2020-01-08 22:49:28 +01:00
Nightapes
7157d64b7b style(hooks): fix lint issues 2020-01-06 17:45:52 +01:00
Nightapes
575ba5d5bd fix(hooks): improve cmd execution 2020-01-06 17:41:10 +01:00
Nightapes
a8b68f9182 build(docker): fix github naming 2020-01-06 12:51:01 +01:00
Nightapes
f6a2d837b1 buikd(docker): add github docker registy 2020-01-06 12:47:02 +01:00
Nightapes
279509c922 docs(README): fix typo 2020-01-06 12:44:10 +01:00
Nightapes
d0035d6bca fix(gitutil): fix log message for tags 2020-01-06 12:14:41 +01:00
Felix Wiedmann
1342714579 Merge pull request #28 from Nightapes/git_provider
Git provider and hooks
2020-01-05 21:38:10 +01:00
Nightapes
d92438b339 test(hooks): fix testing 2020-01-05 19:10:07 +01:00
Nightapes
aff2203d66 style(github): fix lint issues 2020-01-05 19:06:15 +01:00
Nightapes
113ddf2b1f build(ci): use new hooks for docker release 2020-01-05 19:02:20 +01:00
Nightapes
8e3c446605 docs(README): add hooks 2020-01-05 18:56:37 +01:00
Nightapes
8ea92efb90 feat(hooks): add pre and post release hooks 2020-01-05 18:56:37 +01:00
Nightapes
42fc522a43 feat(releaser): add git only as releaser, will create a new tag with version only 2020-01-05 18:55:47 +01:00
Felix Wiedmann
6211095c38 Merge pull request #27 from Nightapes/fix_commits
fix(commits):  don't break if commit is found and error happens on ol…
2019-10-08 21:09:43 +02:00
Nightapes
74e895b5ad fix(commits): don't break if commit is found and error happens on older commits and add better error message when git clone --depth is used 2019-10-08 20:46:57 +02:00
Sebastian
a69da92340 Merge pull request #26 from Nightapes/fix/build
build(workflow): fix call of binary
2019-09-25 10:42:44 +02:00
fwiedmann
ff87725801 build(workflow): fix call of binary 2019-09-25 00:20:41 +02:00
Felix Wiedmann
272a9b6e89 Merge pull request #25 from Nightapes/improvements
Improvements
2019-09-25 00:12:09 +02:00
Sebastian
07b606a21a Merge branch 'master' into improvements 2019-09-22 15:52:45 +02:00
Nightapes
44f95969bf feat(template): allow custom changelog template 2019-09-22 15:50:12 +02:00
Nightapes
399a3515f2 feat(docker): add docker image with go-semantic-release 2019-09-22 15:49:06 +02:00
Nightapes
2074877a3b build(pages): add jemoji plugin 2019-09-22 15:42:08 +02:00
Sebastian
b5551d8432 Set theme jekyll-theme-cayman 2019-09-22 15:34:33 +02:00
Sebastian
c92020d3ac Set theme jekyll-theme-slate 2019-09-22 15:33:05 +02:00
Felix Wiedmann
d9f2b163c7 Merge pull request #24 from Nightapes/fix/gitlab_request
fix(gitlab): fix broken request on release creation
2019-09-17 22:29:17 +02:00
Nightapes
4379551982 fix(gitlab): fix broken request on release creation 2019-09-17 21:01:05 +02:00
Felix Wiedmann
3eb13972ec Merge pull request #23 from Nightapes/fix_readme
docs(README): fix wrong confiig file
2019-09-16 22:36:39 +02:00
Sebastian
8659b40960 docs(README): fix wrong confiig file 2019-09-16 08:23:21 +02:00
Felix Wiedmann
7ead374039 Merge pull request #22 from Nightapes/gitlab_ci
fix(ci): add gitlab ci detection
2019-09-15 20:37:22 +02:00
Nightapes
ee1dc3d8db fix(ci): add gitlab ci detection 2019-09-15 20:13:17 +02:00
Felix Wiedmann
38e4c178ee Merge pull request #21 from Nightapes/golang_1_13
build(ci): update golang
2019-09-10 14:13:10 +02:00
Sebastian
e22d3d07f4 build(ci): update golang 2019-09-10 14:09:57 +02:00
Sebastian
46ae2da821 build(ci): update golangci-lint 2019-09-10 13:33:33 +02:00
Felix Wiedmann
e3265b1843 Merge pull request #19 from Nightapes/fix_release_version_calculation
Fix release version calculation
2019-09-03 23:43:02 +02:00
Nightapes
d03913e6d7 fix(pkg/semanticrelease): remove check for branch prefix for releases 2019-09-03 22:35:17 +02:00
Nightapes
e5ed8edb75 refactor(ci): add trace log 2019-09-03 22:18:13 +02:00
Nightapes
76ffeda95b fix(analyzer/angular): allow multi line commits 2019-09-03 21:44:38 +02:00
Nightapes
0994354089 fix(internal/calculator): fix relase version calculation 2019-09-03 21:34:00 +02:00
Felix Wiedmann
e8d7feeca3 Merge pull request #16 from Nightapes/improvements
Improvements
2019-09-03 09:54:22 +01:00
Sebastian
e0974e3140 Merge branch 'master' into improvements 2019-08-31 17:05:21 +02:00
Nightapes
8643656339 feat(changelog): add docker usage to changelog 2019-08-31 17:05:10 +02:00
Nightapes
829f2925ab fix(analyzer/angular): fix regex to regonize merge commits 2019-08-25 14:16:57 +02:00
Felix Wiedmann
7729ba5914 Merge pull request #14 from Nightapes/fix_first
fix(pkg/semantic-release): fix issue with first release in repository set first version to 1.0.0
2019-08-23 09:14:35 +02:00
Nightapes
72200582fd fix(pkg/semantic-release): fix issue with first release in repository, set first version to 1.0.0 2019-08-23 08:58:44 +02:00
Sebastian
2c26c8aa6d Merge pull request #11 from Nightapes/remove/draft
fix(github): remove 'draft' option because releases will always be pu…
2019-08-22 09:35:30 +02:00
Felix Wiedmann
4b72df07c8 Merge branch 'master' into remove/draft 2019-08-22 09:20:03 +02:00
Felix Wiedmann
df544f5be7 docs(releaser/util): remove unnecessary comment 2019-08-22 09:17:40 +02:00
Felix Wiedmann
812723e1f4 Merge pull request #13 from Nightapes/Nightapes-patch-1
feat(ci): add github actions
2019-08-22 09:12:47 +02:00
Sebastian
e91d9b0dac feat(ci): add github actions 2019-08-22 09:00:58 +02:00
fwiedmann
a2fc03c64d Merge branch 'master' of github.com:Nightapes/go-semantic-release into remove/draft 2019-08-22 00:12:24 +02:00
Felix Wiedmann
e419086dbe Merge pull request #12 from Nightapes/actions
build(ci): add github action
2019-08-22 00:07:31 +02:00
Nightapes
4c7cfd5e90 fix(ci): remove refs/heads/ from branch 2019-08-21 21:50:40 +02:00
Nightapes
89f4842a2b fear(ci): add github actions 2019-08-21 21:39:05 +02:00
Nightapes
7857b5f6f3 build(actions): add debug print 2019-08-21 21:23:07 +02:00
Nightapes
115964c9c1 fix(github): use env GITHUB_TOKEN for releaser 2019-08-21 21:06:57 +02:00
Nightapes
2f2f7e51fb build(ci): remove matrix, cache is missing 2019-08-21 20:52:53 +02:00
Nightapes
f1bb5470c0 test(internal): fix broken test after golang update 2019-08-21 20:39:58 +02:00
Nightapes
be1e483baa build(ci): fix action file 2019-08-21 20:06:30 +02:00
Nightapes
17f1890ca1 build(ci): update github action 2019-08-21 20:03:35 +02:00
Sebastian
6a53c3e587 build(ci): build needs test 2019-08-21 19:46:23 +02:00
Sebastian
a068e65369 build(ci): fix file 2019-08-21 19:41:51 +02:00
Sebastian
9e847c6af9 build(ci): add github action 2019-08-21 19:39:48 +02:00
fwiedmann
2eb64f153a test(util): add defer statement for testServer 2019-08-13 23:28:13 +02:00
fwiedmann
a6c651a97f fix(github): remove 'draft' option because releases will always be published public 2019-08-13 23:01:38 +02:00
Felix Wiedmann
e0a4725f06 Merge pull request #10 from Nightapes/fixes
Fixes
2019-08-11 23:02:12 +02:00
Nightapes
9cf4eab1d2 fix(cmd): don't print current dir in help text 2019-08-11 18:28:13 +02:00
Nightapes
6a514158ce fix(cache): save commits to cache to avoid empty changelog 2019-08-11 18:27:52 +02:00
Felix Wiedmann
f30c508f2a Merge pull request #9 from Nightapes/gitlab
Gitlab
2019-08-11 16:05:17 +02:00
Nightapes
7b16b164f2 test(gitlab): add missing tests 2019-08-11 15:03:55 +02:00
Sebastian
0fd39c72a0 Merge branch 'master' into gitlab 2019-08-08 21:35:39 +02:00
Nightapes
0e61bf7651 Merge branch 'gitlab' of github.com:Nightapes/go-semantic-release into gitlab 2019-08-08 21:33:42 +02:00
Nightapes
829fea1282 build(releaser): fix release config 2019-08-08 21:33:28 +02:00
Nightapes
467ae1f87e build(releaser): fix release config 2019-08-08 21:33:28 +02:00
Nightapes
5791d3b41c refactor(releaser): clean up code 2019-08-08 21:33:28 +02:00
Nightapes
92b42c8ffa feat(releaser): add gitlab as relase option 2019-08-08 21:33:28 +02:00
Nightapes
0bffe32a24 Merge branch 'gitlab' of github.com:Nightapes/go-semantic-release into gitlab 2019-08-08 21:32:42 +02:00
Nightapes
fab3fc030e build(releaser): fix release config 2019-08-08 21:32:29 +02:00
Nightapes
42a6a5fcf0 build(releaser): fix release config 2019-08-08 21:31:30 +02:00
Nightapes
2f20d8c31c refactor(releaser): clean up code 2019-08-08 21:30:29 +02:00
Sebastian
ec10557cb1 Merge pull request #8 from Nightapes/test/github-release
add tests for github releaser - UploadAssets method omitted because of future deprication
2019-08-08 21:12:48 +02:00
fwiedmann
c9d9420037 chore(gitlab/github/util): fix golangci-lint issues 2019-08-08 21:01:40 +02:00
fwiedmann
ff82ec7acd chore(util/github-release): fix golint issues 2019-08-08 20:44:33 +02:00
Nightapes
428514a1ef test(github): merge incoming changes 2019-08-08 20:22:49 +02:00
Nightapes
3bf84d5ad1 test(github): refactor unit test 2019-08-08 20:17:14 +02:00
fwiedmann
0037abfaf7 test(internal/releaser/github): add test for CreateRelease() 2019-08-08 20:07:22 +02:00
Nightapes
703d4b803c Merge branch 'test/github-release' of github.com:Nightapes/go-semantic-release into test/github-release 2019-08-07 23:27:05 +02:00
Nightapes
c086b12f01 feat(releaser): add gitlab as relase option 2019-08-07 23:24:13 +02:00
fwiedmann
3c500142aa temp(releaser/github): modify test 2019-08-07 23:11:04 +02:00
fwiedmann
bdc4fb1d74 tmp(releaser/github): add test for create release 2019-08-06 23:21:25 +02:00
fwiedmann
682fae3239 test(releaser/github): tests for GetCommitURL(), GetCompareURL(), ValisateConfig() 2019-07-29 23:11:07 +02:00
fwiedmann
e170ca9885 test(releaser/github): test New func 2019-07-28 23:00:08 +02:00
Felix Wiedmann
a15eaa597a Merge pull request #7 from Nightapes/add_git_releases
Add git releases
2019-07-28 21:56:06 +02:00
fwiedmann
6bdbc2173f chore(release.yaml): set master branch on beta release 2019-07-28 21:53:30 +02:00
Nightapes
1daff2bc8a refactor(pkg): clean up semantic release interface 2019-07-24 22:14:03 +02:00
Nightapes
e222734a1a test(internal/*): add more unit tests 2019-07-22 20:47:10 +02:00
Nightapes
6267e1cc81 refactor(pkg/semanticrelease): clean up code 2019-07-22 20:46:48 +02:00
fwiedmann
5986e2819a test(internal/releaser/util): add test for PrepareAssets 2019-07-22 18:13:11 +02:00
fwiedmann
581b62d324 ref(internal/releaser/util): check if asset name ist not empty, open file which should be zipped before create zip file 2019-07-22 18:11:48 +02:00
49 changed files with 2756 additions and 480 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
*
!build/

55
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Go
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Lint
run: |
export PATH=$PATH:$(go env GOPATH)/bin
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
golangci-lint run ./...
- name: Run tests
run: go test ./...
- name: Build binary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/
./build/go-semantic-release-temp next --no-cache --loglevel trace
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release -ldflags "-w -s --X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -o build/go-semantic-release.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
- name: Build Docker image
if: github.ref != 'refs/heads/master'
run: |
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
docker login -u nightapes -p ${{ secrets.GITHUB_TOKEN }} docker.pkg.github.com
docker build -t nightapes/go-semantic-release:development .
docker push nightapes/go-semantic-release:development
docker tag nightapes/go-semantic-release:development docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:development
docker push docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:development
- name: Push Docker image
if: github.ref != 'refs/heads/master'
run: |
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
docker login -u nightapes -p $GITHUB_TOKEN docker.pkg.github.com
./build/go-semantic-release-temp release --loglevel trace

2
.gitignore vendored
View File

@@ -16,3 +16,5 @@ go-semantic-release
.vscode/settings.json .vscode/settings.json
CHANGELOG.md CHANGELOG.md
cover.html cover.html
build/
.idea/

View File

@@ -1,22 +1,25 @@
release: "github"
github:
repo: "go-semantic-release"
user: "nightapes"
commitFormat: angular commitFormat: angular
title: "go-semantic-release release"
branch: branch:
master: release master: release
rc: rc
beta: beta
alpha: alpha
add_git_releases: alpha
changelog:
printAll: false
template: ''
templatePath: ''
release: 'github'
assets: assets:
- name: ./build/go-semantic-release - name: ./build/go-semantic-release
compress: false compress: false
- name: ./build/go-semantic-release.exe - name: ./build/go-semantic-release.exe
compress: false compress: false
github: changelog:
repo: "go-semantic-release" docker:
user: "nightapes" latest: true
customUrl: "" repository: "nightapes/go-semantic-release"
hooks:
preRelease:
- docker build -t nightapes/go-semantic-release:latest .
- docker tag nightapes/go-semantic-release:latest docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:$RELEASE_VERSION
- docker tag nightapes/go-semantic-release:latest nightapes/go-semantic-release:$RELEASE_VERSION
postRelease:
- docker push nightapes/go-semantic-release:latest
- docker push nightapes/go-semantic-release:$RELEASE_VERSION
- docker push docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:$RELEASE_VERSION

9
Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM alpine:3.10.2
WORKDIR /code
COPY ./build/go-semantic-release .
USER 1000
ENTRYPOINT [ "./go-semantic-release" ]

219
README.md
View File

@@ -2,24 +2,217 @@
## Release Types ## Release Types
| Type | Git tag | Changelog | Release | Write access git | Api token | | Type | Implemendet | 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: | | :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: | | :white_check_mark: |
| `gitlab` | :white_check_mark: | :white_check_mark: | :white_check_mark:| | :white_check_mark: | | `git` | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | |
| `bitbucket` | Comming soon | :white_check_mark: | | | :white_check_mark: | |
## Supported CI Pipelines
* Github Actions
* Gitlab CI
* Travis CI
* Custom CI, set enviroment `CI=true`
## How to use
`go-semantic-release` config file
Create a file with the name `.release.yml` or anything else, but you need to set to every command `-c <your config file>`
### Example config
```yml
commitFormat: angular
branch:
master: release
release: 'github'
github:
repo: "go-semantic-release"
user: "nightapes"
assets:
- name: ./build/go-semantic-release
compress: false
- name: ./build/go-semantic-release.exe
compress: false
hooks:
preRelease:
- name: echo $RELEASE_VERSION
postRelease:
- name: echo $RELEASE_VERSION
```
#### CommitFormat
Set the commit format, at the moment we support ony [angular](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format), more coming soon.
```yml
commitFormat: angular
```
#### Branch
You can define which kind of release should be created for different branches.
Supported release kinds:
* `release` -> `v1.0.0`
* `rc` -> `v1.0.0-rc.0`
* `beta` -> `v1.0.0-beta.0`
* `alpha` -> `v1.0.0-alpha.0`
Add a branch config to your config
```yml
branch:
<branch-name>: <kind>
```
#### Release
At the moment we support releases to gitlab and github.
##### Github
You need to set the env `GITHUB_TOKEN` with an access token.
```yml
release: 'github'
github:
user: "<user/group"
repo: "<repositroyname>"
## Optional, if your not using github.com
customUrl: <https://your.github>
```
##### Gitlab
You need to set the env `GITLAB_ACCESS_TOKEN` with an personal access token.
```yml
release: 'gitlab'
gitlab:
repo: "<repositroyname>" ## Example group/project
## Optional, if your not using gitlab.com
customUrl: <https://your.gitlab>
```
##### Git only
Only via https at the moment. You need write access to your git repository
```yml
release: 'git'
git:
email: "<email>" # Used for creating tag
user: "<user>" : # Used for creating tag and pushing
auth: "<token>" # Used for pushing, can be env "$GIT_TOKEN", will be replaced with env
```
#### Assets
You can upload assets to a release
Support for gitlab and github.
If you want, you can let the file be compressed before uploading
```yml
assets:
- name: ./build/go-semantic-release
compress: false
```
#### Hooks
Hooks will run when calling `release`. Hooks run only if a release will be triggered.
#### Changelog
Following variables can be used for templates:
* `Commits` string
* `Version` string
* `Now` time.Time
* `Backtick` string
* `HasDocker` bool
* `HasDockerLatest` bool
* `DockerRepository` string
```yml
changelog:
printAll: false ## Print all valid commits to changelog
title: "v{{.Version}} ({{.Now.Format "2006-01-02"}})" ## Used for releases (go template)
templatePath: "./examples/changelog.tmpl" ## Path to a template file (go template)
```
##### Docker
You can print a help text for a docker image
```yml
changelog:
docker:
latest: false ## If you uploaded a latest image
repository: ## Your docker repository, which is used for docker run
```
### Version
`go-semantic-release` has two modes for calcualting the version: automatic or manual.
#### Automatic
Version will be calculated on the `next` or `release` command
#### Manual
If you don't want that `go-semantic-release` is calculating the version from the commits, you can set the version by hand with
following command:
```bash
./go-semantic-release set 1.1.1
```
### Print version
Print the next version, can be used to add version to your program
```bash
./go-semantic-release next
```
Example with go-lang
```bash
go build -ldflags "--X main.version=`./go-semantic-release next`"
```
### Create release
```bash
./go-semantic-release release
```
## Build ## Build from source
`go build ./cmd/go-semantic-release/` ```bash
go build ./cmd/go-semantic-release/
```
## Run ### Testing
Print the next version ```bash
go test ./...
```
`./go-semantic-release version next` ### Linting
Set a version ```
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0
`./go-semantic-release version set v1.1.1` golangci-lint run ./...
```

View File

@@ -1,2 +0,0 @@
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/

3
_config.yml Normal file
View File

@@ -0,0 +1,3 @@
theme: jekyll-theme-cayman
plugins:
- jemoji

View File

@@ -35,16 +35,27 @@ var changelogCmd = &cobra.Command{
return err return err
} }
s, err := semanticrelease.New(readConfig(config), repository) ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil { if err != nil {
return err return err
} }
releaseVersion, err := s.GetNextVersion(force) s, err := semanticrelease.New(readConfig(config), repository, !ignoreConfigChecks)
if err != nil { if err != nil {
return err return err
} }
provider, err := s.GetCIProvider()
if err != nil {
return err
}
releaseVersion, err := s.GetNextVersion(provider, force)
if err != nil {
return err
}
log.Debugf("Found %d commits till last release", len(releaseVersion.Commits))
generatedChangelog, err := s.GetChangelog(releaseVersion) generatedChangelog, err := s.GetChangelog(releaseVersion)
if err != nil { if err != nil {
return err return err

View File

@@ -0,0 +1,64 @@
package commands
import (
"github.com/Nightapes/go-semantic-release/internal/hooks"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(hooksCmd)
}
var hooksCmd = &cobra.Command{
Use: "hooks",
Short: "Run all hooks",
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
}
ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil {
return err
}
releaseConfig := readConfig(config)
s, err := semanticrelease.New(releaseConfig, repository, !ignoreConfigChecks)
if err != nil {
return err
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
releaseVersion, err := s.GetNextVersion(provider, force)
if err != nil {
return err
}
hook := hooks.New(releaseConfig, releaseVersion)
err = hook.PreRelease()
if err != nil {
return err
}
return hook.PostRelease()
},
}

View File

@@ -2,8 +2,8 @@ package commands
import ( import (
"fmt" "fmt"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease" "github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -30,12 +30,25 @@ var nextCmd = &cobra.Command{
return err return err
} }
s, err := semanticrelease.New(readConfig(config), repository) ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil { if err != nil {
return err return err
} }
releaseVersion, err := s.GetNextVersion(force) s, err := semanticrelease.New(readConfig(config), repository, !ignoreConfigChecks)
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 { if err != nil {
return err return err
} }

View File

@@ -28,10 +28,21 @@ var releaseCmd = &cobra.Command{
return err return err
} }
s, err := semanticrelease.New(readConfig(config), repository) ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil { if err != nil {
return err return err
} }
return s.Release(force)
s, err := semanticrelease.New(readConfig(config), repository, !ignoreConfigChecks)
if err != nil {
return err
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
return s.Release(provider, force)
}, },
} }

View File

@@ -31,14 +31,11 @@ func Execute(version string) {
} }
func init() { func init() {
currentDir, err := os.Getwd() rootCmd.PersistentFlags().StringP("repository", "r", "./", "Path to repository")
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("loglevel", "l", "error", "Set loglevel")
rootCmd.PersistentFlags().StringP("config", "c", ".release.yml", "Path to config file") 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") rootCmd.PersistentFlags().Bool("no-cache", false, "Ignore cache, don't use in ci build")
rootCmd.PersistentFlags().Bool("no-checks", false, "Ignore missing values and envs")
} }
func readConfig(file string) *config.ReleaseConfig { func readConfig(file string) *config.ReleaseConfig {

View File

@@ -26,11 +26,21 @@ var setCmd = &cobra.Command{
return err return err
} }
s, err := semanticrelease.New(readConfig(config), repository) ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil { if err != nil {
return err return err
} }
return s.SetVersion(args[0]) s, err := semanticrelease.New(readConfig(config), repository, !ignoreConfigChecks)
if err != nil {
return err
}
provider, err := s.GetCIProvider()
if err != nil {
return err
}
return s.SetVersion(provider, args[0])
}, },
} }

View File

@@ -11,7 +11,7 @@ func init() {
var zipCmd = &cobra.Command{ var zipCmd = &cobra.Command{
Use: "zip", Use: "zip",
Short: "Zip configured artifact from release config", Short: "Zip configured artifact from release config (internal)",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
config, err := cmd.Flags().GetString("config") config, err := cmd.Flags().GetString("config")
if err != nil { if err != nil {
@@ -23,7 +23,12 @@ var zipCmd = &cobra.Command{
return err return err
} }
s, err := semanticrelease.New(readConfig(config), repository) ignoreConfigChecks, err := cmd.Flags().GetBool("no-checks")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, !ignoreConfigChecks)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -29,11 +29,11 @@ script:
- go test -v ./... - go test -v ./...
- go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/ - 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`" - 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/ - go build -o build/go-semantic-release -ldflags "-w -s --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/ - GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
after_success: after_success:
- ./build/go-semantic-release-temp release --loglevel debug - ./build/go-semantic-release-temp release --loglevel trace
branches: branches:
except: except:

16
examples/changelog.tmpl Normal file
View File

@@ -0,0 +1,16 @@
# My custom release template v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ .Commits -}}
{{ if .HasDocker}}
## Docker image
New docker image is released under {{$.Backtick}}{{.DockerRepository}}:{{.Version}}{{$.Backtick}}
### Usage
{{$.Backtick}}docker run {{.DockerRepository}}:{{.Version}}{{$.Backtick}}
{{ if .HasDockerLatest}}
or
{{$.Backtick}}docker run {{.DockerRepository}}:latest{{$.Backtick}}
{{ end -}}
{{ end -}}

3
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/Nightapes/go-semantic-release module github.com/Nightapes/go-semantic-release
go 1.12 go 1.13
require ( require (
github.com/Masterminds/semver v1.4.2 github.com/Masterminds/semver v1.4.2
@@ -8,6 +8,7 @@ require (
github.com/google/go-cmp v0.3.0 // indirect github.com/google/go-cmp v0.3.0 // indirect
github.com/google/go-github/v25 v25.1.3 github.com/google/go-github/v25 v25.1.3
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0

6
go.sum
View File

@@ -48,12 +48,14 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= 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/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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -61,6 +63,7 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -71,14 +74,17 @@ 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.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 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 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 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 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 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 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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=

View File

@@ -4,47 +4,30 @@ package analyzer
import ( import (
"fmt" "fmt"
"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/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
//Analyzer struct //Analyzer struct
type Analyzer struct { type Analyzer struct {
analyzeCommit analyzeCommit analyzeCommits analyzeCommits
Config config.ChangelogConfig Config config.ChangelogConfig
} }
//Release types, like major
type Release string
//Scope of the commit, like feat, fix,..
type Scope string
//Rule for commits //Rule for commits
type Rule struct { type Rule struct {
Tag string Tag string
TagString string TagString string
Release Release Release shared.Release
Changelog bool Changelog bool
} }
type analyzeCommit interface { type analyzeCommits interface {
analyze(commit gitutil.Commit, tag Rule) (AnalyzedCommit, bool, error) analyze(commit shared.Commit, tag Rule) (shared.AnalyzedCommit, bool, error)
getRules() []Rule getRules() []Rule
} }
//AnalyzedCommit struct
type AnalyzedCommit struct {
Commit gitutil.Commit
ParsedMessage string
Scope Scope
ParsedBreakingChangeMessage string
Tag string
TagString string
Print bool
}
//New Analyzer struct for given commit format //New Analyzer struct for given commit format
func New(format string, config config.ChangelogConfig) (*Analyzer, error) { func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
analyzer := &Analyzer{ analyzer := &Analyzer{
@@ -52,9 +35,9 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
} }
switch format { switch format {
case "angular": case ANGULAR:
log.Debugf("Commit format set to angular") analyzer.analyzeCommits = newAngular()
analyzer.analyzeCommit = newAngular() log.Debugf("Commit format set to %s", ANGULAR)
default: default:
return nil, fmt.Errorf("invalid commit format: %s", format) return nil, fmt.Errorf("invalid commit format: %s", format)
} }
@@ -64,21 +47,21 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
// GetRules from current mode // GetRules from current mode
func (a *Analyzer) GetRules() []Rule { func (a *Analyzer) GetRules() []Rule {
return a.analyzeCommit.getRules() return a.analyzeCommits.getRules()
} }
// Analyze commits and return commits splitted by major,minor,patch // Analyze commits and return commits splitted by major,minor,patch
func (a *Analyzer) Analyze(commits []gitutil.Commit) map[Release][]AnalyzedCommit { func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.AnalyzedCommit {
analyzedCommits := make(map[Release][]AnalyzedCommit) analyzedCommits := make(map[shared.Release][]shared.AnalyzedCommit)
analyzedCommits["major"] = make([]AnalyzedCommit, 0) analyzedCommits["major"] = make([]shared.AnalyzedCommit, 0)
analyzedCommits["minor"] = make([]AnalyzedCommit, 0) analyzedCommits["minor"] = make([]shared.AnalyzedCommit, 0)
analyzedCommits["patch"] = make([]AnalyzedCommit, 0) analyzedCommits["patch"] = make([]shared.AnalyzedCommit, 0)
analyzedCommits["none"] = make([]AnalyzedCommit, 0) analyzedCommits["none"] = make([]shared.AnalyzedCommit, 0)
for _, commit := range commits { for _, commit := range commits {
for _, rule := range a.analyzeCommit.getRules() { for _, rule := range a.analyzeCommits.getRules() {
analyzedCommit, hasBreakingChange, err := a.analyzeCommit.analyze(commit, rule) analyzedCommit, hasBreakingChange, err := a.analyzeCommits.analyze(commit, rule)
if err == nil { if err == nil {
if a.Config.PrintAll { if a.Config.PrintAll {
analyzedCommit.Print = true analyzedCommit.Print = true

View File

@@ -8,17 +8,22 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/internal/gitutil" "github.com/Nightapes/go-semantic-release/internal/shared"
) )
type angular struct { type angular struct {
rules []Rule rules []Rule
regex string regex string
log *log.Entry
} }
// ANGULAR identifer
const ANGULAR = "angular"
func newAngular() *angular { func newAngular() *angular {
return &angular{ return &angular{
regex: `(TAG)(?:\((.*)\))?: (.*)`, regex: `^(TAG)(?:\((.*)\))?: (.*)`,
log: log.WithField("analyzer", ANGULAR),
rules: []Rule{ rules: []Rule{
{ {
Tag: "feat", Tag: "feat",
@@ -76,9 +81,9 @@ func (a *angular) getRules() []Rule {
return a.rules return a.rules
} }
func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, bool, error) { func (a *angular) analyze(commit shared.Commit, rule Rule) (shared.AnalyzedCommit, bool, error) {
analyzed := AnalyzedCommit{ analyzed := shared.AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag, Tag: rule.Tag,
TagString: rule.TagString, TagString: rule.TagString,
@@ -89,13 +94,13 @@ func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, boo
if len(matches) >= 1 { if len(matches) >= 1 {
if len(matches[0]) >= 3 { if len(matches[0]) >= 3 {
analyzed.Scope = Scope(matches[0][2]) analyzed.Scope = shared.Scope(matches[0][2])
message := strings.Join(matches[0][3:], "") message := strings.Join(matches[0][3:], "")
if !strings.Contains(message, "BREAKING CHANGE:") { if !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ") analyzed.ParsedMessage = strings.Trim(message, " ")
log.Tracef("%s: found %s", commit.Message, rule.Tag) a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false, nil return analyzed, false, nil
} }
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2) breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
@@ -103,11 +108,11 @@ func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, boo
analyzed.ParsedMessage = strings.Trim(breakingChange[0], " ") analyzed.ParsedMessage = strings.Trim(breakingChange[0], " ")
analyzed.ParsedBreakingChangeMessage = strings.Trim(breakingChange[1], " ") analyzed.ParsedBreakingChangeMessage = strings.Trim(breakingChange[1], " ")
log.Tracef(" %s, BREAKING CHANGE found", commit.Message) a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil return analyzed, true, nil
} }
} }
log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag) a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return analyzed, false, fmt.Errorf("not found") return analyzed, false, fmt.Errorf("not found")
} }

View File

@@ -4,7 +4,7 @@ import (
"testing" "testing"
"github.com/Nightapes/go-semantic-release/internal/analyzer" "github.com/Nightapes/go-semantic-release/internal/analyzer"
"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/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -13,15 +13,15 @@ func TestAngular(t *testing.T) {
testConfigs := []struct { testConfigs := []struct {
testCase string testCase string
commits []gitutil.Commit commits []shared.Commit
analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit analyzedCommits map[shared.Release][]shared.AnalyzedCommit
}{ }{
{ {
testCase: "feat", testCase: "feat",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{ "minor": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -33,12 +33,12 @@ func TestAngular(t *testing.T) {
Print: true, Print: true,
}, },
}, },
"major": []analyzer.AnalyzedCommit{}, "major": []shared.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{}, "patch": []shared.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{}, "none": []shared.AnalyzedCommit{},
}, },
commits: []gitutil.Commit{ commits: []shared.Commit{
gitutil.Commit{ shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -47,10 +47,10 @@ func TestAngular(t *testing.T) {
}, },
{ {
testCase: "feat breaking change", testCase: "feat breaking change",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{ "minor": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -62,9 +62,9 @@ func TestAngular(t *testing.T) {
Print: true, Print: true,
}, },
}, },
"major": []analyzer.AnalyzedCommit{ "major": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2", Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",
@@ -77,16 +77,16 @@ func TestAngular(t *testing.T) {
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
}, },
}, },
"patch": []analyzer.AnalyzedCommit{}, "patch": []shared.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{}, "none": []shared.AnalyzedCommit{},
}, },
commits: []gitutil.Commit{ commits: []shared.Commit{
gitutil.Commit{ shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
}, },
gitutil.Commit{ shared.Commit{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2", Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",
@@ -95,26 +95,31 @@ func TestAngular(t *testing.T) {
}, },
{ {
testCase: "invalid", testCase: "invalid",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{}, "minor": []shared.AnalyzedCommit{},
"major": []analyzer.AnalyzedCommit{}, "major": []shared.AnalyzedCommit{},
"patch": []analyzer.AnalyzedCommit{}, "patch": []shared.AnalyzedCommit{},
"none": []analyzer.AnalyzedCommit{}, "none": []shared.AnalyzedCommit{},
}, },
commits: []gitutil.Commit{ commits: []shared.Commit{
gitutil.Commit{ shared.Commit{
Message: "internal/changelog: my first commit", Message: "internal/changelog: my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
}, },
shared.Commit{
Message: "Merge feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
}, },
}, },
{ {
testCase: "feat and build", testCase: "feat and build",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{ "minor": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -126,9 +131,9 @@ func TestAngular(t *testing.T) {
Print: true, Print: true,
}, },
}, },
"none": []analyzer.AnalyzedCommit{ "none": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "build(internal/changelog): my first build", Message: "build(internal/changelog): my first build",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",
@@ -141,16 +146,16 @@ func TestAngular(t *testing.T) {
ParsedBreakingChangeMessage: "", ParsedBreakingChangeMessage: "",
}, },
}, },
"patch": []analyzer.AnalyzedCommit{}, "patch": []shared.AnalyzedCommit{},
"major": []analyzer.AnalyzedCommit{}, "major": []shared.AnalyzedCommit{},
}, },
commits: []gitutil.Commit{ commits: []shared.Commit{
gitutil.Commit{ shared.Commit{
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
}, },
gitutil.Commit{ shared.Commit{
Message: "build(internal/changelog): my first build", Message: "build(internal/changelog): my first build",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",

View File

@@ -5,49 +5,61 @@ import (
"io/ioutil" "io/ioutil"
"path" "path"
log "github.com/sirupsen/logrus"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/shared"
"gopkg.in/yaml.v2" "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 // Write version into .version
func Write(repository string, versionFileContent ReleaseVersion) error { func Write(repository string, releaseVersion shared.ReleaseVersion) error {
completePath := path.Join(path.Dir(repository), ".version") completePath := path.Join(path.Dir(repository), ".version")
data, err := yaml.Marshal(&versionFileContent) if releaseVersion.Last.Version != nil {
releaseVersion.Last.VersionString = releaseVersion.Last.Version.String()
}
if releaseVersion.Next.Version != nil {
releaseVersion.Next.VersionString = releaseVersion.Next.Version.String()
}
//toCache := &ReleaseVersion(releaseVersion)
data, err := yaml.Marshal(releaseVersion)
if err != nil { if err != nil {
return err return err
} }
log.Infof("Save %s with hash %s to cache %s", releaseVersion.Next.Version.String(), releaseVersion.Next.Commit, completePath)
return ioutil.WriteFile(completePath, data, 0644) return ioutil.WriteFile(completePath, data, 0644)
} }
// Read version into .version // Read version into .version
func Read(repository string) (*ReleaseVersion, error) { func Read(repository string) (*shared.ReleaseVersion, error) {
completePath := path.Join(path.Dir(repository), ".version") completePath := path.Join(path.Dir(repository), ".version")
content, err := ioutil.ReadFile(completePath) content, err := ioutil.ReadFile(completePath)
if err != nil { if err != nil {
return &ReleaseVersion{}, err log.Warnf("Could not read cache %s, will ignore cache", completePath)
return &shared.ReleaseVersion{}, nil
} }
var versionFileContent ReleaseVersion var parsedContent shared.ReleaseVersion
err = yaml.Unmarshal(content, &versionFileContent) err = yaml.Unmarshal(content, &parsedContent)
if err != nil { if err != nil {
return &ReleaseVersion{}, err return &shared.ReleaseVersion{}, err
} }
return &versionFileContent, nil parsedContent.Next.Version, err = semver.NewVersion(parsedContent.Next.VersionString)
if err != nil {
return nil, err
}
parsedContent.Last.Version, err = semver.NewVersion(parsedContent.Last.VersionString)
if err != nil {
return nil, err
}
log.Infof("Found cache, will return cached version %s", parsedContent.Next.Version)
return &parsedContent, nil
} }

View File

@@ -3,17 +3,21 @@ package cache_test
import ( import (
"testing" "testing"
"github.com/Nightapes/go-semantic-release/internal/cache"
"github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"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"
) )
func TestReadCacheNotFound(t *testing.T) { func TestReadCacheNotFound(t *testing.T) {
_, err := cache.Read("notfound/dir") resp, err := cache.Read("notfound/dir")
assert.Errorf(t, err, "Read non exsiting file") assert.NoErrorf(t, err, "Read non exsiting file")
assert.NotNil(t, resp)
} }
@@ -39,17 +43,35 @@ func TestWriteAndReadCache(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
content := cache.ReleaseVersion{ content := shared.ReleaseVersion{
Last: cache.ReleaseVersionEntry{ Last: shared.ReleaseVersionEntry{
Commit: "12345", Commit: "12345",
Version: "1.0.0", Version: createVersion("1.0.0"),
VersionString: "1.0.0",
}, },
Next: cache.ReleaseVersionEntry{ Next: shared.ReleaseVersionEntry{
Commit: "12346", Commit: "12346",
Version: "1.1.0", Version: createVersion("1.1.0"),
VersionString: "1.1.0",
}, },
Branch: "master", Branch: "master",
Draft: true, Commits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
Commit: shared.Commit{
Message: "Message",
Author: "Author",
Hash: "Hash",
},
ParsedMessage: "add gitlab as release option",
Scope: "releaser",
ParsedBreakingChangeMessage: "",
Tag: "feat",
TagString: "Features",
Print: true,
},
},
},
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
@@ -59,13 +81,28 @@ func TestWriteAndReadCache(t *testing.T) {
result, readError := cache.Read(dir) result, readError := cache.Read(dir)
assert.NoErrorf(t, readError, "Should read file") assert.NoErrorf(t, readError, "Should read file")
assert.Equal(t, &content, result) assert.EqualValues(t, &content, result)
} }
func TestWriteNotFound(t *testing.T) { func TestWriteNotFound(t *testing.T) {
err := cache.Write("notfound/dir", cache.ReleaseVersion{}) 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",
})
assert.Errorf(t, err, "Write non exsiting file") 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/shared"
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[shared.Release][]shared.AnalyzedCommit, lastVersion *semver.Version, releaseType string, firstRelease bool) semver.Version {
switch releaseType {
case "beta", "alpha", "rc":
if len(commits["major"]) > 0 || len(commits["minor"]) > 0 || len(commits["patch"]) > 0 {
version, _ := c.IncPrerelease(releaseType, lastVersion)
return version
}
case "release":
if !firstRelease {
if lastVersion.Prerelease() != "" {
newVersion, _ := lastVersion.SetPrerelease("")
return newVersion
}
if len(commits["major"]) > 0 {
return lastVersion.IncMajor()
} else if len(commits["minor"]) > 0 {
return lastVersion.IncMinor()
} else if len(commits["patch"]) > 0 {
return lastVersion.IncPatch()
}
}
}
return *lastVersion
}

View File

@@ -0,0 +1,208 @@
package calculator_test
import (
"testing"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/calculator"
"github.com/Nightapes/go-semantic-release/internal/shared"
"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
isFirst bool
analyzedCommits map[shared.Release][]shared.AnalyzedCommit
}{
{
testCase: "version with preRelease alpha",
releaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-alpha.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{},
},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version with preRelease beta",
releaseType: "beta",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-beta.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{},
},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version without commits",
releaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version with commits and first release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"minor": []shared.AnalyzedCommit{},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
},
isFirst: true,
},
{
testCase: "version with commits and rc release",
releaseType: "rc",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-rc.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"minor": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"patch": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"none": []shared.AnalyzedCommit{},
},
isFirst: 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[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"patch": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version with commits and major release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "2.0.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"minor": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"patch": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version with commits and minor release",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.1.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"patch": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
{
testCase: "version with commits and minor patch",
releaseType: "release",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.1",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{},
"patch": []shared.AnalyzedCommit{shared.AnalyzedCommit{}},
"none": []shared.AnalyzedCommit{},
},
isFirst: false,
},
}
c := calculator.New()
for _, test := range testConfigs {
next := c.CalculateNewVersion(test.analyzedCommits, test.lastVersion, test.releaseType, test.isFirst)
assert.Equalf(t, test.nextVersion, next.String(), "Should have version %s for testcase %s", test.nextVersion, test.testCase)
}
}

View File

@@ -2,6 +2,7 @@ package changelog
import ( import (
"bytes" "bytes"
"io/ioutil"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@@ -13,9 +14,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})` const defaultCommitList string = `{{ range $index,$commit := .BreakingChanges -}}
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ range $index,$commit := .BreakingChanges -}}
{{ if eq $index 0 }} {{ if eq $index 0 }}
## BREAKING CHANGES ## BREAKING CHANGES
{{ end}} {{ end}}
@@ -30,12 +29,39 @@ introduced by commit:
* **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}} * **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}}
{{ end -}} {{ end -}}
{{ end -}} {{ end -}}
{{ end -}}`
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ .Commits -}}
{{ if .HasDocker}}
## Docker image
New docker image is released under {{$.Backtick}}{{.DockerRepository}}:{{.Version}}{{$.Backtick}}
### Usage
{{$.Backtick}}docker run {{.DockerRepository}}:{{.Version}}{{$.Backtick}}
{{ if .HasDockerLatest}}
or
{{$.Backtick}}docker run {{.DockerRepository}}:latest{{$.Backtick}}
{{ end -}}
{{ end -}} {{ end -}}
` `
type changelogContent struct { type changelogContent struct {
Commits map[string][]analyzer.AnalyzedCommit Commits string
BreakingChanges []analyzer.AnalyzedCommit Version string
Now time.Time
Backtick string
HasDocker bool
HasDockerLatest bool
DockerRepository string
}
type commitsContent struct {
Commits map[string][]shared.AnalyzedCommit
BreakingChanges []shared.AnalyzedCommit
Order []string Order []string
Version string Version string
Now time.Time Now time.Time
@@ -49,6 +75,7 @@ type Changelog struct {
config *config.ReleaseConfig config *config.ReleaseConfig
rules []analyzer.Rule rules []analyzer.Rule
releaseTime time.Time releaseTime time.Time
log *log.Entry
} }
//New Changelog struct for generating changelog from commits //New Changelog struct for generating changelog from commits
@@ -57,18 +84,19 @@ func New(config *config.ReleaseConfig, rules []analyzer.Rule, releaseTime time.T
config: config, config: config,
rules: rules, rules: rules,
releaseTime: releaseTime, releaseTime: releaseTime,
log: log.WithField("changelog", config.CommitFormat),
} }
} }
// GenerateChanglog from given commits // GenerateChanglog from given commits
func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit) (*shared.GeneratedChangelog, error) { func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
commitsPerScope := map[string][]analyzer.AnalyzedCommit{} commitsPerScope := map[string][]shared.AnalyzedCommit{}
commitsBreakingChange := []analyzer.AnalyzedCommit{} commitsBreakingChange := []shared.AnalyzedCommit{}
order := make([]string, 0) order := make([]string, 0)
for _, rule := range c.rules { for _, rule := range c.rules {
log.Debugf("Add %s to list", rule.TagString) c.log.Tracef("Add %s to list", rule.TagString)
if rule.Changelog || c.config.Changelog.PrintAll { if rule.Changelog || c.config.Changelog.PrintAll {
order = append(order, rule.TagString) order = append(order, rule.TagString)
} }
@@ -82,14 +110,14 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
continue continue
} }
if _, ok := commitsPerScope[commit.TagString]; !ok { if _, ok := commitsPerScope[commit.TagString]; !ok {
commitsPerScope[commit.TagString] = make([]analyzer.AnalyzedCommit, 0) commitsPerScope[commit.TagString] = make([]shared.AnalyzedCommit, 0)
} }
commitsPerScope[commit.TagString] = append(commitsPerScope[commit.TagString], commit) commitsPerScope[commit.TagString] = append(commitsPerScope[commit.TagString], commit)
} }
} }
} }
changelogContent := changelogContent{ commitsContent := commitsContent{
Version: templateConfig.Version, Version: templateConfig.Version,
Commits: commitsPerScope, Commits: commitsPerScope,
Now: c.releaseTime, Now: c.releaseTime,
@@ -100,16 +128,50 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
URL: templateConfig.CommitURL, URL: templateConfig.CommitURL,
} }
title, err := generateTemplate(defaultChangelogTitle, changelogContent) changelogContent := changelogContent{
Version: templateConfig.Version,
Now: c.releaseTime,
Backtick: "`",
HasDocker: c.config.Changelog.Docker.Repository != "",
HasDockerLatest: c.config.Changelog.Docker.Latest,
DockerRepository: c.config.Changelog.Docker.Repository,
}
template := defaultChangelog
if c.config.Changelog.TemplatePath != "" {
content, err := ioutil.ReadFile(c.config.Changelog.TemplatePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
content, err := generateTemplate(defaultChangelog, changelogContent) template = string(content)
return &shared.GeneratedChangelog{Title: title, Content: content}, err
} }
func generateTemplate(text string, values changelogContent) (string, error) { templateTitle := defaultChangelogTitle
if c.config.Changelog.TemplateTitle != "" {
templateTitle = c.config.Changelog.TemplateTitle
}
log.Debugf("Render title")
renderedTitle, err := generateTemplate(templateTitle, changelogContent)
if err != nil {
return nil, err
}
log.Debugf("Render commits")
renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent)
if err != nil {
return nil, err
}
log.Tracef("Commits %s", renderedCommitList)
changelogContent.Commits = renderedCommitList
log.Debugf("Render changelog")
renderedContent, err := generateTemplate(template, changelogContent)
return &shared.GeneratedChangelog{Title: renderedTitle, Content: renderedContent}, err
}
func generateTemplate(text string, values interface{}) (string, error) {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"replace": replace, "replace": replace,

View File

@@ -5,7 +5,6 @@ import (
"github.com/Nightapes/go-semantic-release/internal/analyzer" "github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/changelog" "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/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config" "github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -23,16 +22,16 @@ func TestChangelog(t *testing.T) {
testConfigs := []struct { testConfigs := []struct {
testCase string testCase string
analyzedCommits map[analyzer.Release][]analyzer.AnalyzedCommit analyzedCommits map[shared.Release][]shared.AnalyzedCommit
result *shared.GeneratedChangelog result *shared.GeneratedChangelog
hasError bool hasError bool
}{ }{
{ {
testCase: "feat", testCase: "feat",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{ "minor": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(test): my first commit", Message: "feat(test): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -53,10 +52,10 @@ func TestChangelog(t *testing.T) {
}, },
{ {
testCase: "feat breaking change", testCase: "feat breaking change",
analyzedCommits: map[analyzer.Release][]analyzer.AnalyzedCommit{ analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []analyzer.AnalyzedCommit{ "minor": []shared.AnalyzedCommit{
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(test): my first commit", Message: "feat(test): my first commit",
Author: "me", Author: "me",
Hash: "12345667", Hash: "12345667",
@@ -67,8 +66,8 @@ func TestChangelog(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
}, },
analyzer.AnalyzedCommit{ shared.AnalyzedCommit{
Commit: gitutil.Commit{ Commit: shared.Commit{
Message: "feat(test): my first break: BREAKING CHANGE: change api to v2", Message: "feat(test): my first break: BREAKING CHANGE: change api to v2",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",

View File

@@ -3,10 +3,11 @@ package ci
import ( import (
"fmt" "fmt"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
log "github.com/sirupsen/logrus"
"os" "os"
"strings" "strings"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
log "github.com/sirupsen/logrus"
) )
//ProviderConfig struct //ProviderConfig struct
@@ -38,20 +39,27 @@ func ReadAllEnvs() map[string]string {
} }
//GetCIProvider get provider //GetCIProvider get provider
func GetCIProvider(gitUtil *gitutil.GitUtil, envs map[string]string) (*ProviderConfig, error) { func GetCIProvider(gitUtil *gitutil.GitUtil, configCheck bool, envs map[string]string) (*ProviderConfig, error) {
services := []Service{ services := []Service{
Travis{}, Travis{},
Git{gitUtil: gitUtil}, // GIt must be the last option to check GithubActions{},
GitlabCI{},
Git{gitUtil: gitUtil}, // Git must be the last option to check
} }
for _, service := range services { for _, service := range services {
config, err := service.detect(envs) config, err := service.detect(envs)
if err == nil { if err == nil {
log.Infof("Found CI: %s", config.Name) log.Infof("Found CI: %s", config.Name)
log.Tracef("Found CI config: %+v", config)
return config, nil return config, nil
} }
log.Infof("%s", err.Error()) log.Debugf("%s", err.Error())
} }
if configCheck {
return nil, fmt.Errorf("could not find any CI, if running locally set env CI=true") return nil, fmt.Errorf("could not find any CI, if running locally set env CI=true")
}
return Git{gitUtil: gitUtil}.detect(map[string]string{"CI": "true"})
} }

View File

@@ -89,10 +89,45 @@ func TestCi(t *testing.T) {
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"}, 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, hasError: false,
}, },
{
service: "Github Actions PR",
envs: map[string]string{
"GITHUB_EVENT_NAME": "pull_request",
"GITHUB_SHA": "190bfd6aa60022afd0ef830342cfb07e33c45f37",
"GITHUB_REF": "master",
"GITHUB_ACTION": "action",
},
result: &ci.ProviderConfig{IsPR: true, PR: "", PRBranch: "", Branch: "master", Tag: "", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "", Service: "GithubActions", Name: "GithubActions CI"},
hasError: false,
},
{
service: "Github Actions Push",
envs: map[string]string{
"GITHUB_EVENT_NAME": "push",
"GITHUB_SHA": "190bfd6aa60022afd0ef830342cfb07e33c45f37",
"GITHUB_REF": "refs/heads/feature-branch-1",
"GITHUB_ACTION": "action",
},
result: &ci.ProviderConfig{IsPR: false, PR: "", PRBranch: "", Branch: "feature-branch-1", Tag: "", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "", Service: "GithubActions", Name: "GithubActions CI"},
hasError: false,
},
{
service: "GitLab CI/CD PR",
envs: map[string]string{
"GITLAB_CI": "true",
"CI_COMMIT_SHA": "190bfd6aa60022afd0ef830342cfb07e33c45f37",
"CI_COMMIT_REF_NAME": "master",
"CI_COMMIT_TAG": "tag",
"CI_PROJECT_URL": "https://my.gitlab.com",
"CI_PIPELINE_ID": "1",
},
result: &ci.ProviderConfig{IsPR: false, PR: "", PRBranch: "", Branch: "master", Tag: "tag", Commit: "190bfd6aa60022afd0ef830342cfb07e33c45f37", BuildURL: "https://my.gitlab.com/pipelines/1", Service: "gitlab", Name: "GitLab CI/CD"},
hasError: false,
},
} }
for _, config := range testConfigs { for _, config := range testConfigs {
provider, err := ci.GetCIProvider(gitUtilInMemory, config.envs) provider, err := ci.GetCIProvider(gitUtilInMemory, true, 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.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) assert.Equalf(t, config.result, provider, "Service %s should have provider", config.service)
} }

View File

@@ -0,0 +1,43 @@
package ci
import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
)
//GithubActions struct
type GithubActions struct{}
//Detect if on GithubActions
func (t GithubActions) detect(envs map[string]string) (*ProviderConfig, error) {
if _, exists := envs["GITHUB_ACTION"]; !exists {
return nil, fmt.Errorf("not running on Github Actions")
}
isPR := false
value := envs["GITHUB_EVENT_NAME"]
if value == "pull_request" {
isPR = true
} else {
log.Debugf("GITHUB_EVENT_NAME=%s, not running on pr", value)
}
branch := envs["GITHUB_REF"]
if strings.HasPrefix(envs["GITHUB_REF"], "refs/heads/") {
branch = strings.Replace(branch, "refs/heads/", "", 1)
}
return &ProviderConfig{
Service: "GithubActions",
Name: "GithubActions CI",
Commit: envs["GITHUB_SHA"],
Branch: branch,
IsPR: isPR,
}, nil
}

26
internal/ci/gitlab_ci.go Normal file
View File

@@ -0,0 +1,26 @@
package ci
import (
"fmt"
)
//GitlabCI struct
type GitlabCI struct{}
//Detect if on GitlabCI
func (t GitlabCI) detect(envs map[string]string) (*ProviderConfig, error) {
if _, exists := envs["GITLAB_CI"]; !exists {
return nil, fmt.Errorf("not running on gitlab")
}
return &ProviderConfig{
Service: "gitlab",
Name: "GitLab CI/CD",
Commit: envs["CI_COMMIT_SHA"],
Tag: envs["CI_COMMIT_TAG"],
BuildURL: envs["CI_PROJECT_URL"] + "/pipelines/" + envs["CI_PIPELINE_ID"],
Branch: envs["CI_COMMIT_REF_NAME"],
IsPR: false,
}, nil
}

View File

@@ -5,20 +5,17 @@ import (
"fmt" "fmt"
"sort" "sort"
"github.com/pkg/errors"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/shared"
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"
"gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/plumbing/storer"
) )
// Commit struct
type Commit struct {
Message string
Author string
Hash string
}
// GitUtil struct // GitUtil struct
type GitUtil struct { type GitUtil struct {
Repository *git.Repository Repository *git.Repository
@@ -93,7 +90,7 @@ func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
err = gitTags.ForEach(func(p *plumbing.Reference) error { err = gitTags.ForEach(func(p *plumbing.Reference) error {
v, err := semver.NewVersion(p.Name().Short()) v, err := semver.NewVersion(p.Name().Short())
log.Tracef("%+v with hash: %s", p.Target(), p.Hash()) log.Tracef("Tag %+v with hash: %s", p.Name().Short(), p.Hash())
if err == nil { if err == nil {
tags = append(tags, v) tags = append(tags, v)
@@ -126,7 +123,7 @@ func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
} }
// GetCommits from git hash to HEAD // GetCommits from git hash to HEAD
func (g *GitUtil) GetCommits(lastTagHash string) ([]Commit, error) { func (g *GitUtil) GetCommits(lastTagHash string) ([]shared.Commit, error) {
ref, err := g.Repository.Head() ref, err := g.Repository.Head()
if err != nil { if err != nil {
@@ -138,18 +135,18 @@ func (g *GitUtil) GetCommits(lastTagHash string) ([]Commit, error) {
return nil, err return nil, err
} }
var commits []Commit var commits []shared.Commit
var foundEnd bool var foundEnd bool
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.Debugf("Found commit with hash %s, will stop here", c.Hash.String()) log.Debugf("Found commit with hash %s, will stop here", c.Hash.String())
foundEnd = true foundEnd = true
return storer.ErrStop
} }
if !foundEnd { if !foundEnd {
log.Tracef("Found commit with hash %s", c.Hash.String()) log.Tracef("Found commit with hash %s", c.Hash.String())
commit := Commit{ commit := shared.Commit{
Message: c.Message, Message: c.Message,
Author: c.Committer.Name, Author: c.Committer.Name,
Hash: c.Hash.String(), Hash: c.Hash.String(),
@@ -159,5 +156,9 @@ func (g *GitUtil) GetCommits(lastTagHash string) ([]Commit, error) {
return nil return nil
}) })
return commits, err if err != nil {
return commits, errors.Wrap(err, "Could not read commits, check git clone depth in your ci")
}
return commits, nil
} }

77
internal/hooks/hooks.go Normal file
View File

@@ -0,0 +1,77 @@
package hooks
import (
"bufio"
"os/exec"
"runtime"
"strings"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus"
)
//Hooks struct
type Hooks struct {
version *shared.ReleaseVersion
config *config.ReleaseConfig
}
// New hooks struct
func New(config *config.ReleaseConfig, version *shared.ReleaseVersion) *Hooks {
return &Hooks{
config: config,
version: version,
}
}
// PreRelease runs before creating release
func (h *Hooks) PreRelease() error {
log.Infof("Run pre release hooks")
for _, cmd := range h.config.Hooks.PreRelease {
log.Debugf("Run %s", cmd)
err := h.runCommand(cmd)
if err != nil {
return err
}
}
return nil
}
// PostRelease runs after creating release
func (h *Hooks) PostRelease() error {
log.Infof("Run post release hooks")
for _, cmd := range h.config.Hooks.PostRelease {
err := h.runCommand(cmd)
if err != nil {
return err
}
}
return nil
}
func (h *Hooks) runCommand(command string) error {
cmdReplaced := strings.ReplaceAll(command, "$RELEASE_VERSION", h.version.Next.Version.String())
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd.exe", "/C", cmdReplaced)
} else {
cmd = exec.Command("sh", "-c", cmdReplaced)
}
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(cmdReader)
go func() {
for scanner.Scan() {
log.WithField("cmd", strings.Fields(cmdReplaced)[0]).Infof("%s\n", scanner.Text())
}
}()
return cmd.Run()
}

View File

@@ -0,0 +1,103 @@
package hooks_test
import (
"os"
"testing"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/hooks"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestPreReleaseHooks(t *testing.T) {
os.Setenv("GO_WANT_HELPER_PROCESS", "1")
hooks := hooks.New(&config.ReleaseConfig{
Hooks: config.Hooks{
PreRelease: []string{
"go test -test.run=TestHelperProcess -- " + "$RELEASE_VERSION",
},
},
},
&shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Version: createVersion("1.0.0"),
},
})
err := hooks.PreRelease()
assert.NoError(t, err)
os.Unsetenv("GO_WANT_HELPER_PROCESS")
}
func TestPreReleaseHooksError(t *testing.T) {
hooks := hooks.New(&config.ReleaseConfig{
Hooks: config.Hooks{
PreRelease: []string{
"exit 1",
},
},
},
&shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Version: createVersion("1.0.0"),
},
})
err := hooks.PreRelease()
assert.Error(t, err)
}
func TestPostReleaseHooks(t *testing.T) {
os.Setenv("GO_WANT_HELPER_PROCESS", "1")
hooks := hooks.New(&config.ReleaseConfig{
Hooks: config.Hooks{
PostRelease: []string{
"go test -test.run=TestHelperProcess -- " + "$RELEASE_VERSION",
},
},
},
&shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Version: createVersion("1.0.0"),
},
})
err := hooks.PostRelease()
assert.NoError(t, err)
os.Unsetenv("GO_WANT_HELPER_PROCESS")
}
func TestPostReleaseHooksError(t *testing.T) {
hooks := hooks.New(&config.ReleaseConfig{
Hooks: config.Hooks{
PostRelease: []string{
"exit 1",
},
},
},
&shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Version: createVersion("1.0.0"),
},
})
err := hooks.PostRelease()
assert.Error(t, err)
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
assert.Contains(t, os.Args, "1.0.0")
}
func createVersion(version string) *semver.Version {
ver, _ := semver.NewVersion(version)
return ver
}

View File

@@ -0,0 +1,101 @@
package git
import (
"fmt"
"time"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
"gopkg.in/src-d/go-git.v4"
gitConfig "gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
log "github.com/sirupsen/logrus"
)
// GITONLY identifer for git interface
const GITONLY = "git"
// Client type struct
type Client struct {
config *config.GitProvider
log *log.Entry
git *gitutil.GitUtil
}
// New initialize a new gitRelease
func New(config *config.GitProvider, git *gitutil.GitUtil, checkConfig bool) (*Client, error) {
logger := log.WithField("releaser", GITONLY)
if config.Email == "" && checkConfig {
return nil, fmt.Errorf("git email not set")
}
if config.Username == "" && checkConfig {
return nil, fmt.Errorf("git username not set")
}
if !config.SSH && config.Auth == "" && checkConfig {
return nil, fmt.Errorf("git auth not set")
}
if config.SSH {
return nil, fmt.Errorf("git ssh not supported yet")
}
return &Client{
config: config,
log: logger,
git: git,
}, nil
}
//GetCommitURL for git
func (g *Client) GetCommitURL() string {
return ""
}
//GetCompareURL for git
func (g *Client) GetCompareURL(oldVersion, newVersion string) string {
return ""
}
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := "v" + releaseVersion.Next.Version.String()
g.log.Infof("create release with version %s", tag)
head, err := g.git.Repository.Head()
if err != nil {
return err
}
_, err = g.git.Repository.CreateTag(tag, head.Hash(), &git.CreateTagOptions{Message: "Release " + tag, Tagger: &object.Signature{
Name: g.config.Username,
Email: g.config.Email,
When: time.Now(),
}})
if err != nil {
return err
}
g.log.Infof("Created release")
return g.git.Repository.Push(&git.PushOptions{
Auth: &http.BasicAuth{
Username: g.config.Username,
Password: g.config.Auth,
},
RefSpecs: []gitConfig.RefSpec{"refs/tags/*:refs/tags/*"},
})
}
// UploadAssets uploads specified assets
func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
return nil
}

View File

@@ -25,20 +25,31 @@ type Client struct {
context context.Context context context.Context
release *github.RepositoryRelease release *github.RepositoryRelease
baseURL string baseURL string
log *log.Entry
} }
// New initialize a new GitHubRelease // New initialize a new GitHubRelease
func New(c *config.GitHubProvider) (*Client, error) { func New(c *config.GitHubProvider, checkConfig bool) (*Client, error) {
var err error
if c.AccessToken, err = util.GetAccessToken(GITHUB); err != nil { token, err := util.GetAccessToken("GITHUB_TOKEN")
if err != nil && checkConfig {
return &Client{}, err return &Client{}, err
} }
c.AccessToken = token
ctx := context.Background() ctx := context.Background()
httpClient := util.CreateBearerHTTPClient(ctx, c.AccessToken) httpClient := util.CreateBearerHTTPClient(ctx, c.AccessToken)
var client *github.Client var client *github.Client
baseURL := "https://github.com" baseURL := "https://github.com"
if c.Repo == "" && checkConfig {
return nil, fmt.Errorf("github repro is not set")
}
if c.User == "" && checkConfig {
return nil, fmt.Errorf("github user is not set")
}
if c.CustomURL == "" { if c.CustomURL == "" {
client = github.NewClient(httpClient) client = github.NewClient(httpClient)
} else { } else {
@@ -52,7 +63,8 @@ func New(c *config.GitHubProvider) (*Client, error) {
client: client, client: client,
context: ctx, context: ctx,
baseURL: baseURL, baseURL: baseURL,
}, err log: log.WithField("releaser", GITHUB),
}, nil
} }
//GetCommitURL for github //GetCommitURL for github
@@ -65,52 +77,34 @@ 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) 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 // CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := releaseVersion.Next.Version.String() tag := "v" + releaseVersion.Next.Version.String()
log.Debugf("create release with version %s", tag) g.log.Debugf("create release with version %s", tag)
prerelease := releaseVersion.Next.Version.Prerelease() != "" prerelease := releaseVersion.Next.Version.Prerelease() != ""
release, resp, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{ release, _, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{
TagName: &tag, TagName: &tag,
TargetCommitish: &releaseVersion.Branch, TargetCommitish: &releaseVersion.Branch,
Name: &generatedChangelog.Title, Name: &generatedChangelog.Title,
Body: &generatedChangelog.Content, Body: &generatedChangelog.Content,
Draft: &releaseVersion.Draft,
Prerelease: &prerelease, Prerelease: &prerelease,
}) })
if err != nil { if err != nil {
if !strings.Contains(err.Error(), "already_exists") && resp.StatusCode >= http.StatusUnprocessableEntity { if strings.Contains(err.Error(), "already_exists") {
return fmt.Errorf("could not create release: %v", err) g.log.Infof("A release with tag %s already exits, will not perform a release or update", tag)
}
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 return nil
} }
return fmt.Errorf("could not create release: %s", err.Error())
}
g.release = release
g.log.Debugf("Release repsone: %+v", *release)
g.log.Infof("Crated release")
return nil
}
// UploadAssets uploads specified assets // UploadAssets uploads specified assets
func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
@@ -133,7 +127,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
} }
if resp.StatusCode >= http.StatusBadRequest { if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("releaser: github: Could not upload asset %s: %s", file.Name(), resp.Status) return fmt.Errorf("could not upload asset %s: %s", file.Name(), resp.Status)
} }
} }
} }

View File

@@ -0,0 +1,200 @@
package github_test
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
log "github.com/sirupsen/logrus"
"github.com/Masterminds/semver"
"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"
"github.com/stretchr/testify/assert"
)
type testHelperMethodStruct struct {
config config.GitHubProvider
valid bool
}
type testReleaseStruct struct {
config config.GitHubProvider
releaseVersion *shared.ReleaseVersion
generatedChangelog *shared.GeneratedChangelog
requestResponseBody string
requestResponseCode int
valid bool
}
var testNewClient = []testHelperMethodStruct{
testHelperMethodStruct{config: config.GitHubProvider{
Repo: "foo",
User: "bar",
},
valid: true,
},
testHelperMethodStruct{config: config.GitHubProvider{
Repo: "foo",
User: "bar",
CustomURL: "https://test.com",
},
valid: false,
},
}
var lastVersion, _ = semver.NewVersion("1.0.0")
var newVersion, _ = semver.NewVersion("2.0.0")
var testReleases = []testReleaseStruct{
testReleaseStruct{
config: config.GitHubProvider{
Repo: "foo",
User: "bar",
},
releaseVersion: &shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Version: lastVersion,
Commit: "foo",
},
Next: shared.ReleaseVersionEntry{
Version: newVersion,
Commit: "bar",
},
Branch: "master",
},
generatedChangelog: &shared.GeneratedChangelog{
Title: "title",
Content: "content",
},
requestResponseBody: "{ \"url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1\", \"html_url\": \"https://github.com/octocat/Hello-World/releases/v1.0.0\", \"assets_url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1/assets\", \"upload_url\": \"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}\", \"tarball_url\": \"https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0\", \"zipball_url\": \"https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0\", \"id\": 1, \"node_id\": \"MDc6UmVsZWFzZTE=\", \"tag_name\": \"v1.0.0\", \"target_commitish\": \"master\", \"name\": \"v1.0.0\", \"body\": \"Description of the release\", \"draft\": false, \"prerelease\": false, \"created_at\": \"2013-02-27T19:35:32Z\", \"published_at\": \"2013-02-27T19:35:32Z\", \"author\": { \"login\": \"octocat\", \"id\": 1, \"node_id\": \"MDQ6VXNlcjE=\", \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/octocat\", \"html_url\": \"https://github.com/octocat\", \"followers_url\": \"https://api.github.com/users/octocat/followers\", \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\", \"organizations_url\": \"https://api.github.com/users/octocat/orgs\", \"repos_url\": \"https://api.github.com/users/octocat/repos\", \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/octocat/received_events\", \"type\": \"User\", \"site_admin\": false }, \"assets\": [ ]}",
requestResponseCode: 200,
valid: true,
},
testReleaseStruct{
config: config.GitHubProvider{
Repo: "foo",
User: "bar",
},
releaseVersion: &shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Version: lastVersion,
Commit: "foo",
},
Next: shared.ReleaseVersionEntry{
Version: newVersion,
Commit: "bar",
},
Branch: "master",
},
generatedChangelog: &shared.GeneratedChangelog{
Title: "title",
Content: "content",
},
requestResponseCode: 400,
valid: false,
},
}
func initHTTPServer(respCode int, body string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
log.Infof("Got call from %s %s", req.Method, req.URL.String())
rw.WriteHeader(respCode)
rw.Header().Set("Content-Type", "application/json")
if _, err := rw.Write([]byte(body)); err != nil {
log.Info(err)
}
}))
}
func TestNew(t *testing.T) {
for _, testOject := range testNewClient {
if testOject.valid {
os.Setenv("GITHUB_TOKEN", "XXX")
}
_, err := github.New(&testOject.config, true)
assert.Equal(t, testOject.valid, err == nil)
os.Unsetenv("GITHUB_TOKEN")
}
}
func TestGetCommitURL(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testOject := range testNewClient {
client, _ := github.New(&testOject.config, false)
actualURL := client.GetCommitURL()
if testOject.config.CustomURL != "" {
expectedURL := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo)
assert.EqualValues(t, expectedURL, actualURL)
} else {
expectedURL := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", "https://github.com", testOject.config.User, testOject.config.Repo)
assert.EqualValues(t, expectedURL, actualURL)
}
}
os.Unsetenv("GITHUB_TOKEN")
}
func TestGetCompareURL(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testOject := range testNewClient {
client, _ := github.New(&testOject.config, false)
actualURL := client.GetCompareURL("1", "2")
if testOject.config.CustomURL != "" {
expectedURL := fmt.Sprintf("%s/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2")
assert.EqualValues(t, expectedURL, actualURL)
} else {
expectedURL := fmt.Sprintf("%s/%s/%s/compare/%s...%s", "https://github.com", testOject.config.User, testOject.config.Repo, "1", "2")
assert.EqualValues(t, expectedURL, actualURL)
}
}
os.Unsetenv("GITHUB_TOKEN")
}
func TestCreateRelease(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testObejct := range testReleases {
if testObejct.valid {
server := initHTTPServer(testObejct.requestResponseCode, testObejct.requestResponseBody)
testObejct.config.CustomURL = server.URL
client, _ := github.New(&testObejct.config, false)
err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
if err != nil {
t.Log(err)
}
assert.Equal(t, testObejct.valid, err == nil)
server.Close()
} else {
testObejct.config.CustomURL = "http://foo"
client, _ := github.New(&testObejct.config, false)
err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
if err != nil {
t.Log(err)
}
assert.Error(t, err)
}
}
os.Unsetenv("GITHUB_TOKEN")
}

View File

@@ -0,0 +1,215 @@
package gitlab
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
"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"
)
// GITLAB identifer for gitlab interface
const GITLAB = "gitlab"
// Client type struct
type Client struct {
config *config.GitLabProvider
client *http.Client
baseURL string
apiURL string
token string
Release string
log *log.Entry
}
// New initialize a new gitlabRelease
func New(config *config.GitLabProvider, checkConfig bool) (*Client, error) {
accessToken, err := util.GetAccessToken(fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(GITLAB)))
if err != nil && checkConfig {
return nil, err
}
tokenHeader := util.NewAddHeaderTransport(nil, "PRIVATE-TOKEN", accessToken)
acceptHeader := util.NewAddHeaderTransport(tokenHeader, "Accept", "application/json")
contentHeader := util.NewAddHeaderTransport(acceptHeader, "Content-Type", "application/json")
httpClient := &http.Client{
Transport: contentHeader,
Timeout: time.Second * 60,
}
logger := log.WithField("releaser", GITLAB)
logger.Debugf("validate gitlab provider config")
if config.Repo == "" && checkConfig {
return nil, fmt.Errorf("gitlab Repro is not set")
}
config.Repo = strings.Trim(config.Repo, "/")
if config.CustomURL == "" {
config.CustomURL = "https://gitlab.com"
}
config.CustomURL = strings.Trim(config.CustomURL, "/")
logger.Debugf("Use gitlab url %s", config.CustomURL)
return &Client{
token: accessToken,
config: config,
baseURL: config.CustomURL,
apiURL: config.CustomURL + "/api/v4",
client: httpClient,
log: logger,
}, nil
}
//GetCommitURL for gitlab
func (g *Client) GetCommitURL() string {
return fmt.Sprintf("%s/%s/commit/{{hash}}", g.baseURL, g.config.Repo)
}
//GetCompareURL for gitlab
func (g *Client) GetCompareURL(oldVersion, newVersion string) string {
return fmt.Sprintf("%s/%s/compare/%s...%s", g.baseURL, g.config.Repo, oldVersion, newVersion)
}
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := "v" + releaseVersion.Next.Version.String()
g.Release = tag
g.log.Infof("create release with version %s", tag)
url := fmt.Sprintf("%s/projects/%s/releases", g.apiURL, util.PathEscape(g.config.Repo))
g.log.Infof("Send release to %s", url)
bodyBytes, err := json.Marshal(Release{
TagName: tag,
Name: generatedChangelog.Title,
Description: generatedChangelog.Content,
Ref: releaseVersion.Branch,
})
if err != nil {
return err
}
g.log.Tracef("Send release config %s", bodyBytes)
req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("could not create request: %s", err.Error())
}
resp, err := util.Do(g.client, req, nil)
if err != nil {
return fmt.Errorf("could not create release: %s", err.Error())
}
if err := util.IsValidResult(resp); err != nil {
return err
}
g.log.Infof("Created release")
return nil
}
// UploadAssets uploads specified assets
func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
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
}
defer file.Close()
fileInfo, _ := file.Stat()
result, err := g.uploadFile(fileInfo.Name(), file)
if err != nil {
return fmt.Errorf("could not upload asset %s: %s", file.Name(), err.Error())
}
downloadURL := fmt.Sprintf("%s/%s%s", g.baseURL, g.config.Repo, result.URL)
g.log.Infof("Uploaded file %s to gitlab can be downloaded under %s", file.Name(), downloadURL)
path := fmt.Sprintf("%s/projects/%s/releases/%s/assets/links?name=%s&url=%s", g.apiURL, util.PathEscape(g.config.Repo), g.Release, util.PathEscape(fileInfo.Name()), downloadURL)
req, err := http.NewRequest("POST", path, nil)
if err != nil {
return err
}
g.log.Infof("Link file %s with release %s", file.Name(), g.Release)
resp, err := util.Do(g.client, req, nil)
if err != nil {
return err
}
if err = util.IsValidResult(resp); err != nil {
return err
}
g.log.Infof("Link file with release %s is done", g.Release)
}
return nil
}
func (g *Client) uploadFile(fileName string, file *os.File) (*ProjectFile, error) {
b := &bytes.Buffer{}
w := multipart.NewWriter(b)
fw, err := w.CreateFormFile("file", fileName)
if err != nil {
return nil, err
}
_, err = io.Copy(fw, file)
if err != nil {
return nil, err
}
w.Close()
url := fmt.Sprintf("%s/projects/%s/uploads", g.apiURL, util.PathEscape(g.config.Repo))
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return nil, err
}
req.Body = ioutil.NopCloser(b)
req.ContentLength = int64(b.Len())
req.Header.Set("Content-Type", w.FormDataContentType())
uf := &ProjectFile{}
resp, err := util.Do(g.client, req, uf)
if err != nil {
return nil, err
}
if err = util.IsValidResult(resp); err != nil {
return nil, err
}
return uf, nil
}

View File

@@ -0,0 +1,333 @@
package gitlab_test
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/Masterminds/semver"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/Nightapes/go-semantic-release/internal/releaser/gitlab"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
)
func TestGetCommitURL(t *testing.T) {
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
Repo: "test/test",
}, true)
assert.NoError(t, err)
assert.Equal(t, "https://localhost/test/test/commit/{{hash}}", client.GetCommitURL())
}
func TestGetCompareURL(t *testing.T) {
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
Repo: "test/test",
}, true)
assert.NoError(t, err)
assert.Equal(t, "https://localhost/test/test/compare/1.0.0...1.0.1", client.GetCompareURL("1.0.0", "1.0.1"))
}
func TestValidateConfig_EmptyRepro(t *testing.T) {
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
_, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
}, true)
assert.Error(t, err)
}
func TestValidateConfig_DefaultURL(t *testing.T) {
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
config := &config.GitLabProvider{
Repo: "localhost/test",
}
_, err := gitlab.New(config, true)
assert.NoError(t, err)
assert.Equal(t, "https://gitlab.com", config.CustomURL)
}
func TestValidateConfig_CustomURL(t *testing.T) {
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
config := &config.GitLabProvider{
Repo: "/localhost/test/",
CustomURL: "https://localhost/",
}
_, err := gitlab.New(config, true)
assert.NoError(t, err)
assert.Equal(t, "https://localhost", config.CustomURL)
assert.Equal(t, "localhost/test", config.Repo)
}
func TestCreateRelease(t *testing.T) {
lastVersion, _ := semver.NewVersion("1.0.0")
newVersion, _ := semver.NewVersion("2.0.0")
testReleases := []struct {
config config.GitLabProvider
releaseVersion *shared.ReleaseVersion
generatedChangelog *shared.GeneratedChangelog
responseBody string
responseCode int
requestBody string
valid bool
}{
{
config: config.GitLabProvider{
Repo: "foo/bar",
},
releaseVersion: &shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Version: lastVersion,
Commit: "foo",
},
Next: shared.ReleaseVersionEntry{
Version: newVersion,
Commit: "bar",
},
Branch: "master",
},
generatedChangelog: &shared.GeneratedChangelog{
Title: "title",
Content: "content",
},
responseBody: "{}",
responseCode: 200,
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: true,
},
{
config: config.GitLabProvider{
Repo: "foo/bar",
},
releaseVersion: &shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Version: lastVersion,
Commit: "foo",
},
Next: shared.ReleaseVersionEntry{
Version: newVersion,
Commit: "bar",
},
Branch: "master",
},
generatedChangelog: &shared.GeneratedChangelog{
Title: "title",
Content: "content",
},
responseBody: "{}",
responseCode: 500,
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: false,
},
{
config: config.GitLabProvider{
Repo: "foo/bar",
CustomURL: "broken",
},
releaseVersion: &shared.ReleaseVersion{
Last: shared.ReleaseVersionEntry{
Version: lastVersion,
Commit: "foo",
},
Next: shared.ReleaseVersionEntry{
Version: newVersion,
Commit: "bar",
},
Branch: "master",
},
generatedChangelog: &shared.GeneratedChangelog{
Title: "title",
Content: "content",
},
responseCode: 400,
responseBody: "{}",
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: false,
},
}
for _, testObject := range testReleases {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
log.Infof("Got call from %s %s", req.Method, req.URL.String())
assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken")
assert.Equal(t, req.Header.Get("Accept"), "application/json")
bodyBytes, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
assert.Equal(t, testObject.requestBody, string(bodyBytes))
rw.WriteHeader(testObject.responseCode)
rw.Header().Set("Content-Type", "application/json")
if _, err := rw.Write([]byte(testObject.responseBody)); err != nil {
log.Info(err)
}
}))
if testObject.config.CustomURL == "" {
testObject.config.CustomURL = testServer.URL
}
os.Setenv("GITLAB_ACCESS_TOKEN", "aToken")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := gitlab.New(&testObject.config, false)
assert.NoError(t, err)
err = client.CreateRelease(testObject.releaseVersion, testObject.generatedChangelog)
if err != nil {
t.Log(err)
}
assert.Equal(t, testObject.valid, err == nil)
testServer.Close()
}
}
func TestUploadAssets(t *testing.T) {
file, err := ioutil.TempFile("", "prefix")
if err != nil {
log.Fatal(err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("testFile")
assert.NoError(t, err)
testReleases := []struct {
config config.GitLabProvider
responseBody []string
responseCode []int
assets []config.Asset
requestBody []string
testDir string
url []string
method []string
valid bool
}{
{
config: config.GitLabProvider{
Repo: "foo/bar",
},
responseBody: []string{`{"alt" : "dk", "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png", "markdown" :"![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"}`, ""},
responseCode: []int{200, 200},
requestBody: []string{
filepath.Base(file.Name()), ""},
url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=<SERVER>/foo/bar/uploads/"},
method: []string{"POST", "POST"},
valid: true,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
Name: filepath.Base(file.Name()),
Compress: false,
},
},
},
{
config: config.GitLabProvider{
Repo: "foo/bar",
},
responseBody: []string{`{"alt" : "dk", "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png", "markdown" :"![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"}`, ""},
responseCode: []int{400, 200},
requestBody: []string{
filepath.Base(file.Name()), ""},
url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=<SERVER>/foo/bar/uploads/"},
method: []string{"POST", "POST"},
valid: false,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
Name: filepath.Base(file.Name()),
Compress: false,
},
},
},
{
config: config.GitLabProvider{
Repo: "foo/bar",
},
responseBody: []string{`broken`, ""},
responseCode: []int{200, 200},
requestBody: []string{
filepath.Base(file.Name()), ""},
url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=<SERVER>/foo/bar/uploads/"},
method: []string{"POST", "POST"},
valid: false,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
Name: filepath.Base(file.Name()),
Compress: false,
},
},
},
}
for _, testObject := range testReleases {
calls := 0
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
log.Infof("Got call from %s %s", req.Method, req.URL.String())
assert.Contains(t, req.URL.String(), strings.ReplaceAll(testObject.url[calls], "<SERVER>", testObject.config.CustomURL))
assert.Equal(t, req.Method, testObject.method[calls])
assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken")
assert.Equal(t, req.Header.Get("Accept"), "application/json")
bodyBytes, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
assert.Contains(t, string(bodyBytes), testObject.requestBody[calls])
rw.WriteHeader(testObject.responseCode[calls])
rw.Header().Set("Content-Type", "application/json")
if _, err := rw.Write([]byte(testObject.responseBody[calls])); err != nil {
log.Info(err)
}
calls++
}))
if testObject.config.CustomURL == "" {
testObject.config.CustomURL = testServer.URL
}
os.Setenv("GITLAB_ACCESS_TOKEN", "aToken")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := gitlab.New(&testObject.config, false)
assert.NoError(t, err)
client.Release = "1.0.0"
err = client.UploadAssets(testObject.testDir, testObject.assets)
if err != nil {
t.Log(err)
}
assert.Equal(t, testObject.valid, err == nil)
testServer.Close()
}
}

View File

@@ -0,0 +1,22 @@
package gitlab
// Release struct
type Release struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Ref string `json:"ref"`
Description string `json:"description,omitempty"`
}
// ReleaseLink struct
type ReleaseLink struct {
Name string `json:"name"`
URL string `json:"url"`
}
// ProjectFile struct
type ProjectFile struct {
Alt string `json:"alt"`
URL string `json:"url"`
Markdown string `json:"markdown"`
}

View File

@@ -3,7 +3,10 @@ package releaser
import ( import (
"fmt" "fmt"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/internal/releaser/git"
"github.com/Nightapes/go-semantic-release/internal/releaser/github" "github.com/Nightapes/go-semantic-release/internal/releaser/github"
"github.com/Nightapes/go-semantic-release/internal/releaser/gitlab"
"github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config" "github.com/Nightapes/go-semantic-release/pkg/config"
@@ -13,30 +16,37 @@ import (
// Releasers struct type // Releasers struct type
type Releasers struct { type Releasers struct {
config *config.ReleaseConfig config *config.ReleaseConfig
git *gitutil.GitUtil
} }
// Releaser interface for providers // Releaser interface for providers
type Releaser interface { type Releaser interface {
ValidateConfig() error
CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog) error CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog) error
UploadAssets(repoDir string, assets []config.Asset) error UploadAssets(repoDir string, assets []config.Asset) error
GetCommitURL() string GetCommitURL() string
GetCompareURL(oldVersion, newVersion string) string GetCompareURL(oldVersion, newVersion string) string
} }
// New initialize a Relerser // New initialize a releaser
func New(c *config.ReleaseConfig) *Releasers { func New(c *config.ReleaseConfig, git *gitutil.GitUtil) *Releasers {
return &Releasers{ return &Releasers{
config: c, config: c,
git: git,
} }
} }
//GetReleaser returns an initialized releaser //GetReleaser returns an initialized releaser
func (r *Releasers) GetReleaser() (Releaser, error) { func (r *Releasers) GetReleaser(checkConfig bool) (Releaser, error) {
switch r.config.Release { switch r.config.Release {
case github.GITHUB: case github.GITHUB:
log.Debugf("initialize new %s-provider", github.GITHUB) log.Debugf("initialize new %s-provider", github.GITHUB)
return github.New(&r.config.GitHubProvider) return github.New(&r.config.GitHubProvider, checkConfig)
case gitlab.GITLAB:
log.Debugf("initialize new %s-provider", gitlab.GITLAB)
return gitlab.New(&r.config.GitLabProvider, checkConfig)
case git.GITONLY:
log.Debugf("initialize new %s-provider", git.GITONLY)
return git.New(&r.config.GitProvider, r.git, checkConfig)
} }
return nil, fmt.Errorf("could not initialize a releaser from this type: %s", r.config.Release) return nil, fmt.Errorf("could not initialize a releaser from this type: %s", r.config.Release)
} }

View File

@@ -3,9 +3,11 @@ package util
import ( import (
"archive/zip" "archive/zip"
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"os" "os"
"strings" "strings"
@@ -25,12 +27,31 @@ func CreateBearerHTTPClient(ctx context.Context, token string) *http.Client {
return client return client
} }
// AddHeaderTransport struct
type AddHeaderTransport struct {
T http.RoundTripper
key string
value string
}
// RoundTrip add header
func (adt *AddHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add(adt.key, adt.value)
return adt.T.RoundTrip(req)
}
//NewAddHeaderTransport to add default header
func NewAddHeaderTransport(T http.RoundTripper, key, value string) *AddHeaderTransport {
if T == nil {
T = http.DefaultTransport
}
return &AddHeaderTransport{T, key, value}
}
// GetAccessToken lookup for the providers accesstoken // GetAccessToken lookup for the providers accesstoken
func GetAccessToken(providerName string) (string, error) { func GetAccessToken(envName string) (string, error) {
var token string var token string
var exists bool var exists bool
envName := fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(providerName))
log.Debugf("check if %s environment variable is set", envName) log.Debugf("check if %s environment variable is set", envName)
if token, exists = os.LookupEnv(envName); !exists { if token, exists = os.LookupEnv(envName); !exists {
@@ -45,7 +66,9 @@ func GetAccessToken(providerName string) (string, error) {
func PrepareAssets(repository string, assets []config.Asset) ([]*string, error) { func PrepareAssets(repository string, assets []config.Asset) ([]*string, error) {
filesToUpload := []*string{} filesToUpload := []*string{}
for _, asset := range assets { for _, asset := range assets {
if asset.Compress { 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("Asset %s will now be compressed", asset.Name)
log.Debugf("Repo url %s", repository) log.Debugf("Repo url %s", repository)
zipNameWithPath, err := zipFile(repository, asset.Name) zipNameWithPath, err := zipFile(repository, asset.Name)
@@ -65,6 +88,12 @@ func PrepareAssets(repository string, assets []config.Asset) ([]*string, error)
// ZipFile compress given file in zip format // ZipFile compress given file in zip format
func zipFile(repository string, file string) (string, error) { 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) zipFileName := fmt.Sprintf("%s/%s.zip", strings.TrimSuffix(repository, "/"), file)
zipFile, err := os.Create(zipFileName) zipFile, err := os.Create(zipFileName)
@@ -75,12 +104,6 @@ func zipFile(repository string, file string) (string, error) {
defer zipFile.Close() defer zipFile.Close()
fileToZip, err := os.Open(repository + "/" + file)
if err != nil {
return "", err
}
defer fileToZip.Close()
fileToZipInfo, err := fileToZip.Stat() fileToZipInfo, err := fileToZip.Stat()
if err != nil { if err != nil {
return "", err return "", err
@@ -107,3 +130,45 @@ func zipFile(repository string, file string) (string, error) {
return zipFileName, nil return zipFileName, nil
} }
//PathEscape to be url save
func PathEscape(s string) string {
return strings.Replace(url.PathEscape(s), ".", "%2E", -1)
}
// Do request for client
func Do(client *http.Client, req *http.Request, v interface{}) (*http.Response, error) {
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200, 201, 202, 204:
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
}
}
return resp, err
}
// IsValidResult validates response code
func IsValidResult(resp *http.Response) error {
switch resp.StatusCode {
case 200, 201, 202, 204:
return nil
default:
return fmt.Errorf("%s %s: %d", resp.Request.Method, resp.Request.URL, resp.StatusCode)
}
}
// ShouldRetry request
func ShouldRetry(resp *http.Response) bool {
return resp.StatusCode == http.StatusTooManyRequests
}

View File

@@ -3,9 +3,17 @@ package util_test
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"net/http/httptest"
"net/url"
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -35,9 +43,189 @@ func TestGetAccessToken(t *testing.T) {
fmt.Println(err.Error()) fmt.Println(err.Error())
} }
_, err := util.GetAccessToken(testObject.providerName) _, err := util.GetAccessToken(envName)
assert.Equal(t, testObject.valid, err == nil) assert.Equal(t, testObject.valid, err == nil)
os.Unsetenv(envName) 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())
}
}
}
}
func TestShouldRetry(t *testing.T) {
assert.True(t, util.ShouldRetry(&http.Response{StatusCode: 429}))
assert.False(t, util.ShouldRetry(&http.Response{StatusCode: 200}))
}
func TestIsValidResult(t *testing.T) {
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 200}))
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 201}))
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 202}))
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 204}))
u, err := url.Parse("https://localhost")
assert.NoError(t, err)
assert.Error(t, util.IsValidResult(&http.Response{StatusCode: 500, Request: &http.Request{
Method: "POST",
URL: u,
}}))
}
func TestPathEscape(t *testing.T) {
assert.Equal(t, "test%2Ftest", util.PathEscape("test/test"))
assert.Equal(t, "test", util.PathEscape("test"))
assert.Equal(t, "test%2Etest", util.PathEscape("test.test"))
}
type example struct {
Test string `json:"test"`
}
func TestDoAndRoundTrip(t *testing.T) {
tokenHeader := util.NewAddHeaderTransport(nil, "PRIVATE-TOKEN", "aToken")
acceptHeader := util.NewAddHeaderTransport(tokenHeader, "Accept", "application/json")
httpClient := &http.Client{
Transport: acceptHeader,
Timeout: time.Second * 60,
}
testsDoMethod := []struct {
statusCode int
body string
responseBody interface{}
responseBodyType interface{}
hasError bool
path string
}{
{
statusCode: 200,
body: `{"test" : "hallo"}`,
responseBody: &example{
Test: "hallo",
},
responseBodyType: &example{},
hasError: false,
path: "",
},
{
statusCode: 400,
body: `{"test" : "hallo"}`,
responseBody: &example{},
responseBodyType: &example{},
hasError: false,
path: "",
},
{
statusCode: 200,
body: `{"test" : "hallo"}`,
hasError: true,
responseBody: &example{},
responseBodyType: &example{},
path: "4/broken",
},
}
for _, testOject := range testsDoMethod {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
log.Infof("Got call from %s %s", req.Method, req.URL.String())
assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken")
assert.Equal(t, req.Header.Get("Accept"), "application/json")
rw.WriteHeader(testOject.statusCode)
rw.Header().Set("Content-Type", "application/json")
if _, err := rw.Write([]byte(testOject.body)); err != nil {
log.Info(err)
}
}))
defer testServer.Close()
req, err := http.NewRequest("POST", testServer.URL+testOject.path, nil)
assert.NoError(t, err)
resp, err := util.Do(httpClient, req, testOject.responseBodyType)
assert.Equal(t, testOject.hasError, err != nil)
if !testOject.hasError {
assert.Equal(t, testOject.statusCode, resp.StatusCode)
assert.Equal(t, testOject.responseBody, testOject.responseBodyType)
}
}
}

View File

@@ -6,16 +6,17 @@ import (
//ReleaseVersion struct //ReleaseVersion struct
type ReleaseVersion struct { type ReleaseVersion struct {
Last ReleaseVersionEntry Last ReleaseVersionEntry `yaml:"last"`
Next ReleaseVersionEntry Next ReleaseVersionEntry `yaml:"next"`
Branch string Branch string `yaml:"branch"`
Draft bool Commits map[Release][]AnalyzedCommit `yaml:"commits"`
} }
//ReleaseVersionEntry struct //ReleaseVersionEntry struct
type ReleaseVersionEntry struct { type ReleaseVersionEntry struct {
Commit string Commit string `yaml:"commit"`
Version *semver.Version VersionString string `yaml:"version"`
Version *semver.Version `yaml:"-"`
} }
//GeneratedChangelog struct //GeneratedChangelog struct
@@ -31,3 +32,27 @@ type ChangelogTemplateConfig struct {
Hash string Hash string
Version string Version string
} }
//AnalyzedCommit struct
type AnalyzedCommit struct {
Commit Commit `yaml:"commit"`
ParsedMessage string `yaml:"parsedMessage"`
Scope Scope `yaml:"scope"`
ParsedBreakingChangeMessage string `yaml:"parsedBreakingChangeMessage"`
Tag string `yaml:"tag"`
TagString string `yaml:"tagString"`
Print bool `yaml:"print"`
}
//Scope of the commit, like feat, fix,..
type Scope string
//Release types, like major
type Release string
// Commit struct
type Commit struct {
Message string `yaml:"message"`
Author string `yaml:"author"`
Hash string `yaml:"hash"`
}

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"io/ioutil" "io/ioutil"
"os"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -11,8 +12,22 @@ import (
// ChangelogConfig struct // ChangelogConfig struct
type ChangelogConfig struct { type ChangelogConfig struct {
PrintAll bool `yaml:"printAll,omitempty"` PrintAll bool `yaml:"printAll,omitempty"`
Template string `yaml:"template,omitempty"` TemplateTitle string `yaml:"title,omitempty"`
TemplatePath string `yaml:"templatePath,omitempty"` TemplatePath string `yaml:"templatePath,omitempty"`
Docker ChangelogDocker `yaml:"docker,omitempty"`
NPM ChangelogNPM `yaml:"npm,omitempty"`
}
//ChangelogDocker type struct
type ChangelogDocker struct {
Latest bool `yaml:"latest"`
Repository string `yaml:"repository"`
}
//ChangelogNPM type struct
type ChangelogNPM struct {
YARN bool `yaml:"latest"`
Repository string `yaml:"repository"`
} }
//Asset type struct //Asset type struct
@@ -29,6 +44,27 @@ type GitHubProvider struct {
AccessToken string AccessToken string
} }
// GitLabProvider struct
type GitLabProvider struct {
Repo string `yaml:"repo"`
CustomURL string `yaml:"customUrl,omitempty"`
AccessToken string
}
// GitProvider struct
type GitProvider struct {
Email string `yaml:"email"`
Username string `yaml:"user"`
Auth string `yaml:"auth"`
SSH bool `yaml:"ssh"`
}
// Hooks struct
type Hooks struct {
PreRelease []string `yaml:"preRelease"`
PostRelease []string `yaml:"postRelease"`
}
// ReleaseConfig struct // ReleaseConfig struct
type ReleaseConfig struct { type ReleaseConfig struct {
CommitFormat string `yaml:"commitFormat"` CommitFormat string `yaml:"commitFormat"`
@@ -36,9 +72,12 @@ type ReleaseConfig struct {
Changelog ChangelogConfig `yaml:"changelog,omitempty"` Changelog ChangelogConfig `yaml:"changelog,omitempty"`
Release string `yaml:"release,omitempty"` Release string `yaml:"release,omitempty"`
GitHubProvider GitHubProvider `yaml:"github,omitempty"` GitHubProvider GitHubProvider `yaml:"github,omitempty"`
GitLabProvider GitLabProvider `yaml:"gitlab,omitempty"`
GitProvider GitProvider `yaml:"git,omitempty"`
Assets []Asset `yaml:"assets"` Assets []Asset `yaml:"assets"`
Hooks Hooks `yaml:"hooks"`
ReleaseTitle string `yaml:"title"` ReleaseTitle string `yaml:"title"`
IsPreRelease, IsDraft bool IsPreRelease bool
} }
// Read ReleaseConfig // Read ReleaseConfig
@@ -49,13 +88,31 @@ func Read(configPath string) (*ReleaseConfig, error) {
return &ReleaseConfig{}, err return &ReleaseConfig{}, err
} }
var releaseConfig ReleaseConfig log.Tracef("Found config %s", string(content))
err = yaml.Unmarshal(content, &releaseConfig) releaseConfig := &ReleaseConfig{}
err = yaml.Unmarshal(content, releaseConfig)
if err != nil { if err != nil {
return &ReleaseConfig{}, err return &ReleaseConfig{}, err
} }
log.Debugf("Found config %+v", releaseConfig) org := *releaseConfig
return &releaseConfig, nil releaseConfig.Hooks = Hooks{}
configWithoutHooks, err := yaml.Marshal(releaseConfig)
if err != nil {
return &ReleaseConfig{}, err
}
configWithoutHooks = []byte(os.ExpandEnv(string(configWithoutHooks)))
releaseConfigWithExpanedEnvs := &ReleaseConfig{}
err = yaml.Unmarshal(configWithoutHooks, releaseConfigWithExpanedEnvs)
if err != nil {
return &ReleaseConfig{}, err
}
releaseConfigWithExpanedEnvs.Hooks = org.Hooks
log.Tracef("Found config %+v", releaseConfigWithExpanedEnvs)
return releaseConfigWithExpanedEnvs, nil
} }

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

@@ -0,0 +1,105 @@
package config_test
import (
"testing"
"io/ioutil"
"os"
"path"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
)
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)
os.Setenv("TEST_CONFIG", "value")
defer os.Unsetenv("TEST_CONFIG")
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: '${TEST_CONFIG}'
release: 'github'
hooks:
preRelease:
- "Test hook ${RELEASE_VERSION}"
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,
TemplateTitle: "",
TemplatePath: "value"},
Release: "github",
GitHubProvider: config.GitHubProvider{
Repo: "go-semantic-release",
User: "nightapes",
CustomURL: "",
AccessToken: ""},
Hooks: config.Hooks{
PreRelease: []string{
"Test hook ${RELEASE_VERSION}",
},
},
Assets: []config.Asset{
config.Asset{
Name: "./build/go-semantic-release",
Compress: false}},
ReleaseTitle: "go-semantic-release release",
IsPreRelease: false,
}, result)
}

View File

@@ -1,87 +0,0 @@
package semanticrelease
import (
"strconv"
"strings"
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/cache"
"github.com/Nightapes/go-semantic-release/internal/shared"
log "github.com/sirupsen/logrus"
)
func (s *SemanticRelease) incPrerelease(preReleaseType string, version semver.Version) semver.Version {
defaultPrerelease := preReleaseType + ".0"
if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) {
version, _ = version.SetPrerelease(defaultPrerelease)
} else {
parts := strings.Split(version.Prerelease(), ".")
if len(parts) == 2 {
i, err := strconv.Atoi(parts[1])
if err != nil {
version, _ = version.SetPrerelease(defaultPrerelease)
log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String())
} else {
version, _ = version.SetPrerelease(preReleaseType + "." + strconv.Itoa((i + 1)))
}
} else {
version, _ = version.SetPrerelease(defaultPrerelease)
log.Warnf("Could not parse release tag %s, use version %s", version.Prerelease(), version.String())
}
}
return version
}
func (s *SemanticRelease) saveToCache(releaseVersion shared.ReleaseVersion) error {
toCache := cache.ReleaseVersion{
Next: cache.ReleaseVersionEntry{
Commit: releaseVersion.Next.Commit,
Version: releaseVersion.Next.Version.String(),
},
Last: cache.ReleaseVersionEntry{
Commit: releaseVersion.Last.Commit,
Version: releaseVersion.Last.Version.String(),
},
Branch: releaseVersion.Branch,
}
log.Debugf("Save %s with hash %s to cache", releaseVersion.Next.Version.String(), releaseVersion.Next.Commit)
return cache.Write(s.repository, toCache)
}
func (s *SemanticRelease) readFromCache(currentHash string) (*shared.ReleaseVersion, error) {
content, err := cache.Read(s.repository)
if err == nil && content.Next.Commit == currentHash {
nextVersion, err := semver.NewVersion(content.Next.Version)
if err != nil {
return nil, err
}
lastVersion, err := semver.NewVersion(content.Last.Version)
if err != nil {
return nil, err
}
releaseVersion := &shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Commit: content.Next.Commit,
Version: nextVersion,
},
Last: shared.ReleaseVersionEntry{
Commit: content.Last.Commit,
Version: lastVersion,
},
Branch: content.Branch,
}
log.Infof("Found cache, will return cached version %s", content.Next.Version)
return releaseVersion, nil
}
log.Debugf("Mismatch git and version file %s - %s", content.Next.Commit, currentHash)
return nil, nil
}

View File

@@ -1,16 +1,17 @@
package semanticrelease package semanticrelease
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"strings"
"time" "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/changelog"
"github.com/Nightapes/go-semantic-release/internal/ci" "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/hooks"
"github.com/Nightapes/go-semantic-release/internal/releaser" "github.com/Nightapes/go-semantic-release/internal/releaser"
"github.com/Nightapes/go-semantic-release/internal/releaser/util" "github.com/Nightapes/go-semantic-release/internal/releaser/util"
"github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/internal/shared"
@@ -23,12 +24,14 @@ type SemanticRelease struct {
config *config.ReleaseConfig config *config.ReleaseConfig
gitutil *gitutil.GitUtil gitutil *gitutil.GitUtil
analyzer *analyzer.Analyzer analyzer *analyzer.Analyzer
calculator *calculator.Calculator
releaser releaser.Releaser releaser releaser.Releaser
repository string repository string
checkConfig bool
} }
// New SemanticRelease struct // New SemanticRelease struct
func New(c *config.ReleaseConfig, repository string) (*SemanticRelease, error) { func New(c *config.ReleaseConfig, repository string, checkConfig bool) (*SemanticRelease, error) {
util, err := gitutil.New(repository) util, err := gitutil.New(repository)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -39,7 +42,11 @@ func New(c *config.ReleaseConfig, repository string) (*SemanticRelease, error) {
return nil, err return nil, err
} }
releaser, err := releaser.New(c).GetReleaser() if !checkConfig {
log.Infof("Ignore config checks!. No guarantee to run without issues")
}
releaser, err := releaser.New(c, util).GetReleaser(checkConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -50,32 +57,26 @@ func New(c *config.ReleaseConfig, repository string) (*SemanticRelease, error) {
releaser: releaser, releaser: releaser,
analyzer: analyzer, analyzer: analyzer,
repository: repository, repository: repository,
checkConfig: checkConfig,
calculator: calculator.New(),
}, nil }, nil
} }
//GetCIProvider result with ci config
func (s *SemanticRelease) GetCIProvider() (*ci.ProviderConfig, error) {
return ci.GetCIProvider(s.gitutil, s.checkConfig, ci.ReadAllEnvs())
}
// GetNextVersion from .version or calculate new from commits // GetNextVersion from .version or calculate new from commits
func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, error) { func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool) (*shared.ReleaseVersion, error) {
provider, err := ci.GetCIProvider(s.gitutil, ci.ReadAllEnvs())
if err != nil {
fakeVersion, _ := semver.NewVersion("0.0.0-fake.0")
log.Warnf("Will not calculate version, set fake version. Could not find CI Provider, if running locally, set env CI=true")
return &shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Commit: "",
Version: fakeVersion,
},
}, nil
}
log.Debugf("Ignore .version file if exits, %t", force) log.Debugf("Ignore .version file if exits, %t", force)
if !force { if !force {
releaseVersion, err := s.readFromCache(provider.Commit) releaseVersion, err := cache.Read(s.repository)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if releaseVersion != nil { if releaseVersion.Next.Commit == provider.Commit && releaseVersion != nil {
return releaseVersion, nil return releaseVersion, nil
} }
} }
@@ -85,16 +86,12 @@ func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, er
return nil, err return nil, err
} }
var newVersion semver.Version
firstRelease := false firstRelease := false
if lastVersion == nil { if lastVersion == nil {
defaultVersion, _ := semver.NewVersion("1.0.0") lastVersion, _ = semver.NewVersion("1.0.0")
newVersion = *defaultVersion log.Infof("This is the first release, will set version to %s", lastVersion.String())
lastVersion = defaultVersion
firstRelease = true firstRelease = true
} else {
newVersion = *lastVersion
} }
commits, err := s.gitutil.GetCommits(lastVersionHash) commits, err := s.gitutil.GetCommits(lastVersionHash)
@@ -104,33 +101,22 @@ func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, er
log.Debugf("Found %d commits till last release", len(commits)) log.Debugf("Found %d commits till last release", len(commits))
a, err := analyzer.New(s.config.CommitFormat, s.config.Changelog) analyzedCommits := s.analyzer.Analyze(commits)
if err != nil {
return nil, err var newVersion semver.Version
} foundBranchConfig := false
result := a.Analyze(commits)
isDraft := false
for branch, releaseType := range s.config.Branch { for branch, releaseType := range s.config.Branch {
if provider.Branch == branch || strings.HasPrefix(provider.Branch, branch) { if provider.Branch == branch {
log.Debugf("Found branch config for branch %s with release type %s", provider.Branch, releaseType) log.Debugf("Found branch config for branch %s with release type %s", provider.Branch, releaseType)
switch releaseType { newVersion = s.calculator.CalculateNewVersion(analyzedCommits, lastVersion, releaseType, firstRelease)
case "beta", "alpha": foundBranchConfig = true
isDraft = true break
newVersion = s.incPrerelease(releaseType, newVersion)
case "rc":
newVersion = s.incPrerelease(releaseType, newVersion)
case "release":
if !firstRelease {
if len(result["major"]) > 0 {
newVersion = newVersion.IncMajor()
} else if len(result["minor"]) > 0 {
newVersion = newVersion.IncMinor()
} else if len(result["patch"]) > 0 {
newVersion = newVersion.IncPatch()
}
}
} }
} }
if !foundBranchConfig {
log.Warnf("No branch config found for branch %s, will return last known version", provider.Branch)
newVersion = *lastVersion
} }
releaseVersion := shared.ReleaseVersion{ releaseVersion := shared.ReleaseVersion{
@@ -143,11 +129,15 @@ func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, er
Version: lastVersion, Version: lastVersion,
}, },
Branch: provider.Branch, Branch: provider.Branch,
Draft: isDraft, Commits: analyzedCommits,
}
if firstRelease {
releaseVersion.Last.Version, _ = semver.NewVersion("0.0.0")
} }
log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String()) log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String())
err = s.saveToCache(releaseVersion) err = cache.Write(s.repository, releaseVersion)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -155,19 +145,13 @@ func (s *SemanticRelease) GetNextVersion(force bool) (*shared.ReleaseVersion, er
} }
//SetVersion for git repository //SetVersion for git repository
func (s *SemanticRelease) SetVersion(version string) error { func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string) error {
newVersion, err := semver.NewVersion(version) newVersion, err := semver.NewVersion(version)
if err != nil { if err != nil {
return err return err
} }
provider, err := ci.GetCIProvider(s.gitutil, ci.ReadAllEnvs())
if err != nil {
return fmt.Errorf("will not set version. Could not find CI Provider, if running locally, set env CI=true")
}
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion() lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion()
if err != nil { if err != nil {
return err return err
@@ -176,7 +160,7 @@ func (s *SemanticRelease) SetVersion(version string) error {
lastVersion, _ = semver.NewVersion("1.0.0") lastVersion, _ = semver.NewVersion("1.0.0")
} }
return s.saveToCache(shared.ReleaseVersion{ return cache.Write(s.repository, shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{ Next: shared.ReleaseVersionEntry{
Commit: provider.Commit, Commit: provider.Commit,
Version: newVersion, Version: newVersion,
@@ -191,22 +175,13 @@ func (s *SemanticRelease) SetVersion(version string) error {
// GetChangelog from last version till now // GetChangelog from last version till now
func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) { func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) {
commits, err := s.gitutil.GetCommits(releaseVersion.Last.Commit)
if err != nil {
return nil, err
}
result := s.analyzer.Analyze(commits)
log.Debugf("Found %d commits till last release", len(commits))
c := changelog.New(s.config, s.analyzer.GetRules(), time.Now()) c := changelog.New(s.config, s.analyzer.GetRules(), time.Now())
return c.GenerateChanglog(shared.ChangelogTemplateConfig{ return c.GenerateChanglog(shared.ChangelogTemplateConfig{
Version: releaseVersion.Next.Version.String(), Version: releaseVersion.Next.Version.String(),
Hash: releaseVersion.Last.Commit, Hash: releaseVersion.Last.Commit,
CommitURL: s.releaser.GetCommitURL(), CommitURL: s.releaser.GetCommitURL(),
CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()), CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()),
}, result) }, releaseVersion.Commits)
} }
@@ -215,53 +190,55 @@ func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error {
return ioutil.WriteFile(file, []byte(changelogContent), 0644) return ioutil.WriteFile(file, []byte(changelogContent), 0644)
} }
// Release pusblish release to provider // Release publish release to provider
func (s *SemanticRelease) Release(force bool) error { func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error {
provider, err := ci.GetCIProvider(s.gitutil, ci.ReadAllEnvs())
if err != nil {
log.Debugf("Will not perform a new release. Could not find CI Provider")
return nil
}
if provider.IsPR { if provider.IsPR {
log.Debugf("Will not perform a new release. This is a pull request") log.Infof("Will not perform a new release. This is a pull request")
return nil return nil
} }
if _, ok := s.config.Branch[provider.Branch]; !ok { 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) log.Infof("Will not perform a new release. Current %s branch is not configured in release config", provider.Branch)
return nil return nil
} }
releaseVersion, err := s.GetNextVersion(force) releaseVersion, err := s.GetNextVersion(provider, force)
if err != nil { if err != nil {
log.Debugf("Could not get next version") log.Debugf("Could not get next version")
return err return err
} }
generatedChanglog, err := s.GetChangelog(releaseVersion) if releaseVersion.Next.Version.Equal(releaseVersion.Last.Version) {
log.Infof("No new version, no release needed %s <> %s", releaseVersion.Next.Version.String(), releaseVersion.Last.Version.String())
return nil
}
hook := hooks.New(s.config, releaseVersion)
generatedChangelog, err := s.GetChangelog(releaseVersion)
if err != nil { if err != nil {
log.Debugf("Could not get changelog") log.Debugf("Could not get changelog")
return err return err
} }
releaser, err := releaser.New(s.config).GetReleaser() err = hook.PreRelease()
if err != nil { if err != nil {
log.Debugf("Error during pre release hook")
return err return err
} }
err = releaser.ValidateConfig() if err = s.releaser.CreateRelease(releaseVersion, generatedChangelog); err != nil {
return err
}
if err = s.releaser.UploadAssets(s.repository, s.config.Assets); err != nil {
return err
}
err = hook.PostRelease()
if err != nil { if err != nil {
return err log.Debugf("Error during post release hook")
}
if err = releaser.CreateRelease(releaseVersion, generatedChanglog); err != nil {
return err
}
if err = releaser.UploadAssets(s.repository, s.config.Assets); err != nil {
return err return err
} }