Compare commits

...

149 Commits

Author SHA1 Message Date
Sebastian
2c8d2d9ade style(changelog): simplify string sort 2022-04-11 21:56:56 +02:00
Sebastian
8acb8b74b8 refactor(changelog): sort authors 2022-04-11 21:56:56 +02:00
Sebastian
fd1063132f refactor(changelog): clean up author map 2022-04-11 21:56:56 +02:00
Sebastian Beisch
1d0c93b678 test(analyzer): fix test 2022-04-11 21:56:56 +02:00
Sebastian Beisch
0c7338ab13 feat(changelog): show authors of commits and show body as header
Enable new features in `.release.yml` like

```yml

changelog:
  showAuthors: false  ## Show authors in changelog
  showBodyAsHeader: false  ## Show all bodies of the commits as header of changelog (useful for squash commit flow to show long text in release)

```
2022-04-11 21:56:56 +02:00
Sebastian Beisch
03f2eeadaa fix(analyzer): fix commit message body parser
In special cases the body of a commit was not correctly interpreted and the changelog was displayed wrongly.
2022-04-11 21:56:56 +02:00
Sebastian
bcb21d917d chore(codeql): enable code scan 2022-04-11 13:38:30 +02:00
Sebastian Beisch
ea2fbfba3d chore(ci): add go 1.17 2022-03-04 13:54:47 +01:00
Sebastian Beisch
c5f993e7de fix(#62): show message block with right indent 2022-03-04 13:54:47 +01:00
Ferenc
ec8dc17df2 docs(examples/github-actions.yml): add github actions example 2021-08-20 08:39:19 +02:00
Sebastian
9e40e81fea Merge pull request #75 from Nightapes/doc/gitlab 2021-08-11 19:16:52 +02:00
Felix Wiedmann
2782fb5d03 docs(gitlab): add missing entrypoint overwrite 2021-08-11 19:00:47 +02:00
Felix Wiedmann
0800219d60 Merge branch 'master' of github.com:Nightapes/go-semantic-release 2021-08-11 16:08:57 +02:00
Sebastian
f89150b698 fix(Dockerfile): move binary in /usr/local/bin to call go-semantic-releaser via $PATH in terminal directly and update alpine 2021-08-10 21:05:52 +02:00
Felix Wiedmann
780a6dbc2e docs(gitlab): add example gitlab-ci.yml 2021-08-10 18:34:16 +02:00
Felix Wiedmann
2d7977993b build(Dockerfile): move binary in /usr/local/bin to call go-semantic-releaservia $PATH in terminal directly 2021-08-10 18:34:16 +02:00
Felix Wiedmann
9d7edbf756 docs(gitlab): add example gitlab-ci.yml 2021-08-10 16:28:47 +02:00
Felix Wiedmann
d9e2f36927 build(Dockerfile): move binary in /usr/local/bin to call go-semantic-releaservia $PATH in terminal directly 2021-08-10 16:21:42 +02:00
Sebastian
a6783f23bd Merge pull request #67 from Nightapes/feat/changelog 2021-05-08 23:52:26 +02:00
Felix Wiedmann
fbb3684f68 docs(readme): add Write changelog to file section 2021-05-08 23:39:52 +02:00
Felix Wiedmann
9bc3143f53 ref(changelog): add unit description of max-file-size 2021-05-08 23:23:43 +02:00
Felix Wiedmann
7b93801e88 test(semantic-release): add tests for WriteChangelog 2021-05-08 22:23:03 +02:00
Felix Wiedmann
749bbf720b chore(changelog): fix calculation of file name 2021-05-08 22:23:03 +02:00
Felix Wiedmann
d9e5dc4515 chore(cmd): pass max file size of changelog via options 2021-05-08 22:23:03 +02:00
Felix Wiedmann
d66364e474 ref(changelog): check for max changelog file size, generate new change log file if exceeded.feat/changelog
still in WIO
2021-05-08 22:23:03 +02:00
Felix Wiedmann
2fc6145149 ci(go): remove go 1.15 in matrix build 2021-05-08 22:23:03 +02:00
fwiedmann
a054cf9a60 feat(changelog): prepend a changelog to a file as default, use --overwrite to overwrite the whole file
BREAKING CHANGE: changelog command will now prepend changelogs to the changelog-file
2021-05-08 22:23:03 +02:00
fwiedmann
155a16ddd5 ci(build): set upload artifact go version to 1.16,
remove version 1.13, 1.14
2021-05-08 22:21:43 +02:00
fwiedmann
d7878222bb build(go): set module version to go 1.16 2021-05-08 22:21:43 +02:00
Sebastian Beisch
f79466b238 fix(gitutil): don't crash if no last version was found 2021-05-06 19:07:07 +02:00
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
Felix Wiedmann
b9cbbd435f feat(analyzer): add conventional commit format 2021-01-24 00:22:42 +01:00
Felix Wiedmann
564c033cbb Merge pull request #51 from Nightapes/feat/conventional-commits
feat(analyzer): add conventional commit format
2021-01-23 23:17:13 +01:00
Felix Wiedmann
3deead130c Merge pull request #53 from Nightapes/ref/feat-conv-commits
ref(analyzer): simplified analyze method for angular and conventional
2021-01-23 23:11:37 +01:00
Felix Wiedmann
ac1bb779bb lint(semanticrelease): update file permissions 2021-01-23 22:53:55 +01:00
fwiedmann
6cd43d7957 ref(analyzer): simplified analyze method for angular and conventional 2021-01-23 22:49:42 +01:00
Sebastian
450383bdbf Merge branch 'master' into feat/conventional-commits 2021-01-21 22:43:53 +01:00
Sebastian
d6c5e395a8 chore(lint): fix lint version 2021-01-21 22:40:07 +01:00
Felix Wiedmann
3731fa6e55 Merge pull request #50 from maulik13/custom-tag-prefix 2021-01-21 21:54:21 +01:00
Sebastian Beisch
8db8b0d72c feat(analyzer): add conventional commit format
See: [conventional](https://www.conventionalcommits.org/en/v1.0.0/#summaryhttps://www.conventionalcommits.org/en/v1.0.0/#summary)
2021-01-21 21:41:14 +01:00
maulik13
15a17e546b docs: update the readme file to include the tagPrefix parameter 2021-01-21 21:39:52 +01:00
maulik13
037983df1e feat(release): add an option to specify a custom prefix for the version tag 2021-01-21 17:52:08 +01:00
Felix Wiedmann
01af837b40 Merge pull request #46 from Nightapes/fix/commits
refactor(internal/git): revert list commits changes
2020-11-11 16:01:03 +01:00
Sebastian Beisch
0f1275fc30 refactor(internal/git): revert list commits changes 2020-11-11 10:53:10 +01:00
Felix Wiedmann
115bc85d8d Merge pull request #45 from Nightapes/fix/merge-commits
Fix/merge commits
2020-11-10 12:59:36 +01:00
Sebastian Beisch
dee85c6877 fix(internal/git): check merge commits and collect parent commits for changelog 2020-11-09 14:43:01 +01:00
Sebastian Beisch
ccfce5e135 chore(git): update to new git version go-git.v5 2020-11-09 14:40:38 +01:00
Sebastian
8ffc0804ec Merge pull request #44 from Nightapes/fix/hooks
feat(hooks): pass environment variables to hooks
2020-08-18 19:02:16 +02:00
Sebastian Beisch
348ea6607e chore(*): update dependencies 2020-08-18 17:43:39 +02:00
Sebastian Beisch
00cbfd39a2 chore(go): switch to golang 1.15 2020-08-18 17:38:28 +02:00
Sebastian Beisch
0cbc7a7885 feat(hooks): pass environment variables to hooks 2020-08-18 17:33:53 +02:00
Sebastian
1bc0185f70 Merge pull request #43 from zeroshade/breaking
fix(internal/angular): fix angular parsing of breaking change messages that have multiple lines
2020-07-28 21:13:58 +02:00
Matthew Topol
f0ebb03e46 fix(internal/angular): fix angular parsing of breaking change messages that have multiple lines 2020-07-27 16:42:15 -04:00
Sebastian
cc22d5d4db Merge pull request #42 from Nightapes/fix/asset
fix(internal/assets): close zip writer before closing zip file
2020-05-21 11:06:24 +02:00
fwiedmann
c84b51d18c fix(internal/assets): close zip writer before closing zip file 2020-05-21 00:45:59 +02:00
Felix Wiedmann
11acc95a34 Merge pull request #41 from Nightapes/fix.assets
fix(assets): when file is zipped, upload zipped file instead of unzipped file
2020-03-25 09:01:03 +01:00
Sebastian Beisch
be35b743b4 fix(assets): when file is zipped, upload zipped file instead of unzipped file 2020-03-25 08:45:27 +01:00
Felix Wiedmann
322455b6d4 Merge pull request #40 from Nightapes/checksum
feat(assets): calculate checksum for all assets and upload it
2020-03-24 21:18:18 +01:00
Nightapes
400a25f950 style(assets): rename container to set 2020-03-24 19:56:57 +01:00
Nightapes
92600059c2 style(assets): rename container to set 2020-03-24 19:51:18 +01:00
Nightapes
666716d627 refactor(assets): check asset only on release 2020-03-21 16:04:18 +01:00
Nightapes
6fd34d3e0a feat(assets): calculate checksum for all assets and upload it 2020-03-21 15:47:03 +01:00
Sebastian
cc957cb3f2 Merge pull request #39 from Nightapes/fix/ci
ci(upload-artifacts): always upload artifact and build docker image f…
2020-02-29 16:16:08 +01:00
fwiedmann
cf87c74d1d ci(upload-artifacts): always upload artifact and build docker image for go 1.14 2020-02-28 17:35:23 +01:00
Felix Wiedmann
0aa8146902 Merge pull request #37 from Nightapes/ci.matrix
ci(github): add golang 1.14 and macos
2020-02-27 23:15:19 +01:00
Nightapes
632e6ea2d3 ci(actions): checkout code for release job 2020-02-27 22:19:55 +01:00
Nightapes
460710ff39 ci(actions): make file executable 2020-02-27 22:14:26 +01:00
Nightapes
ba59e8db63 fix(deps): update cobra,net,crypto and sys 2020-02-27 22:12:08 +01:00
Nightapes
5c5ca98978 ci(actions): only use latest artifacts 2020-02-27 21:56:42 +01:00
Nightapes
9954e67e88 ci(actions): fix depends on build 2020-02-27 21:46:09 +01:00
Nightapes
4f728a7984 Merge branch 'ci.matrix' of github.com:Nightapes/go-semantic-release into ci.matrix 2020-02-27 21:44:35 +01:00
Nightapes
a76aec25dd ci(github): add golang 1.14 and macos 2020-02-27 21:43:12 +01:00
Nightapes
1b3687a99d ci(github): add golang 1.14 and macos 2020-02-27 21:40:49 +01:00
Sebastian
875dbdcfc4 dcos(README): add badge 2020-02-19 19:40:24 +01:00
Felix Wiedmann
2208427b65 Merge pull request #35 from Nightapes/feat/currentversion
feat(cli): add current version command
2020-02-18 22:55:16 +01:00
Nightapes
5cb83dead4 chore(deps): update all dependencies 2020-02-18 19:19:22 +01:00
Nightapes
343e660e8e feat(cli): add current version command 2020-02-18 19:19:09 +01:00
Sebastian
95bef6cb2d docs(README): add download info 2020-02-07 16:36:38 +01:00
Felix Wiedmann
a6dcad2b69 Merge pull request #33 from Nightapes/beta
fix(prerelease): calculate right version if branch is set to alpha,beta or rc
2020-01-26 17:16:48 +01:00
Sebastian
af5c8f5ae3 Merge pull request #32 from Nightapes/fix.beta-release
fix(prerelease): calculate right version if branch is set to alpha,beta or rc
2020-01-26 17:11:49 +01:00
Nightapes
c361744a35 fix(prerelease): calculate right version if branch is set to alpha,beta or rc 2020-01-26 17:08:57 +01:00
Felix Wiedmann
6a375f3bab Merge pull request #30 from jthurman42/jthurman/optional-scope
Only print scope if one was defined
2020-01-15 22:58:50 +01:00
Jonathan Thurman
d4627a9d46 fix(internal/changelog): Only print scope if one was defined 2020-01-14 19:35:54 -08:00
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
60 changed files with 4370 additions and 855 deletions

2
.dockerignore Normal file
View File

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

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '21 16 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,39 +1,88 @@
name: Go
on: [push, pull_request]
on:
pull_request:
push:
branches:
- master
jobs:
build:
name: Build
strategy:
matrix:
go: ["1.17", "1.18"]
env:
DEFAULT_GO: "1.18"
name: Build with go version ${{ matrix.go }}
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Set up GoLang ${{ matrix.go }}
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- 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.16.0
golangci-lint run ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
- name: Run tests
run: go test ./...
- 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/
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 "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./build/go-semantic-release-temp release --loglevel trace
- 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.linux_x86_64 -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=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 PR
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: |
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-${{matrix.go}} .
docker push nightapes/go-semantic-release:development-${{matrix.go}}
docker tag nightapes/go-semantic-release:development-${{matrix.go}} docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:development-${{matrix.go}}
docker push docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:development-${{matrix.go}}
- uses: actions/upload-artifact@v1
if: matrix.go == env.DEFAULT_GO
with:
name: build
path: build
release:
name: Release
runs-on: ubuntu-latest
needs: build
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- uses: actions/download-artifact@v1
with:
name: build
path: build
- name: Release
if: github.ref == 'refs/heads/master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
chmod -R +x build
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
- 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

2
.gitignore vendored
View File

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

View File

@@ -1,21 +1,32 @@
commitFormat: angular
title: "go-semantic-release release"
branch:
master: beta
rc: rc
beta: beta
alpha: alpha
changelog:
printAll: false
template: ''
templatePath: ''
release: 'github'
assets:
- name: ./build/go-semantic-release
compress: false
- name: ./build/go-semantic-release.exe
compress: false
github:
repo: "go-semantic-release"
user: "nightapes"
customUrl: ""
release: "github"
github:
repo: "go-semantic-release"
user: "nightapes"
commitFormat: angular
branch:
master: release
beta: beta
assets:
- name: ./build/go-semantic-release.linux_x86_64
compress: true
- name: ./build/go-semantic-release.windows_i386.exe
compress: true
- name: ./build/go-semantic-release.windows_x86_64.exe
compress: true
- name: ./build/go-semantic-release.darwin_x86_64
compress: true
changelog:
docker:
latest: true
repository: "nightapes/go-semantic-release"
showAuthors: true
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.14
WORKDIR /code
COPY ./build/go-semantic-release.linux_x86_64 /usr/local/bin/go-semantic-release
USER 1000
ENTRYPOINT [ "go-semantic-release" ]

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 -parallel 20 ./...

347
README.md
View File

@@ -1,25 +1,348 @@
# go-semantic-release
![go-semantic-release](https://github.com/Nightapes/go-semantic-release/workflows/Go/badge.svg)
## Release Types
| Type | Git tag | Changelog | Release | Write access git | Api token |
|--- |:---: |:---: |:---: |:---: |:---: |
| `git` | :white_check_mark: | | | :white_check_mark:| |
| `github` | :white_check_mark: | :white_check_mark: | :white_check_mark:| | :white_check_mark: |
| `gitlab` | :white_check_mark: | :white_check_mark: | :white_check_mark:| | :white_check_mark: |
| Type | Implemented | Git tag | Changelog | Release | Write access git | Api token |
| ----------- | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: |
| `github` | :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: | | :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`
## Download
You can download the newest version under [releases](https://github.com/Nightapes/go-semantic-release/releases)
or
you can use a Docker image
`docker pull nightapes/go-semantic-release:<VERSION>` or `docker pull docker.pkg.github.com/nightapes/go-semantic-release/go-semantic-release:<VERSION>`
## Build
## How to use
`go build ./cmd/go-semantic-release/`
`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>`
## Run
### Example config
Print the next version
```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
integrations:
npm:
enabled: true
```
`./go-semantic-release version next`
#### CommitFormat
Set a version
Supported formats:
`./go-semantic-release version set v1.1.1`
* [angular](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format)
```yml
commitFormat: angular
```
* [conventional](https://www.conventionalcommits.org/en/v1.0.0/#summaryhttps://www.conventionalcommits.org/en/v1.0.0/#summary)
```yml
commitFormat: conventional
```
#### 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 you are not using github.com
customUrl: <https://your.github>
## Optional, if you want to change the default tag prefix ("v")
tagPrefix: ""
```
##### 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>
## Optional, if you want to change the default tag prefix ("v")
tagPrefix: ""
```
You can find an example `.gitlab-ci.yml` in the [examples](examples/.gitlab-ci.yml) folder.
##### 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
## Optional, if you want to change the default tag prefix ("v")
tagPrefix: ""
```
#### 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.
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
Following variables and objects can be used for templates:
__Top level__
| Field | Type | Description |
| -------- | ------ | ----- |
| `Commits` | string | Fully rendered commit messages. This is left for backward compatibility. |
| `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
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)
showAuthors: false ## Show authors in changelog
showBodyAsHeader: false ## Show all bodies of the commits as header of changelog (useful for squash commit flow to show long text in release)
```
##### 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
```
##### 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
`go-semantic-release` has two modes for calculating 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 // show next version (calculated by new commits since last version)
./go-semantic-release last // show last released version
```
Example with go-lang
```bash
go build -ldflags "--X main.version=`./go-semantic-release next`"
```
### Create release
```bash
./go-semantic-release release
```
### Write changelog to file
This will write all changes beginning from the last git tag til HEAD to a changelog file.
Default changelog file name if nothing is given via `--file`: `CHANGELOG.md`.
Note that per default the new changelog will be prepended to the existing file.
With `--max-file-size` a maximum sizes of the changelog file in megabytes can be specified.
If the size exceeds the limit, the current changelog file will be moved to a new file called `<filename>-<1-n>.<file extension>`. The new changelog will be written to the `<filename>`.
The default maximum file size limit is `10 megabytes`.
```bash
./go-semantic-release changelog --max-file-size 10
```
This will overwrite the given changelog file if its existing, if not it will be created.
```bash
./go-semantic-release changelog --overwrite
```
## Build from source
```bash
go build ./cmd/go-semantic-release/
```
### Testing
```bash
go test ./...
```
### Linting
```
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0
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

@@ -7,7 +7,11 @@ import (
)
func init() {
changelogCmd.Flags().Bool("checks", false, "Check for missing values and envs")
changelogCmd.Flags().Bool("overwrite", false, "Overwrite the content of the changelog. Default is to prepend the new changelog to the existing file.")
changelogCmd.Flags().StringP("out", "o", "CHANGELOG.md", "Name of the file")
changelogCmd.Flags().String("from", "", "Generate combined changelog from given version until latest version ")
changelogCmd.Flags().Int64("max-file-size", 10, "The max allowed file size in MB for a changelog file. If the file size is larger, the current file will be moved to a new file named <filename>-01.md. The next changelog will be written to de default file.")
rootCmd.AddCommand(changelogCmd)
}
@@ -30,12 +34,32 @@ var changelogCmd = &cobra.Command{
return err
}
overwrite, err := cmd.Flags().GetBool("overwrite")
if err != nil {
return err
}
file, err := cmd.Flags().GetString("out")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository)
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
fromVersion, err := cmd.Flags().GetString("from")
if err != nil {
return err
}
maxFileSize, err := cmd.Flags().GetInt64("max-file-size")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, configChecks)
if err != nil {
return err
}
@@ -45,7 +69,7 @@ var changelogCmd = &cobra.Command{
return err
}
releaseVersion, err := s.GetNextVersion(provider, force)
releaseVersion, err := s.GetNextVersion(provider, force, fromVersion)
if err != nil {
return err
}
@@ -55,11 +79,6 @@ var changelogCmd = &cobra.Command{
if err != nil {
return err
}
if err = s.WriteChangeLog(generatedChangelog.Content, file); err != nil {
log.Fatal(err)
}
return nil
return s.WriteChangeLog(generatedChangelog.Content, file, overwrite, maxFileSize)
},
}

View File

@@ -0,0 +1,65 @@
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() {
hooksCmd.Flags().Bool("checks", false, "Check for missing values and envs")
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
}
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
}
hook := hooks.New(releaseConfig, releaseVersion)
err = hook.PreRelease()
if err != nil {
return err
}
return hook.PostRelease()
},
}

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

@@ -0,0 +1,59 @@
package commands
import (
"fmt"
"github.com/Nightapes/go-semantic-release/pkg/semanticrelease"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
lastCmd.Flags().Bool("checks", false, "Check for missing values and envs")
rootCmd.AddCommand(lastCmd)
}
var lastCmd = &cobra.Command{
Use: "last",
Short: "Get last version",
RunE: func(cmd *cobra.Command, args []string) error {
config, err := cmd.Flags().GetString("config")
if err != nil {
return err
}
repository, err := cmd.Flags().GetString("repository")
if err != nil {
return err
}
force, err := cmd.Flags().GetBool("no-cache")
if err != nil {
return err
}
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, configChecks)
if err != nil {
return err
}
provider, err := s.GetCIProvider()
if err != nil {
log.Infof("Will not calculate version, set fake version. Could not find CI Provider, if running locally, set env CI=true")
fmt.Println("0.0.0-fake.0")
return nil
}
releaseVersion, err := s.GetNextVersion(provider, force, "")
if err != nil {
return err
}
fmt.Println(releaseVersion.Last.Version.String())
return nil
},
}

View File

@@ -8,6 +8,7 @@ import (
)
func init() {
nextCmd.Flags().Bool("checks", false, "Check for missing values and envs")
rootCmd.AddCommand(nextCmd)
}
@@ -30,7 +31,12 @@ var nextCmd = &cobra.Command{
return err
}
s, err := semanticrelease.New(readConfig(config), repository)
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, configChecks)
if err != nil {
return err
}
@@ -43,7 +49,7 @@ var nextCmd = &cobra.Command{
return nil
}
releaseVersion, err := s.GetNextVersion(provider, force)
releaseVersion, err := s.GetNextVersion(provider, force, "")
if err != nil {
return err
}

View File

@@ -6,6 +6,7 @@ import (
)
func init() {
releaseCmd.Flags().Bool("no-checks", false, "Ignore missing values and envs")
rootCmd.AddCommand(releaseCmd)
}
@@ -28,7 +29,12 @@ var releaseCmd = &cobra.Command{
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 {
return err
}

View File

@@ -7,6 +7,7 @@ import (
)
func init() {
setCmd.Flags().Bool("checks", false, "Check for missing values and envs")
rootCmd.AddCommand(setCmd)
}
@@ -26,7 +27,12 @@ var setCmd = &cobra.Command{
return err
}
s, err := semanticrelease.New(readConfig(config), repository)
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, configChecks)
if err != nil {
return err
}

View File

@@ -6,12 +6,14 @@ import (
)
func init() {
zipCmd.Flags().Bool("checks", false, "Check for missing values and envs")
zipCmd.Flags().StringP("algorithm", "a", "sha256", "Algorithm for checksum (crc32,md5,sha1,sha224,sha384,sha256,sha512)")
rootCmd.AddCommand(zipCmd)
}
var zipCmd = &cobra.Command{
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 {
config, err := cmd.Flags().GetString("config")
if err != nil {
@@ -23,7 +25,12 @@ var zipCmd = &cobra.Command{
return err
}
s, err := semanticrelease.New(readConfig(config), repository)
configChecks, err := cmd.Flags().GetBool("checks")
if err != nil {
return err
}
s, err := semanticrelease.New(readConfig(config), repository, configChecks)
if err != nil {
return err
}

12
examples/.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,12 @@
stages:
- release
release:
stage: release
image:
name: nightapes/go-semantic-release:latest
entrypoint: [""]
script:
- go-semantic-release next
only:
- master

37
examples/changelog.tmpl Normal file
View File

@@ -0,0 +1,37 @@
{{ 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"}})
{{ template "commitList" .CommitsContent -}}
{{ 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 -}}

View File

@@ -0,0 +1,29 @@
name: Go
on: [ push, pull_request ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Init go-semantic-release
run: |
wget https://github.com/Nightapes/go-semantic-release/releases/download/v2.0.1/go-semantic-release.linux_x86_64.zip
unzip go-semantic-release.linux_x86_64.zip
chmod +x go-semantic-release.linux_x86_64
- name: Build binary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o <your-build-name>.linux_x86_64
GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -o <your-build-name>.windows_i386.exe
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o <your-build-name>.windows_x86_64.exe
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o <your-build-name>.darwin_x86_64
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./go-semantic-release.linux_x86_64 release --loglevel trace

58
go.mod
View File

@@ -1,22 +1,48 @@
module github.com/Nightapes/go-semantic-release
go 1.12
go 1.17
require (
github.com/Masterminds/semver v1.4.2
github.com/gliderlabs/ssh v0.2.2 // indirect
github.com/google/go-cmp v0.3.0 // indirect
github.com/Masterminds/semver v1.5.0
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
github.com/google/go-github/v25 v25.1.3
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
google.golang.org/appengine v1.6.1 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.1
gopkg.in/src-d/go-git.v4 v4.12.0
gopkg.in/yaml.v2 v2.2.2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.0
github.com/tidwall/sjson v1.2.4
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emirpasic/gods v1.12.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/gjson v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 // indirect
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

533
go.sum
View File

@@ -1,156 +1,507 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU=
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/emirpasic/gods v1.12.1 h1:KEXpRg94qvWNpl3F8PRlzJRFhy1kr6SiBiFH6X2Nwp8=
github.com/emirpasic/gods v1.12.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v25 v25.1.3 h1:Ht4YIQgUh4l4lc80fvGnw60khXysXvlgPxPP8uJG3EA=
github.com/google/go-github/v25 v25.1.3/go.mod h1:6z5pC69qHtrPJ0sXPsj4BLnd82b+r6sLB7qcBoRZqpw=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-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-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 h1:lkiLiLBHGoH3XnqSLUIaBsilGMUjI+Uy2Xu2JLUtTas=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-billy.v4 v4.3.1 h1:OkK1DmefDy1Z6Veu82wdNj/cLpYORhdX4qdaYCPwc7s=
gopkg.in/src-d/go-billy.v4 v4.3.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.12.0 h1:CKgvBCJCcdfNnyXPYI4Cp8PaDDAmAPEN0CtfEdEAbd8=
gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@@ -2,20 +2,30 @@
package analyzer
import (
"bufio"
"fmt"
"regexp"
"strings"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus"
)
//Analyzer struct
const breakingChangeKeywords = "BREAKING CHANGE"
const defaultBreakingChangePrefix = breakingChangeKeywords + ":"
const footerTokenRegex = "^(?P<token>[^\\s*-][\\w\\- ]+[^\\s])<SEP>.*"
var defaultTokenSeparators = [2]string{": ", " #"}
// Analyzer struct
type Analyzer struct {
analyzeCommits analyzeCommits
Config config.ChangelogConfig
analyzeCommits analyzeCommits
ChangelogConfig config.ChangelogConfig
AnalyzerConfig config.AnalyzerConfig
}
//Rule for commits
// Rule for commits
type Rule struct {
Tag string
TagString string
@@ -24,25 +34,28 @@ type Rule struct {
}
type analyzeCommits interface {
analyze(commit shared.Commit, tag Rule) (shared.AnalyzedCommit, bool, error)
analyze(commit shared.Commit, tag Rule) *shared.AnalyzedCommit
getRules() []Rule
}
//New Analyzer struct for given commit format
func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
// New Analyzer struct for given commit format
func New(format string, analyzerConfig config.AnalyzerConfig, chglogConfig config.ChangelogConfig) (*Analyzer, error) {
analyzer := &Analyzer{
Config: config,
AnalyzerConfig: analyzerConfig,
ChangelogConfig: chglogConfig,
}
switch format {
case ANGULAR:
analyzer.analyzeCommits = newAngular()
log.Debugf("Commit format set to %s", ANGULAR)
case CONVENTIONAL:
analyzer.analyzeCommits = newConventional(analyzerConfig)
log.Debugf("Commit format set to %s", CONVENTIONAL)
default:
return nil, fmt.Errorf("invalid commit format: %s", format)
}
return analyzer, nil
}
// GetRules from current mode
@@ -50,9 +63,8 @@ func (a *Analyzer) GetRules() []Rule {
return a.analyzeCommits.getRules()
}
// Analyze commits and return commits splitted by major,minor,patch
// Analyze commits and return commits split by major,minor,patch
func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.AnalyzedCommit {
analyzedCommits := make(map[shared.Release][]shared.AnalyzedCommit)
analyzedCommits["major"] = make([]shared.AnalyzedCommit, 0)
analyzedCommits["minor"] = make([]shared.AnalyzedCommit, 0)
@@ -61,25 +73,132 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
for _, commit := range commits {
for _, rule := range a.analyzeCommits.getRules() {
analyzedCommit, hasBreakingChange, err := a.analyzeCommits.analyze(commit, rule)
if err == nil {
if a.Config.PrintAll {
analyzedCommit.Print = true
} else {
analyzedCommit.Print = rule.Changelog
}
if hasBreakingChange {
analyzedCommits["major"] = append(analyzedCommits["major"], analyzedCommit)
} else {
analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], analyzedCommit)
}
analyzedCommit := a.analyzeCommits.analyze(commit, rule)
if analyzedCommit == nil {
continue
}
if a.ChangelogConfig.PrintAll || rule.Changelog {
analyzedCommit.Print = true
}
if analyzedCommit.IsBreaking {
analyzedCommits["major"] = append(analyzedCommits["major"], *analyzedCommit)
break
}
analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], *analyzedCommit)
break
}
}
log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"]))
return analyzedCommits
}
//
// 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) {
_, err := analyzer.New("unknown", config.ChangelogConfig{})
_, err := analyzer.New("unknown", config.AnalyzerConfig{}, config.ChangelogConfig{})
assert.Error(t, err)
}

View File

@@ -2,8 +2,7 @@
package analyzer
import (
"fmt"
"regexp"
"github.com/Nightapes/go-semantic-release/pkg/config"
"strings"
log "github.com/sirupsen/logrus"
@@ -12,17 +11,20 @@ import (
)
type angular struct {
rules []Rule
regex string
log *log.Entry
rules []Rule
regex string
log *log.Entry
config config.AnalyzerConfig
}
// ANGULAR identifer
// ANGULAR identifier
const ANGULAR = "angular"
var angularFooterTokenSep = defaultTokenSeparators
func newAngular() *angular {
return &angular{
regex: `(TAG)(?:\((.*)\))?: (.*)`,
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?: (?P<subject>.*)`,
log: log.WithField("analyzer", ANGULAR),
rules: []Rule{
{
@@ -36,12 +38,14 @@ func newAngular() *angular {
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
}, {
},
{
Tag: "perf",
TagString: "Performance improvments",
TagString: "Performance improvements",
Release: "patch",
Changelog: true,
}, {
},
{
Tag: "docs",
TagString: "Documentation changes",
Release: "none",
@@ -52,22 +56,26 @@ func newAngular() *angular {
TagString: "Style",
Release: "none",
Changelog: false,
}, {
},
{
Tag: "refactor",
TagString: "Code refactor",
Release: "none",
Changelog: false,
}, {
},
{
Tag: "test",
TagString: "Testing",
Release: "none",
Changelog: false,
}, {
},
{
Tag: "chore",
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
Release: "none",
Changelog: false,
}, {
},
{
Tag: "build",
TagString: "Changes to CI/CD",
Release: "none",
@@ -81,38 +89,54 @@ func (a *angular) getRules() []Rule {
return a.rules
}
func (a *angular) analyze(commit shared.Commit, rule Rule) (shared.AnalyzedCommit, bool, error) {
func (a *angular) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
tokenSep := append(a.config.TokenSeparators, angularFooterTokenSep[:]...)
analyzed := shared.AnalyzedCommit{
Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
firstSplit := strings.SplitN(commit.Message, "\n", 2)
header := firstSplit[0]
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
}
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
matches := re.FindAllStringSubmatch(commit.Message, -1)
if len(matches) >= 1 {
if len(matches[0]) >= 3 {
msgBlockMap := getDefaultMessageBlockMap(body, tokenSep)
analyzed.Scope = shared.Scope(matches[0][2])
log.Debugf("Found commit from Author %s", commit.Author)
message := strings.Join(matches[0][3:], "")
if !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false, nil
}
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
analyzed.ParsedMessage = strings.Trim(breakingChange[0], " ")
analyzed.ParsedBreakingChangeMessage = strings.Trim(breakingChange[1], " ")
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil
}
analyzed := &shared.AnalyzedCommit{
Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
Scope: shared.Scope(matches["scope"]),
Subject: strings.TrimSpace(matches["subject"]),
MessageBlocks: msgBlockMap,
}
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return analyzed, false, fmt.Errorf("not found")
isBreaking := strings.Contains(commit.Message, defaultBreakingChangePrefix)
analyzed.IsBreaking = isBreaking
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
if !isBreaking {
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed
}
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
} else {
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
}
return analyzed
}

View File

@@ -10,7 +10,7 @@ import (
)
func TestAngular(t *testing.T) {
t.Parallel()
testConfigs := []struct {
testCase string
commits []shared.Commit
@@ -19,8 +19,8 @@ func TestAngular(t *testing.T) {
{
testCase: "feat",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
@@ -31,14 +31,16 @@ func TestAngular(t *testing.T) {
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"major": []shared.AnalyzedCommit{},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
"major": {},
"patch": {},
"none": {},
},
commits: []shared.Commit{
shared.Commit{
{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
@@ -48,8 +50,8 @@ func TestAngular(t *testing.T) {
{
testCase: "feat breaking change",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
@@ -60,10 +62,12 @@ func TestAngular(t *testing.T) {
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"major": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"major": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me",
@@ -75,18 +79,21 @@ func TestAngular(t *testing.T) {
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "change api to v2",
IsBreaking: true,
Subject: "my first break BREAKING CHANGE: change api to v2",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
"patch": {},
"none": {},
},
commits: []shared.Commit{
shared.Commit{
{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
shared.Commit{
{
Message: "feat(internal/changelog): my first break BREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
@@ -94,26 +101,22 @@ func TestAngular(t *testing.T) {
},
},
{
testCase: "invalid",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{},
"major": []shared.AnalyzedCommit{},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
},
testCase: "feat breaking change footer",
commits: []shared.Commit{
shared.Commit{
Message: "internal/changelog: my first commit",
{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat(internal/changelog): my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
},
},
{
testCase: "feat and build",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first commit",
Author: "me",
@@ -124,10 +127,81 @@ func TestAngular(t *testing.T) {
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"none": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"major": {
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
Scope: "internal/changelog",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
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": {},
"none": {},
},
},
{
testCase: "invalid",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {},
"major": {},
"patch": {},
"none": {},
},
commits: []shared.Commit{
{
Message: "internal/changelog: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "Merge feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
},
},
{
testCase: "feat and build",
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{},
},
},
"none": {
{
Commit: shared.Commit{
Message: "build(internal/changelog): my first build",
Author: "me",
@@ -139,18 +213,20 @@ func TestAngular(t *testing.T) {
TagString: "Changes to CI/CD",
Print: false,
ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"patch": []shared.AnalyzedCommit{},
"major": []shared.AnalyzedCommit{},
"patch": {},
"major": {},
},
commits: []shared.Commit{
shared.Commit{
{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
shared.Commit{
{
Message: "build(internal/changelog): my first build",
Author: "me",
Hash: "12345668",
@@ -159,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)
for _, test := range testConfigs {
@@ -169,5 +245,4 @@ func TestAngular(t *testing.T) {
assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
}
}

View File

@@ -0,0 +1,143 @@
// Package analyzer provides different commit analyzer
package analyzer
import (
"github.com/Nightapes/go-semantic-release/pkg/config"
"strings"
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/internal/shared"
)
type conventional struct {
rules []Rule
regex string
log *log.Entry
config config.AnalyzerConfig
}
// CONVENTIONAL identifier
const CONVENTIONAL = "conventional"
var conventionalFooterTokenSep = defaultTokenSeparators
func newConventional(config config.AnalyzerConfig) *conventional {
return &conventional{
config: config,
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?(?P<breaking>\!)?: (?P<subject>.*)`,
log: log.WithField("analyzer", CONVENTIONAL),
rules: []Rule{
{
Tag: "feat",
TagString: "Features",
Release: "minor",
Changelog: true,
},
{
Tag: "fix",
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
},
{
Tag: "perf",
TagString: "Performance improvements",
Release: "patch",
Changelog: true,
},
{
Tag: "docs",
TagString: "Documentation changes",
Release: "none",
Changelog: false,
},
{
Tag: "style",
TagString: "Style",
Release: "none",
Changelog: false,
},
{
Tag: "refactor",
TagString: "Code refactor",
Release: "none",
Changelog: false,
},
{
Tag: "test",
TagString: "Testing",
Release: "none",
Changelog: false,
},
{
Tag: "chore",
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
Release: "none",
Changelog: false,
},
{
Tag: "build",
TagString: "Changes to CI/CD",
Release: "none",
Changelog: false,
},
},
}
}
func (a *conventional) getRules() []Rule {
return a.rules
}
func (a *conventional) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
tokenSep := append(a.config.TokenSeparators, conventionalFooterTokenSep[:]...)
firstSplit := strings.SplitN(commit.Message, "\n", 2)
header := firstSplit[0]
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{
Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
Scope: shared.Scope(matches["scope"]),
Subject: strings.TrimSpace(matches["subject"]),
MessageBlocks: msgBlockMap,
}
isBreaking := matches["breaking"] == "!" || strings.Contains(commit.Message, defaultBreakingChangePrefix)
analyzed.IsBreaking = isBreaking
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
if !isBreaking {
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed
}
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
} else {
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
}
return analyzed
}

View File

@@ -0,0 +1,496 @@
package analyzer_test
import (
"testing"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
)
func TestConventional(t *testing.T) {
t.Parallel()
testConfigs := []struct {
testCase string
commits []shared.Commit
wantAnalyzedCommits map[shared.Release][]shared.AnalyzedCommit
}{
{
testCase: "feat",
wantAnalyzedCommits: 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",
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
Print: true,
},
{
Commit: shared.Commit{
Message: "feat: no scope",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "no scope",
Tag: "feat",
TagString: "Features",
Subject: "no scope",
MessageBlocks: map[string][]shared.MessageBlock{},
Print: true,
},
},
"major": {},
"patch": {},
"none": {},
},
commits: []shared.Commit{
{
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat: no scope",
Author: "me",
Hash: "12345667",
},
},
},
{
testCase: "feat breaking change",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"major": {
{
Commit: shared.Commit{
Message: "feat!: my first break",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "",
Tag: "feat",
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "my first break",
IsBreaking: true,
Subject: "my first break",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"patch": {},
"none": {},
},
commits: []shared.Commit{
{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat!: my first break",
Author: "me",
Hash: "12345668",
},
},
},
{
testCase: "feat breaking change footer",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"major": {
{
Commit: shared.Commit{
Message: "feat: my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
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{
Message: "feat!: my first break \n\nBREAKING CHANGE: hey from the change",
Author: "me",
Hash: "12345669",
},
Scope: "",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
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": {},
"none": {},
},
commits: []shared.Commit{
{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat: my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
{
Message: "feat!: my first break \n\nBREAKING CHANGE: hey from the change",
Author: "me",
Hash: "12345669",
},
},
},
{
testCase: "invalid",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {},
"major": {},
"patch": {},
"none": {},
},
commits: []shared.Commit{
{
Message: "internal/changelog: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "Merge feat: my first commit",
Author: "me",
Hash: "12345667",
},
},
},
{
testCase: "feat and build",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"none": {
{
Commit: shared.Commit{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my first build",
Tag: "build",
TagString: "Changes to CI/CD",
Print: false,
ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"patch": {},
"major": {},
},
commits: []shared.Commit{
{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
},
},
{
testCase: "fix and build",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {},
"none": {
{
Commit: shared.Commit{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my first build",
Tag: "build",
TagString: "Changes to CI/CD",
Print: false,
ParsedBreakingChangeMessage: "",
Subject: "my first build",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
"patch": {{
Commit: shared.Commit{
Message: "fix: my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "fix",
TagString: "Bug fixes",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
}},
"major": {},
},
commits: []shared.Commit{
{
Message: "fix: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
},
},
}
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.wantAnalyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase)
assert.Equalf(t, test.wantAnalyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase)
assert.Equalf(t, test.wantAnalyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch 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)
}
}

179
internal/assets/asset.go Normal file
View File

@@ -0,0 +1,179 @@
package assets
import (
"archive/zip"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"hash/crc32"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// Asset struct
type Asset struct {
name string
path string
zippedPath string
algorithm string
isCompressed bool
}
//NewAsset from a config
func NewAsset(repository string, assetConfig config.Asset, algorithm string) (*Asset, error) {
filePath := assetConfig.Path
if assetConfig.Name != "" && assetConfig.Path == "" {
filePath = assetConfig.Name
log.Warn("Name is deprecated. Please update your config. See https://nightapes.github.io/go-semantic-release/")
}
realPath := path.Join(repository, filePath)
file, err := os.Open(realPath)
if err != nil {
file.Close()
return nil, errors.Wrapf(err, "Could not open file %s", realPath)
}
defer file.Close()
name := assetConfig.Rename
if assetConfig.Rename == "" {
info, _ := file.Stat()
name = info.Name()
}
asset := &Asset{
path: realPath,
name: name,
isCompressed: assetConfig.Compress,
algorithm: algorithm,
}
return asset, nil
}
func (a *Asset) getChecksum() (string, error) {
path, err := a.GetPath()
if err != nil {
return "", nil
}
log.Debugf("Calculating checksum for %s", path)
file, err := os.Open(path)
if err != nil {
return "", errors.Wrapf(err, "Failed to open file %s to calculate checksum", a.name)
}
defer file.Close() // nolint: errcheck
var hash hash.Hash
switch a.algorithm {
case "crc32":
hash = crc32.NewIEEE()
case "md5":
hash = md5.New()
case "sha1":
hash = sha1.New()
case "sha224":
hash = sha256.New224()
case "sha384":
hash = sha512.New384()
case "sha256":
hash = sha256.New()
case "sha512":
hash = sha512.New()
default:
hash = sha256.New()
}
_, err = io.Copy(hash, file)
if err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// GetPath where the file is located, if zipped true, it will compress it and give you the new location
func (a *Asset) GetPath() (string, error) {
if a.isCompressed {
return a.ZipFile()
}
return a.path, nil
}
// GetName of asset
func (a *Asset) GetName() string {
if a.isCompressed {
return fmt.Sprintf("%s.zip", a.name)
}
return a.name
}
// IsCompressed return true if file was zipped
func (a *Asset) IsCompressed() bool {
return a.isCompressed
}
// ZipFile compress given file in zip format
func (a *Asset) ZipFile() (string, error) {
if a.zippedPath != "" {
return a.zippedPath, nil
}
path := a.path
fileToZip, err := os.Open(path)
if err != nil {
return "", errors.Wrapf(err, "Could not open file %s", path)
}
defer fileToZip.Close()
zipFile, err := ioutil.TempFile(os.TempDir(), "asset.*.zip")
if err != nil {
return "", errors.Wrap(err, "Could not generate tmp file")
}
log.Debugf("Created zipfile %s", zipFile.Name())
fileToZipInfo, err := fileToZip.Stat()
if err != nil {
return "", errors.Wrap(err, "Could not read file infos")
}
zipWriter := zip.NewWriter(zipFile)
fileToZipHeader, err := zip.FileInfoHeader(fileToZipInfo)
if err != nil {
return "", errors.Wrap(err, "Could not add file infos to zip handler")
}
fileToZipHeader.Name = fileToZipInfo.Name()
fileToZipWriter, err := zipWriter.CreateHeader(fileToZipHeader)
if err != nil {
return "", errors.Wrap(err, "Could not create zip header")
}
if _, err = io.Copy(fileToZipWriter, fileToZip); err != nil {
return "", errors.Wrap(err, "Could not zip file")
}
if err := zipWriter.Close(); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("Could not close zipwriter for zip %s", a.path))
}
if err := zipFile.Close(); err != nil {
return "", errors.Wrap(err, "Could not close file")
}
a.zippedPath, err = filepath.Abs(zipFile.Name())
return a.zippedPath, err
}

79
internal/assets/assets.go Normal file
View File

@@ -0,0 +1,79 @@
package assets
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/pkg/errors"
)
// Set struct
type Set struct {
assets []*Asset
repository string
algorithm string
}
//New container for assets
func New(repository, algorithm string) *Set {
return &Set{
assets: []*Asset{},
repository: repository,
algorithm: algorithm,
}
}
// Add assets to the list
func (s *Set) Add(assetConfigs ...config.Asset) error {
for _, assetConfig := range assetConfigs {
asset, err := NewAsset(s.repository, assetConfig, s.algorithm)
if err != nil {
return err
}
s.assets = append(s.assets, asset)
}
return nil
}
func (s *Set) All() []*Asset {
return s.assets
}
func (s *Set) GenerateChecksum() error {
checksumFile, err := ioutil.TempFile(os.TempDir(), "checksum.*.txt")
if err != nil {
return errors.Wrap(err, "Could not generate tmp file for checksum")
}
defer checksumFile.Close()
lines := []string{}
for _, asset := range s.assets {
checksum, err := asset.getChecksum()
if err != nil {
return err
}
lines = append(lines, fmt.Sprintf("%s %s", checksum, asset.GetName()))
}
w := bufio.NewWriter(checksumFile)
for _, line := range lines {
fmt.Fprintln(w, line)
}
filePath, err := filepath.Abs(checksumFile.Name())
if err != nil {
return err
}
s.assets = append(s.assets, &Asset{
path: filePath,
name: "checksum.txt",
isCompressed: false,
algorithm: "",
})
return w.Flush()
}

View File

@@ -56,19 +56,21 @@ func TestWriteAndReadCache(t *testing.T) {
},
Branch: "master",
Commits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"major": {
{
Commit: shared.Commit{
Message: "Message",
Author: "Author",
Hash: "Hash",
},
ParsedMessage: "add gitlab as relase option",
ParsedMessage: "add gitlab as release option",
Scope: "releaser",
ParsedBreakingChangeMessage: "",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "add gitlab as release option",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
},

View File

@@ -19,9 +19,9 @@ func New() *Calculator {
}
//IncPrerelease increase prerelease by one
func (c *Calculator) IncPrerelease(preReleaseType string, version *semver.Version) (semver.Version, error) {
func (c *Calculator) IncPrerelease(preReleaseType string, version semver.Version) (semver.Version, error) {
defaultPrerelease := preReleaseType + ".0"
if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) {
if !c.hasPrerelease(version, preReleaseType) {
return version.SetPrerelease(defaultPrerelease)
}
@@ -32,32 +32,56 @@ func (c *Calculator) IncPrerelease(preReleaseType string, version *semver.Versio
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)))
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)
}
func (c *Calculator) hasPrerelease(version semver.Version, preReleaseType string) bool {
if version.Prerelease() == "" || !strings.HasPrefix(version.Prerelease(), preReleaseType) {
return false
}
return true
}
//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":
var version = *lastVersion
if !c.hasPrerelease(*lastVersion, releaseType) {
version, _ = c.inc(commits, lastVersion)
}
if len(commits["major"]) > 0 || len(commits["minor"]) > 0 || len(commits["patch"]) > 0 {
version, _ := c.IncPrerelease(releaseType, lastVersion)
version, _ := c.IncPrerelease(releaseType, version)
return version
}
case "release":
if !firstRelease {
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()
if lastVersion.Prerelease() != "" {
newVersion, _ := lastVersion.SetPrerelease("")
return newVersion
}
version, done := c.inc(commits, lastVersion)
if done {
return version
}
}
}
return *lastVersion
}
func (c *Calculator) inc(commits map[shared.Release][]shared.AnalyzedCommit, lastVersion *semver.Version) (semver.Version, bool) {
if len(commits["major"]) > 0 {
return lastVersion.IncMajor(), true
} else if len(commits["minor"]) > 0 {
return lastVersion.IncMinor(), true
} else if len(commits["patch"]) > 0 {
return lastVersion.IncPatch(), true
}
return semver.Version{}, false
}

View File

@@ -58,9 +58,11 @@ func TestCalculator_IncPrerelease(t *testing.T) {
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())
t.Run(test.testCase, func(t *testing.T) {
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())
})
}
}
@@ -79,14 +81,14 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
testCase: "version with preRelease alpha",
releaseType: "alpha",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-alpha.0",
nextVersion: "1.1.0-alpha.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{},
"major": {},
"minor": {
{},
},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
"patch": {},
"none": {},
},
isFirst: false,
},
@@ -94,14 +96,14 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
testCase: "version with preRelease beta",
releaseType: "beta",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-beta.0",
nextVersion: "1.1.0-beta.0",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"major": []shared.AnalyzedCommit{},
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{},
"major": {},
"minor": {
{},
},
"patch": []shared.AnalyzedCommit{},
"none": []shared.AnalyzedCommit{},
"patch": {},
"none": {},
},
isFirst: false,
},
@@ -111,10 +113,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {},
"minor": {},
"patch": {},
"none": {},
},
isFirst: false,
},
@@ -124,10 +126,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {{}},
"minor": {},
"patch": {},
"none": {},
},
isFirst: true,
},
@@ -135,12 +137,12 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
testCase: "version with commits and rc release",
releaseType: "rc",
lastVersion: createVersion("1.0.0"),
nextVersion: "1.0.0-rc.0",
nextVersion: "2.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{},
"major": {{}},
"minor": {{}},
"patch": {{}},
"none": {},
},
isFirst: false,
},
@@ -150,10 +152,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {},
"minor": {{}},
"patch": {{}},
"none": {},
},
isFirst: false,
},
@@ -163,10 +165,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {{}},
"minor": {{}},
"patch": {{}},
"none": {},
},
isFirst: false,
},
@@ -176,10 +178,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {},
"minor": {{}},
"patch": {{}},
"none": {},
},
isFirst: false,
},
@@ -189,10 +191,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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{},
"major": {},
"minor": {},
"patch": {{}},
"none": {},
},
isFirst: false,
},
@@ -201,8 +203,10 @@ func TestCalculator_CalculateNewVersion(t *testing.T) {
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)
t.Run(test.testCase, func(t *testing.T) {
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

@@ -1,7 +1,10 @@
package changelog
import (
"bufio"
"bytes"
"io/ioutil"
"sort"
"strings"
"text/template"
"time"
@@ -13,35 +16,114 @@ import (
log "github.com/sirupsen/logrus"
)
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ range $index,$commit := .BreakingChanges -}}
{{ if eq $index 0 }}
const defaultCommitList string = `{{ range $index,$commit := .BreakingChanges -}}
{{ if eq $index 0 -}}
## BREAKING CHANGES
{{ end}}
* **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedBreakingChangeMessage}}
introduced by commit:
{{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}}
{{ end -}}
{{ range $key := .Order }}
{{ $commits := index $.Commits $key}} {{if $commits -}}
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}**{{ end }} {{$commit.ParsedBreakingChangeMessage}}
introduced by commit:
{{$commit.Subject}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
{{ end -}}
{{ range $key := .Order -}}
{{ $commits := index $.Commits $key -}}
{{ if $commits -}}
### {{ $key }}
{{ range $index,$commit := $commits -}}
* **{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})) {{end}}
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{end}}{{$commit.Subject}}{{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
{{ if not $.ShowBodyAsHeader -}}
{{ if $commit.MessageBlocks.body -}}
{{ range $indexBlock,$bodyBlock := $commit.MessageBlocks.body -}}
{{ addPrefixToLines $bodyBlock.Content " > "}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}`
const defaultCommitListSubTemplate = `{{ define "commitList" }}` + defaultCommitList + "{{ end }}"
const defaultChangelogTitle = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
const defaultChangelog = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
{{ if .ShowBodyAsHeader -}}
{{ range $key := .CommitsContent.Order -}}
{{ $commits := index $.CommitsContent.Commits $key -}}
{{ if $commits -}}
{{ range $index,$commit := $commits -}}
{{ if $commit.MessageBlocks.body -}}
{{ range $indexBlock,$bodyBlock := $commit.MessageBlocks.body -}}
{{ $bodyBlock.Content }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
{{ template "commitList" .CommitsContent -}}
{{ 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 -}}
{{ 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 -}}
{{ if .ShowAuthors -}}
# Special Thanks
{{range $i,$a := .Authors}}{{if gt $i 0 }}, {{end}}{{$a}}{{end}}
{{ end -}}
`
type changelogContent struct {
Commits map[string][]shared.AnalyzedCommit
BreakingChanges []shared.AnalyzedCommit
Order []string
Version string
Now time.Time
Backtick string
HasURL bool
URL string
Commits string
CommitsContent commitsContent
Version string
Now time.Time
Backtick string
ShowBodyAsHeader bool
HasDocker bool
HasDockerLatest bool
DockerRepository string
HasNPM bool
IsYarn bool
NPMRepository string
NPMPackageName string
Authors []string
ShowAuthors bool
}
type commitsContent struct {
Commits map[string][]shared.AnalyzedCommit
BreakingChanges []shared.AnalyzedCommit
Order []string
ShowBodyAsHeader bool
Backtick string
HasURL bool
URL string
}
//Changelog struct
@@ -62,11 +144,11 @@ func New(config *config.ReleaseConfig, rules []analyzer.Rule, releaseTime time.T
}
}
// GenerateChanglog from given commits
func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
// GenerateChangelog from given commits
func (c *Changelog) GenerateChangelog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
commitsPerScope := map[string][]shared.AnalyzedCommit{}
commitsBreakingChange := []shared.AnalyzedCommit{}
var commitsBreakingChange []shared.AnalyzedCommit
order := make([]string, 0)
for _, rule := range c.rules {
@@ -76,10 +158,13 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
}
}
authors := map[string]bool{}
for _, commits := range analyzedCommits {
for _, commit := range commits {
authors[commit.Commit.Author] = true
if commit.Print {
if commit.ParsedBreakingChangeMessage != "" {
if commit.IsBreaking {
commitsBreakingChange = append(commitsBreakingChange, commit)
continue
}
@@ -91,30 +176,91 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
}
}
changelogContent := changelogContent{
Version: templateConfig.Version,
Commits: commitsPerScope,
Now: c.releaseTime,
BreakingChanges: commitsBreakingChange,
Backtick: "`",
Order: order,
HasURL: templateConfig.CommitURL != "",
URL: templateConfig.CommitURL,
commitsContent := commitsContent{
Commits: commitsPerScope,
BreakingChanges: commitsBreakingChange,
Backtick: "`",
Order: order,
ShowBodyAsHeader: c.config.Changelog.ShowBodyAsHeader,
HasURL: templateConfig.CommitURL != "",
URL: templateConfig.CommitURL,
}
title, err := generateTemplate(defaultChangelogTitle, changelogContent)
authorsNames := make([]string, len(authors))
i := 0
for k := range authors {
authorsNames[i] = k
i++
}
sort.Strings(authorsNames)
changelogContent := changelogContent{
CommitsContent: commitsContent,
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,
HasNPM: c.config.Changelog.NPM.PackageName != "",
NPMPackageName: c.config.Changelog.NPM.PackageName,
NPMRepository: c.config.Changelog.NPM.Repository,
ShowBodyAsHeader: c.config.Changelog.ShowBodyAsHeader,
ShowAuthors: c.config.Changelog.ShowAuthors && len(authors) > 0,
Authors: authorsNames,
}
chglogTemplate := defaultCommitListSubTemplate + defaultChangelog
if c.config.Changelog.TemplatePath != "" {
content, err := ioutil.ReadFile(c.config.Changelog.TemplatePath)
if err != nil {
return nil, err
}
chglogTemplate = string(content)
}
templateTitle := defaultChangelogTitle
if c.config.Changelog.TemplateTitle != "" {
templateTitle = c.config.Changelog.TemplateTitle
}
log.Debugf("Render title")
renderedTitle, err := generateTemplate(templateTitle, changelogContent, nil)
if err != nil {
return nil, err
}
content, err := generateTemplate(defaultChangelog, changelogContent)
return &shared.GeneratedChangelog{Title: title, Content: content}, err
log.Debugf("Render commits")
renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent, nil)
if err != nil {
return nil, err
}
log.Tracef("Commits %s", renderedCommitList)
changelogContent.Commits = renderedCommitList
extraFuncMap := template.FuncMap{
"commitUrl": func() string { return templateConfig.CommitURL },
}
log.Debugf("Render changelog")
renderedContent, err := generateTemplate(chglogTemplate, changelogContent, extraFuncMap)
return &shared.GeneratedChangelog{Title: renderedTitle, Content: renderedContent}, err
}
func generateTemplate(text string, values changelogContent) (string, error) {
func generateTemplate(text string, values interface{}, extraFuncMap template.FuncMap) (string, error) {
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
@@ -132,3 +278,30 @@ func generateTemplate(text string, values changelogContent) (string, error) {
func replace(input, from, to string) string {
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

@@ -25,14 +25,15 @@ func TestChangelog(t *testing.T) {
analyzedCommits map[shared.Release][]shared.AnalyzedCommit
result *shared.GeneratedChangelog
hasError bool
showAuthors bool
}{
{
testCase: "feat",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(test): my first commit",
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
@@ -41,22 +42,89 @@ func TestChangelog(t *testing.T) {
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ",
Content: "# v1.0.0 (2019-07-19)\n### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url))\n",
},
hasError: false,
},
{
testCase: "feat with authors",
showAuthors: true,
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{},
},
{
Commit: shared.Commit{
Message: "feat(internal/changelog): my second commit",
Author: "secondAuthor",
Hash: "12345667",
},
Scope: "internal/changelog",
ParsedMessage: "my second commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my second commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
},
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* **`internal/changelog`** my second commit ([1234566](https://commit.url))\n# Special Thanks\n\nme, secondAuthor\n"},
hasError: false,
},
{
testCase: "feat no scope",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n### Features\n* my first commit ([1234566](https://commit.url))\n",
},
hasError: false,
},
{
testCase: "feat breaking change",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": []shared.AnalyzedCommit{
shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat(test): my first commit",
Message: "feat(internal/changelog): my first commit",
Author: "me",
Hash: "12345667",
},
@@ -65,10 +133,12 @@ func TestChangelog(t *testing.T) {
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my first commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
shared.AnalyzedCommit{
{
Commit: shared.Commit{
Message: "feat(test): my first break: BREAKING CHANGE: change api to v2",
Message: "feat(internal/changelog): my first break: BREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
},
@@ -78,42 +148,267 @@ func TestChangelog(t *testing.T) {
TagString: "Features",
Print: true,
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",
},
},
},
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n\n## BREAKING CHANGES\n\n* **`internal/changelog`** change api to v2 \nintroduced by commit: \nmy first break ([1234566](https://commit.url)) \n\n ### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url)) \n\n ",
Content: "# v1.0.0 (2019-07-19)\n## BREAKING CHANGES\n* **`internal/changelog`** change api to v2 \nintroduced by commit: \nmy first break ([1234566](https://commit.url))\n### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url))\n",
},
hasError: false,
},
{
testCase: "conventional commits",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {
{
Commit: shared.Commit{
Message: "feat!: my first break \n\nBREAKING CHANGE: hey from the change",
Author: "me",
Hash: "12345669",
},
Scope: "",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
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{
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{},
},
{
Commit: shared.Commit{
Message: "feat: my second commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "feat",
TagString: "Features",
Print: true,
Subject: "my second commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
{
Commit: shared.Commit{
Message: "feat: my first break \n\nBREAKING CHANGE: change api to v2",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my first break",
Tag: "feat",
TagString: "Features",
Print: true,
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",
}},
},
},
{
Commit: shared.Commit{
Message: "feat: my awesome features \n\n * Feature1: Lists in changelog \n* Feature2: Lists in changelog2",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my awesome features",
Tag: "feat",
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "",
IsBreaking: false,
Subject: "my awesome features",
MessageBlocks: map[string][]shared.MessageBlock{
"body": {shared.MessageBlock{
Label: "",
Content: "* Feature1: Lists in changelog \n* Feature2: Lists in changelog2",
}},
},
},
{
Commit: shared.Commit{
Message: "feat!: my next commit",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "",
Tag: "feat",
TagString: "Features",
Print: true,
ParsedBreakingChangeMessage: "my next commit",
IsBreaking: true,
Subject: "my next commit",
MessageBlocks: map[string][]shared.MessageBlock{},
},
},
},
result: &shared.GeneratedChangelog{
Title: "v1.0.0 (2019-07-19)",
Content: "# v1.0.0 (2019-07-19)\n## BREAKING CHANGES\n* hey from the change \nintroduced by commit: \nmy first break ([1234566](https://commit.url))\n* change api to v2 \nintroduced by commit: \nmy first break ([1234566](https://commit.url))\n* my next commit \nintroduced by commit: \nmy next commit ([1234566](https://commit.url))\n### Features\n* **`internal/changelog`** my first commit ([1234566](https://commit.url))\n* my second commit ([1234566](https://commit.url))\n* my awesome features ([1234566](https://commit.url))\n > * Feature1: Lists in changelog \n > * Feature2: Lists in changelog2\n"},
hasError: false,
},
}
cl := changelog.New(&config.ReleaseConfig{}, []analyzer.Rule{
{
Tag: "feat",
TagString: "Features",
Release: "minor",
Changelog: true,
},
{
Tag: "fix",
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
},
{
Tag: "build",
TagString: "Build",
Release: "none",
Changelog: false,
},
}, time.Date(2019, 7, 19, 0, 0, 0, 0, time.UTC))
for _, testConfig := range testConfigs {
t.Run(testConfig.testCase, func(t *testing.T) {
cl := changelog.New(&config.ReleaseConfig{
Changelog: config.ChangelogConfig{
ShowBodyAsHeader: false,
ShowAuthors: testConfig.showAuthors,
},
}, []analyzer.Rule{
{
Tag: "feat",
TagString: "Features",
Release: "minor",
Changelog: true,
},
{
Tag: "fix",
TagString: "Bug fixes",
Release: "patch",
Changelog: true,
},
{
Tag: "build",
TagString: "Build",
Release: "none",
Changelog: false,
},
}, time.Date(2019, 7, 19, 0, 0, 0, 0, time.UTC))
for _, config := range testConfigs {
generatedChangelog, err := cl.GenerateChanglog(templateConfig, config.analyzedCommits)
assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err)
assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase)
generatedChangelog, err := cl.GenerateChangelog(templateConfig, testConfig.analyzedCommits)
assert.Equalf(t, testConfig.hasError, err != nil, "Testcase %s should have error: %t -> %s", testConfig.testCase, testConfig.hasError, err)
assert.Equalf(t, testConfig.result, generatedChangelog, "Testcase %s should have generated changelog", testConfig.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

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

@@ -6,11 +6,11 @@ import (
"github.com/Nightapes/go-semantic-release/internal/ci"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/stretchr/testify/assert"
"gopkg.in/src-d/go-billy.v4/memfs"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/storage/memory"
)
func TestCi(t *testing.T) {
@@ -111,10 +111,23 @@ func TestCi(t *testing.T) {
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 {
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.result, provider, "Service %s should have provider", config.service)
}

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,12 +5,14 @@ import (
"fmt"
"sort"
"github.com/pkg/errors"
"github.com/Masterminds/semver"
log "github.com/sirupsen/logrus"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
log "github.com/sirupsen/logrus"
)
// GitUtil struct
@@ -75,19 +77,35 @@ func (g *GitUtil) GetBranch() (string, error) {
}
// GetLastVersion from git tags
func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
func (g *GitUtil) GetVersion(version string) (*semver.Version, *plumbing.Reference, error) {
v, err := semver.NewVersion(version)
if err != nil {
return nil, nil, err
}
tag, err := g.Repository.Tag(version)
if err != nil {
return nil, nil, err
}
log.Debugf("Found old hash %s", tag.Hash().String())
return v, tag, nil
}
// GetLastVersion from git tags
func (g *GitUtil) GetLastVersion() (*semver.Version, *plumbing.Reference, error) {
var tags []*semver.Version
gitTags, err := g.Repository.Tags()
if err != nil {
return nil, "", err
return nil, nil, err
}
err = gitTags.ForEach(func(p *plumbing.Reference) error {
v, err := semver.NewVersion(p.Name().Short())
log.Tracef("%+v with hash: %s", p.Target(), p.Hash())
log.Tracef("Tag %+v with hash: %s", p.Name().Short(), p.Hash())
if err == nil {
tags = append(tags, v)
@@ -98,60 +116,84 @@ func (g *GitUtil) GetLastVersion() (*semver.Version, string, error) {
})
if err != nil {
return nil, "", err
return nil, nil, err
}
sort.Sort(sort.Reverse(semver.Collection(tags)))
if len(tags) == 0 {
log.Debugf("Found no tags")
return nil, "", nil
return nil, nil, nil
}
log.Debugf("Found old version %s", tags[0].String())
tag, err := g.Repository.Tag(tags[0].Original())
if err != nil {
return nil, "", err
return nil, nil, err
}
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
func (g *GitUtil) GetCommits(lastTagHash string) ([]shared.Commit, error) {
func (g *GitUtil) GetCommits(lastTagHash *plumbing.Reference) ([]shared.Commit, error) {
ref, err := g.Repository.Head()
if err != nil {
return nil, err
}
logOptions := &git.LogOptions{From: ref.Hash()}
cIter, err := g.Repository.Log(&git.LogOptions{From: ref.Hash(), Order: git.LogOrderCommitterTime})
if lastTagHash != nil {
logOptions = &git.LogOptions{From: lastTagHash.Hash()}
}
excludeIter, err := g.Repository.Log(logOptions)
if err != nil {
return nil, fmt.Errorf("could not get git log %w", 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 commits []shared.Commit
var foundEnd bool
var isValid object.CommitFilter = func(commit *object.Commit) bool {
_, ok := seen[commit.Hash]
return !ok && len(commit.ParentHashes) < 2
}
startCommit, err := g.Repository.CommitObject(ref.Hash())
if err != nil {
return nil, err
}
cIter := object.NewFilterCommitIter(startCommit, &isValid, nil)
commits := make(map[string]shared.Commit)
err = cIter.ForEach(func(c *object.Commit) error {
if c.Hash.String() == lastTagHash {
log.Debugf("Found commit with hash %s, will stop here", c.Hash.String())
foundEnd = true
}
if !foundEnd {
log.Tracef("Found commit with hash %s", c.Hash.String())
commit := shared.Commit{
Message: c.Message,
Author: c.Committer.Name,
Hash: c.Hash.String(),
}
commits = append(commits, commit)
log.Debugf("Found commit with hash %s from %s", c.Hash.String(), c.Author.Name)
commits[c.Hash.String()] = shared.Commit{
Message: c.Message,
Author: c.Author.Name,
Hash: c.Hash.String(),
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "Could not read commits, check git clone depth in your ci")
}
return commits, err
l := make([]shared.Commit, 0)
for _, value := range commits {
l = append(l, value)
}
return l, nil
}

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

@@ -0,0 +1,91 @@
package hooks
import (
"bufio"
"io"
"os"
"os/exec"
"runtime"
"strings"
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
)
//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)
}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "RELEASE_VERSION="+h.version.Next.Version.String())
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return err
}
h.printOutput(cmdReader, strings.Fields(cmdReplaced)[0])
cmdErrReader, err := cmd.StderrPipe()
if err != nil {
return err
}
h.printOutput(cmdErrReader, strings.Fields(cmdReplaced)[0])
return cmd.Run()
}
func (h *Hooks) printOutput(read io.ReadCloser, cmd string) {
scanner := bufio.NewScanner(read)
go func() {
for scanner.Scan() {
log.WithField("cmd", cmd).Infof("%s\n", scanner.Text())
}
}()
}

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,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

@@ -0,0 +1,107 @@
package git
import (
"fmt"
"time"
"github.com/Nightapes/go-semantic-release/internal/assets"
"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/go-git/go-git/v5"
gitConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/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, _ *assets.Set) error {
tagPrefix := config.DefaultTagPrefix
if g.config.TagPrefix != nil{
tagPrefix = *g.config.TagPrefix
}
tag := tagPrefix + 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

@@ -7,6 +7,7 @@ import (
"os"
"strings"
"github.com/Nightapes/go-semantic-release/internal/assets"
"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"
@@ -29,24 +30,37 @@ type Client struct {
}
// New initialize a new GitHubRelease
func New(c *config.GitHubProvider) (*Client, error) {
var err error
func New(c *config.GitHubProvider, checkConfig bool) (*Client, error) {
if c.AccessToken, err = util.GetAccessToken("GITHUB_TOKEN"); err != nil {
token, err := util.GetAccessToken("GITHUB_TOKEN")
if err != nil && checkConfig {
return &Client{}, err
}
c.AccessToken = token
ctx := context.Background()
httpClient := util.CreateBearerHTTPClient(ctx, c.AccessToken)
var client *github.Client
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 == "" {
client = github.NewClient(httpClient)
} 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
}
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{
config: c,
@@ -54,7 +68,7 @@ func New(c *config.GitHubProvider) (*Client, error) {
context: ctx,
baseURL: baseURL,
log: log.WithField("releaser", GITHUB),
}, err
}, nil
}
//GetCommitURL for github
@@ -67,26 +81,23 @@ func (g *Client) GetCompareURL(oldVersion, newVersion string) string {
return fmt.Sprintf("%s/%s/%s/compare/%s...%s", g.baseURL, g.config.User, g.config.Repo, oldVersion, newVersion)
}
//ValidateConfig for github
func (g *Client) ValidateConfig() error {
g.log.Debugf("validate GitHub provider config")
if g.config.Repo == "" {
return fmt.Errorf("github Repro is not set")
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog, assets *assets.Set) error {
err := g.makeRelease(releaseVersion, generatedChangelog)
if err != nil {
return err
}
if g.config.User == "" {
return fmt.Errorf("github User is not set")
}
return nil
return g.uploadAssets(assets)
}
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
func (g *Client) makeRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := releaseVersion.Next.Version.String()
tagPrefix := config.DefaultTagPrefix
if g.config.TagPrefix != nil {
tagPrefix = *g.config.TagPrefix
}
tag := tagPrefix + releaseVersion.Next.Version.String()
g.log.Debugf("create release with version %s", tag)
prerelease := releaseVersion.Next.Version.Prerelease() != ""
@@ -106,34 +117,32 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC
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")
g.log.Debugf("Release response: %+v", *release)
g.log.Infof("Created release")
return nil
}
// UploadAssets uploads specified assets
func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
func (g *Client) uploadAssets(assets *assets.Set) error {
if g.release != nil {
filesToUpload, err := util.PrepareAssets(repoDir, assets)
if err != nil {
return err
}
for _, f := range filesToUpload {
file, err := os.Open(*f)
for _, asset := range assets.All() {
path, err := asset.GetPath()
if err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
fileInfo, _ := file.Stat()
_, resp, err := g.client.Repositories.UploadReleaseAsset(g.context, g.config.User, g.config.Repo, g.release.GetID(), &github.UploadOptions{Name: fileInfo.Name()}, file)
_, resp, err := g.client.Repositories.UploadReleaseAsset(g.context, g.config.User, g.config.Repo, g.release.GetID(), &github.UploadOptions{Name: asset.GetName()}, file)
if err != nil {
return err
}
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("could not upload asset %s: %s", file.Name(), resp.Status)
return fmt.Errorf("could not upload asset %s: %s", asset.GetName(), resp.Status)
}
}
}

View File

@@ -1,4 +1,4 @@
package github_test
package github
import (
"fmt"
@@ -11,7 +11,6 @@ import (
"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"
@@ -32,14 +31,14 @@ type testReleaseStruct struct {
}
var testNewClient = []testHelperMethodStruct{
testHelperMethodStruct{config: config.GitHubProvider{
{config: config.GitHubProvider{
Repo: "foo",
User: "bar",
},
valid: true,
},
testHelperMethodStruct{config: config.GitHubProvider{
{config: config.GitHubProvider{
Repo: "foo",
User: "bar",
CustomURL: "https://test.com",
@@ -48,34 +47,11 @@ var testNewClient = []testHelperMethodStruct{
},
}
var testHelperMethod = []testHelperMethodStruct{
testHelperMethodStruct{config: config.GitHubProvider{
Repo: "foo",
User: "bar",
},
valid: true,
},
testHelperMethodStruct{config: config.GitHubProvider{
Repo: "",
User: "bar",
},
valid: false,
},
testHelperMethodStruct{config: config.GitHubProvider{
Repo: "foo",
User: "",
},
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",
@@ -99,7 +75,7 @@ var testReleases = []testReleaseStruct{
requestResponseCode: 200,
valid: true,
},
testReleaseStruct{
{
config: config.GitHubProvider{
Repo: "foo",
User: "bar",
@@ -146,7 +122,7 @@ func TestNew(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XXX")
}
_, err := github.New(&testOject.config)
_, err := New(&testOject.config, true)
assert.Equal(t, testOject.valid, err == nil)
os.Unsetenv("GITHUB_TOKEN")
@@ -157,10 +133,10 @@ func TestNew(t *testing.T) {
func TestGetCommitURL(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testOject := range testNewClient {
client, _ := github.New(&testOject.config)
client, _ := 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)
expectedURL := fmt.Sprintf("%s/api/v3/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo)
assert.EqualValues(t, expectedURL, actualURL)
} else {
@@ -175,10 +151,10 @@ func TestGetCommitURL(t *testing.T) {
func TestGetCompareURL(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testOject := range testNewClient {
client, _ := github.New(&testOject.config)
client, _ := 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")
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)
} else {
@@ -190,18 +166,6 @@ func TestGetCompareURL(t *testing.T) {
}
func TestValidateConfig(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
for _, testOject := range testHelperMethod {
client, _ := github.New(&testOject.config)
err := client.ValidateConfig()
assert.Equal(t, testOject.valid, err == nil)
}
os.Unsetenv("GITHUB_TOKEN")
}
func TestCreateRelease(t *testing.T) {
os.Setenv("GITHUB_TOKEN", "XX")
@@ -209,9 +173,9 @@ func TestCreateRelease(t *testing.T) {
if testObejct.valid {
server := initHTTPServer(testObejct.requestResponseCode, testObejct.requestResponseBody)
testObejct.config.CustomURL = server.URL
client, _ := github.New(&testObejct.config)
client, _ := New(&testObejct.config, false)
err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
err := client.makeRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
if err != nil {
t.Log(err)
}
@@ -221,9 +185,9 @@ func TestCreateRelease(t *testing.T) {
} else {
testObejct.config.CustomURL = "http://foo"
client, _ := github.New(&testObejct.config)
client, _ := New(&testObejct.config, false)
err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
err := client.makeRelease(testObejct.releaseVersion, testObejct.generatedChangelog)
if err != nil {
t.Log(err)
}

View File

@@ -2,7 +2,6 @@ package gitlab
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@@ -13,6 +12,7 @@ import (
"strings"
"time"
"github.com/Nightapes/go-semantic-release/internal/assets"
"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"
@@ -26,7 +26,6 @@ const GITLAB = "gitlab"
// Client type struct
type Client struct {
config *config.GitLabProvider
context context.Context
client *http.Client
baseURL string
apiURL string
@@ -36,12 +35,17 @@ type Client struct {
}
// New initialize a new gitlabRelease
func New(config *config.GitLabProvider, accessToken string) (*Client, error) {
ctx := context.Background()
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: acceptHeader,
Transport: contentHeader,
Timeout: time.Second * 60,
}
@@ -49,7 +53,7 @@ func New(config *config.GitLabProvider, accessToken string) (*Client, error) {
logger.Debugf("validate gitlab provider config")
if config.Repo == "" {
if config.Repo == "" && checkConfig {
return nil, fmt.Errorf("gitlab Repro is not set")
}
@@ -65,7 +69,6 @@ func New(config *config.GitLabProvider, accessToken string) (*Client, error) {
return &Client{
token: accessToken,
config: config,
context: ctx,
baseURL: config.CustomURL,
apiURL: config.CustomURL + "/api/v4",
client: httpClient,
@@ -83,20 +86,27 @@ func (g *Client) GetCompareURL(oldVersion, newVersion string) string {
return fmt.Sprintf("%s/%s/compare/%s...%s", g.baseURL, g.config.Repo, oldVersion, newVersion)
}
//ValidateConfig for gitlab
func (g *Client) ValidateConfig() error {
return nil
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog, assets *assets.Set) error {
err := g.makeRelease(releaseVersion, generatedChangelog)
if err != nil {
return err
}
return g.uploadAssets(assets)
}
// CreateRelease creates release on remote
func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
func (g *Client) makeRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error {
tag := releaseVersion.Next.Version.String()
tagPrefix := config.DefaultTagPrefix
if g.config.TagPrefix != nil{
tagPrefix = *g.config.TagPrefix
}
tag := tagPrefix + 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)
g.log.Infof("Send release to %s", url)
bodyBytes, err := json.Marshal(Release{
TagName: tag,
@@ -108,6 +118,8 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC
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())
@@ -123,28 +135,24 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC
return err
}
g.log.Infof("Crated release")
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)
func (g *Client) uploadAssets(assets *assets.Set) error {
for _, asset := range assets.All() {
path, err := asset.GetPath()
if err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
fileInfo, _ := file.Stat()
result, err := g.uploadFile(fileInfo.Name(), file)
result, err := g.uploadFile(asset.GetName(), file)
if err != nil {
return fmt.Errorf("could not upload asset %s: %s", file.Name(), err.Error())
}
@@ -153,9 +161,9 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error {
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)
uploadURL := fmt.Sprintf("%s/projects/%s/releases/%s/assets/links?name=%s&url=%s", g.apiURL, util.PathEscape(g.config.Repo), g.Release, util.PathEscape(asset.GetName()), downloadURL)
req, err := http.NewRequest("POST", path, nil)
req, err := http.NewRequest("POST", uploadURL, nil)
if err != nil {
return err
}

View File

@@ -1,4 +1,4 @@
package gitlab_test
package gitlab
import (
"io/ioutil"
@@ -13,57 +13,64 @@ import (
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
//"github.com/Nightapes/go-semantic-release/internal/releaser/util"
"github.com/Nightapes/go-semantic-release/internal/releaser/gitlab"
"github.com/Nightapes/go-semantic-release/internal/assets"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
)
func TestGetCommitURL(t *testing.T) {
client, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := New(&config.GitLabProvider{
CustomURL: "https://127.0.0.1/",
Repo: "test/test",
}, "aToken")
}, true)
assert.NoError(t, err)
assert.Equal(t, "https://localhost/test/test/commit/{{hash}}", client.GetCommitURL())
assert.Equal(t, "https://127.0.0.1/test/test/commit/{{hash}}", client.GetCommitURL())
}
func TestGetCompareURL(t *testing.T) {
client, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := New(&config.GitLabProvider{
CustomURL: "https://127.0.0.1/",
Repo: "test/test",
}, "aToken")
}, 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"))
assert.Equal(t, "https://127.0.0.1/test/test/compare/1.0.0...1.0.1", client.GetCompareURL("1.0.0", "1.0.1"))
}
func TestValidateConfig_EmptyRepro(t *testing.T) {
_, err := gitlab.New(&config.GitLabProvider{
CustomURL: "https://localhost/",
}, "aToken")
os.Setenv("GITLAB_ACCESS_TOKEN", "XXX")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
_, err := New(&config.GitLabProvider{
CustomURL: "https://127.0.0.1/",
}, 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",
Repo: "127.0.0.1/test",
}
_, err := gitlab.New(config, "aToken")
_, err := 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/",
Repo: "/127.0.0.1/test/",
CustomURL: "https://127.0.0.1/",
}
_, err := gitlab.New(config, "aToken")
_, err := New(config, true)
assert.NoError(t, err)
assert.Equal(t, "https://localhost", config.CustomURL)
assert.Equal(t, "localhost/test", config.Repo)
assert.Equal(t, "https://127.0.0.1", config.CustomURL)
assert.Equal(t, "127.0.0.1/test", config.Repo)
}
func TestCreateRelease(t *testing.T) {
@@ -101,7 +108,7 @@ func TestCreateRelease(t *testing.T) {
},
responseBody: "{}",
responseCode: 200,
requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`,
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: true,
},
{
@@ -125,7 +132,7 @@ func TestCreateRelease(t *testing.T) {
},
responseBody: "{}",
responseCode: 500,
requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`,
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: false,
},
{
@@ -150,7 +157,7 @@ func TestCreateRelease(t *testing.T) {
},
responseCode: 400,
responseBody: "{}",
requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`,
requestBody: `{"tag_name":"v2.0.0","name":"title","ref":"master","description":"content"}`,
valid: false,
},
}
@@ -181,11 +188,12 @@ func TestCreateRelease(t *testing.T) {
if testObject.config.CustomURL == "" {
testObject.config.CustomURL = testServer.URL
}
client, err := gitlab.New(&testObject.config, "aToken")
os.Setenv("GITLAB_ACCESS_TOKEN", "aToken")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := New(&testObject.config, false)
assert.NoError(t, err)
err = client.CreateRelease(testObject.releaseVersion, testObject.generatedChangelog)
err = client.makeRelease(testObject.releaseVersion, testObject.generatedChangelog)
if err != nil {
t.Log(err)
}
@@ -231,7 +239,7 @@ func TestUploadAssets(t *testing.T) {
valid: true,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
{
Name: filepath.Base(file.Name()),
Compress: false,
},
@@ -250,7 +258,7 @@ func TestUploadAssets(t *testing.T) {
valid: false,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
{
Name: filepath.Base(file.Name()),
Compress: false,
},
@@ -269,7 +277,7 @@ func TestUploadAssets(t *testing.T) {
valid: false,
testDir: os.TempDir(),
assets: []config.Asset{
config.Asset{
{
Name: filepath.Base(file.Name()),
Compress: false,
},
@@ -307,12 +315,18 @@ func TestUploadAssets(t *testing.T) {
if testObject.config.CustomURL == "" {
testObject.config.CustomURL = testServer.URL
}
client, err := gitlab.New(&testObject.config, "aToken")
os.Setenv("GITLAB_ACCESS_TOKEN", "aToken")
defer os.Unsetenv("GITLAB_ACCESS_TOKEN")
client, err := New(&testObject.config, false)
assert.NoError(t, err)
client.Release = "1.0.0"
err = client.UploadAssets(testObject.testDir, testObject.assets)
assets := assets.New(testObject.testDir, "")
err = assets.Add(testObject.assets...)
if err != nil {
t.Log(err)
}
err = client.uploadAssets(assets)
if err != nil {
t.Log(err)
}

View File

@@ -6,9 +6,6 @@ type Release struct {
Name string `json:"name"`
Ref string `json:"ref"`
Description string `json:"description,omitempty"`
Assets struct {
Links []*ReleaseLink `json:"links"`
} `json:"assets"`
}
// ReleaseLink struct

View File

@@ -2,11 +2,12 @@ package releaser
import (
"fmt"
"strings"
"github.com/Nightapes/go-semantic-release/internal/assets"
"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/gitlab"
"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"
@@ -16,37 +17,36 @@ import (
// Releasers struct type
type Releasers struct {
config *config.ReleaseConfig
git *gitutil.GitUtil
}
// Releaser interface for providers
type Releaser interface {
ValidateConfig() error
CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog) error
UploadAssets(repoDir string, assets []config.Asset) error
CreateRelease(*shared.ReleaseVersion, *shared.GeneratedChangelog, *assets.Set) error
GetCommitURL() string
GetCompareURL(oldVersion, newVersion string) string
}
// New initialize a Relerser
func New(c *config.ReleaseConfig) *Releasers {
// New initialize a releaser
func New(c *config.ReleaseConfig, git *gitutil.GitUtil) *Releasers {
return &Releasers{
config: c,
git: git,
}
}
//GetReleaser returns an initialized releaser
func (r *Releasers) GetReleaser() (Releaser, error) {
func (r *Releasers) GetReleaser(checkConfig bool) (Releaser, error) {
switch r.config.Release {
case 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)
accessToken, err := util.GetAccessToken(fmt.Sprintf("%s_ACCESS_TOKEN", strings.ToUpper(gitlab.GITLAB)))
if err != nil {
return nil, err
}
return gitlab.New(&r.config.GitLabProvider, accessToken)
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)
}

View File

@@ -1,7 +1,6 @@
package util
import (
"archive/zip"
"context"
"encoding/json"
"fmt"
@@ -11,7 +10,6 @@ import (
"os"
"strings"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)
@@ -62,74 +60,78 @@ func GetAccessToken(envName string) (string, error) {
return token, nil
}
// PrepareAssets prepare all files before uploading
func PrepareAssets(repository string, assets []config.Asset) ([]*string, error) {
filesToUpload := []*string{}
for _, asset := range assets {
if asset.Name == "" {
return nil, fmt.Errorf("asset name declaration is empty, please check your configuration file")
} else if asset.Compress {
log.Debugf("Asset %s will now be compressed", asset.Name)
log.Debugf("Repo url %s", repository)
zipNameWithPath, err := zipFile(repository, asset.Name)
if err != nil {
return filesToUpload, err
}
filesToUpload = append(filesToUpload, &zipNameWithPath)
} else {
tmpFileName := fmt.Sprintf("%s/%s", repository, asset.Name)
filesToUpload = append(filesToUpload, &tmpFileName)
}
log.Debugf("Add asset %s to files to upload", asset.Name)
}
return filesToUpload, nil
}
// // PrepareAssets prepare all files before uploading
// func PrepareAssets(repository string, assets []config.Asset) ([]*string, error) {
// filesToUpload := []*string{}
// for _, asset := range assets {
// if asset.Name != "" && asset.Path == "" {
// log.Warn("Name is deprecated. Please update your config. See https://nightapes.github.io/go-semantic-release/")
// }
// ZipFile compress given file in zip format
func zipFile(repository string, file string) (string, error) {
// if asset.Path == "" {
// return nil, fmt.Errorf("asset path declaration is empty, please check your configuration file")
// } else if asset.Compress {
// log.Debugf("Asset %s will now be compressed", asset.Name)
// log.Debugf("Repo url %s", repository)
// zipNameWithPath, err := zipFile(repository, asset.Name)
// if err != nil {
// return filesToUpload, err
// }
// filesToUpload = append(filesToUpload, &zipNameWithPath)
// } else {
// tmpFileName := fmt.Sprintf("%s/%s", repository, asset.Name)
// filesToUpload = append(filesToUpload, &tmpFileName)
// }
// log.Debugf("Add asset %s to files to upload", asset.Name)
// }
// return filesToUpload, nil
// }
fileToZip, err := os.Open(repository + "/" + file)
if err != nil {
return "", err
}
defer fileToZip.Close()
// // ZipFile compress given file in zip format
// func zipFile(repository string, file string) (string, error) {
zipFileName := fmt.Sprintf("%s/%s.zip", strings.TrimSuffix(repository, "/"), file)
zipFile, err := os.Create(zipFileName)
// fileToZip, err := os.Open(repository + "/" + file)
// if err != nil {
// return "", err
// }
// defer fileToZip.Close()
if err != nil {
return "", err
}
log.Debugf("Created zipfile %s", zipFile.Name())
// zipFileName := fmt.Sprintf("%s/%s.zip", strings.TrimSuffix(repository, "/"), file)
// zipFile, err := os.Create(zipFileName)
defer zipFile.Close()
// if err != nil {
// return "", err
// }
// log.Debugf("Created zipfile %s", zipFile.Name())
fileToZipInfo, err := fileToZip.Stat()
if err != nil {
return "", err
}
// defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// fileToZipInfo, err := fileToZip.Stat()
// if err != nil {
// return "", err
// }
fileToZipHeader, err := zip.FileInfoHeader(fileToZipInfo)
if err != nil {
return "", err
}
// zipWriter := zip.NewWriter(zipFile)
// defer zipWriter.Close()
fileToZipHeader.Name = fileToZipInfo.Name()
// fileToZipHeader, err := zip.FileInfoHeader(fileToZipInfo)
// if err != nil {
// return "", err
// }
fileToZipWriter, err := zipWriter.CreateHeader(fileToZipHeader)
if err != nil {
return "", err
}
// fileToZipHeader.Name = fileToZipInfo.Name()
if _, err = io.Copy(fileToZipWriter, fileToZip); err != nil {
return "", err
}
// fileToZipWriter, err := zipWriter.CreateHeader(fileToZipHeader)
// if err != nil {
// return "", err
// }
return zipFileName, nil
}
// if _, err = io.Copy(fileToZipWriter, fileToZip); err != nil {
// return "", err
// }
// return zipFileName, nil
// }
//PathEscape to be url save
func PathEscape(s string) string {

View File

@@ -13,8 +13,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/Nightapes/go-semantic-release/internal/releaser/util"
@@ -32,8 +30,8 @@ type testDoubleToken struct {
}
var testDoubles = []testDoubleToken{
testDoubleToken{providerName: "test0", token: "foo", valid: true},
testDoubleToken{providerName: "test1", token: "", valid: false},
{providerName: "test0", token: "foo", valid: true},
{providerName: "test1", token: "", valid: false},
}
func TestGetAccessToken(t *testing.T) {
@@ -50,80 +48,6 @@ func TestGetAccessToken(t *testing.T) {
}
}
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}))
@@ -135,7 +59,7 @@ func TestIsValidResult(t *testing.T) {
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 202}))
assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 204}))
u, err := url.Parse("https://localhost")
u, err := url.Parse("https://127.0.0.1")
assert.NoError(t, err)
assert.Error(t, util.IsValidResult(&http.Response{StatusCode: 500, Request: &http.Request{
Method: "POST",

View File

@@ -35,13 +35,22 @@ type ChangelogTemplateConfig struct {
//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"`
Commit Commit `yaml:"commit"`
ParsedMessage string `yaml:"parsedMessage"`
ParsedBreakingChangeMessage string `yaml:"parsedBreakingChangeMessage"`
Tag string `yaml:"tag"`
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"`
}
// 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,..

View File

@@ -3,21 +3,49 @@ package config
import (
"io/ioutil"
"os"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
const (
DefaultTagPrefix = "v"
)
// AnalyzerConfig struct
type AnalyzerConfig struct {
TokenSeparators []string `yaml:"tokenSeparators"`
}
// ChangelogConfig struct
type ChangelogConfig struct {
PrintAll bool `yaml:"printAll,omitempty"`
Template string `yaml:"template,omitempty"`
TemplatePath string `yaml:"templatePath,omitempty"`
PrintAll bool `yaml:"printAll,omitempty"`
TemplateTitle string `yaml:"title,omitempty"`
TemplatePath string `yaml:"templatePath,omitempty"`
ShowBodyAsHeader bool `yaml:"showBodyAsHeader,omitempty"`
ShowAuthors bool `yaml:"showAuthors,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 {
Repository string `yaml:"repository"`
PackageName string `yaml:"name"`
}
//Asset type struct
type Asset struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Rename string `yaml:"rename,omitempty"`
Name string `yaml:"name,omitempty"` // Deprecated
Compress bool `yaml:"compress"`
}
@@ -27,6 +55,7 @@ type GitHubProvider struct {
User string `yaml:"user"`
CustomURL string `yaml:"customUrl,omitempty"`
AccessToken string
TagPrefix *string `yaml:"tagPrefix,omitempty"`
}
// GitLabProvider struct
@@ -34,17 +63,54 @@ type GitLabProvider struct {
Repo string `yaml:"repo"`
CustomURL string `yaml:"customUrl,omitempty"`
AccessToken string
TagPrefix *string `yaml:"tagPrefix,omitempty"`
}
// GitProvider struct
type GitProvider struct {
Email string `yaml:"email"`
Username string `yaml:"user"`
Auth string `yaml:"auth"`
SSH bool `yaml:"ssh"`
TagPrefix *string `yaml:"tagPrefix,omitempty"`
}
// Hooks struct
type Hooks struct {
PreRelease []string `yaml:"preRelease"`
PostRelease []string `yaml:"postRelease"`
}
// Checksum struct
type Checksum struct {
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
type ReleaseConfig struct {
CommitFormat string `yaml:"commitFormat"`
Branch map[string]string `yaml:"branch"`
Analyzer AnalyzerConfig `yaml:"analyzer"`
Changelog ChangelogConfig `yaml:"changelog,omitempty"`
Release string `yaml:"release,omitempty"`
GitHubProvider GitHubProvider `yaml:"github,omitempty"`
GitLabProvider GitLabProvider `yaml:"gitlab,omitempty"`
GitProvider GitProvider `yaml:"git,omitempty"`
Assets []Asset `yaml:"assets"`
Checksum Checksum `yaml:"checksum,omitempty"`
Hooks Hooks `yaml:"hooks"`
Integrations Integrations `yaml:"integrations"`
ReleaseTitle string `yaml:"title"`
IsPreRelease bool
}
@@ -57,13 +123,31 @@ func Read(configPath string) (*ReleaseConfig, error) {
return &ReleaseConfig{}, err
}
var releaseConfig ReleaseConfig
err = yaml.Unmarshal(content, &releaseConfig)
log.Tracef("Found config %s", string(content))
releaseConfig := &ReleaseConfig{}
err = yaml.Unmarshal(content, releaseConfig)
if err != nil {
return &ReleaseConfig{}, err
}
log.Tracef("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
}

View File

@@ -41,6 +41,9 @@ func TestWriteAndReadCache(t *testing.T) {
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
@@ -53,9 +56,12 @@ branch:
add_git_releases: alpha
changelog:
printAll: false
template: ''
templatePath: ''
template: ""
templatePath: '${TEST_CONFIG}'
release: 'github'
hooks:
preRelease:
- "Test hook ${RELEASE_VERSION}"
assets:
- name: ./build/go-semantic-release
compress: false
@@ -74,38 +80,27 @@ github:
CommitFormat: "angular",
Branch: map[string]string{"add_git_releases": "alpha", "alpha": "alpha", "beta": "beta", "master": "release", "rc": "rc"},
Changelog: config.ChangelogConfig{
PrintAll: false,
Template: "",
TemplatePath: ""},
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,
Analyzer: config.AnalyzerConfig{TokenSeparators: []string{}},
}, result)
}
// func TestWriteNotFound(t *testing.T) {
// err := cache.Write("notfound/dir", shared.ReleaseVersion{
// Last: shared.ReleaseVersionEntry{
// Commit: "12345",
// Version: createVersion("1.0.0"),
// },
// Next: shared.ReleaseVersionEntry{
// Commit: "12346",
// Version: createVersion("1.1.0"),
// },
// Branch: "master",
// })
// assert.Errorf(t, err, "Write non exsiting file")
// }

View File

@@ -1,70 +1,87 @@
package semanticrelease
import (
"io/ioutil"
"fmt"
"github.com/go-git/go-git/v5/plumbing"
"os"
"path/filepath"
"strings"
"time"
"github.com/Nightapes/go-semantic-release/internal/integrations"
"github.com/Masterminds/semver"
log "github.com/sirupsen/logrus"
"github.com/Nightapes/go-semantic-release/internal/analyzer"
"github.com/Nightapes/go-semantic-release/internal/assets"
"github.com/Nightapes/go-semantic-release/internal/cache"
"github.com/Nightapes/go-semantic-release/internal/calculator"
"github.com/Nightapes/go-semantic-release/internal/changelog"
"github.com/Nightapes/go-semantic-release/internal/ci"
"github.com/Nightapes/go-semantic-release/internal/gitutil"
"github.com/Nightapes/go-semantic-release/internal/hooks"
"github.com/Nightapes/go-semantic-release/internal/releaser"
"github.com/Nightapes/go-semantic-release/internal/releaser/util"
"github.com/Nightapes/go-semantic-release/internal/shared"
"github.com/Nightapes/go-semantic-release/pkg/config"
log "github.com/sirupsen/logrus"
)
// SemanticRelease struct
type SemanticRelease struct {
config *config.ReleaseConfig
gitutil *gitutil.GitUtil
analyzer *analyzer.Analyzer
calculator *calculator.Calculator
releaser releaser.Releaser
repository string
config *config.ReleaseConfig
gitUtil *gitutil.GitUtil
analyzer *analyzer.Analyzer
calculator *calculator.Calculator
releaser releaser.Releaser
assets *assets.Set
repository string
checkConfig bool
}
// 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)
if err != nil {
return nil, err
}
analyzer, err := analyzer.New(c.CommitFormat, c.Changelog)
analyzer, err := analyzer.New(c.CommitFormat, c.Analyzer, c.Changelog)
if err != nil {
return nil, err
}
releaser, err := releaser.New(c).GetReleaser()
if !checkConfig {
log.Infof("Ignore config checks!. No guarantee to run without issues")
}
assets := assets.New(repository, c.Checksum.Algorithm)
releaser, err := releaser.New(c, util).GetReleaser(checkConfig)
if err != nil {
return nil, err
}
return &SemanticRelease{
config: c,
gitutil: util,
releaser: releaser,
analyzer: analyzer,
repository: repository,
calculator: calculator.New(),
config: c,
gitUtil: util,
releaser: releaser,
analyzer: analyzer,
repository: repository,
assets: assets,
checkConfig: checkConfig,
calculator: calculator.New(),
}, nil
}
//GetCIProvider result with ci config
// GetCIProvider result with ci config
func (s *SemanticRelease) GetCIProvider() (*ci.ProviderConfig, error) {
return ci.GetCIProvider(s.gitutil, ci.ReadAllEnvs())
return ci.GetCIProvider(s.gitUtil, s.checkConfig, ci.ReadAllEnvs())
}
// GetNextVersion from .version or calculate new from commits
func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool) (*shared.ReleaseVersion, error) {
func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool, from string) (*shared.ReleaseVersion, error) {
log.Debugf("Ignore .version file if exits, %t", force)
if !force {
if !force && from == "" {
releaseVersion, err := cache.Read(s.repository)
if err != nil {
return nil, err
@@ -75,22 +92,33 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
}
}
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion()
if err != nil {
return nil, err
var lastVersion *semver.Version
var lastVersionHash *plumbing.Reference
var err error
if from == "" {
lastVersion, lastVersionHash, err = s.gitUtil.GetLastVersion()
if err != nil {
return nil, err
}
} else {
lastVersion, lastVersionHash, err = s.gitUtil.GetVersion(from)
if err != nil {
return nil, err
}
}
firstRelease := false
if lastVersion == nil {
defaultVersion, _ := semver.NewVersion("1.0.0")
lastVersion = defaultVersion
lastVersion, _ = semver.NewVersion("1.0.0")
log.Infof("This is the first release, will set version to %s", lastVersion.String())
firstRelease = true
}
commits, err := s.gitutil.GetCommits(lastVersionHash)
commits, err := s.gitUtil.GetCommits(lastVersionHash)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not get commits %w", err)
}
log.Debugf("Found %d commits till last release", len(commits))
@@ -98,26 +126,40 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
analyzedCommits := s.analyzer.Analyze(commits)
var newVersion semver.Version
foundBranchConfig := false
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)
newVersion = s.calculator.CalculateNewVersion(analyzedCommits, lastVersion, releaseType, firstRelease)
foundBranchConfig = true
break
}
}
if !foundBranchConfig {
log.Warnf("No branch config found for branch %s, will return last known version", provider.Branch)
newVersion = *lastVersion
}
releaseVersion := shared.ReleaseVersion{
Next: shared.ReleaseVersionEntry{
Commit: provider.Commit,
Version: &newVersion,
},
Last: shared.ReleaseVersionEntry{
Commit: lastVersionHash,
Commit: "",
Version: lastVersion,
},
Branch: provider.Branch,
Commits: analyzedCommits,
}
if lastVersionHash != nil {
releaseVersion.Last.Commit = lastVersionHash.Hash().String()
}
if firstRelease {
releaseVersion.Last.Version, _ = semver.NewVersion("0.0.0")
}
log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String())
err = cache.Write(s.repository, releaseVersion)
@@ -127,15 +169,14 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
return &releaseVersion, err
}
//SetVersion for git repository
// SetVersion for git repository
func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string) error {
newVersion, err := semver.NewVersion(version)
if err != nil {
return err
}
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion()
lastVersion, lastVersionHash, err := s.gitUtil.GetLastVersion()
if err != nil {
return err
}
@@ -149,7 +190,7 @@ func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string
Version: newVersion,
},
Last: shared.ReleaseVersionEntry{
Commit: lastVersionHash,
Commit: lastVersionHash.Hash().String(),
Version: lastVersion,
},
Branch: provider.Branch,
@@ -159,23 +200,94 @@ func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string
// GetChangelog from last version till now
func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) {
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(),
Hash: releaseVersion.Last.Commit,
CommitURL: s.releaser.GetCommitURL(),
CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()),
}, releaseVersion.Commits)
}
// WriteChangeLog wirtes changelog content to the given file
func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error {
return ioutil.WriteFile(file, []byte(changelogContent), 0644)
// WriteChangeLog writes changelog content to the given file
func (s *SemanticRelease) WriteChangeLog(changelogContent, file string, overwrite bool, maxChangelogFileSize int64) error {
info, err := os.Stat(file)
if overwrite || err != nil {
return os.WriteFile(file, []byte(changelogContent), 0644)
}
if bytesToMB(info.Size()) >= float64(maxChangelogFileSize) {
err := moveExistingChangelogFile(file)
if err != nil {
return err
}
}
return prependToFile(changelogContent, file)
}
// Release pusblish release to provider
func bytesToMB(bytes int64) float64 {
return float64(bytes) / 1024 / 1024 / 1024
}
func moveExistingChangelogFile(file string) error {
filenameSeparated := strings.Split(filepath.Base(file), ".")
// check if file had several "." included.
// if yes the filename will be separated like this: "my.file.name", ".md"
if len(filenameSeparated) > 2 {
separatedFilenameWithExtension := make([]string, 0)
separatedFilenameWithExtension = append(separatedFilenameWithExtension, strings.Join(filenameSeparated[:len(filenameSeparated)-1], "."))
separatedFilenameWithExtension = append(separatedFilenameWithExtension, filenameSeparated[len(filenameSeparated)-1])
filenameSeparated = separatedFilenameWithExtension
}
var newFileName string
counter := 1
for {
newFileName = buildNewFileName(filenameSeparated, counter)
if _, err := os.Stat(newFileName); err != nil {
break
}
counter++
}
content, err := os.ReadFile(file)
if err != nil {
return err
}
err = os.WriteFile(newFileName, content, 0666)
if err != nil {
return err
}
_, err = os.Create(file)
return err
}
func buildNewFileName(currentFileNameSeparated []string, counter int) string {
if len(currentFileNameSeparated) == 1 {
return fmt.Sprintf("%s-%d", currentFileNameSeparated[0], counter)
}
fileNameWithoutExtension := strings.Join(currentFileNameSeparated[:len(currentFileNameSeparated)-1], ".")
fileExtension := currentFileNameSeparated[len(currentFileNameSeparated)-1]
return fmt.Sprintf("%s-%d.%s", fileNameWithoutExtension, counter, fileExtension)
}
func prependToFile(newChangelogContent, file string) error {
currentContent, err := os.ReadFile(file)
if err != nil {
return err
}
content := make([]byte, 0)
content = append(content, []byte(newChangelogContent)...)
content = append(content, []byte("\n---\n\n")...)
content = append(content, currentContent...)
return os.WriteFile(file, content, 0644)
}
// Release publish release to provider
func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error {
if provider.IsPR {
log.Infof("Will not perform a new release. This is a pull request")
return nil
@@ -186,7 +298,11 @@ func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error
return nil
}
releaseVersion, err := s.GetNextVersion(provider, force)
if err := s.assets.Add(s.config.Assets...); err != nil {
return err
}
releaseVersion, err := s.GetNextVersion(provider, force, "")
if err != nil {
log.Debugf("Could not get next version")
return err
@@ -197,27 +313,44 @@ func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error
return nil
}
generatedChanglog, err := s.GetChangelog(releaseVersion)
generatedChangelog, err := s.GetChangelog(releaseVersion)
if err != nil {
log.Debugf("Could not get changelog")
return err
}
releaser, err := releaser.New(s.config).GetReleaser()
if err != nil {
integrations := integrations.New(&s.config.Integrations, releaseVersion)
if err := integrations.Run(); err != nil {
log.Debugf("Error during integrations run")
return err
}
err = releaser.ValidateConfig()
if err != nil {
hook := hooks.New(s.config, releaseVersion)
if err := hook.PreRelease(); err != nil {
log.Debugf("Error during pre release hook")
return err
}
if err = releaser.CreateRelease(releaseVersion, generatedChanglog); err != nil {
if s.config.Checksum.Algorithm != "" {
if err := s.assets.GenerateChecksum(); err != nil {
return err
}
}
for _, asset := range s.assets.All() {
if asset.IsCompressed() {
if _, err := asset.ZipFile(); err != nil {
return err
}
}
}
if err = s.releaser.CreateRelease(releaseVersion, generatedChangelog, s.assets); err != nil {
return err
}
if err = releaser.UploadAssets(s.repository, s.config.Assets); err != nil {
if err := hook.PostRelease(); err != nil {
log.Debugf("Error during post release hook")
return err
}
@@ -226,12 +359,21 @@ func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error
// ZipFiles zip files configured in release config
func (s *SemanticRelease) ZipFiles() error {
for _, file := range s.config.Assets {
if file.Compress {
if _, err := util.PrepareAssets(s.repository, s.config.Assets); err != nil {
return err
}
assets := assets.New(s.repository, "")
if err := assets.Add(s.config.Assets...); err != nil {
return err
}
if err := assets.GenerateChecksum(); err != nil {
return err
}
for _, asset := range assets.All() {
path, err := asset.GetPath()
if err != nil {
return err
}
log.Infof("File %s under %s is zipped %t", asset.GetName(), path, asset.IsCompressed())
}
return nil
}

View File

@@ -0,0 +1,83 @@
package semanticrelease
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/Nightapes/go-semantic-release/pkg/config"
)
func TestSemanticRelease_WriteChangeLog(t *testing.T) {
type args struct {
changelogContent string
file string
overwrite bool
maxChangelogFileSize int64
}
tests := []struct {
config *config.ReleaseConfig
name string
args args
wantErr bool
}{
{
name: "MoveExisting",
args: args{
changelogContent: "go-semantic-release-rocks!",
file: "test1.changelog.md",
overwrite: false,
maxChangelogFileSize: 0,
},
wantErr: false,
},
{
name: "ValidWithOverwrite",
args: args{
changelogContent: "go-semantic-release-rocks!",
file: "test2.changelog.md",
overwrite: true,
maxChangelogFileSize: 0,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := os.Create(tt.args.file)
if err != nil {
t.Error(err)
}
releaser := &SemanticRelease{}
if err := releaser.WriteChangeLog(tt.args.changelogContent, tt.args.file, tt.args.overwrite, tt.args.maxChangelogFileSize); (err != nil) != tt.wantErr {
t.Errorf("WriteChangeLog() error = %v, wantErr %v", err, tt.wantErr)
}
name := strings.Join(strings.Split(tt.args.file, ".")[:len(strings.Split(tt.args.file, "."))-1], ".")
files, err := filepath.Glob("./" + name + "*")
if err != nil {
t.Error(err)
}
if !tt.wantErr && !tt.args.overwrite && tt.args.maxChangelogFileSize == 0 && len(files) <= 1 {
t.Errorf("WriteChangelog() = should create a copy of the existing changelog file")
}
if !tt.wantErr && tt.args.overwrite && len(files) > 1 {
t.Errorf("WriteChangelog() = should not create a copy of the changelog file")
}
for _, i := range files {
err := os.Remove(i)
if err != nil {
t.Error(err)
}
}
})
}
}