Compare commits

...

22 Commits

Author SHA1 Message Date
Sebastian Beisch
5225b12c00 style(internal) clean up code 2021-04-28 08:43:10 +02:00
Sebastian Beisch
2cd24777b3 fix(internal): improve git log for commits on current branch since last release
At the moment when PRs where done in parallel it could happen that some commits where ignored in the release. This should be fixed now
2021-04-28 08:43:10 +02:00
Naven Gogineni
cb3084d0b7 docs(github): fix typo in comment 2021-03-10 09:09:07 +01:00
Naveen Gogineni
795f5d54ef fix(github): fix baseURL for enterprise and test failures 2021-03-10 09:09:07 +01:00
Naveen Gogineni
382cb54bcb Add correct URLs for base and upload URLs for enterprise 2021-03-08 19:29:13 +01:00
Sebastian Beisch
3bc68d9794 feat(changelog): add npm helper text to changelog 2021-02-25 07:19:04 +01:00
Sebastian Beisch
c7d6c7cc7b feat(integrations): add first simple npm integration
Integrations are simple helpers to make integration with existing tools easier.
At basic npm support, the integration will set the version before release to the `package.json`

```yml
integrations:
  npm:
    enabled: true
```
2021-02-25 07:19:04 +01:00
maulik13
47a54436f5 feat(changelog): add a function in the funcMap to return commit URL 2021-02-23 14:25:09 +01:00
maulik13
deed3a630e docs(README): update available fields/objects for a changelog template 2021-02-23 14:25:09 +01:00
maulik13
df058a927f refactor(changelog): remove unused Version and Now fields, fixed spelling 2021-02-23 14:25:09 +01:00
maulik13
5a58d039fb refactor(angular): update default separator variable in angular 2021-02-23 14:25:09 +01:00
maulik13
08ab3af547 fix(analyzer): remove extra quote in structtag 2021-02-23 14:25:09 +01:00
maulik13
7208daed1f feat(angular): update angular to include new structured fields 2021-02-23 14:25:09 +01:00
maulik13
a20992af14 feat(conventional): parse body and footers according to the rules
Previous assumption about multiple labeled body blocks and footers is
not correct. There is only one body text block with multi-line support.
A footer always starts with a token with a separator.
- A body ends when a footer is found or text ends.
- A footer ends when another footer is found or text ends.
2021-02-23 14:25:09 +01:00
maulik13
dc4d1c581a feat(analyzer): update AnalyzedCommit to add flexibility in parsing a message
This provides flexibility of parsing and rendering structured messages
with more detail in the changelog and helps extract metadata from the 
message. The new structure can be used to split a message in multiple 
blocks (e.g. footer)
2021-02-23 14:25:09 +01:00
Sebastian
81bdb68ee4 Merge pull request #58 from maulik13/changelog-full-template
Changelog full template
2021-02-12 17:23:32 +01:00
maulik13
c485c3ee85 docs(changelog): update changelog template example 2021-02-12 13:55:08 +01:00
Sebastian
86c9512479 Update main.yml 2021-02-11 19:36:20 +01:00
Sebastian
4574d00c28 chore(ci): add pull request 2021-02-11 19:36:20 +01:00
Sebastian
0c4310d60b chore(ci): check if pr is fork 2021-02-11 19:36:20 +01:00
maulik13
3a37a5e1db feat(changelog): add string functions for changelog template 2021-02-08 11:30:06 +01:00
maulik13
9594f39caa feat(changelog): allow using of TemplatePath file for full changelog text 2021-02-08 11:15:27 +01:00
27 changed files with 1083 additions and 159 deletions

View File

@@ -1,14 +1,14 @@
name: Go name: Go
on: on:
pull_request:
push: push:
branches: branches:
- master - master
pull_request:
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
go: ["1.13", "1.14", "1.15"] go: ["1.13", "1.14", "1.15", "1.16"]
name: Build with go version ${{ matrix.go }} name: Build with go version ${{ matrix.go }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -21,11 +21,10 @@ jobs:
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Lint - name: golangci-lint
run: | uses: golangci/golangci-lint-action@v2
export PATH=$PATH:$(go env GOPATH)/bin with:
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.35.2 version: v1.29
golangci-lint run ./...
- name: Run tests - name: Run tests
run: go test ./... run: go test ./...
@@ -40,8 +39,12 @@ jobs:
GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -o build/go-semantic-release.windows_i386.exe -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.windows_i386.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.windows_x86_64.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.windows_x86_64.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.darwin_x86_64 -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.darwin_x86_64 -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
- name: Build Docker image - name: Build Docker image PR
if: matrix.go == '1.15' if: github.ref != 'refs/heads/master'
run: |
docker build -t nightapes/go-semantic-release:development-${{matrix.go}} .
- name: Build Docker image master
if: github.ref == 'refs/heads/master'
run: | run: |
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }} docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
docker login -u nightapes -p ${{ secrets.GITHUB_TOKEN }} docker.pkg.github.com docker login -u nightapes -p ${{ secrets.GITHUB_TOKEN }} docker.pkg.github.com
@@ -66,6 +69,7 @@ jobs:
name: build name: build
path: build path: build
- name: Release - name: Release
if: github.ref == 'refs/heads/master'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
@@ -73,3 +77,10 @@ jobs:
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }} docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
docker login -u nightapes -p $GITHUB_TOKEN docker.pkg.github.com docker login -u nightapes -p $GITHUB_TOKEN docker.pkg.github.com
./build/go-semantic-release-temp release --loglevel trace ./build/go-semantic-release-temp release --loglevel trace
- name: Release PR
if: github.ref != 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
chmod -R +x build
./build/go-semantic-release-temp release --loglevel trace

14
Makefile Normal file
View File

@@ -0,0 +1,14 @@
all: build
.PHONY: build
build:
go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/
lint:
golangci-lint run --print-issued-lines=false --fix ./...
test:
go test --coverprofile coverage.out -v -race -parallel 20 ./...

View File

@@ -56,6 +56,9 @@ hooks:
- name: echo $RELEASE_VERSION - name: echo $RELEASE_VERSION
postRelease: postRelease:
- name: echo $RELEASE_VERSION - name: echo $RELEASE_VERSION
integrations:
npm:
enabled: true
``` ```
#### CommitFormat #### CommitFormat
@@ -157,17 +160,81 @@ assets:
#### Hooks #### Hooks
Hooks will run when calling `release`. Hooks run only if a release will be triggered. Hooks will run when calling `release`. Hooks run only if a release will be triggered.
You can define hooks which run before or after the release. The shell commands will run in order, you can access the current release version via
an environment variable `RELEASE_VERSION`
```yml
hooks:
preRelease:
- name: echo $RELEASE_VERSION
postRelease:
- name: echo $RELEASE_VERSION
```
#### Integrations
Integrations are simple helpers to make integration with existing tools easier.
At the moment npm is supported, the integration will set the version before release to the `package.json`
```yml
integrations:
npm:
enabled: true
```
#### Changelog #### Changelog
Following variables can be used for templates: Following variables and objects can be used for templates:
* `Commits` string
* `Version` string __Top level__
* `Now` time.Time
* `Backtick` string | Field | Type | Description |
* `HasDocker` bool | -------- | ------ | ----- |
* `HasDockerLatest` bool | `Commits` | string | Fully rendered commit messages. This is left for backward compatibility. |
* `DockerRepository` string | `CommitsContent` | commitsContent | Raw parsed commit data. Use this if you want to customize the output. |
| `Version` | string | Next release version |
| `Now` | time.Time | Current time of generating changelog |
| `Backtick` | string | Backtick character |
| `HasDocker` | bool | If a docker repository is set in the config. |
| `HasDockerLatest` | bool | If `latest` image was uploaded |
| `DockerRepository` | string | Docker repository |
__commitsContent__
| Field | Type | Description |
| -------- | ------ | ----- |
| `Commits` | map[string][]AnalyzedCommit | Commits grouped by commit type |
| `BreakingChanges` | []AnalyzedCommit | Analyzed commit structure |
| `Order` | []string | Ordered list of types |
| `HasURL` | bool | If a URL is available for commits |
| `URL` | string | URL for to the commit with {{hash}} suffix |
__AnalyzedCommit__
| Field | Type | Description |
| -------- | ------ | ----- |
| `Commit` | Commit | Original GIT commit |
| `Tag` | string | Type of commit (e.g. feat, fix, ...) |
| `TagString` | string | Full name of the type |
| `Scope` | bool | Scope value from the commit |
| `Subject` | string | URL for to the commit with {{hash}} suffix |
| `MessageBlocks` | map[string][]MessageBlock | Different sections of a message (e.g. body, footer etc.) |
| `IsBreaking` | bool | If this commit contains a breaking change |
| `Print` | bool | Should this commit be included in Changelog output |
__Commit__
| Field | Type | Description |
| -------- | ------ | ----- |
| `Message` | string | Original git commit message |
| `Author` | string | Name of the author |
| `Hash` | string | Commit hash value "|
__MessageBlock__
| Field | Type | Description |
| -------- | ------ | ----- |
| `Label` | string | Label for a block (optional). This will usually be a token used in a footer |
| `Content` | string | The parsed content of a block |
```yml ```yml
changelog: changelog:
@@ -187,6 +254,18 @@ changelog:
repository: ## Your docker repository, which is used for docker run repository: ## Your docker repository, which is used for docker run
``` ```
##### NPM
You can print a help text for a npm package
```yml
changelog:
npm:
name: ## Name of the npm package
repository: ## Your docker repository, which is used for docker run
```
### Version ### Version
`go-semantic-release` has two modes for calculating the version: automatic or manual. `go-semantic-release` has two modes for calculating the version: automatic or manual.

View File

@@ -62,10 +62,6 @@ var changelogCmd = &cobra.Command{
return err return err
} }
if err = s.WriteChangeLog(generatedChangelog.Content, file); err != nil { return s.WriteChangeLog(generatedChangelog.Content, file)
log.Fatal(err)
}
return nil
}, },
} }

View File

@@ -0,0 +1,62 @@
package commands
import (
"github.com/Nightapes/go-semantic-release/internal/integrations"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
integrationsCmd.Flags().Bool("checks", false, "Check for missing values and envs")
integrationsCmd.Flags().StringP("out", "o", "CHANGELOG.md", "Name of the file")
rootCmd.AddCommand(integrationsCmd)
}
var integrationsCmd = &cobra.Command{
Use: "integrations",
Short: "Call integrations from config file manual",
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
}
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
releaseConfig := readConfig(config)
s, err := semanticrelease.New(releaseConfig, repository, configChecks)
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
}
log.Debugf("Found %d commits till last release", len(releaseVersion.Commits))
i := integrations.New(&releaseConfig.Integrations, releaseVersion)
return i.Run()
},
}

View File

@@ -1,5 +1,26 @@
{{ define "commitList" }}
{{ range $index,$commit := .BreakingChanges -}}
{{ if eq $index 0 -}}
## BREAKING CHANGES
{{ end -}}
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}**{{ end }} {{$commit.ParsedBreakingChangeMessage}}
introduced by commit:
{{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
{{ end -}}
{{ range $key := .Order -}}
{{ $commits := index $.Commits $key -}}
{{ if $commits -}}
### {{ $key }}
{{ range $index,$commit := $commits -}}
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{end}}{{$commit.ParsedMessage}}{{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
# My custom release template v{{$.Version}} ({{.Now.Format "2006-01-02"}}) # My custom release template v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ .Commits -}} {{ template "commitList" .CommitsContent -}}
{{ if .HasDocker}} {{ if .HasDocker}}
## Docker image ## Docker image

15
go.mod
View File

@@ -11,15 +11,18 @@ require (
github.com/google/go-github/v25 v25.1.3 github.com/google/go-github/v25 v25.1.3
github.com/imdario/mergo v0.3.11 // indirect github.com/imdario/mergo v0.3.11 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/magefile/mage v1.11.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.8.0
github.com/spf13/cobra v1.1.1 github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tidwall/pretty v1.1.0 // indirect
github.com/tidwall/sjson v1.1.5
github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 // indirect
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect golang.org/x/sys v0.0.0-20210223212115-eede4237b368 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

39
go.sum
View File

@@ -218,6 +218,10 @@ 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -267,16 +271,16 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
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/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -291,6 +295,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
@@ -318,8 +332,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -380,16 +394,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4=
golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -432,8 +446,8 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210223212115-eede4237b368 h1:fDE3p0qf2V1co1vfj3/o87Ps8Hq6QTGNxJ5Xe7xSp80=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210223212115-eede4237b368/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -590,7 +604,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@@ -2,17 +2,26 @@
package analyzer package analyzer
import ( import (
"bufio"
"fmt" "fmt"
"regexp"
"strings"
"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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const breakingChangeKeywords = "BREAKING CHANGE"
const defaultBreakingChangePrefix = breakingChangeKeywords + ":"
const footerTokenRegex = "^(?P<token>[^\\s][\\w\\- ]+[^\\s])<SEP>.*"
var defaultTokenSeparators = [2]string{ ": ", " #"}
// Analyzer struct // Analyzer struct
type Analyzer struct { type Analyzer struct {
analyzeCommits analyzeCommits analyzeCommits analyzeCommits
Config config.ChangelogConfig ChangelogConfig config.ChangelogConfig
AnalyzerConfig config.AnalyzerConfig
} }
// Rule for commits // Rule for commits
@@ -24,14 +33,15 @@ type Rule struct {
} }
type analyzeCommits interface { type analyzeCommits interface {
analyze(commit shared.Commit, tag Rule) (*shared.AnalyzedCommit, bool) analyze(commit shared.Commit, tag Rule) *shared.AnalyzedCommit
getRules() []Rule getRules() []Rule
} }
// 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, analyzerConfig config.AnalyzerConfig, chglogConfig config.ChangelogConfig) (*Analyzer, error) {
analyzer := &Analyzer{ analyzer := &Analyzer{
Config: config, AnalyzerConfig: analyzerConfig,
ChangelogConfig: chglogConfig,
} }
switch format { switch format {
@@ -39,7 +49,7 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
analyzer.analyzeCommits = newAngular() analyzer.analyzeCommits = newAngular()
log.Debugf("Commit format set to %s", ANGULAR) log.Debugf("Commit format set to %s", ANGULAR)
case CONVENTIONAL: case CONVENTIONAL:
analyzer.analyzeCommits = newConventional() analyzer.analyzeCommits = newConventional(analyzerConfig)
log.Debugf("Commit format set to %s", CONVENTIONAL) log.Debugf("Commit format set to %s", CONVENTIONAL)
default: default:
return nil, fmt.Errorf("invalid commit format: %s", format) return nil, fmt.Errorf("invalid commit format: %s", format)
@@ -62,14 +72,14 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
for _, commit := range commits { for _, commit := range commits {
for _, rule := range a.analyzeCommits.getRules() { for _, rule := range a.analyzeCommits.getRules() {
analyzedCommit, hasBreakingChange := a.analyzeCommits.analyze(commit, rule) analyzedCommit := a.analyzeCommits.analyze(commit, rule)
if analyzedCommit == nil { if analyzedCommit == nil {
continue continue
} }
if a.Config.PrintAll || rule.Changelog { if a.ChangelogConfig.PrintAll || rule.Changelog {
analyzedCommit.Print = true analyzedCommit.Print = true
} }
if hasBreakingChange { if analyzedCommit.IsBreaking {
analyzedCommits["major"] = append(analyzedCommits["major"], *analyzedCommit) analyzedCommits["major"] = append(analyzedCommits["major"], *analyzedCommit)
break break
} }
@@ -80,3 +90,114 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"])) log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"]))
return analyzedCommits return analyzedCommits
} }
//
// getRegexMatchedMap will match a regex with named groups and map the matching
// results to corresponding group names
//
func getRegexMatchedMap(regEx, url string) (paramsMap map[string]string) {
var compRegEx = regexp.MustCompile(regEx)
match := compRegEx.FindStringSubmatch(url)
paramsMap = make(map[string]string)
for i, name := range compRegEx.SubexpNames() {
if i > 0 && i <= len(match) {
paramsMap[name] = match[i]
}
}
return paramsMap
}
//
// getMessageBlocksFromTexts converts strings to an array of MessageBlock
//
func getMessageBlocksFromTexts(txtArray, separators []string) []shared.MessageBlock {
blocks := make([]shared.MessageBlock, len(txtArray))
for i, line := range txtArray{
blocks[i] = parseMessageBlock(line, separators)
}
return blocks
}
//
// parseMessageBlock parses a text in to MessageBlock
//
func parseMessageBlock(msg string, separators []string) shared.MessageBlock {
msgBlock := shared.MessageBlock{
Label: "",
Content: msg,
}
if token, sep := findFooterToken(msg, separators); len(token) > 0{
msgBlock.Label = token
content := strings.Replace(msg, token + sep, "", 1)
msgBlock.Content = strings.TrimSpace(content)
}
return msgBlock
}
//
// findFooterToken checks if given text has a token with one of the separators and returns a token
//
func findFooterToken(text string, separators []string) (token string, sep string) {
for _, sep := range separators {
regex := strings.Replace(footerTokenRegex, "<SEP>", sep, 1)
matches := getRegexMatchedMap(regex, text)
if token, ok := matches["token"]; ok {
return token, sep
}
}
return "", ""
}
//
// getDefaultMessageBlockMap parses a text block and splits in to different sections.
// default logic to distinguish different parts is:
// - Body starts right after the header (without beginning with a token)
// - Body ends when a footer is discovered or text ends
// - A footer is detected when it starts with a token ending with a separator
// - A footer ends when another footer is found or text ends
//
func getDefaultMessageBlockMap(txtBlock string, tokenSep []string) map[string][]shared.MessageBlock{
msgBlockMap := make(map[string][]shared.MessageBlock)
footers := make([]string, 0)
body, footerBlock, line := "", "", ""
footerFound := false
// Look through each line
scanner := bufio.NewScanner(strings.NewReader(txtBlock))
for scanner.Scan() {
line = scanner.Text()
if token, _ := findFooterToken(line, tokenSep); len(token) > 0 {
// if footer was already found from before
if len(footerBlock) > 0{
footers = append(footers, strings.TrimSpace(footerBlock))
}
footerFound = true
footerBlock = ""
}
//'\n' is removed when reading from scanner
if !footerFound {
body += line + "\n"
}else{
footerBlock += line + "\n"
}
}
if len(footerBlock) > 0 {
footers = append(footers, strings.TrimSpace(footerBlock))
}
body = strings.TrimSpace(body)
if len(body) > 0{
msgBlockMap["body"] = []shared.MessageBlock {{
Label: "",
Content: body,
} }
}
footerBlocks := getMessageBlocksFromTexts(footers, tokenSep)
if len(footerBlocks) > 0 {
msgBlockMap["footer"] = footerBlocks
}
return msgBlockMap
}

View File

@@ -10,7 +10,7 @@ import (
func TestAnalyzer(t *testing.T) { func TestAnalyzer(t *testing.T) {
_, err := analyzer.New("unknown", config.ChangelogConfig{}) _, err := analyzer.New("unknown", config.AnalyzerConfig{}, config.ChangelogConfig{})
assert.Error(t, err) assert.Error(t, err)
} }

View File

@@ -2,7 +2,7 @@
package analyzer package analyzer
import ( import (
"regexp" "github.com/Nightapes/go-semantic-release/pkg/config"
"strings" "strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -14,14 +14,16 @@ type angular struct {
rules []Rule rules []Rule
regex string regex string
log *log.Entry log *log.Entry
config config.AnalyzerConfig
} }
// ANGULAR identifier // ANGULAR identifier
const ANGULAR = "angular" const ANGULAR = "angular"
var angularFooterTokenSep = defaultTokenSeparators
func newAngular() *angular { func newAngular() *angular {
return &angular{ return &angular{
regex: `^(TAG)(?:\((.*)\))?: (?s)(.*)`, regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?: (?P<subject>.*)`,
log: log.WithField("analyzer", ANGULAR), log: log.WithField("analyzer", ANGULAR),
rules: []Rule{ rules: []Rule{
{ {
@@ -86,37 +88,52 @@ func (a *angular) getRules() []Rule {
return a.rules return a.rules
} }
func (a *angular) analyze(commit shared.Commit, rule Rule) (*shared.AnalyzedCommit, bool) { func (a *angular) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1)) tokenSep := append(a.config.TokenSeparators, angularFooterTokenSep[:]...)
matches := re.FindStringSubmatch(commit.Message)
if matches == nil { firstSplit := strings.SplitN(commit.Message, "\n", 2)
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag) header := firstSplit[0]
return nil, false body := ""
if len(firstSplit) > 1 {
body = firstSplit[1]
} }
matches := getRegexMatchedMap(a.regex, header)
if len(matches) == 0 || matches["type"] != rule.Tag{
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return nil
}
msgBlockMap := getDefaultMessageBlockMap(body, tokenSep)
analyzed := &shared.AnalyzedCommit{ analyzed := &shared.AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag, Tag: rule.Tag,
TagString: rule.TagString, TagString: rule.TagString,
Scope: shared.Scope(matches[2]), Scope: shared.Scope(matches["scope"]),
Subject: strings.TrimSpace(matches["subject"]),
MessageBlocks: msgBlockMap,
} }
message := strings.Join(matches[3:], "") isBreaking := strings.Contains(commit.Message, defaultBreakingChangePrefix)
if !strings.Contains(message, "BREAKING CHANGE:") { analyzed.IsBreaking = isBreaking
analyzed.ParsedMessage = strings.Trim(message, " ")
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
if !isBreaking {
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag) a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false return analyzed
} }
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message) a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2) breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
if len(breakingChange) > 1 { if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0]) analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1]) analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
return analyzed, true } else {
}
analyzed.ParsedBreakingChangeMessage = breakingChange[0] analyzed.ParsedBreakingChangeMessage = breakingChange[0]
return analyzed, true }
return analyzed
} }

View File

@@ -31,6 +31,8 @@ func TestAngular(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"major": {}, "major": {},
@@ -60,6 +62,8 @@ func TestAngular(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"major": { "major": {
@@ -75,6 +79,9 @@ func TestAngular(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my first break BREAKING CHANGE: change api to v2",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"patch": {}, "patch": {},
@@ -120,6 +127,8 @@ func TestAngular(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"major": { "major": {
@@ -135,6 +144,16 @@ func TestAngular(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{
"footer": {
shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "change api to v2",
},
},
},
}, },
}, },
"patch": {}, "patch": {},
@@ -177,6 +196,8 @@ func TestAngular(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"none": { "none": {
@@ -192,6 +213,8 @@ func TestAngular(t *testing.T) {
TagString: "Changes to CI/CD", TagString: "Changes to CI/CD",
Print: false, Print: false,
ParsedBreakingChangeMessage: "", ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"patch": {}, "patch": {},
@@ -212,7 +235,7 @@ func TestAngular(t *testing.T) {
}, },
} }
angular, err := analyzer.New("angular", config.ChangelogConfig{}) angular, err := analyzer.New("angular", config.AnalyzerConfig{}, config.ChangelogConfig{})
assert.NoError(t, err) assert.NoError(t, err)
for _, test := range testConfigs { for _, test := range testConfigs {

View File

@@ -2,7 +2,7 @@
package analyzer package analyzer
import ( import (
"regexp" "github.com/Nightapes/go-semantic-release/pkg/config"
"strings" "strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -14,14 +14,17 @@ type conventional struct {
rules []Rule rules []Rule
regex string regex string
log *log.Entry log *log.Entry
config config.AnalyzerConfig
} }
// CONVENTIONAL identifier // CONVENTIONAL identifier
const CONVENTIONAL = "conventional" const CONVENTIONAL = "conventional"
var conventionalFooterTokenSep = defaultTokenSeparators
func newConventional() *conventional { func newConventional(config config.AnalyzerConfig) *conventional {
return &conventional{ return &conventional{
regex: `^(TAG)(?:\((.*)\))?(\!)?: (?s)(.*)`, config: config,
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?(?P<breaking>\!)?: (?P<subject>.*)`,
log: log.WithField("analyzer", CONVENTIONAL), log: log.WithField("analyzer", CONVENTIONAL),
rules: []Rule{ rules: []Rule{
{ {
@@ -86,37 +89,55 @@ func (a *conventional) getRules() []Rule {
return a.rules return a.rules
} }
func (a *conventional) analyze(commit shared.Commit, rule Rule) (*shared.AnalyzedCommit, bool) { func (a *conventional) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1)) tokenSep := append(a.config.TokenSeparators, conventionalFooterTokenSep[:]...)
matches := re.FindStringSubmatch(commit.Message)
if matches == nil { firstSplit := strings.SplitN(commit.Message, "\n", 2)
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag) header := firstSplit[0]
return nil, false body := ""
if len(firstSplit) > 1 {
body = firstSplit[1]
} }
matches := getRegexMatchedMap(a.regex, header)
if len(matches) == 0 || matches["type"] != rule.Tag{
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return nil
}
msgBlockMap := getDefaultMessageBlockMap(body, tokenSep)
analyzed := &shared.AnalyzedCommit{ analyzed := &shared.AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag, Tag: rule.Tag,
TagString: rule.TagString, TagString: rule.TagString,
Scope: shared.Scope(matches[2]), Scope: shared.Scope(matches["scope"]),
Subject: strings.TrimSpace(matches["subject"]),
MessageBlocks: msgBlockMap,
} }
message := strings.Join(matches[4:], "") isBreaking := matches["breaking"] == "!" || strings.Contains(commit.Message, defaultBreakingChangePrefix)
if matches[3] == "" && !strings.Contains(message, "BREAKING CHANGE:") { analyzed.IsBreaking = isBreaking
analyzed.ParsedMessage = strings.Trim(message, " ")
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
if !isBreaking {
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag) a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false return analyzed
} }
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message) a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2) breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
if len(breakingChange) > 1 { if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0]) analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1]) analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
return analyzed, true } else {
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
} }
analyzed.ParsedBreakingChangeMessage = breakingChange[0] return analyzed
return analyzed, true
} }

View File

@@ -30,6 +30,8 @@ func TestConventional(t *testing.T) {
ParsedMessage: "my first commit", ParsedMessage: "my first commit",
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
Print: true, Print: true,
}, },
{ {
@@ -42,6 +44,8 @@ func TestConventional(t *testing.T) {
ParsedMessage: "no scope", ParsedMessage: "no scope",
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Subject: "no scope",
MessageBlocks: map[string][]shared.MessageBlock{},
Print: true, Print: true,
}, },
}, },
@@ -77,6 +81,8 @@ func TestConventional(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"major": { "major": {
@@ -92,6 +98,9 @@ func TestConventional(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "my first break", ParsedBreakingChangeMessage: "my first break",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"patch": {}, "patch": {},
@@ -125,6 +134,8 @@ func TestConventional(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"major": { "major": {
@@ -140,6 +151,15 @@ func TestConventional(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{
"footer" : { shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "change api to v2",
},
},
},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -153,6 +173,15 @@ func TestConventional(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "hey from the change", ParsedBreakingChangeMessage: "hey from the change",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{
"footer" : {shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "hey from the change",
},
},
},
}, },
}, },
"patch": {}, "patch": {},
@@ -212,6 +241,8 @@ func TestConventional(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"none": { "none": {
@@ -227,6 +258,8 @@ func TestConventional(t *testing.T) {
TagString: "Changes to CI/CD", TagString: "Changes to CI/CD",
Print: false, Print: false,
ParsedBreakingChangeMessage: "", ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"patch": {}, "patch": {},
@@ -262,6 +295,8 @@ func TestConventional(t *testing.T) {
TagString: "Changes to CI/CD", TagString: "Changes to CI/CD",
Print: false, Print: false,
ParsedBreakingChangeMessage: "", ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
"patch": {{ "patch": {{
@@ -275,6 +310,8 @@ func TestConventional(t *testing.T) {
Tag: "fix", Tag: "fix",
TagString: "Bug fixes", TagString: "Bug fixes",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}}, }},
"major": {}, "major": {},
}, },
@@ -293,7 +330,7 @@ func TestConventional(t *testing.T) {
}, },
} }
conventional, err := analyzer.New("conventional", config.ChangelogConfig{}) conventional, err := analyzer.New("conventional", config.AnalyzerConfig{}, config.ChangelogConfig{})
assert.NoError(t, err) assert.NoError(t, err)
for _, test := range testConfigs { for _, test := range testConfigs {
@@ -304,3 +341,156 @@ func TestConventional(t *testing.T) {
assert.Equalf(t, test.wantAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase) assert.Equalf(t, test.wantAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
} }
} }
func TestConventional_BodyAndFooters(t *testing.T) {
t.Parallel()
testConfigs := []struct {
testCase string
commits []shared.Commit
expectedAnalyzedCommits map[shared.Release][]shared.AnalyzedCommit
}{
{
testCase: "Only body, no footer",
commits: []shared.Commit{
{
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
Author: "me",
Hash: "12345667",
},
},
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"patch": {
{
Commit: shared.Commit{
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
Tag: "fix",
TagString: "Bug fixes",
Print: true,
Subject: "squash bug for logging",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {
shared.MessageBlock{
Label: "",
Content: "Now the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
},
},
},
},
},
"major": {},
"minor": {},
"none": {},
},
},
{
testCase: "Only footers, no body",
commits: []shared.Commit{
{
Message: "fix: squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
Author: "me",
Hash: "12345667",
},
},
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"patch": {
{
Commit: shared.Commit{
Message: "fix: squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
Tag: "fix",
TagString: "Bug fixes",
Print: true,
Subject: "squash bug for logging",
MessageBlocks: map[string][]shared.MessageBlock{
"footer": {
shared.MessageBlock{
Label: "Note",
Content: "now the logs will not print lines twice.",
},
shared.MessageBlock{
Label: "Issue",
Content: "#123",
},
shared.MessageBlock{
Label: "Severity",
Content: "medium",
},
},
},
},
},
"major": {},
"minor": {},
"none": {},
},
},
{
testCase: "Body and footers",
commits: []shared.Commit{
{
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
Author: "me",
Hash: "12345667",
},
},
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"patch": {
{
Commit: shared.Commit{
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
Tag: "fix",
TagString: "Bug fixes",
Print: true,
Subject: "squash bug for logging",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {
shared.MessageBlock{
Label: "",
Content: "Now the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
},
},
"footer": {
shared.MessageBlock{
Label: "Issue",
Content: "#123",
},
shared.MessageBlock{
Label: "Severity",
Content: "medium",
},
},
},
},
},
"major": {},
"minor": {},
"none": {},
},
},
}
conventional, err := analyzer.New("conventional", config.AnalyzerConfig{}, config.ChangelogConfig{})
assert.NoError(t, err)
for _, test := range testConfigs {
analyzedCommits := conventional.Analyze(test.commits)
assert.Equalf(t, test.expectedAnalyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase)
assert.Equalf(t, test.expectedAnalyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase)
assert.Equalf(t, test.expectedAnalyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
assert.Equalf(t, test.expectedAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
}
}

View File

@@ -69,6 +69,8 @@ func TestWriteAndReadCache(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "add gitlab as release option",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
}, },

View File

@@ -1,6 +1,7 @@
package changelog package changelog
import ( import (
"bufio"
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"strings" "strings"
@@ -31,9 +32,11 @@ introduced by commit:
{{ end -}} {{ end -}}
{{ end -}} {{ end -}}
{{ end -}}` {{ end -}}`
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})` const defaultCommitListSubTemplate = `{{ define "commitList" }}` + defaultCommitList + "{{ end }}"
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}}) const defaultChangelogTitle = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
{{ .Commits -}} const defaultChangelog = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ template "commitList" .CommitsContent -}}
{{ if .HasDocker}} {{ if .HasDocker}}
## Docker image ## Docker image
@@ -47,25 +50,43 @@ or
{{$.Backtick}}docker run {{.DockerRepository}}:latest{{$.Backtick}} {{$.Backtick}}docker run {{.DockerRepository}}:latest{{$.Backtick}}
{{ end -}} {{ end -}}
{{ end -}}
{{ if .HasNPM}}
## NodeJS Package
New NodeJS package is released under [{{.NPMPackageName}}]({{.NPMRepository}})
### Usage
{{$.Backtick}}yarn add {{.NPMPackageName}}@{{.Version}}{{$.Backtick}}
or
{{$.Backtick}}npm install -save {{.NPMPackageName}}@{{.Version}}{{$.Backtick}}
{{ end -}} {{ end -}}
` `
type changelogContent struct { type changelogContent struct {
Commits string Commits string
CommitsContent commitsContent
Version string Version string
Now time.Time Now time.Time
Backtick string Backtick string
HasDocker bool HasDocker bool
HasDockerLatest bool HasDockerLatest bool
DockerRepository string DockerRepository string
HasNPM bool
IsYarn bool
NPMRepository string
NPMPackageName string
} }
type commitsContent struct { type commitsContent struct {
Commits map[string][]shared.AnalyzedCommit Commits map[string][]shared.AnalyzedCommit
BreakingChanges []shared.AnalyzedCommit BreakingChanges []shared.AnalyzedCommit
Order []string Order []string
Version string
Now time.Time
Backtick string Backtick string
HasURL bool HasURL bool
URL string URL string
@@ -89,11 +110,11 @@ func New(config *config.ReleaseConfig, rules []analyzer.Rule, releaseTime time.T
} }
} }
// GenerateChanglog from given commits // GenerateChangelog from given commits
func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) { func (c *Changelog) GenerateChangelog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
commitsPerScope := map[string][]shared.AnalyzedCommit{} commitsPerScope := map[string][]shared.AnalyzedCommit{}
commitsBreakingChange := []shared.AnalyzedCommit{} var commitsBreakingChange []shared.AnalyzedCommit
order := make([]string, 0) order := make([]string, 0)
for _, rule := range c.rules { for _, rule := range c.rules {
@@ -106,7 +127,7 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
for _, commits := range analyzedCommits { for _, commits := range analyzedCommits {
for _, commit := range commits { for _, commit := range commits {
if commit.Print { if commit.Print {
if commit.ParsedBreakingChangeMessage != "" { if commit.IsBreaking {
commitsBreakingChange = append(commitsBreakingChange, commit) commitsBreakingChange = append(commitsBreakingChange, commit)
continue continue
} }
@@ -119,9 +140,7 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
} }
commitsContent := commitsContent{ commitsContent := commitsContent{
Version: templateConfig.Version,
Commits: commitsPerScope, Commits: commitsPerScope,
Now: c.releaseTime,
BreakingChanges: commitsBreakingChange, BreakingChanges: commitsBreakingChange,
Backtick: "`", Backtick: "`",
Order: order, Order: order,
@@ -130,20 +149,25 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
} }
changelogContent := changelogContent{ changelogContent := changelogContent{
CommitsContent: commitsContent,
Version: templateConfig.Version, Version: templateConfig.Version,
Now: c.releaseTime, Now: c.releaseTime,
Backtick: "`", Backtick: "`",
HasDocker: c.config.Changelog.Docker.Repository != "", HasDocker: c.config.Changelog.Docker.Repository != "",
HasDockerLatest: c.config.Changelog.Docker.Latest, HasDockerLatest: c.config.Changelog.Docker.Latest,
DockerRepository: c.config.Changelog.Docker.Repository, DockerRepository: c.config.Changelog.Docker.Repository,
HasNPM: c.config.Changelog.NPM.PackageName != "",
NPMPackageName: c.config.Changelog.NPM.PackageName,
NPMRepository: c.config.Changelog.NPM.Repository,
} }
template := defaultChangelog
chglogTemplate := defaultCommitListSubTemplate + defaultChangelog
if c.config.Changelog.TemplatePath != "" { if c.config.Changelog.TemplatePath != "" {
content, err := ioutil.ReadFile(c.config.Changelog.TemplatePath) content, err := ioutil.ReadFile(c.config.Changelog.TemplatePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
template = string(content) chglogTemplate = string(content)
} }
templateTitle := defaultChangelogTitle templateTitle := defaultChangelogTitle
@@ -152,30 +176,41 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
} }
log.Debugf("Render title") log.Debugf("Render title")
renderedTitle, err := generateTemplate(templateTitle, changelogContent) renderedTitle, err := generateTemplate(templateTitle, changelogContent, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("Render commits") log.Debugf("Render commits")
renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent) renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Tracef("Commits %s", renderedCommitList) log.Tracef("Commits %s", renderedCommitList)
changelogContent.Commits = renderedCommitList changelogContent.Commits = renderedCommitList
extraFuncMap := template.FuncMap{
"commitUrl": func() string { return templateConfig.CommitURL },
}
log.Debugf("Render changelog") log.Debugf("Render changelog")
renderedContent, err := generateTemplate(template, changelogContent) renderedContent, err := generateTemplate(chglogTemplate, changelogContent, extraFuncMap)
return &shared.GeneratedChangelog{Title: renderedTitle, Content: renderedContent}, err return &shared.GeneratedChangelog{Title: renderedTitle, Content: renderedContent}, err
} }
func generateTemplate(text string, values interface{}) (string, error) { func generateTemplate(text string, values interface{}, extraFuncMap template.FuncMap) (string, error) {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"replace": replace, "replace": replace,
"lower": lower,
"upper": upper,
"capitalize": capitalize,
"addPrefixToLines": addPrefixToLines,
}
for k, v := range extraFuncMap {
funcMap[k] = v
} }
var tpl bytes.Buffer var tpl bytes.Buffer
@@ -193,3 +228,30 @@ func generateTemplate(text string, values interface{}) (string, error) {
func replace(input, from, to string) string { func replace(input, from, to string) string {
return strings.Replace(input, from, to, -1) return strings.Replace(input, from, to, -1)
} }
func lower(input string) string {
return strings.ToLower(input)
}
func upper(input string) string {
return strings.ToUpper(input)
}
func capitalize(input string) string {
if len(input) > 0 {
return strings.ToUpper(string(input[0])) + input[1:]
}
return ""
}
// Adds a prefix to each line of the given text block
// this can be helpful in rendering correct indentation or bullets for multi-line texts
func addPrefixToLines(input, prefix string) string {
output := ""
scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
output += prefix + scanner.Text() + "\n"
}
output = strings.TrimRight(output, "\n")
return output
}

View File

@@ -41,6 +41,8 @@ func TestChangelog(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
}, },
@@ -64,6 +66,8 @@ func TestChangelog(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
}, },
@@ -88,6 +92,8 @@ func TestChangelog(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -101,6 +107,15 @@ func TestChangelog(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "change api to v2",
},
},
},
}, },
}, },
}, },
@@ -126,6 +141,15 @@ func TestChangelog(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "hey from the change", ParsedBreakingChangeMessage: "hey from the change",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "hey from the change",
},
},
},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -138,6 +162,8 @@ func TestChangelog(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -150,10 +176,12 @@ func TestChangelog(t *testing.T) {
Tag: "feat", Tag: "feat",
TagString: "Features", TagString: "Features",
Print: true, Print: true,
Subject: "my second commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
Message: "feat: my new commit \n\nmy first break: BREAKING CHANGE: change api to v2", Message: "feat: my new commit \n\nBREAKING CHANGE: change api to v2",
Author: "me", Author: "me",
Hash: "12345668", Hash: "12345668",
}, },
@@ -163,6 +191,14 @@ func TestChangelog(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "change api to v2", ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my new commit",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {shared.MessageBlock{
Label: "BREAKING CHANGE",
Content: "change api to v2",
}},
},
}, },
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -176,6 +212,9 @@ func TestChangelog(t *testing.T) {
TagString: "Features", TagString: "Features",
Print: true, Print: true,
ParsedBreakingChangeMessage: "my next commit", ParsedBreakingChangeMessage: "my next commit",
IsBreaking: true,
Subject: "my next commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}, },
}, },
}, },
@@ -210,10 +249,100 @@ func TestChangelog(t *testing.T) {
for _, config := range testConfigs { for _, config := range testConfigs {
t.Run(config.testCase, func(t *testing.T) { t.Run(config.testCase, func(t *testing.T) {
generatedChangelog, err := cl.GenerateChanglog(templateConfig, config.analyzedCommits) generatedChangelog, err := cl.GenerateChangelog(templateConfig, config.analyzedCommits)
assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err) assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err)
assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase) assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase)
}) })
} }
} }
func TestChangelogExtensions(t *testing.T) {
testConfigs := []struct {
testCase string
result *shared.GeneratedChangelog
releaseConfig *config.ReleaseConfig
}{
{
testCase: "docker",
releaseConfig: &config.ReleaseConfig{
Changelog: config.ChangelogConfig{
Docker: config.ChangelogDocker{
Latest: true,
Repository: "mydocker.de",
},
NPM: config.ChangelogNPM{},
},
},
result: &shared.GeneratedChangelog{Title: "v1.0.0 (2019-07-19)", Content: "# v1.0.0 (2019-07-19)\n### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url))\n\n## Docker image\n\nNew docker image is released under `mydocker.de:1.0.0`\n\n### Usage\n\n`docker run mydocker.de:1.0.0`\n\nor\n\n`docker run mydocker.de:latest`\n"},
},
{
testCase: "npm",
releaseConfig: &config.ReleaseConfig{
Changelog: config.ChangelogConfig{
Docker: config.ChangelogDocker{},
NPM: config.ChangelogNPM{
Repository: "https://github.com/Nightapes/ngx-validators/packages/102720",
PackageName: "ngx-validators",
},
},
},
result: &shared.GeneratedChangelog{Title: "v1.0.0 (2019-07-19)", Content: "# v1.0.0 (2019-07-19)\n### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url))\n\n## NodeJS Package\n\nNew NodeJS package is released under [ngx-validators](https://github.com/Nightapes/ngx-validators/packages/102720)\n\n### Usage\n\n`yarn add ngx-validators@1.0.0`\n\nor\n\n`npm install -save ngx-validators@1.0.0`\n\n"},
},
}
analyzedCommits := map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
}
for _, config := range testConfigs {
t.Run(config.testCase, func(t *testing.T) {
templateConfig := shared.ChangelogTemplateConfig{
CommitURL: "https://commit.url",
CompareURL: "https://compare.url",
Hash: "hash",
Version: "1.0.0",
}
cl := changelog.New(config.releaseConfig, []analyzer.Rule{
{
Tag: "feat",
TagString: "Features",
Release: "minor",
Changelog: true,
},
{
Tag: "fix",
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
},
{
Tag: "build",
TagString: "Build",
Release: "none",
Changelog: false,
},
}, time.Date(2019, 7, 19, 0, 0, 0, 0, time.UTC))
generatedChangelog, err := cl.GenerateChangelog(templateConfig, analyzedCommits)
assert.NoError(t, err)
assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase)
})
}
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/storer"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -77,14 +76,14 @@ func (g *GitUtil) GetBranch() (string, error) {
} }
// GetLastVersion from git tags // GetLastVersion from git tags
func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) { func (g *GitUtil) GetLastVersion() (*semver.Version, *plumbing.Reference, error) {
var tags []*semver.Version var tags []*semver.Version
gitTags, err := g.Repository.Tags() gitTags, err := g.Repository.Tags()
if err != nil { if err != nil {
return nil, "", err return nil, nil, err
} }
err = gitTags.ForEach(func(p *plumbing.Reference) error { err = gitTags.ForEach(func(p *plumbing.Reference) error {
@@ -100,63 +99,70 @@ func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
}) })
if err != nil { if err != nil {
return nil, "", err return nil, nil, err
} }
sort.Sort(sort.Reverse(semver.Collection(tags))) sort.Sort(sort.Reverse(semver.Collection(tags)))
if len(tags) == 0 { if len(tags) == 0 {
log.Debugf("Found no tags") log.Debugf("Found no tags")
return nil, "", nil return nil, nil, nil
} }
log.Debugf("Found old version %s", tags[0].String()) log.Debugf("Found old version %s", tags[0].String())
tag, err := g.Repository.Tag(tags[0].Original()) tag, err := g.Repository.Tag(tags[0].Original())
if err != nil { if err != nil {
return nil, "", err return nil, nil, err
} }
log.Debugf("Found old hash %s", tag.Hash().String()) log.Debugf("Found old hash %s", tag.Hash().String())
return tags[0], tag.Hash().String(), nil return tags[0], tag, nil
} }
// GetCommits from git hash to HEAD // GetCommits from git hash to HEAD
func (g *GitUtil) GetCommits(lastTagHash string) ([]shared.Commit, error) { func (g *GitUtil) GetCommits(lastTagHash *plumbing.Reference) ([]shared.Commit, error) {
excludeIter, err := g.Repository.Log(&git.LogOptions{From: lastTagHash.Hash()})
if err != nil {
return nil, err
}
seen := map[plumbing.Hash]struct{}{}
err = excludeIter.ForEach(func(c *object.Commit) error {
seen[c.Hash] = struct{}{}
return nil
})
if err != nil {
return nil, err
}
var isValid object.CommitFilter = func(commit *object.Commit) bool {
_, ok := seen[commit.Hash]
return !ok && len(commit.ParentHashes) < 2
}
ref, err := g.Repository.Head() ref, err := g.Repository.Head()
if err != nil { if err != nil {
return nil, err return nil, err
} }
startCommit, err := g.Repository.CommitObject(ref.Hash())
cIter, err := g.Repository.Log(&git.LogOptions{From: ref.Hash(), Order: git.LogOrderCommitterTime})
if err != nil { if err != nil {
return nil, err return nil, err
} }
cIter := object.NewFilterCommitIter(startCommit, &isValid, nil)
commits := make(map[string]shared.Commit) commits := make(map[string]shared.Commit)
var foundEnd bool
err = cIter.ForEach(func(c *object.Commit) error { err = cIter.ForEach(func(c *object.Commit) error {
log.Debugf("Found commit with hash %s", c.Hash.String())
if c.Hash.String() == lastTagHash {
log.Debugf("Found commit with hash %s, will stop here", c.Hash.String())
foundEnd = true
return storer.ErrStop
}
if !foundEnd {
log.Tracef("Found commit with hash %s", c.Hash.String())
commits[c.Hash.String()] = shared.Commit{ commits[c.Hash.String()] = shared.Commit{
Message: c.Message, Message: c.Message,
Author: c.Committer.Name, Author: c.Committer.Name,
Hash: c.Hash.String(), Hash: c.Hash.String(),
} }
}
return nil return nil
}) })
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Could not read commits, check git clone depth in your ci") return nil, errors.Wrap(err, "Could not read commits, check git clone depth in your ci")
} }

View File

@@ -0,0 +1,26 @@
package integrations
import (
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
)
// Integrations struct
type Integrations struct {
version *shared.ReleaseVersion
config *config.Integrations
}
func New(config *config.Integrations, version *shared.ReleaseVersion) *Integrations {
return &Integrations{
config: config,
version: version,
}
}
func (i Integrations) Run() error {
if i.config.NPM.Enabled {
return i.updateNPM()
}
return nil
}

View File

@@ -0,0 +1,28 @@
package integrations
import (
log "github.com/sirupsen/logrus"
"github.com/tidwall/sjson"
"io/ioutil"
)
func (i *Integrations) updateNPM() error {
npmConfig := i.config.NPM
if npmConfig.Path == "" {
npmConfig.Path = "./package.json"
}
log.Debugf("Set version %s to %s", i.version.Next.Version, npmConfig.Path)
data, err := ioutil.ReadFile(npmConfig.Path)
if err != nil {
return err
}
newData, err := sjson.Set(string(data), "version", i.version.Next.Version)
if err != nil {
return err
}
return ioutil.WriteFile(npmConfig.Path, []byte(newData), 0777)
}

View File

@@ -0,0 +1,62 @@
package integrations
import (
"github.com/Masterminds/semver"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
)
func TestIntegrations_updateNPM(t *testing.T) {
file, err := ioutil.TempFile("", "package")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = ioutil.WriteFile(file.Name(), []byte(`{
"name": "test",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"nx": "nx"
}
}`), 0777)
if err != nil {
t.Fatal(err)
}
testVersion, err := semver.NewVersion("1.2.0")
if err != nil {
t.Fatal(err)
}
i := New(&config.Integrations{NPM: config.IntegrationNPM{
Enabled: true,
Path: file.Name(),
}}, &shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Version: testVersion,
},
})
assert.NoError(t, i.updateNPM())
updatedFile, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
assert.Equal(t, `{
"name": "test",
"version": "1.2.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"nx": "nx"
}
}`, string(updatedFile))
}

View File

@@ -54,10 +54,13 @@ func New(c *config.GitHubProvider, checkConfig bool) (*Client, error) {
if c.CustomURL == "" { if c.CustomURL == "" {
client = github.NewClient(httpClient) client = github.NewClient(httpClient)
} else { } else {
if client, err = github.NewEnterpriseClient(c.CustomURL, c.CustomURL+"/api/v3/", httpClient); err != nil { // v25.0 of google github does not append prefixes for base and upload URLs
if client, err = github.NewEnterpriseClient(c.CustomURL+"/api/v3/", c.CustomURL+"/api/uploads/", httpClient); err != nil {
return &Client{}, err return &Client{}, err
} }
baseURL = c.CustomURL // note: do not append / to end of the url since all the url constructions using this
// assume no trailing /
baseURL = c.CustomURL + "/api/v3"
} }
return &Client{ return &Client{
config: c, config: c,
@@ -91,7 +94,7 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC
func (g *Client) makeRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { func (g *Client) makeRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tagPrefix := config.DefaultTagPrefix tagPrefix := config.DefaultTagPrefix
if g.config.TagPrefix != nil{ if g.config.TagPrefix != nil {
tagPrefix = *g.config.TagPrefix tagPrefix = *g.config.TagPrefix
} }
tag := tagPrefix + releaseVersion.Next.Version.String() tag := tagPrefix + releaseVersion.Next.Version.String()

View File

@@ -136,7 +136,7 @@ func TestGetCommitURL(t *testing.T) {
client, _ := New(&testOject.config, false) client, _ := New(&testOject.config, false)
actualURL := client.GetCommitURL() actualURL := client.GetCommitURL()
if testOject.config.CustomURL != "" { if testOject.config.CustomURL != "" {
expectedURL := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo) expectedURL := fmt.Sprintf("%s/api/v3/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo)
assert.EqualValues(t, expectedURL, actualURL) assert.EqualValues(t, expectedURL, actualURL)
} else { } else {
@@ -154,7 +154,7 @@ func TestGetCompareURL(t *testing.T) {
client, _ := New(&testOject.config, false) client, _ := New(&testOject.config, false)
actualURL := client.GetCompareURL("1", "2") actualURL := client.GetCompareURL("1", "2")
if testOject.config.CustomURL != "" { if testOject.config.CustomURL != "" {
expectedURL := fmt.Sprintf("%s/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2") expectedURL := fmt.Sprintf("%s/api/v3/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2")
assert.EqualValues(t, expectedURL, actualURL) assert.EqualValues(t, expectedURL, actualURL)
} else { } else {

View File

@@ -37,13 +37,22 @@ type ChangelogTemplateConfig struct {
type AnalyzedCommit struct { type AnalyzedCommit struct {
Commit Commit `yaml:"commit"` Commit Commit `yaml:"commit"`
ParsedMessage string `yaml:"parsedMessage"` ParsedMessage string `yaml:"parsedMessage"`
Scope Scope `yaml:"scope"`
ParsedBreakingChangeMessage string `yaml:"parsedBreakingChangeMessage"` ParsedBreakingChangeMessage string `yaml:"parsedBreakingChangeMessage"`
Tag string `yaml:"tag"` Tag string `yaml:"tag"`
TagString string `yaml:"tagString"` TagString string `yaml:"tagString"`
Scope Scope `yaml:"scope"`
Subject string `yaml:"subject"`
MessageBlocks map[string][]MessageBlock `yaml:"messageBlocks"`
IsBreaking bool `yaml:"isBreaking"`
Print bool `yaml:"print"` Print bool `yaml:"print"`
} }
// MessageBlock represents a block in the body section of a commit message
type MessageBlock struct {
Label string `yaml:"label"`
Content string `yaml:"content"`
}
//Scope of the commit, like feat, fix,.. //Scope of the commit, like feat, fix,..
type Scope string type Scope string

View File

@@ -13,6 +13,11 @@ const (
DefaultTagPrefix = "v" DefaultTagPrefix = "v"
) )
// AnalyzerConfig struct
type AnalyzerConfig struct {
TokenSeparators []string `yaml:"tokenSeparators"`
}
// ChangelogConfig struct // ChangelogConfig struct
type ChangelogConfig struct { type ChangelogConfig struct {
PrintAll bool `yaml:"printAll,omitempty"` PrintAll bool `yaml:"printAll,omitempty"`
@@ -30,8 +35,8 @@ type ChangelogDocker struct {
//ChangelogNPM type struct //ChangelogNPM type struct
type ChangelogNPM struct { type ChangelogNPM struct {
YARN bool `yaml:"latest"`
Repository string `yaml:"repository"` Repository string `yaml:"repository"`
PackageName string `yaml:"name"`
} }
//Asset type struct //Asset type struct
@@ -79,10 +84,22 @@ type Checksum struct {
Algorithm string `yaml:"algorithm"` Algorithm string `yaml:"algorithm"`
} }
// Checksum struct
type Integrations struct {
NPM IntegrationNPM `yaml:"npm"`
}
// Checksum struct
type IntegrationNPM struct {
Enabled bool `yaml:"enabled"`
Path string `yaml:"path"`
}
// ReleaseConfig struct // ReleaseConfig struct
type ReleaseConfig struct { type ReleaseConfig struct {
CommitFormat string `yaml:"commitFormat"` CommitFormat string `yaml:"commitFormat"`
Branch map[string]string `yaml:"branch"` Branch map[string]string `yaml:"branch"`
Analyzer AnalyzerConfig `yaml:"analyzer"`
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"`
@@ -91,6 +108,7 @@ type ReleaseConfig struct {
Assets []Asset `yaml:"assets"` Assets []Asset `yaml:"assets"`
Checksum Checksum `yaml:"checksum,omitempty"` Checksum Checksum `yaml:"checksum,omitempty"`
Hooks Hooks `yaml:"hooks"` Hooks Hooks `yaml:"hooks"`
Integrations Integrations `yaml:"integrations"`
ReleaseTitle string `yaml:"title"` ReleaseTitle string `yaml:"title"`
IsPreRelease bool IsPreRelease bool
} }

View File

@@ -100,6 +100,7 @@ github:
Compress: false}}, Compress: false}},
ReleaseTitle: "go-semantic-release release", ReleaseTitle: "go-semantic-release release",
IsPreRelease: false, IsPreRelease: false,
Analyzer: config.AnalyzerConfig{TokenSeparators: []string{}},
}, result) }, result)
} }

View File

@@ -1,6 +1,7 @@
package semanticrelease package semanticrelease
import ( import (
"github.com/Nightapes/go-semantic-release/internal/integrations"
"io/ioutil" "io/ioutil"
"time" "time"
@@ -39,7 +40,7 @@ func New(c *config.ReleaseConfig, repository string, checkConfig bool) (*Semanti
return nil, err return nil, err
} }
analyzer, err := analyzer.New(c.CommitFormat, c.Changelog) analyzer, err := analyzer.New(c.CommitFormat, c.Analyzer, c.Changelog)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -130,7 +131,7 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
Version: &newVersion, Version: &newVersion,
}, },
Last: shared.ReleaseVersionEntry{ Last: shared.ReleaseVersionEntry{
Commit: lastVersionHash, Commit: lastVersionHash.Hash().String(),
Version: lastVersion, Version: lastVersion,
}, },
Branch: provider.Branch, Branch: provider.Branch,
@@ -170,7 +171,7 @@ func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string
Version: newVersion, Version: newVersion,
}, },
Last: shared.ReleaseVersionEntry{ Last: shared.ReleaseVersionEntry{
Commit: lastVersionHash, Commit: lastVersionHash.Hash().String(),
Version: lastVersion, Version: lastVersion,
}, },
Branch: provider.Branch, Branch: provider.Branch,
@@ -180,7 +181,7 @@ func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string
// 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) {
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.GenerateChangelog(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(),
@@ -226,6 +227,12 @@ func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error
return err return err
} }
integrations := integrations.New(&s.config.Integrations, releaseVersion)
if err := integrations.Run(); err != nil {
log.Debugf("Error during integrations run")
return err
}
hook := hooks.New(s.config, releaseVersion) hook := hooks.New(s.config, releaseVersion)
if err := hook.PreRelease(); err != nil { if err := hook.PreRelease(); err != nil {
log.Debugf("Error during pre release hook") log.Debugf("Error during pre release hook")