mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-23 14:50:39 +00:00
initial commit after fork
This commit is contained in:
parent
20e64f8fcf
commit
00e2fe31e5
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@ -1,6 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
71
.github/settings.yml
vendored
Normal file
71
.github/settings.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
repository:
|
||||||
|
name: git-sv
|
||||||
|
description: Woodpecker CI plugin to perform git actions
|
||||||
|
homepage: https://woodpecker-plugins.geekdocs.de/plugins/git-sv
|
||||||
|
topics: woodpecker-ci, woodpecker, woodpecker-plugin
|
||||||
|
|
||||||
|
private: false
|
||||||
|
has_issues: true
|
||||||
|
has_wiki: false
|
||||||
|
has_downloads: true
|
||||||
|
|
||||||
|
default_branch: main
|
||||||
|
|
||||||
|
allow_squash_merge: true
|
||||||
|
allow_merge_commit: true
|
||||||
|
allow_rebase_merge: true
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- name: bug
|
||||||
|
color: d73a4a
|
||||||
|
description: Something isn't working
|
||||||
|
- name: documentation
|
||||||
|
color: 0075ca
|
||||||
|
description: Improvements or additions to documentation
|
||||||
|
- name: duplicate
|
||||||
|
color: cfd3d7
|
||||||
|
description: This issue or pull request already exists
|
||||||
|
- name: enhancement
|
||||||
|
color: a2eeef
|
||||||
|
description: New feature or request
|
||||||
|
- name: good first issue
|
||||||
|
color: 7057ff
|
||||||
|
description: Good for newcomers
|
||||||
|
- name: help wanted
|
||||||
|
color: 008672
|
||||||
|
description: Extra attention is needed
|
||||||
|
- name: invalid
|
||||||
|
color: e4e669
|
||||||
|
description: This doesn't seem right
|
||||||
|
- name: question
|
||||||
|
color: d876e3
|
||||||
|
description: Further information is requested
|
||||||
|
- name: wontfix
|
||||||
|
color: ffffff
|
||||||
|
description: This will not be worked on
|
||||||
|
|
||||||
|
branches:
|
||||||
|
- name: main
|
||||||
|
protection:
|
||||||
|
required_pull_request_reviews: null
|
||||||
|
required_status_checks:
|
||||||
|
strict: false
|
||||||
|
contexts:
|
||||||
|
- ci/woodpecker/pr/test
|
||||||
|
- ci/woodpecker/pr/build-package
|
||||||
|
- ci/woodpecker/pr/build-container
|
||||||
|
- ci/woodpecker/pr/docs
|
||||||
|
enforce_admins: false
|
||||||
|
required_linear_history: true
|
||||||
|
restrictions: null
|
||||||
|
- name: docs
|
||||||
|
protection:
|
||||||
|
required_pull_request_reviews: null
|
||||||
|
required_status_checks: null
|
||||||
|
enforce_admins: true
|
||||||
|
required_linear_history: true
|
||||||
|
restrictions:
|
||||||
|
apps: []
|
||||||
|
users: []
|
||||||
|
teams:
|
||||||
|
- bot
|
102
.github/workflows/ci.yml
vendored
102
.github/workflows/ci.yml
vendored
@ -1,102 +0,0 @@
|
|||||||
name: ci
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
- "**/.gitignore"
|
|
||||||
- ".github/workflows/**"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Run golangci lint
|
|
||||||
uses: golangci/golangci-lint-action@v3
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: ^1.19
|
|
||||||
- name: Build
|
|
||||||
run: make build
|
|
||||||
|
|
||||||
tag:
|
|
||||||
name: Tag
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [lint, build]
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set GitHub Actions as commit author
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Setup sv4git
|
|
||||||
run: |
|
|
||||||
curl -s https://api.github.com/repos/bvieira/sv4git/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux")) | .browser_download_url' | wget -O /tmp/sv4git.tar.gz -qi - \
|
|
||||||
&& tar -C /usr/local/bin -xzf /tmp/sv4git.tar.gz
|
|
||||||
|
|
||||||
- name: Create tag
|
|
||||||
id: create-tag
|
|
||||||
run: |
|
|
||||||
git sv tag
|
|
||||||
VERSION=$(git sv cv)
|
|
||||||
echo "::set-output name=tag::v$VERSION"
|
|
||||||
outputs:
|
|
||||||
tag: ${{ steps.create-tag.outputs.tag }}
|
|
||||||
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [tag]
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup sv4git
|
|
||||||
run: |
|
|
||||||
curl -s https://api.github.com/repos/bvieira/sv4git/releases/latest | jq -r '.assets[] | select(.browser_download_url | contains("linux")) | .browser_download_url' | wget -O /tmp/sv4git.tar.gz -qi - \
|
|
||||||
&& tar -C /usr/local/bin -xzf /tmp/sv4git.tar.gz
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
id: go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: ^1.19
|
|
||||||
|
|
||||||
- name: Create release notes
|
|
||||||
run: |
|
|
||||||
git sv rn -t "${{ needs.tag.outputs.tag }}" > release-notes.md
|
|
||||||
|
|
||||||
- name: Build releases
|
|
||||||
run: make release-all
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
body_path: release-notes.md
|
|
||||||
tag_name: ${{ needs.tag.outputs.tag }}
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
files: |
|
|
||||||
bin/git-sv_*
|
|
34
.github/workflows/pull-request.yml
vendored
34
.github/workflows/pull-request.yml
vendored
@ -1,34 +0,0 @@
|
|||||||
name: pull_request
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**/.gitignore'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Run golangci lint
|
|
||||||
uses: golangci/golangci-lint-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.19
|
|
||||||
id: go
|
|
||||||
- name: Build
|
|
||||||
run: make build
|
|
27
.gitignore
vendored
27
.gitignore
vendored
@ -1,23 +1,6 @@
|
|||||||
# Binaries for programs and plugins
|
/dist
|
||||||
bin/
|
/release
|
||||||
*.exe
|
/git-sv*
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
coverage.out
|
||||||
*.test
|
CHANGELOG.md
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
*.sample
|
|
||||||
todo
|
|
||||||
|
|
||||||
# Additional generated artifacts
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# Mac metadata
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
|
47
.gitsv/config.yml
Normal file
47
.gitsv/config.yml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
version: "1.1"
|
||||||
|
|
||||||
|
versioning:
|
||||||
|
update-major: []
|
||||||
|
update-minor: [feat]
|
||||||
|
update-patch: [fix, perf, refactor, chore, test, ci, docs]
|
||||||
|
|
||||||
|
tag:
|
||||||
|
pattern: "v%d.%d.%d"
|
||||||
|
|
||||||
|
release-notes:
|
||||||
|
sections:
|
||||||
|
- name: Features
|
||||||
|
commit-types: [feat]
|
||||||
|
section-type: commits
|
||||||
|
- name: Bug Fixes
|
||||||
|
commit-types: [fix]
|
||||||
|
section-type: commits
|
||||||
|
- name: Performance Improvements
|
||||||
|
commit-types: [perf]
|
||||||
|
section-type: commits
|
||||||
|
- name: Code Refactoring
|
||||||
|
commit-types: [refactor]
|
||||||
|
section-type: commits
|
||||||
|
- name: Others
|
||||||
|
commit-types: [chore]
|
||||||
|
section-type: commits
|
||||||
|
- name: Testing
|
||||||
|
commit-types: [test]
|
||||||
|
section-type: commits
|
||||||
|
- name: CI Pipeline
|
||||||
|
commit-types: [ci]
|
||||||
|
section-type: commits
|
||||||
|
- name: Documentation
|
||||||
|
commit-types: [docs]
|
||||||
|
section-type: commits
|
||||||
|
- name: Breaking Changes
|
||||||
|
section-type: breaking-changes
|
||||||
|
|
||||||
|
commit-message:
|
||||||
|
footer:
|
||||||
|
issue:
|
||||||
|
key: issue
|
||||||
|
add-value-prefix: "#"
|
||||||
|
issue:
|
||||||
|
regex: "#?[0-9]+"
|
110
.golangci.yml
110
.golangci.yml
@ -1,35 +1,109 @@
|
|||||||
linters:
|
linters:
|
||||||
|
enable-all: false
|
||||||
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- contextcheck
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- execinquery
|
||||||
|
# - exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- forcetypeassert
|
||||||
|
- ginkgolinter
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- godot
|
||||||
|
# - godox
|
||||||
|
- goerr113
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goheader
|
||||||
|
- goimports
|
||||||
|
- gomnd
|
||||||
|
- gomoddirectives
|
||||||
|
- gomodguard
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- grouper
|
||||||
|
- importas
|
||||||
|
- interfacebloat
|
||||||
|
- ireturn
|
||||||
|
- lll
|
||||||
|
- loggercheck
|
||||||
|
- maintidx
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- musttag
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nonamedreturns
|
||||||
|
- nosprintfhostport
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
# - rowserrcheck
|
||||||
|
# - sqlclosecheck
|
||||||
|
# - structcheck
|
||||||
|
- stylecheck
|
||||||
- tagliatelle
|
- tagliatelle
|
||||||
|
- tenv
|
||||||
|
- testableexamples
|
||||||
|
- thelper
|
||||||
|
- tparallel
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
# - wastedassign
|
||||||
|
- whitespace
|
||||||
|
- wsl
|
||||||
|
fast: false
|
||||||
|
|
||||||
run:
|
run:
|
||||||
skip-dirs:
|
timeout: 3m
|
||||||
- build
|
|
||||||
- artifacts
|
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
tagliatelle:
|
tagliatelle:
|
||||||
case:
|
case:
|
||||||
use-field-name: true
|
|
||||||
rules:
|
rules:
|
||||||
json: camel
|
|
||||||
yaml: kebab
|
yaml: kebab
|
||||||
xml: camel
|
gofumpt:
|
||||||
bson: camel
|
extra-rules: true
|
||||||
avro: snake
|
lang-version: "1.21"
|
||||||
mapstructure: kebab
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: _test\.go
|
- path: (.+)_test.go
|
||||||
linters:
|
linters:
|
||||||
- gocyclo
|
|
||||||
- errcheck
|
|
||||||
- dupl
|
|
||||||
- gosec
|
- gosec
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
- testpackage
|
- prealloc
|
||||||
- path: cmd/git-sv/main.go
|
|
||||||
linters:
|
|
||||||
- gochecknoglobals
|
|
||||||
- funlen
|
|
||||||
|
6
.markdownlint.yml
Normal file
6
.markdownlint.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
default: True
|
||||||
|
MD013: False
|
||||||
|
MD041: False
|
||||||
|
MD004:
|
||||||
|
style: dash
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.tpl.md
|
||||||
|
LICENSE
|
31
.sv4git.yml
31
.sv4git.yml
@ -1,31 +0,0 @@
|
|||||||
version: "1.1"
|
|
||||||
|
|
||||||
versioning:
|
|
||||||
update-major: []
|
|
||||||
update-minor: [feat]
|
|
||||||
update-patch: [build, ci, chore, fix, perf, refactor, test]
|
|
||||||
|
|
||||||
tag:
|
|
||||||
pattern: "v%d.%d.%d"
|
|
||||||
|
|
||||||
release-notes:
|
|
||||||
sections:
|
|
||||||
- name: Features
|
|
||||||
section-type: commits
|
|
||||||
commit-types: [feat]
|
|
||||||
- name: Bug Fixes
|
|
||||||
section-type: commits
|
|
||||||
commit-types: [fix]
|
|
||||||
- name: Misc
|
|
||||||
section-type: commits
|
|
||||||
commit-types: [build]
|
|
||||||
- name: Breaking Changes
|
|
||||||
section-type: breaking-changes
|
|
||||||
|
|
||||||
commit-message:
|
|
||||||
footer:
|
|
||||||
issue:
|
|
||||||
key: issue
|
|
||||||
add-value-prefix: "#"
|
|
||||||
issue:
|
|
||||||
regex: "#?[0-9]+"
|
|
66
.woodpecker/build-container.yml
Normal file
66
.woodpecker/build-container.yml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
when:
|
||||||
|
- event: [pull_request, tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
dryrun:
|
||||||
|
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||||
|
settings:
|
||||||
|
containerfile: Containerfile.multiarch
|
||||||
|
dry_run: true
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
provenance: false
|
||||||
|
repo: ${CI_REPO}
|
||||||
|
when:
|
||||||
|
- event: [pull_request]
|
||||||
|
|
||||||
|
publish-dockerhub:
|
||||||
|
group: container
|
||||||
|
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
containerfile: Containerfile.multiarch
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
provenance: false
|
||||||
|
repo: ${CI_REPO}
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
when:
|
||||||
|
- event: [tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
publish-quay:
|
||||||
|
group: container
|
||||||
|
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||||
|
settings:
|
||||||
|
auto_tag: true
|
||||||
|
containerfile: Containerfile.multiarch
|
||||||
|
password:
|
||||||
|
from_secret: quay_password
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
provenance: false
|
||||||
|
registry: quay.io
|
||||||
|
repo: quay.io/${CI_REPO}
|
||||||
|
username:
|
||||||
|
from_secret: quay_username
|
||||||
|
when:
|
||||||
|
- event: [tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
44
.woodpecker/build-package.yml
Normal file
44
.woodpecker/build-package.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
when:
|
||||||
|
- event: [pull_request, tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
image: docker.io/techknowlogick/xgo:go-1.21.x
|
||||||
|
commands:
|
||||||
|
- ln -s $(pwd) /source
|
||||||
|
- make release
|
||||||
|
|
||||||
|
executable:
|
||||||
|
image: quay.io/thegeeklab/alpine-tools
|
||||||
|
commands:
|
||||||
|
- $(find dist/ -executable -type f -iname ${CI_REPO_NAME}-linux-amd64) --help
|
||||||
|
|
||||||
|
# changelog-generate:
|
||||||
|
# image: quay.io/thegeeklab/git-chglog
|
||||||
|
# commands:
|
||||||
|
# - git fetch -tq
|
||||||
|
# - git-chglog --no-color --no-emoji -o CHANGELOG.md ${CI_COMMIT_TAG:---next-tag unreleased unreleased}
|
||||||
|
|
||||||
|
# changelog-format:
|
||||||
|
# image: quay.io/thegeeklab/alpine-tools
|
||||||
|
# commands:
|
||||||
|
# - prettier CHANGELOG.md
|
||||||
|
# - prettier -w CHANGELOG.md
|
||||||
|
|
||||||
|
publish-github:
|
||||||
|
image: docker.io/plugins/github-release
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: github_token
|
||||||
|
note: CHANGELOG.md
|
||||||
|
overwrite: true
|
||||||
|
title: ${CI_COMMIT_TAG}
|
||||||
|
when:
|
||||||
|
- event: [tag]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
72
.woodpecker/docs.yml
Normal file
72
.woodpecker/docs.yml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
when:
|
||||||
|
- event: [pull_request, tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
markdownlint:
|
||||||
|
image: quay.io/thegeeklab/markdownlint-cli
|
||||||
|
commands:
|
||||||
|
- markdownlint 'README.md' 'CONTRIBUTING.md'
|
||||||
|
|
||||||
|
spellcheck:
|
||||||
|
image: quay.io/thegeeklab/alpine-tools
|
||||||
|
commands:
|
||||||
|
- spellchecker --files '_docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
|
||||||
|
environment:
|
||||||
|
FORCE_COLOR: "true"
|
||||||
|
NPM_CONFIG_LOGLEVEL: "error"
|
||||||
|
|
||||||
|
publish:
|
||||||
|
image: quay.io/thegeeklab/git-sv
|
||||||
|
settings:
|
||||||
|
action:
|
||||||
|
- pages
|
||||||
|
author_email: bot@thegeeklab.de
|
||||||
|
author_name: thegeeklab-bot
|
||||||
|
branch: docs
|
||||||
|
message: "[skip ci] auto-update documentation"
|
||||||
|
netrc_password:
|
||||||
|
from_secret: github_token
|
||||||
|
pages_directory: _docs/
|
||||||
|
when:
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
pushrm-dockerhub:
|
||||||
|
image: docker.io/chko/docker-pushrm:1
|
||||||
|
secrets:
|
||||||
|
- source: docker_password
|
||||||
|
target: DOCKER_PASS
|
||||||
|
- source: docker_username
|
||||||
|
target: DOCKER_USER
|
||||||
|
environment:
|
||||||
|
PUSHRM_FILE: README.md
|
||||||
|
PUSHRM_SHORT: Woodpecker CI plugin to perform git actions
|
||||||
|
PUSHRM_TARGET: ${CI_REPO}
|
||||||
|
when:
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
status: [success]
|
||||||
|
|
||||||
|
pushrm-quay:
|
||||||
|
image: docker.io/chko/docker-pushrm:1
|
||||||
|
secrets:
|
||||||
|
- source: quay_token
|
||||||
|
target: APIKEY__QUAY_IO
|
||||||
|
environment:
|
||||||
|
PUSHRM_FILE: README.md
|
||||||
|
PUSHRM_TARGET: quay.io/${CI_REPO}
|
||||||
|
when:
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
status: [success]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- build-package
|
||||||
|
- build-container
|
26
.woodpecker/notify.yml
Normal file
26
.woodpecker/notify.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
when:
|
||||||
|
- event: [tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
runs_on: [success, failure]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
matrix:
|
||||||
|
image: quay.io/thegeeklab/wp-matrix
|
||||||
|
settings:
|
||||||
|
homeserver:
|
||||||
|
from_secret: matrix_homeserver
|
||||||
|
password:
|
||||||
|
from_secret: matrix_password
|
||||||
|
roomid:
|
||||||
|
from_secret: matrix_roomid
|
||||||
|
username:
|
||||||
|
from_secret: matrix_username
|
||||||
|
when:
|
||||||
|
- status: [success, failure]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- docs
|
17
.woodpecker/test.yml
Normal file
17
.woodpecker/test.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
when:
|
||||||
|
- event: [pull_request, tag]
|
||||||
|
- event: [push, manual]
|
||||||
|
branch:
|
||||||
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
lint:
|
||||||
|
image: docker.io/library/golang:1.21
|
||||||
|
commands:
|
||||||
|
- make lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
image: docker.io/library/golang:1.21
|
||||||
|
commands:
|
||||||
|
- make test
|
25
Containerfile.multiarch
Normal file
25
Containerfile.multiarch
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM --platform=$BUILDPLATFORM golang:1.21@sha256:19600fdcae402165dcdab18cb9649540bde6be7274dedb5d205b2f84029fe909 as build
|
||||||
|
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ADD . /src
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN make build
|
||||||
|
|
||||||
|
FROM alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978
|
||||||
|
|
||||||
|
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
|
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
|
LABEL org.opencontainers.image.title="git-sv"
|
||||||
|
LABEL org.opencontainers.image.url="https://github.com/thegeeklab/git-sv"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/git-sv"
|
||||||
|
LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/git-sv"
|
||||||
|
|
||||||
|
RUN apk --update add --no-cache git && \
|
||||||
|
rm -rf /var/cache/apk/* && \
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
COPY --from=build /src/dist/git-sv /bin/git-sv
|
||||||
|
ENTRYPOINT ["/bin/git-sv"]
|
22
LICENSE
22
LICENSE
@ -1,21 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Beatriz Vieira
|
Copyright (c) 2022 Robert Kaussow <mail@thegeeklab.de>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
furnished to do so, subject to the following conditions:
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice (including the next
|
||||||
copies or substantial portions of the Software.
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
SOFTWARE.
|
|
||||||
|
168
Makefile
168
Makefile
@ -1,89 +1,101 @@
|
|||||||
.PHONY: usage build lint lint-autofix test test-coverage test-show-coverage run tidy release release-all
|
# renovate: datasource=github-releases depName=mvdan/gofumpt
|
||||||
|
GOFUMPT_PACKAGE_VERSION := v0.5.0
|
||||||
|
# renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||||
|
GOLANGCI_LINT_PACKAGE_VERSION := v1.54.2
|
||||||
|
|
||||||
OK_COLOR=\033[32;01m
|
EXECUTABLE := git-sv
|
||||||
NO_COLOR=\033[0m
|
|
||||||
ERROR_COLOR=\033[31;01m
|
|
||||||
WARN_COLOR=\033[33;01m
|
|
||||||
|
|
||||||
PKGS = $(shell go list ./...)
|
DIST := dist
|
||||||
BIN = git-sv
|
DIST_DIRS := $(DIST)
|
||||||
|
IMPORT := github.com/thegeeklab/$(EXECUTABLE)
|
||||||
|
|
||||||
ECHOFLAGS ?=
|
GO ?= go
|
||||||
|
CWD ?= $(shell pwd)
|
||||||
|
PACKAGES ?= $(shell go list ./...)
|
||||||
|
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||||
|
|
||||||
BUILD_TIME = $(shell date +"%Y%m%d%H%M")
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION)
|
||||||
VERSION ?= dev-$(BUILD_TIME)
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION)
|
||||||
|
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||||
|
|
||||||
BUILDOS ?= linux
|
GENERATE ?=
|
||||||
BUILDARCH ?= amd64
|
XGO_VERSION := go-1.21.x
|
||||||
BUILDENVS ?= CGO_ENABLED=0 GOOS=$(BUILDOS) GOARCH=$(BUILDARCH)
|
XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
|
||||||
BUILDFLAGS ?= -a -installsuffix cgo --ldflags '-X main.Version=$(VERSION) -extldflags "-lm -lstdc++ -static"'
|
|
||||||
|
|
||||||
COMPRESS_TYPE ?= targz
|
TARGETOS ?= linux
|
||||||
|
TARGETARCH ?= amd64
|
||||||
|
ifneq ("$(TARGETVARIANT)","")
|
||||||
|
GOARM ?= $(subst v,,$(TARGETVARIANT))
|
||||||
|
endif
|
||||||
|
TAGS ?= netgo,osusergo
|
||||||
|
|
||||||
usage: Makefile
|
ifndef VERSION
|
||||||
@echo $(ECHOFLAGS) "to use make call:"
|
ifneq ($(CI_COMMIT_TAG),)
|
||||||
@echo $(ECHOFLAGS) " make <action>"
|
VERSION ?= $(subst v,,$(CI_COMMIT_TAG))
|
||||||
@echo $(ECHOFLAGS) ""
|
else
|
||||||
@echo $(ECHOFLAGS) "list of available actions:"
|
VERSION ?= $(shell git rev-parse --short HEAD)
|
||||||
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
|
endif
|
||||||
|
|
||||||
## build: build git-sv
|
|
||||||
build: test
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Building binary ($(BUILDOS)/$(BUILDARCH)/$(BIN))...$(NO_COLOR)"
|
|
||||||
@$(BUILDENVS) go build -v $(BUILDFLAGS) -o bin/$(BUILDOS)_$(BUILDARCH)/$(BIN) ./cmd/git-sv
|
|
||||||
|
|
||||||
## lint: run golangci-lint without autofix
|
|
||||||
lint:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running golangci-lint...$(NO_COLOR)"
|
|
||||||
@golangci-lint run ./... --config .golangci.yml
|
|
||||||
|
|
||||||
## lint-autofix: run golangci-lint with autofix enabled
|
|
||||||
lint-autofix:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running golangci-lint...$(NO_COLOR)"
|
|
||||||
@golangci-lint run ./... --config .golangci.yml --fix
|
|
||||||
|
|
||||||
## test: run unit tests
|
|
||||||
test:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running tests...$(NO_COLOR)"
|
|
||||||
@go test $(PKGS)
|
|
||||||
|
|
||||||
## test-coverage: run tests with coverage
|
|
||||||
test-coverage:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running tests with coverage...$(NO_COLOR)"
|
|
||||||
@go test -race -covermode=atomic -coverprofile coverage.out ./...
|
|
||||||
|
|
||||||
## test-show-coverage: show coverage
|
|
||||||
test-show-coverage: test-coverage
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Show test coverage...$(NO_COLOR)"
|
|
||||||
@go tool cover -html coverage.out
|
|
||||||
|
|
||||||
## run: run git-sv
|
|
||||||
run:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running bin/$(BUILDOS)_$(BUILDARCH)/$(BIN)...$(NO_COLOR)"
|
|
||||||
@./bin/$(BUILDOS)_$(BUILDARCH)/$(BIN) $(args)
|
|
||||||
|
|
||||||
## tidy: execute go mod tidy
|
|
||||||
tidy:
|
|
||||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> runing tidy"
|
|
||||||
@go mod tidy
|
|
||||||
|
|
||||||
## release: prepare binary for release
|
|
||||||
release:
|
|
||||||
make build
|
|
||||||
ifeq ($(COMPRESS_TYPE), zip)
|
|
||||||
@zip -j bin/git-sv_$(VERSION)_$(BUILDOS)_$(BUILDARCH).zip bin/$(BUILDOS)_$(BUILDARCH)/$(BIN)
|
|
||||||
else
|
|
||||||
@tar -czf bin/git-sv_$(VERSION)_$(BUILDOS)_$(BUILDARCH).tar.gz -C bin/$(BUILDOS)_$(BUILDARCH)/ $(BIN)
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
## release-all: prepare linux, darwin and windows binary for release (requires sv4git)
|
ifndef DATE
|
||||||
release-all:
|
DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%z")
|
||||||
@rm -rf bin
|
endif
|
||||||
|
|
||||||
VERSION=$(shell git sv nv) BUILDOS=linux BUILDARCH=amd64 make release
|
LDFLAGS += -s -w -X "main.BuildVersion=$(VERSION)" -X "main.BuildDate=$(DATE)"
|
||||||
VERSION=$(shell git sv nv) BUILDOS=darwin BUILDARCH=amd64 make release
|
|
||||||
VERSION=$(shell git sv nv) COMPRESS_TYPE=zip BUILDOS=windows BUILDARCH=amd64 make release
|
|
||||||
|
|
||||||
VERSION=$(shell git sv nv) BUILDOS=linux BUILDARCH=arm64 make release
|
.PHONY: all
|
||||||
VERSION=$(shell git sv nv) BUILDOS=darwin BUILDARCH=arm64 make release
|
all: clean build
|
||||||
VERSION=$(shell git sv nv) COMPRESS_TYPE=zip BUILDOS=windows BUILDARCH=arm64 make release
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
$(GO) clean -i ./...
|
||||||
|
rm -rf $(DIST_DIRS)
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
$(GO) run $(GOFUMPT_PACKAGE) -extra -w $(SOURCES)
|
||||||
|
|
||||||
|
.PHONY: golangci-lint
|
||||||
|
golangci-lint:
|
||||||
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: golangci-lint
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate:
|
||||||
|
$(GO) generate $(GENERATE)
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
$(GO) test -v -coverprofile coverage.out $(PACKAGES)
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: $(DIST)/$(EXECUTABLE)
|
||||||
|
|
||||||
|
$(DIST)/$(EXECUTABLE): $(SOURCES)
|
||||||
|
GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) GOARM=$(GOARM) $(GO) build -v -tags '$(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -o $@ ./cmd/$(EXECUTABLE)
|
||||||
|
|
||||||
|
$(DIST_DIRS):
|
||||||
|
mkdir -p $(DIST_DIRS)
|
||||||
|
|
||||||
|
.PHONY: xgo
|
||||||
|
xgo: | $(DIST_DIRS)
|
||||||
|
$(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -v -ldflags '-extldflags "-static" $(LDFLAGS)' -tags '$(TAGS)' -targets '$(XGO_TARGETS)' -out $(EXECUTABLE) --pkg cmd/$(EXECUTABLE) .
|
||||||
|
cp /build/* $(CWD)/$(DIST)
|
||||||
|
ls -l $(CWD)/$(DIST)
|
||||||
|
|
||||||
|
.PHONY: checksum
|
||||||
|
checksum:
|
||||||
|
cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||||
|
ls -l $(CWD)/$(DIST)
|
||||||
|
|
||||||
|
.PHONY: release
|
||||||
|
release: xgo checksum
|
||||||
|
|
||||||
|
.PHONY: deps
|
||||||
|
deps:
|
||||||
|
$(GO) mod download
|
||||||
|
$(GO) install $(GOFUMPT_PACKAGE)
|
||||||
|
$(GO) install $(GOLANGCI_LINT_PACKAGE)
|
||||||
|
$(GO) install $(XGO_PACKAGE)
|
||||||
|
146
README.md
146
README.md
@ -1,18 +1,6 @@
|
|||||||
<p align="center">
|
# git-sv
|
||||||
<h1 align="center">sv4git</h1>
|
|
||||||
<p align="center">A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs!</p>
|
A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs.
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/bvieira/sv4git/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/bvieira/sv4git.svg?style=for-the-badge"></a>
|
|
||||||
<a href="https://pkg.go.dev/github.com/bvieira/sv4git/v2"><img alt="Go Reference" src="https://img.shields.io/badge/-Reference-blue?style=for-the-badge&logo=go&labelColor=gray"></a>
|
|
||||||
<a href="https://github.com/bvieira/sv4git/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/bvieira/sv4git?style=for-the-badge"></a>
|
|
||||||
<a href="https://github.com/bvieira/sv4git/releases/latest"><img alt="GitHub release (latest by date)" src="https://img.shields.io/github/downloads/bvieira/sv4git/latest/total?color=blue&style=for-the-badge"></a>
|
|
||||||
<a href="https://github.com/bvieira/sv4git/releases/latest"><img alt="GitHub all releases" src="https://img.shields.io/github/downloads/bvieira/sv4git/total?color=blue&style=for-the-badge"></a>
|
|
||||||
<a href="/LICENSE"><img alt="Software License" src="https://img.shields.io/badge/license-MIT-informational.svg?style=for-the-badge"></a>
|
|
||||||
<a href="https://github.com/bvieira/sv4git/actions?workflow=ci"><img alt="GitHub Actions Status" src="https://img.shields.io/github/actions/workflow/status/bvieira/sv4git/ci.yml?style=for-the-badge"></a>
|
|
||||||
<a href="https://goreportcard.com/report/github.com/bvieira/sv4git"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/bvieira/sv4git?style=for-the-badge"></a>
|
|
||||||
<a href="https://conventionalcommits.org"><img alt="Conventional Commits" src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-informational.svg?style=for-the-badge"></a>
|
|
||||||
</p>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -23,23 +11,23 @@
|
|||||||
### Installing
|
### Installing
|
||||||
|
|
||||||
- Download the latest release and add the binary to your path.
|
- Download the latest release and add the binary to your path.
|
||||||
- Optional: Set `SV4GIT_HOME` to define user configs. Check the [Config](#config) topic for more information.
|
- Optional: Set `GITSV_HOME` to define user configs. Check the [Config](#config) topic for more information.
|
||||||
|
|
||||||
If you want to install from source using `go install`, just run:
|
If you want to install from source using `go install`, just run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# keep in mind that with this, it will compile from source and won't show the version on cli -h.
|
# keep in mind that with this, it will compile from source and won't show the version on cli -h.
|
||||||
go install github.com/bvieira/sv4git/v2/cmd/git-sv@latest
|
go install github.com/thegeeklab/git-sv/v2/cmd/git-sv@latest
|
||||||
|
|
||||||
# if you want to add the version on the binary, run this command instead.
|
# if you want to add the version on the binary, run this command instead.
|
||||||
SV4GIT_VERSION=$(go list -f '{{ .Version }}' -m github.com/bvieira/sv4git/v2@latest | sed 's/v//') && go install --ldflags "-X main.Version=$SV4GIT_VERSION" github.com/bvieira/sv4git/v2/cmd/git-sv@v$SV4GIT_VERSION
|
GITSV_VERSION=$(go list -f '{{ .Version }}' -m github.com/thegeeklab/git-sv/v2@latest | sed 's/v//') && go install --ldflags "-X main.Version=$SGITSV_VERSION" github.com/thegeeklab/git-sv/v2/cmd/git-sv@v$GITSV_VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
#### YAML
|
#### YAML
|
||||||
|
|
||||||
There are 3 config levels when using sv4git: [default](#default), [user](#user), [repository](#repository). All of them are merged considering the follow priority: **repository > user > default**.
|
There are 3 config levels when using git-sv: [default](#default), [user](#user), [repository](#repository). All of them are merged considering the follow priority: **repository > user > default**.
|
||||||
|
|
||||||
To see the current config, run:
|
To see the current config, run:
|
||||||
|
|
||||||
@ -59,22 +47,22 @@ git sv cfg default
|
|||||||
|
|
||||||
###### User
|
###### User
|
||||||
|
|
||||||
For user config, it is necessary to define the `SV4GIT_HOME` environment variable, eg.:
|
For user config, it is necessary to define the `GITSV_HOME` environment variable, eg.:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SV4GIT_HOME=/home/myuser/.sv4git # myuser is just an example.
|
GITSV_HOME=/home/myuser/.gitsv # myuser is just an example.
|
||||||
```
|
```
|
||||||
|
|
||||||
And create a `config.yml` file inside it, eg.:
|
And create a `config.yml` file inside it, eg.:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.sv4git
|
.gitsv
|
||||||
└── config.yml
|
└── config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Repository
|
###### Repository
|
||||||
|
|
||||||
Create a `.sv4git.yml` file on the root of your repository, eg.: [.sv4git.yml](.sv4git.yml).
|
Create a `.gitsv/config.yml` file on the root of your repository, eg. [.gitsv/config.yml](.gitsv/config.yml).
|
||||||
|
|
||||||
##### Configuration format
|
##### Configuration format
|
||||||
|
|
||||||
@ -82,73 +70,85 @@ Create a `.sv4git.yml` file on the root of your repository, eg.: [.sv4git.yml](.
|
|||||||
version: "1.1" #config version
|
version: "1.1" #config version
|
||||||
|
|
||||||
versioning: # versioning bump
|
versioning: # versioning bump
|
||||||
update-major: [] # Commit types used to bump major.
|
update-major: [] # Commit types used to bump major.
|
||||||
update-minor: [feat] # Commit types used to bump minor.
|
update-minor: [feat] # Commit types used to bump minor.
|
||||||
update-patch: [build, ci, chore, fix, perf, refactor, test] # Commit types used to bump patch.
|
update-patch: [build, ci, chore, fix, perf, refactor, test] # Commit types used to bump patch.
|
||||||
# When type is not present on update rules and is unknown (not mapped on commit message types);
|
# When type is not present on update rules and is unknown (not mapped on commit message types);
|
||||||
# if ignore-unknown=false bump patch, if ignore-unknown=true do not bump version
|
# if ignore-unknown=false bump patch, if ignore-unknown=true do not bump version
|
||||||
ignore-unknown: false
|
ignore-unknown: false
|
||||||
|
|
||||||
tag:
|
tag:
|
||||||
pattern: '%d.%d.%d' # Pattern used to create git tag.
|
pattern: "%d.%d.%d" # Pattern used to create git tag.
|
||||||
filter: '' # Enables you to filter for considerable tags using git pattern syntax
|
filter: "" # Enables you to filter for considerable tags using git pattern syntax
|
||||||
|
|
||||||
release-notes:
|
release-notes:
|
||||||
# Deprecated!!! please use 'sections' instead!
|
# Deprecated!!! please use 'sections' instead!
|
||||||
# Headers names for release notes markdown. To disable a section just remove the header
|
# Headers names for release notes markdown. To disable a section just remove the header
|
||||||
# line. It's possible to add other commit types, the release note will be created
|
# line. It's possible to add other commit types, the release note will be created
|
||||||
# respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
|
# respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
|
||||||
headers:
|
headers:
|
||||||
breaking-change: Breaking Changes
|
breaking-change: Breaking Changes
|
||||||
feat: Features
|
feat: Features
|
||||||
fix: Bug Fixes
|
fix: Bug Fixes
|
||||||
|
|
||||||
sections: # Array with each section of release note. Check template section for more information.
|
sections: # Array with each section of release note. Check template section for more information.
|
||||||
- name: Features # Name used on section.
|
- name: Features # Name used on section.
|
||||||
section-type: commits # Type of the section, supported types: commits, breaking-changes.
|
section-type: commits # Type of the section, supported types: commits, breaking-changes.
|
||||||
commit-types: [feat] # Commit types for commit section-type, one commit type cannot be in more than one section.
|
commit-types: [feat] # Commit types for commit section-type, one commit type cannot be in more than one section.
|
||||||
- name: Bug Fixes
|
- name: Bug Fixes
|
||||||
section-type: commits
|
section-type: commits
|
||||||
commit-types: [fix]
|
commit-types: [fix]
|
||||||
- name: Breaking Changes
|
- name: Breaking Changes
|
||||||
section-type: breaking-changes
|
section-type: breaking-changes
|
||||||
|
|
||||||
branches: # Git branches config.
|
branches: # Git branches config.
|
||||||
prefix: ([a-z]+\/)? # Prefix used on branch name, it should be a regex group.
|
prefix: ([a-z]+\/)? # Prefix used on branch name, it should be a regex group.
|
||||||
suffix: (-.*)? # Suffix used on branch name, it should be a regex group.
|
suffix: (-.*)? # Suffix used on branch name, it should be a regex group.
|
||||||
disable-issue: false # Set true if there is no need to recover issue id from branch name.
|
disable-issue: false # Set true if there is no need to recover issue id from branch name.
|
||||||
skip: [master, main, developer] # List of branch names ignored on commit message validation.
|
skip: [master, main, developer] # List of branch names ignored on commit message validation.
|
||||||
skip-detached: false # Set true if a detached branch should be ignored on commit message validation.
|
skip-detached: false # Set true if a detached branch should be ignored on commit message validation.
|
||||||
|
|
||||||
commit-message:
|
commit-message:
|
||||||
types: [build, ci, chore, docs, feat, fix, perf, refactor, revert, style, test] # Supported commit types.
|
types: [
|
||||||
header-selector: '' # You can put in a regex here to select only a certain part of the commit message. Please define a regex group 'header'.
|
build,
|
||||||
scope:
|
ci,
|
||||||
# Define supported scopes, if blank, scope will not be validated, if not, only scope listed will be valid.
|
chore,
|
||||||
# Don't forget to add "" on your list if you need to define scopes and keep it optional.
|
docs,
|
||||||
values: []
|
feat,
|
||||||
footer:
|
fix,
|
||||||
issue: # Use "issue: {}" if you wish to disable issue footer.
|
perf,
|
||||||
key: jira # Name used to define an issue on footer metadata.
|
refactor,
|
||||||
key-synonyms: [Jira, JIRA] # Supported variations for footer metadata.
|
revert,
|
||||||
use-hash: false # If false, use :<space> separator. If true, use <space># separator.
|
style,
|
||||||
add-value-prefix: '' # Add a prefix to issue value.
|
test,
|
||||||
issue:
|
] # Supported commit types.
|
||||||
regex: '[A-Z]+-[0-9]+' # Regex for issue id.
|
header-selector: "" # You can put in a regex here to select only a certain part of the commit message. Please define a regex group 'header'.
|
||||||
|
scope:
|
||||||
|
# Define supported scopes, if blank, scope will not be validated, if not, only scope listed will be valid.
|
||||||
|
# Don't forget to add "" on your list if you need to define scopes and keep it optional.
|
||||||
|
values: []
|
||||||
|
footer:
|
||||||
|
issue: # Use "issue: {}" if you wish to disable issue footer.
|
||||||
|
key: jira # Name used to define an issue on footer metadata.
|
||||||
|
key-synonyms: [Jira, JIRA] # Supported variations for footer metadata.
|
||||||
|
use-hash: false # If false, use :<space> separator. If true, use <space># separator.
|
||||||
|
add-value-prefix: "" # Add a prefix to issue value.
|
||||||
|
issue:
|
||||||
|
regex: "[A-Z]+-[0-9]+" # Regex for issue id.
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Templates
|
#### Templates
|
||||||
|
|
||||||
**sv4git** uses *go templates* to format the output for `release-notes` and `changelog`, to see how the default template is configured check [template directory](cmd/git-sv/resources/templates). On v2.7.0+, its possible to overwrite the default configuration by adding `.sv4git/templates` on your repository. The cli expects that at least 2 files exists on your directory: `changelog-md.tpl` and `releasenotes-md.tpl`.
|
**git-sv** uses _go templates_ to format the output for `release-notes` and `changelog`, to see how the default template is configured check [template directory](cmd/git-sv/resources/templates). It's possible to overwrite the default configuration by adding `.gitsv/templates` on your repository. The cli expects that at least 2 files exists on your directory: `changelog-md.tpl` and `releasenotes-md.tpl`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.sv4git
|
.gitsv
|
||||||
└── templates
|
└── templates
|
||||||
├── changelog-md.tpl
|
├── changelog-md.tpl
|
||||||
└── releasenotes-md.tpl
|
└── releasenotes-md.tpl
|
||||||
```
|
```
|
||||||
|
|
||||||
Everything inside `.sv4git/templates` will be loaded, so it's possible to add more files to be used as needed.
|
Everything inside `.gitsv/templates` will be loaded, so it's possible to add more files to be used as needed.
|
||||||
|
|
||||||
##### Variables
|
##### Variables
|
||||||
|
|
||||||
@ -156,9 +156,9 @@ To execute the template the `releasenotes-md.tpl` will receive a single **Releas
|
|||||||
|
|
||||||
Each **ReleaseNoteSection** will be configured according with `release-notes.section` from config file. The order for each section will be maintained and the **SectionType** is defined according with `section-type` attribute as described on the table below.
|
Each **ReleaseNoteSection** will be configured according with `release-notes.section` from config file. The order for each section will be maintained and the **SectionType** is defined according with `section-type` attribute as described on the table below.
|
||||||
|
|
||||||
| section-type | ReleaseNoteSection |
|
| section-type | ReleaseNoteSection |
|
||||||
| -- | -- |
|
| ---------------- | -------------------------------- |
|
||||||
| commits | ReleaseNoteCommitsSection |
|
| commits | ReleaseNoteCommitsSection |
|
||||||
| breaking-changes | ReleaseNoteBreakingChangeSection |
|
| breaking-changes | ReleaseNoteBreakingChangeSection |
|
||||||
|
|
||||||
> :warning: currently only `commits` and `breaking-changes` are supported as `section-types`, using a different value for this field will make the section to be removed from the template variables.
|
> :warning: currently only `commits` and `breaking-changes` are supported as `section-types`, using a different value for this field will make the section to be removed from the template variables.
|
||||||
|
@ -4,27 +4,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bvieira/sv4git/v2/sv"
|
"dario.cat/mergo"
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
|
"github.com/thegeeklab/git-sv/v2/sv"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvConfig env vars for cli configuration.
|
// EnvConfig env vars for cli configuration.
|
||||||
type EnvConfig struct {
|
type EnvConfig struct {
|
||||||
Home string `envconfig:"SV4GIT_HOME" default:""`
|
Home string `envconfig:"GITSV_HOME" default:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEnvConfig() EnvConfig {
|
func loadEnvConfig() EnvConfig {
|
||||||
var c EnvConfig
|
var c EnvConfig
|
||||||
|
|
||||||
err := envconfig.Process("", &c)
|
err := envconfig.Process("", &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to load env config, error: ", err.Error())
|
log.Fatal("failed to load env config, error: ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,20 +38,6 @@ type Config struct {
|
|||||||
CommitMessage sv.CommitMessageConfig `yaml:"commit-message"`
|
CommitMessage sv.CommitMessageConfig `yaml:"commit-message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRepoPath() (string, error) {
|
|
||||||
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "", combinedOutputErr(err, out)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(out)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func combinedOutputErr(err error, out []byte) error {
|
|
||||||
msg := strings.Split(string(out), "\n")
|
|
||||||
return fmt.Errorf("%v - %s", err, msg[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig(filepath string) (Config, error) {
|
func readConfig(filepath string) (Config, error) {
|
||||||
content, rerr := os.ReadFile(filepath)
|
content, rerr := os.ReadFile(filepath)
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
@ -59,9 +45,10 @@ func readConfig(filepath string) (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cfg Config
|
var cfg Config
|
||||||
|
|
||||||
cerr := yaml.Unmarshal(content, &cfg)
|
cerr := yaml.Unmarshal(content, &cfg)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
return Config{}, fmt.Errorf("could not parse config from path: %s, error: %v", filepath, cerr)
|
return Config{}, fmt.Errorf("could not parse config from path: %s, error: %w", filepath, cerr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
@ -71,6 +58,7 @@ func defaultConfig() Config {
|
|||||||
skipDetached := false
|
skipDetached := false
|
||||||
pattern := "%d.%d.%d"
|
pattern := "%d.%d.%d"
|
||||||
filter := ""
|
filter := ""
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
Version: "1.1",
|
Version: "1.1",
|
||||||
Versioning: sv.VersioningConfig{
|
Versioning: sv.VersioningConfig{
|
||||||
@ -116,6 +104,7 @@ func merge(dst *Config, src Config) error {
|
|||||||
dst.ReleaseNotes.Headers = src.ReleaseNotes.Headers
|
dst.ReleaseNotes.Headers = src.ReleaseNotes.Headers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +116,7 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
|
|||||||
if dst.CanSet() && !src.IsNil() {
|
if dst.CanSet() && !src.IsNil() {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,9 +126,11 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
|
|||||||
if dst.CanSet() && !src.IsNil() {
|
if dst.CanSet() && !src.IsNil() {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +138,7 @@ func migrateConfig(cfg Config, filename string) Config {
|
|||||||
if cfg.ReleaseNotes.Headers == nil {
|
if cfg.ReleaseNotes.Headers == nil {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
warnf("config 'release-notes.headers' on %s is deprecated, please use 'sections' instead!", filename)
|
warnf("config 'release-notes.headers' on %s is deprecated, please use 'sections' instead!", filename)
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
@ -162,14 +155,29 @@ func migrateConfig(cfg Config, filename string) Config {
|
|||||||
|
|
||||||
func migrateReleaseNotesConfig(headers map[string]string) []sv.ReleaseNotesSectionConfig {
|
func migrateReleaseNotesConfig(headers map[string]string) []sv.ReleaseNotesSectionConfig {
|
||||||
order := []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}
|
order := []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}
|
||||||
|
|
||||||
var sections []sv.ReleaseNotesSectionConfig
|
var sections []sv.ReleaseNotesSectionConfig
|
||||||
|
|
||||||
for _, key := range order {
|
for _, key := range order {
|
||||||
if name, exists := headers[key]; exists {
|
if name, exists := headers[key]; exists {
|
||||||
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{key}})
|
sections = append(
|
||||||
|
sections,
|
||||||
|
sv.ReleaseNotesSectionConfig{
|
||||||
|
Name: name,
|
||||||
|
SectionType: sv.ReleaseNotesSectionTypeCommits,
|
||||||
|
CommitTypes: []string{key},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if name, exists := headers["breaking-change"]; exists {
|
if name, exists := headers["breaking-change"]; exists {
|
||||||
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeBreakingChanges})
|
sections = append(
|
||||||
|
sections,
|
||||||
|
sv.ReleaseNotesSectionConfig{
|
||||||
|
Name: name,
|
||||||
|
SectionType: sv.ReleaseNotesSectionTypeBreakingChanges,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bvieira/sv4git/v2/sv"
|
"github.com/thegeeklab/git-sv/v2/sv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_merge(t *testing.T) {
|
func Test_merge(t *testing.T) {
|
||||||
@ -20,24 +20,135 @@ func Test_merge(t *testing.T) {
|
|||||||
want Config
|
want Config
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"overwrite string", Config{Version: "a"}, Config{Version: "b"}, Config{Version: "b"}, false},
|
{
|
||||||
{"default string", Config{Version: "a"}, Config{Version: ""}, Config{Version: "a"}, false},
|
"overwrite string",
|
||||||
|
Config{Version: "a"},
|
||||||
|
Config{Version: "b"},
|
||||||
|
Config{Version: "b"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default string",
|
||||||
|
Config{Version: "a"},
|
||||||
|
Config{Version: ""},
|
||||||
|
Config{Version: "a"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overwrite list",
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"c", "d"}}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"c", "d"}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overwrite list with empty",
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: make([]string, 0)}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: make([]string, 0)}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default list",
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: nil}},
|
||||||
|
Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
{"overwrite list", Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}}, Config{Branches: sv.BranchesConfig{Skip: []string{"c", "d"}}}, Config{Branches: sv.BranchesConfig{Skip: []string{"c", "d"}}}, false},
|
{
|
||||||
{"overwrite list with empty", Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}}, Config{Branches: sv.BranchesConfig{Skip: make([]string, 0)}}, Config{Branches: sv.BranchesConfig{Skip: make([]string, 0)}}, false},
|
"overwrite pointer bool false",
|
||||||
{"default list", Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}}, Config{Branches: sv.BranchesConfig{Skip: nil}}, Config{Branches: sv.BranchesConfig{Skip: []string{"a", "b"}}}, false},
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
|
||||||
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
|
||||||
{"overwrite pointer bool false", Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, false},
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
|
||||||
{"overwrite pointer bool true", Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, false},
|
false,
|
||||||
{"default pointer bool", Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, Config{Branches: sv.BranchesConfig{SkipDetached: nil}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, false},
|
},
|
||||||
|
{
|
||||||
{"merge maps", Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}}}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue2": {Key: "jira2"}}}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}, "issue2": {Key: "jira2"}}}}, false},
|
"overwrite pointer bool true",
|
||||||
{"default maps", Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}}}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: nil}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}}}}, false},
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
|
||||||
{"merge empty maps", Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}}}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{}}}, Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}}}}, false},
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
|
||||||
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
|
||||||
{"overwrite release notes header", Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"a": "aa"}}}, Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"b": "bb"}}}, Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"b": "bb"}}}, false},
|
false,
|
||||||
|
},
|
||||||
{"overwrite tag config", Config{Version: "a", Tag: sv.TagConfig{Pattern: &nonEmptyStr, Filter: &nonEmptyStr}}, Config{Version: "", Tag: sv.TagConfig{Pattern: &emptyStr, Filter: &emptyStr}}, Config{Version: "a", Tag: sv.TagConfig{Pattern: &emptyStr, Filter: &emptyStr}}, false},
|
{
|
||||||
|
"default pointer bool",
|
||||||
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
|
||||||
|
Config{Branches: sv.BranchesConfig{SkipDetached: nil}},
|
||||||
|
Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"merge maps",
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}},
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue2": {Key: "jira2"}},
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{Footer: map[string]sv.CommitMessageFooterConfig{
|
||||||
|
"issue": {Key: "jira"},
|
||||||
|
"issue2": {Key: "jira2"},
|
||||||
|
}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default maps",
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}},
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: nil,
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}},
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"merge empty maps",
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}},
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{},
|
||||||
|
}},
|
||||||
|
Config{CommitMessage: sv.CommitMessageConfig{
|
||||||
|
Footer: map[string]sv.CommitMessageFooterConfig{"issue": {Key: "jira"}},
|
||||||
|
}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overwrite release notes header",
|
||||||
|
Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"a": "aa"}}},
|
||||||
|
Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"b": "bb"}}},
|
||||||
|
Config{ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"b": "bb"}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"overwrite tag config",
|
||||||
|
Config{
|
||||||
|
Version: "a",
|
||||||
|
Tag: sv.TagConfig{
|
||||||
|
Pattern: &nonEmptyStr,
|
||||||
|
Filter: &nonEmptyStr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Config{
|
||||||
|
Version: "",
|
||||||
|
Tag: sv.TagConfig{
|
||||||
|
Pattern: &emptyStr,
|
||||||
|
Filter: &emptyStr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Config{
|
||||||
|
Version: "a",
|
||||||
|
Tag: sv.TagConfig{
|
||||||
|
Pattern: &emptyStr,
|
||||||
|
Filter: &emptyStr,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -10,19 +11,32 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/bvieira/sv4git/v2/sv"
|
"github.com/thegeeklab/git-sv/v2/sv"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const laxFilePerm = 0o644
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCanNotCreateTagFlag = errors.New("cannot define tag flag with range, start or end flags")
|
||||||
|
errUnknownTag = errors.New("unknown tag")
|
||||||
|
errReadCommitMessage = errors.New("failed to read commit message")
|
||||||
|
errAppendFooter = errors.New("failed to append meta-informations on footer")
|
||||||
|
errInvalidRange = errors.New("invalid log range")
|
||||||
|
)
|
||||||
|
|
||||||
func configDefaultHandler() func(c *cli.Context) error {
|
func configDefaultHandler() func(c *cli.Context) error {
|
||||||
cfg := defaultConfig()
|
cfg := defaultConfig()
|
||||||
|
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
content, err := yaml.Marshal(&cfg)
|
content, err := yaml.Marshal(&cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(content))
|
fmt.Println(string(content))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,7 +47,9 @@ func configShowHandler(cfg Config) func(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(content))
|
fmt.Println(string(content))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,9 +60,11 @@ func currentVersionHandler(git sv.Git) func(c *cli.Context) error {
|
|||||||
|
|
||||||
currentVer, err := sv.ToVersion(lastTag)
|
currentVer, err := sv.ToVersion(lastTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing version: %s from git tag, message: %v", lastTag, err)
|
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%d.%d.%d\n", currentVer.Major(), currentVer.Minor(), currentVer.Patch())
|
fmt.Printf("%d.%d.%d\n", currentVer.Major(), currentVer.Minor(), currentVer.Patch())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,30 +75,62 @@ func nextVersionHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) f
|
|||||||
|
|
||||||
currentVer, err := sv.ToVersion(lastTag)
|
currentVer, err := sv.ToVersion(lastTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing version: %s from git tag, message: %v", lastTag, err)
|
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting git log, message: %v", err)
|
return fmt.Errorf("error getting git log, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextVer, _ := semverProcessor.NextVersion(currentVer, commits)
|
nextVer, _ := semverProcessor.NextVersion(currentVer, commits)
|
||||||
|
|
||||||
fmt.Printf("%d.%d.%d\n", nextVer.Major(), nextVer.Minor(), nextVer.Patch())
|
fmt.Printf("%d.%d.%d\n", nextVer.Major(), nextVer.Minor(), nextVer.Patch())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commitLogFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "t",
|
||||||
|
Aliases: []string{"tag"},
|
||||||
|
Usage: "get commit log from a specific tag",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "r",
|
||||||
|
Aliases: []string{"range"},
|
||||||
|
Usage: "type of range of commits, use: tag, date or hash",
|
||||||
|
Value: string(sv.TagRange),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "s",
|
||||||
|
Aliases: []string{"start"},
|
||||||
|
Usage: "start range of git log revision range, if date, the value is used on since flag instead",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "e",
|
||||||
|
Aliases: []string{"end"},
|
||||||
|
Usage: "end range of git log revision range, if date, the value is used on until flag instead",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func commitLogHandler(git sv.Git) func(c *cli.Context) error {
|
func commitLogHandler(git sv.Git) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
var commits []sv.GitCommitLog
|
var (
|
||||||
var err error
|
commits []sv.GitCommitLog
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
tagFlag := c.String("t")
|
tagFlag := c.String("t")
|
||||||
rangeFlag := c.String("r")
|
rangeFlag := c.String("r")
|
||||||
startFlag := c.String("s")
|
startFlag := c.String("s")
|
||||||
endFlag := c.String("e")
|
endFlag := c.String("e")
|
||||||
|
|
||||||
if tagFlag != "" && (rangeFlag != string(sv.TagRange) || startFlag != "" || endFlag != "") {
|
if tagFlag != "" && (rangeFlag != string(sv.TagRange) || startFlag != "" || endFlag != "") {
|
||||||
return fmt.Errorf("cannot define tag flag with range, start or end flags")
|
return errCanNotCreateTagFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagFlag != "" {
|
if tagFlag != "" {
|
||||||
@ -92,8 +142,9 @@ func commitLogHandler(git sv.Git) func(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
commits, err = git.Log(r)
|
commits, err = git.Log(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting git log, message: %v", err)
|
return fmt.Errorf("error getting git log, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, commit := range commits {
|
for _, commit := range commits {
|
||||||
@ -101,8 +152,10 @@ func commitLogHandler(git sv.Git) func(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(content))
|
fmt.Println(string(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,6 +165,7 @@ func getTagCommits(git sv.Git, tag string) ([]sv.GitCommitLog, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return git.Log(sv.NewLogRange(sv.TagRange, prev, tag))
|
return git.Log(sv.NewLogRange(sv.TagRange, prev, tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,15 +178,45 @@ func logRange(git sv.Git, rangeFlag, startFlag, endFlag string) (sv.LogRange, er
|
|||||||
case string(sv.HashRange):
|
case string(sv.HashRange):
|
||||||
return sv.NewLogRange(sv.HashRange, startFlag, endFlag), nil
|
return sv.NewLogRange(sv.HashRange, startFlag, endFlag), nil
|
||||||
default:
|
default:
|
||||||
return sv.LogRange{}, fmt.Errorf("invalid range: %s, expected: %s, %s or %s", rangeFlag, sv.TagRange, sv.DateRange, sv.HashRange)
|
return sv.LogRange{}, fmt.Errorf(
|
||||||
|
"%w: %s, expected: %s, %s or %s",
|
||||||
|
errInvalidRange,
|
||||||
|
rangeFlag,
|
||||||
|
sv.TagRange,
|
||||||
|
sv.DateRange,
|
||||||
|
sv.HashRange,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commitNotesHandler(git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputFormatter sv.OutputFormatter) func(c *cli.Context) error {
|
func commitNotesFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "r", Aliases: []string{"range"},
|
||||||
|
Usage: "type of range of commits, use: tag, date or hash",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "s",
|
||||||
|
Aliases: []string{"start"},
|
||||||
|
Usage: "start range of git log revision range, if date, the value is used on since flag instead",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "e",
|
||||||
|
Aliases: []string{"end"},
|
||||||
|
Usage: "end range of git log revision range, if date, the value is used on until flag instead",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitNotesHandler(
|
||||||
|
git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputFormatter sv.OutputFormatter,
|
||||||
|
) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
var date time.Time
|
var date time.Time
|
||||||
|
|
||||||
rangeFlag := c.String("r")
|
rangeFlag := c.String("r")
|
||||||
|
|
||||||
lr, err := logRange(git, rangeFlag, c.String("s"), c.String("e"))
|
lr, err := logRange(git, rangeFlag, c.String("s"), c.String("e"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -140,7 +224,7 @@ func commitNotesHandler(git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputF
|
|||||||
|
|
||||||
commits, err := git.Log(lr)
|
commits, err := git.Log(lr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting git log from range: %s, message: %v", rangeFlag, err)
|
return fmt.Errorf("error getting git log from range: %s, message: %w", rangeFlag, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(commits) > 0 {
|
if len(commits) > 0 {
|
||||||
@ -149,20 +233,39 @@ func commitNotesHandler(git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputF
|
|||||||
|
|
||||||
output, err := outputFormatter.FormatReleaseNote(rnProcessor.Create(nil, "", date, commits))
|
output, err := outputFormatter.FormatReleaseNote(rnProcessor.Create(nil, "", date, commits))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not format release notes, message: %v", err)
|
return fmt.Errorf("could not format release notes, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func releaseNotesHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor, outputFormatter sv.OutputFormatter) func(c *cli.Context) error {
|
func releaseNotesFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "t",
|
||||||
|
Aliases: []string{"tag"},
|
||||||
|
Usage: "get release note from tag",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseNotesHandler(
|
||||||
|
git sv.Git,
|
||||||
|
semverProcessor sv.SemVerCommitsProcessor,
|
||||||
|
rnProcessor sv.ReleaseNoteProcessor,
|
||||||
|
outputFormatter sv.OutputFormatter,
|
||||||
|
) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
var commits []sv.GitCommitLog
|
var (
|
||||||
var rnVersion *semver.Version
|
commits []sv.GitCommitLog
|
||||||
var tag string
|
rnVersion *semver.Version
|
||||||
var date time.Time
|
tag string
|
||||||
var err error
|
date time.Time
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if tag = c.String("t"); tag != "" {
|
if tag = c.String("t"); tag != "" {
|
||||||
rnVersion, date, commits, err = getTagVersionInfo(git, tag)
|
rnVersion, date, commits, err = getTagVersionInfo(git, tag)
|
||||||
@ -176,11 +279,14 @@ func releaseNotesHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor,
|
|||||||
}
|
}
|
||||||
|
|
||||||
releasenote := rnProcessor.Create(rnVersion, tag, date, commits)
|
releasenote := rnProcessor.Create(rnVersion, tag, date, commits)
|
||||||
|
|
||||||
output, err := outputFormatter.FormatReleaseNote(releasenote)
|
output, err := outputFormatter.FormatReleaseNote(releasenote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not format release notes, message: %v", err)
|
return fmt.Errorf("could not format release notes, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,12 +296,12 @@ func getTagVersionInfo(git sv.Git, tag string) (*semver.Version, time.Time, []sv
|
|||||||
|
|
||||||
previousTag, currentTag, err := getTags(git, tag)
|
previousTag, currentTag, err := getTags(git, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, time.Time{}, nil, fmt.Errorf("error listing tags, message: %v", err)
|
return nil, time.Time{}, nil, fmt.Errorf("error listing tags, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := git.Log(sv.NewLogRange(sv.TagRange, previousTag, tag))
|
commits, err := git.Log(sv.NewLogRange(sv.TagRange, previousTag, tag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, time.Time{}, nil, fmt.Errorf("error getting git log from tag: %s, message: %v", tag, err)
|
return nil, time.Time{}, nil, fmt.Errorf("error getting git log from tag: %s, message: %w", tag, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagVersion, currentTag.Date, commits, nil
|
return tagVersion, currentTag.Date, commits, nil
|
||||||
@ -209,13 +315,14 @@ func getTags(git sv.Git, tag string) (string, sv.GitTag, error) {
|
|||||||
|
|
||||||
index := find(tag, tags)
|
index := find(tag, tags)
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return "", sv.GitTag{}, fmt.Errorf("tag: %s not found, check tag filter", tag)
|
return "", sv.GitTag{}, fmt.Errorf("%w: %s not found, check tag filter", errUnknownTag, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
previousTag := ""
|
previousTag := ""
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
previousTag = tags[index-1].Name
|
previousTag = tags[index-1].Name
|
||||||
}
|
}
|
||||||
|
|
||||||
return previousTag, tags[index], nil
|
return previousTag, tags[index], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,15 +332,18 @@ func find(tag string, tags []sv.GitTag) int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNextVersionInfo(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) (*semver.Version, bool, time.Time, []sv.GitCommitLog, error) {
|
func getNextVersionInfo(
|
||||||
|
git sv.Git, semverProcessor sv.SemVerCommitsProcessor,
|
||||||
|
) (*semver.Version, bool, time.Time, []sv.GitCommitLog, error) {
|
||||||
lastTag := git.LastTag()
|
lastTag := git.LastTag()
|
||||||
|
|
||||||
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, time.Time{}, nil, fmt.Errorf("error getting git log, message: %v", err)
|
return nil, false, time.Time{}, nil, fmt.Errorf("error getting git log, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVer, _ := sv.ToVersion(lastTag)
|
currentVer, _ := sv.ToVersion(lastTag)
|
||||||
@ -248,20 +358,23 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c
|
|||||||
|
|
||||||
currentVer, err := sv.ToVersion(lastTag)
|
currentVer, err := sv.ToVersion(lastTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error parsing version: %s from git tag, message: %v", lastTag, err)
|
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting git log, message: %v", err)
|
return fmt.Errorf("error getting git log, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextVer, _ := semverProcessor.NextVersion(currentVer, commits)
|
nextVer, _ := semverProcessor.NextVersion(currentVer, commits)
|
||||||
tagname, err := git.Tag(*nextVer)
|
tagname, err := git.Tag(*nextVer)
|
||||||
|
|
||||||
fmt.Println(tagname)
|
fmt.Println(tagname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating tag version: %s, message: %v", nextVer.String(), err)
|
return fmt.Errorf("error generating tag version: %s, message: %w", nextVer.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,8 +382,10 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c
|
|||||||
func getCommitType(cfg Config, p sv.MessageProcessor, input string) (string, error) {
|
func getCommitType(cfg Config, p sv.MessageProcessor, input string) (string, error) {
|
||||||
if input == "" {
|
if input == "" {
|
||||||
t, err := promptType(cfg.CommitMessage.Types)
|
t, err := promptType(cfg.CommitMessage.Types)
|
||||||
|
|
||||||
return t.Type, err
|
return t.Type, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return input, p.ValidateType(input)
|
return input, p.ValidateType(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +393,7 @@ func getCommitScope(cfg Config, p sv.MessageProcessor, input string, noScope boo
|
|||||||
if input == "" && !noScope {
|
if input == "" && !noScope {
|
||||||
return promptScope(cfg.CommitMessage.Scope.Values)
|
return promptScope(cfg.CommitMessage.Scope.Values)
|
||||||
}
|
}
|
||||||
|
|
||||||
return input, p.ValidateScope(input)
|
return input, p.ValidateScope(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +401,7 @@ func getCommitDescription(p sv.MessageProcessor, input string) (string, error) {
|
|||||||
if input == "" {
|
if input == "" {
|
||||||
return promptSubject()
|
return promptSubject()
|
||||||
}
|
}
|
||||||
|
|
||||||
return input, p.ValidateDescription(input)
|
return input, p.ValidateDescription(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,17 +411,21 @@ func getCommitBody(noBody bool) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fullBody strings.Builder
|
var fullBody strings.Builder
|
||||||
|
|
||||||
for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() {
|
for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fullBody.Len() > 0 {
|
if fullBody.Len() > 0 {
|
||||||
fullBody.WriteString("\n")
|
fullBody.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if body != "" {
|
if body != "" {
|
||||||
fullBody.WriteString(body)
|
fullBody.WriteString(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullBody.String(), nil
|
return fullBody.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,6 +459,7 @@ func getCommitBreakingChange(noBreaking bool, input string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasBreakingChanges {
|
if !hasBreakingChanges {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -345,6 +467,51 @@ func getCommitBreakingChange(noBreaking bool, input string) (string, error) {
|
|||||||
return promptBreakingChanges()
|
return promptBreakingChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commitFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-scope",
|
||||||
|
Aliases: []string{"nsc"},
|
||||||
|
Usage: "do not prompt for commit scope",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-body",
|
||||||
|
Aliases: []string{"nbd"},
|
||||||
|
Usage: "do not prompt for commit body",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-issue",
|
||||||
|
Aliases: []string{"nis"},
|
||||||
|
Usage: "do not prompt for commit issue, will try to recover from branch if enabled",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-breaking",
|
||||||
|
Aliases: []string{"nbc"},
|
||||||
|
Usage: "do not prompt for breaking changes",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "define commit type",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "scope",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "define commit scope",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "define commit description",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "breaking-change",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "define commit breaking change message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
noBreaking := c.Bool("no-breaking")
|
noBreaking := c.Bool("no-breaking")
|
||||||
@ -386,22 +553,54 @@ func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype, scope, subject, fullBody, issue, breakingChange))
|
header, body, footer := messageProcessor.Format(
|
||||||
|
sv.NewCommitMessage(ctype, scope, subject, fullBody, issue, breakingChange),
|
||||||
|
)
|
||||||
|
|
||||||
err = git.Commit(header, body, footer)
|
err = git.Commit(header, body, footer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error executing git commit, message: %v", err)
|
return fmt.Errorf("error executing git commit, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor, formatter sv.OutputFormatter) func(c *cli.Context) error {
|
func changelogFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "size",
|
||||||
|
Value: 10, //nolint:gomnd
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "get changelog from last 'n' tags",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "all",
|
||||||
|
Usage: "ignore size parameter, get changelog for every tag",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "add-next-version",
|
||||||
|
Usage: "add next version on change log (commits since last tag, but only if there is a new version to release)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "semantic-version-only",
|
||||||
|
Usage: "only show tags 'SemVer-ish'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func changelogHandler(
|
||||||
|
git sv.Git,
|
||||||
|
semverProcessor sv.SemVerCommitsProcessor,
|
||||||
|
rnProcessor sv.ReleaseNoteProcessor,
|
||||||
|
formatter sv.OutputFormatter,
|
||||||
|
) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
tags, err := git.Tags()
|
tags, err := git.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(tags, func(i, j int) bool {
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
return tags[i].Date.After(tags[j].Date)
|
return tags[i].Date.After(tags[j].Date)
|
||||||
})
|
})
|
||||||
@ -418,10 +617,12 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
|
|||||||
if uerr != nil {
|
if uerr != nil {
|
||||||
return uerr
|
return uerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated {
|
if updated {
|
||||||
releaseNotes = append(releaseNotes, rnProcessor.Create(rnVersion, "", date, commits))
|
releaseNotes = append(releaseNotes, rnProcessor.Create(rnVersion, "", date, commits))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tag := range tags {
|
for i, tag := range tags {
|
||||||
if !all && i >= size {
|
if !all && i >= size {
|
||||||
break
|
break
|
||||||
@ -438,7 +639,7 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
|
|||||||
|
|
||||||
commits, err := git.Log(sv.NewLogRange(sv.TagRange, previousTag, tag.Name))
|
commits, err := git.Log(sv.NewLogRange(sv.TagRange, previousTag, tag.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting git log from tag: %s, message: %v", tag.Name, err)
|
return fmt.Errorf("error getting git log from tag: %s, message: %w", tag.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVer, _ := sv.ToVersion(tag.Name)
|
currentVer, _ := sv.ToVersion(tag.Name)
|
||||||
@ -447,14 +648,35 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
|
|||||||
|
|
||||||
output, err := formatter.FormatChangelog(releaseNotes)
|
output, err := formatter.FormatChangelog(releaseNotes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not format changelog, message: %v", err)
|
return fmt.Errorf("could not format changelog, message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(output)
|
fmt.Println(output)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateCommitMessageFlags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "path",
|
||||||
|
Required: true,
|
||||||
|
Usage: "git working directory",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "file",
|
||||||
|
Required: true,
|
||||||
|
Usage: "name of the file that contains the commit log message",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "source",
|
||||||
|
Required: true,
|
||||||
|
Usage: "source of the commit message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
branch := git.Branch()
|
branch := git.Branch()
|
||||||
@ -462,11 +684,13 @@ func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcess
|
|||||||
|
|
||||||
if messageProcessor.SkipBranch(branch, derr == nil && detached) {
|
if messageProcessor.SkipBranch(branch, derr == nil && detached) {
|
||||||
warnf("commit message validation skipped, branch in ignore list or detached...")
|
warnf("commit message validation skipped, branch in ignore list or detached...")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if source := c.String("source"); source == "merge" {
|
if source := c.String("source"); source == "merge" {
|
||||||
warnf("commit message validation skipped, ignoring source: %s...", source)
|
warnf("commit message validation skipped, ignoring source: %s...", source)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,24 +698,26 @@ func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcess
|
|||||||
|
|
||||||
commitMessage, err := readFile(filepath)
|
commitMessage, err := readFile(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read commit message, error: %s", err.Error())
|
return fmt.Errorf("%w: %s", errReadCommitMessage, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageProcessor.Validate(commitMessage); err != nil {
|
if err := messageProcessor.Validate(commitMessage); err != nil {
|
||||||
return fmt.Errorf("invalid commit message, error: %s", err.Error())
|
return fmt.Errorf("%w: %s", errReadCommitMessage, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := messageProcessor.Enhance(branch, commitMessage)
|
msg, err := messageProcessor.Enhance(branch, commitMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnf("could not enhance commit message, %s", err.Error())
|
warnf("could not enhance commit message, %s", err.Error())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := appendOnFile(msg, filepath); err != nil {
|
if err := appendOnFile(msg, filepath); err != nil {
|
||||||
return fmt.Errorf("failed to append meta-informations on footer, error: %s", err.Error())
|
return fmt.Errorf("%w: %s", errAppendFooter, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -503,17 +729,19 @@ func readFile(filepath string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(f), nil
|
return string(f), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendOnFile(message, filepath string) error {
|
func appendOnFile(message, filepath string) error {
|
||||||
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY, laxFilePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
_, err = f.WriteString(message)
|
_, err = f.WriteString(message)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -521,5 +749,6 @@ func str(value, defaultValue string) string {
|
|||||||
if value != "" {
|
if value != "" {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
@ -2,161 +2,146 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bvieira/sv4git/v2/sv"
|
"github.com/thegeeklab/git-sv/v2/sv"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version for git-sv.
|
//nolint:gochecknoglobals
|
||||||
var Version = "source"
|
var (
|
||||||
|
BuildVersion = "devel"
|
||||||
|
BuildDate = "00000000"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configFilename = "config.yml"
|
configFilename = "config.yml"
|
||||||
repoConfigFilename = ".sv4git.yml"
|
configDir = ".gitsv"
|
||||||
configDir = ".sv4git"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
//go:embed resources/templates/*.tpl
|
||||||
//go:embed resources/templates/*.tpl
|
var defaultTemplatesFS embed.FS
|
||||||
defaultTemplatesFS embed.FS
|
|
||||||
)
|
|
||||||
|
|
||||||
func templateFS(filepath string) fs.FS {
|
func templateFS(filepath string) fs.FS {
|
||||||
if _, err := os.Stat(filepath); err != nil {
|
if _, err := os.Stat(filepath); err != nil {
|
||||||
defaultTemplatesFS, _ := fs.Sub(defaultTemplatesFS, "resources/templates")
|
defaultTemplatesFS, _ := fs.Sub(defaultTemplatesFS, "resources/templates")
|
||||||
|
|
||||||
return defaultTemplatesFS
|
return defaultTemplatesFS
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.DirFS(filepath)
|
return os.DirFS(filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
repoPath, rerr := getRepoPath()
|
wd, err := os.Getwd()
|
||||||
if rerr != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to discovery repository top level, error: ", rerr)
|
log.Fatal("error while retrieving working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := loadCfg(repoPath)
|
cfg := loadCfg(wd)
|
||||||
messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches)
|
messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches)
|
||||||
git := sv.NewGit(messageProcessor, cfg.Tag)
|
git := sv.NewGit(messageProcessor, cfg.Tag)
|
||||||
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning, cfg.CommitMessage)
|
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning, cfg.CommitMessage)
|
||||||
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotes)
|
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotes)
|
||||||
outputFormatter := sv.NewOutputFormatter(templateFS(filepath.Join(repoPath, configDir, "templates")))
|
outputFormatter := sv.NewOutputFormatter(templateFS(filepath.Join(wd, configDir, "templates")))
|
||||||
|
|
||||||
app := cli.NewApp()
|
cli.VersionPrinter = func(c *cli.Context) {
|
||||||
app.Name = "sv"
|
fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate)
|
||||||
app.Version = Version
|
}
|
||||||
app.Usage = "semantic version for git"
|
|
||||||
app.Commands = []*cli.Command{
|
app := &cli.App{
|
||||||
{
|
Name: "git-sv",
|
||||||
Name: "config",
|
Usage: "Semantic version for git.",
|
||||||
Aliases: []string{"cfg"},
|
Version: BuildVersion,
|
||||||
Usage: "cli configuration",
|
Commands: []*cli.Command{
|
||||||
Subcommands: []*cli.Command{
|
{
|
||||||
{
|
Name: "config",
|
||||||
Name: "default",
|
Aliases: []string{"cfg"},
|
||||||
Usage: "show default config",
|
Usage: "cli configuration",
|
||||||
Action: configDefaultHandler(),
|
Subcommands: []*cli.Command{
|
||||||
},
|
{
|
||||||
{
|
Name: "default",
|
||||||
Name: "show",
|
Usage: "show default config",
|
||||||
Usage: "show current config",
|
Action: configDefaultHandler(),
|
||||||
Action: configShowHandler(cfg),
|
},
|
||||||
|
{
|
||||||
|
Name: "show",
|
||||||
|
Usage: "show current config",
|
||||||
|
Action: configShowHandler(cfg),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "current-version",
|
||||||
Name: "current-version",
|
Aliases: []string{"cv"},
|
||||||
Aliases: []string{"cv"},
|
Usage: "get last released version from git",
|
||||||
Usage: "get last released version from git",
|
Action: currentVersionHandler(git),
|
||||||
Action: currentVersionHandler(git),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "next-version",
|
|
||||||
Aliases: []string{"nv"},
|
|
||||||
Usage: "generate the next version based on git commit messages",
|
|
||||||
Action: nextVersionHandler(git, semverProcessor),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "commit-log",
|
|
||||||
Aliases: []string{"cl"},
|
|
||||||
Usage: "list all commit logs according to range as jsons",
|
|
||||||
Description: "The range filter is used based on git log filters, check https://git-scm.com/docs/git-log for more info. When flag range is \"tag\" and start is empty, last tag created will be used instead. When flag range is \"date\", if \"end\" is YYYY-MM-DD the range will be inclusive.",
|
|
||||||
Action: commitLogHandler(git),
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{Name: "t", Aliases: []string{"tag"}, Usage: "get commit log from a specific tag"},
|
|
||||||
&cli.StringFlag{Name: "r", Aliases: []string{"range"}, Usage: "type of range of commits, use: tag, date or hash", Value: string(sv.TagRange)},
|
|
||||||
&cli.StringFlag{Name: "s", Aliases: []string{"start"}, Usage: "start range of git log revision range, if date, the value is used on since flag instead"},
|
|
||||||
&cli.StringFlag{Name: "e", Aliases: []string{"end"}, Usage: "end range of git log revision range, if date, the value is used on until flag instead"},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "next-version",
|
||||||
Name: "commit-notes",
|
Aliases: []string{"nv"},
|
||||||
Aliases: []string{"cn"},
|
Usage: "generate the next version based on git commit messages",
|
||||||
Usage: "generate a commit notes according to range",
|
Action: nextVersionHandler(git, semverProcessor),
|
||||||
Description: "The range filter is used based on git log filters, check https://git-scm.com/docs/git-log for more info. When flag range is \"tag\" and start is empty, last tag created will be used instead. When flag range is \"date\", if \"end\" is YYYY-MM-DD the range will be inclusive.",
|
|
||||||
Action: commitNotesHandler(git, releasenotesProcessor, outputFormatter),
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{Name: "r", Aliases: []string{"range"}, Usage: "type of range of commits, use: tag, date or hash", Required: true},
|
|
||||||
&cli.StringFlag{Name: "s", Aliases: []string{"start"}, Usage: "start range of git log revision range, if date, the value is used on since flag instead"},
|
|
||||||
&cli.StringFlag{Name: "e", Aliases: []string{"end"}, Usage: "end range of git log revision range, if date, the value is used on until flag instead"},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "commit-log",
|
||||||
Name: "release-notes",
|
Aliases: []string{"cl"},
|
||||||
Aliases: []string{"rn"},
|
Usage: "list all commit logs according to range as jsons",
|
||||||
Usage: "generate release notes",
|
Description: `The range filter is used based on git log filters, check https://git-scm.com/docs/git-log
|
||||||
Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
|
for more info. When flag range is "tag" and start is empty, last tag created will be used instead.
|
||||||
Flags: []cli.Flag{&cli.StringFlag{Name: "t", Aliases: []string{"tag"}, Usage: "get release note from tag"}},
|
When flag range is "date", if "end" is YYYY-MM-DD the range will be inclusive.`,
|
||||||
},
|
Action: commitLogHandler(git),
|
||||||
{
|
Flags: commitLogFlags(),
|
||||||
Name: "changelog",
|
|
||||||
Aliases: []string{"cgl"},
|
|
||||||
Usage: "generate changelog",
|
|
||||||
Action: changelogHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.IntFlag{Name: "size", Value: 10, Aliases: []string{"n"}, Usage: "get changelog from last 'n' tags"},
|
|
||||||
&cli.BoolFlag{Name: "all", Usage: "ignore size parameter, get changelog for every tag"},
|
|
||||||
&cli.BoolFlag{Name: "add-next-version", Usage: "add next version on change log (commits since last tag, but only if there is a new version to release)"},
|
|
||||||
&cli.BoolFlag{Name: "semantic-version-only", Usage: "only show tags 'SemVer-ish'"},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "commit-notes",
|
||||||
Name: "tag",
|
Aliases: []string{"cn"},
|
||||||
Aliases: []string{"tg"},
|
Usage: "generate a commit notes according to range",
|
||||||
Usage: "generate tag with version based on git commit messages",
|
Description: `The range filter is used based on git log filters, check https://git-scm.com/docs/git-log
|
||||||
Action: tagHandler(git, semverProcessor),
|
for more info. When flag range is "tag" and start is empty, last tag created will be used instead.
|
||||||
},
|
When flag range is "date", if "end" is YYYY-MM-DD the range will be inclusive.`,
|
||||||
{
|
Action: commitNotesHandler(git, releasenotesProcessor, outputFormatter),
|
||||||
Name: "commit",
|
Flags: commitNotesFlags(),
|
||||||
Aliases: []string{"cmt"},
|
|
||||||
Usage: "execute git commit with convetional commit message helper",
|
|
||||||
Action: commitHandler(cfg, git, messageProcessor),
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "no-scope", Aliases: []string{"nsc"}, Usage: "do not prompt for commit scope"},
|
|
||||||
&cli.BoolFlag{Name: "no-body", Aliases: []string{"nbd"}, Usage: "do not prompt for commit body"},
|
|
||||||
&cli.BoolFlag{Name: "no-issue", Aliases: []string{"nis"}, Usage: "do not prompt for commit issue, will try to recover from branch if enabled"},
|
|
||||||
&cli.BoolFlag{Name: "no-breaking", Aliases: []string{"nbc"}, Usage: "do not prompt for breaking changes"},
|
|
||||||
&cli.StringFlag{Name: "type", Aliases: []string{"t"}, Usage: "define commit type"},
|
|
||||||
&cli.StringFlag{Name: "scope", Aliases: []string{"s"}, Usage: "define commit scope"},
|
|
||||||
&cli.StringFlag{Name: "description", Aliases: []string{"d"}, Usage: "define commit description"},
|
|
||||||
&cli.StringFlag{Name: "breaking-change", Aliases: []string{"b"}, Usage: "define commit breaking change message"},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "release-notes",
|
||||||
Name: "validate-commit-message",
|
Aliases: []string{"rn"},
|
||||||
Aliases: []string{"vcm"},
|
Usage: "generate release notes",
|
||||||
Usage: "use as prepare-commit-message hook to validate and enhance commit message",
|
Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
|
||||||
Action: validateCommitMessageHandler(git, messageProcessor),
|
Flags: releaseNotesFlags(),
|
||||||
Flags: []cli.Flag{
|
},
|
||||||
&cli.StringFlag{Name: "path", Required: true, Usage: "git working directory"},
|
{
|
||||||
&cli.StringFlag{Name: "file", Required: true, Usage: "name of the file that contains the commit log message"},
|
Name: "changelog",
|
||||||
&cli.StringFlag{Name: "source", Required: true, Usage: "source of the commit message"},
|
Aliases: []string{"cgl"},
|
||||||
|
Usage: "generate changelog",
|
||||||
|
Action: changelogHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
|
||||||
|
Flags: changelogFlags(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tag",
|
||||||
|
Aliases: []string{"tg"},
|
||||||
|
Usage: "generate tag with version based on git commit messages",
|
||||||
|
Action: tagHandler(git, semverProcessor),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "commit",
|
||||||
|
Aliases: []string{"cmt"},
|
||||||
|
Usage: "execute git commit with convetional commit message helper",
|
||||||
|
Action: commitHandler(cfg, git, messageProcessor),
|
||||||
|
Flags: commitFlags(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "validate-commit-message",
|
||||||
|
Aliases: []string{"vcm"},
|
||||||
|
Usage: "use as prepare-commit-message hook to validate and enhance commit message",
|
||||||
|
Action: validateCommitMessageHandler(git, messageProcessor),
|
||||||
|
Flags: validateCommitMessageFlags(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -166,7 +151,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCfg(repoPath string) Config {
|
func loadCfg(wd string) Config {
|
||||||
cfg := defaultConfig()
|
cfg := defaultConfig()
|
||||||
|
|
||||||
envCfg := loadEnvConfig()
|
envCfg := loadEnvConfig()
|
||||||
@ -179,11 +164,12 @@ func loadCfg(repoPath string) Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repoCfgFilepath := filepath.Join(repoPath, repoConfigFilename)
|
repoCfgFilepath := filepath.Join(wd, configDir, configFilename)
|
||||||
if repoCfg, err := readConfig(repoCfgFilepath); err == nil {
|
if repoCfg, err := readConfig(repoCfgFilepath); err == nil {
|
||||||
if merr := merge(&cfg, migrateConfig(repoCfg, repoCfgFilepath)); merr != nil {
|
if merr := merge(&cfg, migrateConfig(repoCfg, repoCfgFilepath)); merr != nil {
|
||||||
log.Fatal("failed to merge repo config, error: ", merr)
|
log.Fatal("failed to merge repo config, error: ", merr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
|
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
|
||||||
cfg.ReleaseNotes.Headers = repoCfg.ReleaseNotes.Headers
|
cfg.ReleaseNotes.Headers = repoCfg.ReleaseNotes.Headers
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -14,22 +15,62 @@ type commitType struct {
|
|||||||
Example string
|
Example string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errInvalidValue = errors.New("invalid value")
|
||||||
|
|
||||||
func promptType(types []string) (commitType, error) {
|
func promptType(types []string) (commitType, error) {
|
||||||
defaultTypes := map[string]commitType{
|
defaultTypes := map[string]commitType{
|
||||||
"build": {Type: "build", Description: "changes that affect the build system or external dependencies", Example: "gradle, maven, go mod, npm"},
|
"build": {
|
||||||
"ci": {Type: "ci", Description: "changes to our CI configuration files and scripts", Example: "Circle, BrowserStack, SauceLabs"},
|
Type: "build",
|
||||||
"chore": {Type: "chore", Description: "update something without impacting the user", Example: "gitignore"},
|
Description: "changes that affect the build system or external dependencies",
|
||||||
"docs": {Type: "docs", Description: "documentation only changes"},
|
Example: "gradle, maven, go mod, npm",
|
||||||
"feat": {Type: "feat", Description: "a new feature"},
|
},
|
||||||
"fix": {Type: "fix", Description: "a bug fix"},
|
"ci": {
|
||||||
"perf": {Type: "perf", Description: "a code change that improves performance"},
|
Type: "ci",
|
||||||
"refactor": {Type: "refactor", Description: "a code change that neither fixes a bug nor adds a feature"},
|
Description: "changes to our CI configuration files and scripts",
|
||||||
"style": {Type: "style", Description: "changes that do not affect the meaning of the code", Example: "white-space, formatting, missing semi-colons, etc"},
|
Example: "Circle, BrowserStack, SauceLabs",
|
||||||
"test": {Type: "test", Description: "adding missing tests or correcting existing tests"},
|
},
|
||||||
"revert": {Type: "revert", Description: "revert a single commit"},
|
"chore": {
|
||||||
|
Type: "chore",
|
||||||
|
Description: "update something without impacting the user",
|
||||||
|
Example: "gitignore",
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
Type: "docs",
|
||||||
|
Description: "documentation only changes",
|
||||||
|
},
|
||||||
|
"feat": {
|
||||||
|
Type: "feat",
|
||||||
|
Description: "a new feature",
|
||||||
|
},
|
||||||
|
"fix": {
|
||||||
|
Type: "fix",
|
||||||
|
Description: "a bug fix",
|
||||||
|
},
|
||||||
|
"perf": {
|
||||||
|
Type: "perf",
|
||||||
|
Description: "a code change that improves performance",
|
||||||
|
},
|
||||||
|
"refactor": {
|
||||||
|
Type: "refactor",
|
||||||
|
Description: "a code change that neither fixes a bug nor adds a feature",
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
Type: "style",
|
||||||
|
Description: "changes that do not affect the meaning of the code",
|
||||||
|
Example: "white-space, formatting, missing semi-colons, etc",
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
Type: "test",
|
||||||
|
Description: "adding missing tests or correcting existing tests",
|
||||||
|
},
|
||||||
|
"revert": {
|
||||||
|
Type: "revert",
|
||||||
|
Description: "revert a single commit",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var items []commitType
|
var items []commitType
|
||||||
|
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
if v, exists := defaultTypes[t]; exists {
|
if v, exists := defaultTypes[t]; exists {
|
||||||
items = append(items, v)
|
items = append(items, v)
|
||||||
@ -53,6 +94,7 @@ func promptType(types []string) (commitType, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commitType{}, err
|
return commitType{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return items[i], nil
|
return items[i], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +104,10 @@ func promptScope(values []string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return values[selected], nil
|
return values[selected], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return promptText("scope", "^[a-z0-9-]*$", "")
|
return promptText("scope", "^[a-z0-9-]*$", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +129,7 @@ func promptBreakingChanges() (string, error) {
|
|||||||
|
|
||||||
func promptSelect(label string, items interface{}, template *promptui.SelectTemplates) (int, error) {
|
func promptSelect(label string, items interface{}, template *promptui.SelectTemplates) (int, error) {
|
||||||
if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice {
|
if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice {
|
||||||
return 0, fmt.Errorf("items %v is not a slice", items)
|
return 0, fmt.Errorf("%w: %v is not a slice", errInvalidValue, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt := promptui.Select{
|
prompt := promptui.Select{
|
||||||
@ -96,6 +140,7 @@ func promptSelect(label string, items interface{}, template *promptui.SelectTemp
|
|||||||
}
|
}
|
||||||
|
|
||||||
index, _, err := prompt.Run()
|
index, _, err := prompt.Run()
|
||||||
|
|
||||||
return index, err
|
return index, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,8 +148,9 @@ func promptText(label, regex, defaultValue string) (string, error) {
|
|||||||
validate := func(input string) error {
|
validate := func(input string) error {
|
||||||
regex := regexp.MustCompile(regex)
|
regex := regexp.MustCompile(regex)
|
||||||
if !regex.MatchString(input) {
|
if !regex.MatchString(input) {
|
||||||
return fmt.Errorf("invalid value, expected: %s", regex)
|
return fmt.Errorf("%w, expected: %s", errInvalidValue, regex)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,5 +168,6 @@ func promptConfirm(label string) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r == "y", nil
|
return r == "y", nil
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,11 @@ func Test_checkTemplatesFiles(t *testing.T) {
|
|||||||
got, err := defaultTemplatesFS.ReadFile(tt)
|
got, err := defaultTemplatesFS.ReadFile(tt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("missing template error = %v", err)
|
t.Errorf("missing template error = %v", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(got) <= 0 {
|
|
||||||
|
if len(got) == 0 {
|
||||||
t.Errorf("empty template")
|
t.Errorf("empty template")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
13
go.mod
13
go.mod
@ -1,22 +1,23 @@
|
|||||||
module github.com/bvieira/sv4git/v2
|
module github.com/thegeeklab/git-sv/v2
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.0
|
dario.cat/mergo v1.0.0
|
||||||
github.com/imdario/mergo v0.3.13
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/manifoldco/promptui v0.9.0
|
github.com/manifoldco/promptui v0.9.0
|
||||||
github.com/urfave/cli/v2 v2.24.1
|
github.com/urfave/cli/v2 v2.25.7
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
|
24
go.sum
24
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
@ -9,11 +11,9 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
|
||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@ -26,21 +26,21 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
|
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||||
github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
4
renovate.json
Normal file
4
renovate.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": ["github>thegeeklab/renovate-presets:golang"]
|
||||||
|
}
|
@ -16,6 +16,7 @@ func (c CommitMessageConfig) IssueFooterConfig() CommitMessageFooterConfig {
|
|||||||
if v, exists := c.Footer[issueMetadataKey]; exists {
|
if v, exists := c.Footer[issueMetadataKey]; exists {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommitMessageFooterConfig{}
|
return CommitMessageFooterConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +81,7 @@ func (cfg ReleaseNotesConfig) sectionConfig(sectionType string) *ReleaseNotesSec
|
|||||||
return §ionCfg
|
return §ionCfg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
sv/errors.go
Normal file
11
sv/errors.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package sv
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnknownGitError = errors.New("git command failed")
|
||||||
|
errInvalidCommitMessage = errors.New("commit message not valid")
|
||||||
|
errIssueIDNotFound = errors.New("could not find issue id using configured regex")
|
||||||
|
errInvalidIssueRegex = errors.New("could not compile issue regex")
|
||||||
|
errInvalidHeaderRegex = errors.New("invalid regex on header-selector")
|
||||||
|
)
|
@ -39,6 +39,7 @@ func NewOutputFormatter(templatesFS fs.FS) *OutputFormatterImpl {
|
|||||||
"getenv": os.Getenv,
|
"getenv": os.Getenv,
|
||||||
}
|
}
|
||||||
tpls := template.Must(template.New("templates").Funcs(templateFNs).ParseFS(templatesFS, "*"))
|
tpls := template.Must(template.New("templates").Funcs(templateFNs).ParseFS(templatesFS, "*"))
|
||||||
|
|
||||||
return &OutputFormatterImpl{templates: tpls}
|
return &OutputFormatterImpl{templates: tpls}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ func (p OutputFormatterImpl) FormatReleaseNote(releasenote ReleaseNote) (string,
|
|||||||
if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil {
|
if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ func (p OutputFormatterImpl) FormatChangelog(releasenotes []ReleaseNote) (string
|
|||||||
if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil {
|
if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +73,7 @@ func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables
|
|||||||
if releasenote.Version != nil {
|
if releasenote.Version != nil {
|
||||||
release = "v" + releasenote.Version.String()
|
release = "v" + releasenote.Version.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return releaseNoteTemplateVariables{
|
return releaseNoteTemplateVariables{
|
||||||
Release: release,
|
Release: release,
|
||||||
Tag: releasenote.Tag,
|
Tag: releasenote.Tag,
|
||||||
@ -83,10 +87,13 @@ func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables
|
|||||||
func toSortedArray(input map[string]struct{}) []string {
|
func toSortedArray(input map[string]struct{}) []string {
|
||||||
result := make([]string, len(input))
|
result := make([]string, len(input))
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for k := range input {
|
for k := range input {
|
||||||
result[i] = k
|
result[i] = k
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(result)
|
sort.Strings(result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,16 @@ func timeFormat(t time.Time, format string) string {
|
|||||||
if t.IsZero() {
|
if t.IsZero() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.Format(format)
|
return t.Format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSection(sections []ReleaseNoteSection, name string) ReleaseNoteSection {
|
func getSection(sections []ReleaseNoteSection, name string) ReleaseNoteSection { //nolint:ireturn
|
||||||
for _, section := range sections {
|
for _, section := range sections {
|
||||||
if section.SectionName() == name {
|
if section.SectionName() == name {
|
||||||
return section
|
return section
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,20 @@ func Test_getSection(t *testing.T) {
|
|||||||
sectionName string
|
sectionName string
|
||||||
want ReleaseNoteSection
|
want ReleaseNoteSection
|
||||||
}{
|
}{
|
||||||
{"existing section", []ReleaseNoteSection{ReleaseNoteCommitsSection{Name: "section 0"}, ReleaseNoteCommitsSection{Name: "section 1"}, ReleaseNoteCommitsSection{Name: "section 2"}}, "section 1", ReleaseNoteCommitsSection{Name: "section 1"}},
|
{
|
||||||
{"nonexisting section", []ReleaseNoteSection{ReleaseNoteCommitsSection{Name: "section 0"}, ReleaseNoteCommitsSection{Name: "section 1"}, ReleaseNoteCommitsSection{Name: "section 2"}}, "section 10", nil},
|
"existing section", []ReleaseNoteSection{
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 0"},
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 1"},
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 2"},
|
||||||
|
}, "section 1", ReleaseNoteCommitsSection{Name: "section 1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nonexisting section", []ReleaseNoteSection{
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 0"},
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 1"},
|
||||||
|
ReleaseNoteCommitsSection{Name: "section 2"},
|
||||||
|
}, "section 10", nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -73,6 +73,7 @@ func TestOutputFormatterImpl_FormatReleaseNote(t *testing.T) {
|
|||||||
|
|
||||||
func emptyReleaseNote(tag string, date time.Time) ReleaseNote {
|
func emptyReleaseNote(tag string, date time.Time) ReleaseNote {
|
||||||
v, _ := semver.NewVersion(tag)
|
v, _ := semver.NewVersion(tag)
|
||||||
|
|
||||||
return ReleaseNote{
|
return ReleaseNote{
|
||||||
Version: v,
|
Version: v,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
@ -83,11 +84,24 @@ func emptyReleaseNote(tag string, date time.Time) ReleaseNote {
|
|||||||
func fullReleaseNote(tag string, date time.Time) ReleaseNote {
|
func fullReleaseNote(tag string, date time.Time) ReleaseNote {
|
||||||
v, _ := semver.NewVersion(tag)
|
v, _ := semver.NewVersion(tag)
|
||||||
sections := []ReleaseNoteSection{
|
sections := []ReleaseNoteSection{
|
||||||
newReleaseNoteCommitsSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}),
|
newReleaseNoteCommitsSection(
|
||||||
newReleaseNoteCommitsSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}),
|
"Features",
|
||||||
newReleaseNoteCommitsSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}),
|
[]string{"feat"},
|
||||||
|
[]GitCommitLog{commitlog("feat", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
|
newReleaseNoteCommitsSection(
|
||||||
|
"Bug Fixes",
|
||||||
|
[]string{"fix"},
|
||||||
|
[]GitCommitLog{commitlog("fix", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
|
newReleaseNoteCommitsSection(
|
||||||
|
"Build",
|
||||||
|
[]string{"build"},
|
||||||
|
[]GitCommitLog{commitlog("build", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
return releaseNote(v, tag, date, sections, map[string]struct{}{"a": {}})
|
return releaseNote(v, tag, date, sections, map[string]struct{}{"a": {}})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,15 +114,18 @@ func Test_checkTemplatesExecution(t *testing.T) {
|
|||||||
{"changelog-md.tpl", changelogVariables("v1.0.0", "v1.0.1")},
|
{"changelog-md.tpl", changelogVariables("v1.0.0", "v1.0.1")},
|
||||||
{"releasenotes-md.tpl", releaseNotesVariables("v1.0.0")},
|
{"releasenotes-md.tpl", releaseNotesVariables("v1.0.0")},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.template, func(t *testing.T) {
|
t.Run(tt.template, func(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
err := tpls.ExecuteTemplate(&b, tt.template, tt.variables)
|
err := tpls.ExecuteTemplate(&b, tt.template, tt.variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("invalid template err = %v", err)
|
t.Errorf("invalid template err = %v", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(b.Bytes()) <= 0 {
|
|
||||||
|
if len(b.Bytes()) == 0 {
|
||||||
t.Errorf("empty template")
|
t.Errorf("empty template")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -118,11 +135,20 @@ func Test_checkTemplatesExecution(t *testing.T) {
|
|||||||
func releaseNotesVariables(release string) releaseNoteTemplateVariables {
|
func releaseNotesVariables(release string) releaseNoteTemplateVariables {
|
||||||
return releaseNoteTemplateVariables{
|
return releaseNoteTemplateVariables{
|
||||||
Release: release,
|
Release: release,
|
||||||
Date: time.Date(2006, 1, 02, 0, 0, 0, 0, time.UTC),
|
Date: time.Date(2006, 1, 0o2, 0, 0, 0, 0, time.UTC),
|
||||||
Sections: []ReleaseNoteSection{
|
Sections: []ReleaseNoteSection{
|
||||||
newReleaseNoteCommitsSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}),
|
newReleaseNoteCommitsSection("Features",
|
||||||
newReleaseNoteCommitsSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}),
|
[]string{"feat"},
|
||||||
newReleaseNoteCommitsSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}),
|
[]GitCommitLog{commitlog("feat", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
|
newReleaseNoteCommitsSection("Bug Fixes",
|
||||||
|
[]string{"fix"},
|
||||||
|
[]GitCommitLog{commitlog("fix", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
|
newReleaseNoteCommitsSection("Build",
|
||||||
|
[]string{"build"},
|
||||||
|
[]GitCommitLog{commitlog("build", map[string]string{}, "a")},
|
||||||
|
),
|
||||||
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -130,9 +156,10 @@ func releaseNotesVariables(release string) releaseNoteTemplateVariables {
|
|||||||
|
|
||||||
func changelogVariables(releases ...string) []releaseNoteTemplateVariables {
|
func changelogVariables(releases ...string) []releaseNoteTemplateVariables {
|
||||||
var variables []releaseNoteTemplateVariables
|
var variables []releaseNoteTemplateVariables
|
||||||
|
|
||||||
for _, r := range releases {
|
for _, r := range releases {
|
||||||
variables = append(variables, releaseNotesVariables(r))
|
variables = append(variables, releaseNotesVariables(r))
|
||||||
}
|
}
|
||||||
return variables
|
|
||||||
|
|
||||||
|
return variables
|
||||||
}
|
}
|
||||||
|
74
sv/git.go
74
sv/git.go
@ -3,7 +3,6 @@ package sv
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -83,17 +82,35 @@ func NewGit(messageProcessor MessageProcessor, cfg TagConfig) *GitImpl {
|
|||||||
|
|
||||||
// LastTag get last tag, if no tag found, return empty.
|
// LastTag get last tag, if no tag found, return empty.
|
||||||
func (g GitImpl) LastTag() string {
|
func (g GitImpl) LastTag() string {
|
||||||
cmd := exec.Command("git", "for-each-ref", "refs/tags/"+*g.tagCfg.Filter, "--sort", "-creatordate", "--format", "%(refname:short)", "--count", "1")
|
//nolint:gosec
|
||||||
|
cmd := exec.Command(
|
||||||
|
"git",
|
||||||
|
"for-each-ref",
|
||||||
|
fmt.Sprintf("refs/tags/%s", *g.tagCfg.Filter),
|
||||||
|
"--sort",
|
||||||
|
"-creatordate",
|
||||||
|
"--format",
|
||||||
|
"%(refname:short)",
|
||||||
|
"--count",
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log return git log.
|
// Log return git log.
|
||||||
func (g GitImpl) Log(lr LogRange) ([]GitCommitLog, error) {
|
func (g GitImpl) Log(lr LogRange) ([]GitCommitLog, error) {
|
||||||
format := "--pretty=format:\"%ad" + logSeparator + "%at" + logSeparator + "%cN" + logSeparator + "%h" + logSeparator + "%s" + logSeparator + "%b" + endLine + "\""
|
format := "--pretty=format:\"%ad" + logSeparator +
|
||||||
|
"%at" + logSeparator +
|
||||||
|
"%cN" + logSeparator +
|
||||||
|
"%h" + logSeparator +
|
||||||
|
"%s" + logSeparator +
|
||||||
|
"%b" + endLine + "\""
|
||||||
params := []string{"log", "--date=short", format}
|
params := []string{"log", "--date=short", format}
|
||||||
|
|
||||||
if lr.start != "" || lr.end != "" {
|
if lr.start != "" || lr.end != "" {
|
||||||
@ -110,14 +127,17 @@ func (g GitImpl) Log(lr LogRange) ([]GitCommitLog, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("git", params...)
|
cmd := exec.Command("git", params...)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, combinedOutputErr(err, out)
|
return nil, combinedOutputErr(err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
logs, parseErr := parseLogOutput(g.messageProcessor, string(out))
|
logs, parseErr := parseLogOutput(g.messageProcessor, string(out))
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, parseErr
|
return nil, parseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +146,7 @@ func (g GitImpl) Commit(header, body, footer string) error {
|
|||||||
cmd := exec.Command("git", "commit", "-m", header, "-m", "", "-m", body, "-m", "", "-m", footer)
|
cmd := exec.Command("git", "commit", "-m", header, "-m", "", "-m", body, "-m", "", "-m", footer)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,45 +164,66 @@ func (g GitImpl) Tag(version semver.Version) (string, error) {
|
|||||||
if out, err := pushCommand.CombinedOutput(); err != nil {
|
if out, err := pushCommand.CombinedOutput(); err != nil {
|
||||||
return tag, combinedOutputErr(err, out)
|
return tag, combinedOutputErr(err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tags list repository tags.
|
// Tags list repository tags.
|
||||||
func (g GitImpl) Tags() ([]GitTag, error) {
|
func (g GitImpl) Tags() ([]GitTag, error) {
|
||||||
cmd := exec.Command("git", "for-each-ref", "--sort", "creatordate", "--format", "%(creatordate:iso8601)#%(refname:short)", "refs/tags/"+*g.tagCfg.Filter)
|
//nolint:gosec
|
||||||
|
cmd := exec.Command(
|
||||||
|
"git",
|
||||||
|
"for-each-ref",
|
||||||
|
"--sort",
|
||||||
|
"creatordate",
|
||||||
|
"--format",
|
||||||
|
"%(creatordate:iso8601)#%(refname:short)",
|
||||||
|
fmt.Sprintf("refs/tags/%s", *g.tagCfg.Filter),
|
||||||
|
)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, combinedOutputErr(err, out)
|
return nil, combinedOutputErr(err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseTagsOutput(string(out))
|
return parseTagsOutput(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Branch get git branch.
|
// Branch get git branch.
|
||||||
func (GitImpl) Branch() string {
|
func (GitImpl) Branch() string {
|
||||||
cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
|
cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDetached check if is detached.
|
// IsDetached check if is detached.
|
||||||
func (GitImpl) IsDetached() (bool, error) {
|
func (GitImpl) IsDetached() (bool, error) {
|
||||||
cmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
|
cmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if output := string(out); err != nil { //-q: do not issue an error message if the <name> is not a symbolic ref, but a detached HEAD; instead exit with non-zero status silently.
|
// -q: do not issue an error message if the <name> is not a symbolic ref, but a detached HEAD;
|
||||||
|
// instead exit with non-zero status silently.
|
||||||
|
if output := string(out); err != nil {
|
||||||
if output == "" {
|
if output == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
return false, errors.New(output)
|
|
||||||
|
return false, fmt.Errorf("%w: %s", errUnknownGitError, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTagsOutput(input string) ([]GitTag, error) {
|
func parseTagsOutput(input string) ([]GitTag, error) {
|
||||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||||
|
|
||||||
var result []GitTag
|
var result []GitTag
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
if line := strings.TrimSpace(scanner.Text()); line != "" {
|
||||||
values := strings.Split(line, "#")
|
values := strings.Split(line, "#")
|
||||||
@ -189,31 +231,35 @@ func parseTagsOutput(input string) ([]GitTag, error) {
|
|||||||
result = append(result, GitTag{Name: values[1], Date: date})
|
result = append(result, GitTag{Name: values[1], Date: date})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLogOutput(messageProcessor MessageProcessor, log string) ([]GitCommitLog, error) {
|
func parseLogOutput(messageProcessor MessageProcessor, log string) ([]GitCommitLog, error) {
|
||||||
scanner := bufio.NewScanner(strings.NewReader(log))
|
scanner := bufio.NewScanner(strings.NewReader(log))
|
||||||
scanner.Split(splitAt([]byte(endLine)))
|
scanner.Split(splitAt([]byte(endLine)))
|
||||||
|
|
||||||
var logs []GitCommitLog
|
var logs []GitCommitLog
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if text := strings.TrimSpace(strings.Trim(scanner.Text(), "\"")); text != "" {
|
if text := strings.TrimSpace(strings.Trim(scanner.Text(), "\"")); text != "" {
|
||||||
log, err := parseCommitLog(messageProcessor, text)
|
log, err := parseCommitLog(messageProcessor, text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logs = append(logs, log)
|
logs = append(logs, log)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCommitLog(messageProcessor MessageProcessor, commit string) (GitCommitLog, error) {
|
func parseCommitLog(messageProcessor MessageProcessor, commit string) (GitCommitLog, error) {
|
||||||
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
|
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
|
||||||
|
|
||||||
timestamp, _ := strconv.Atoi(content[1])
|
timestamp, _ := strconv.Atoi(content[1])
|
||||||
message, err := messageProcessor.Parse(content[4], content[5])
|
|
||||||
|
|
||||||
|
message, err := messageProcessor.Parse(content[4], content[5])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GitCommitLog{}, err
|
return GitCommitLog{}, err
|
||||||
}
|
}
|
||||||
@ -228,10 +274,8 @@ func parseCommitLog(messageProcessor MessageProcessor, commit string) (GitCommit
|
|||||||
}
|
}
|
||||||
|
|
||||||
func splitAt(b []byte) func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
func splitAt(b []byte) func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
return func(data []byte, atEOF bool) (advance int, token []byte, err error) { //nolint:nonamedreturns
|
||||||
dataLen := len(data)
|
if atEOF && len(data) == 0 {
|
||||||
|
|
||||||
if atEOF && dataLen == 0 {
|
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +284,7 @@ func splitAt(b []byte) func(data []byte, atEOF bool) (advance int, token []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if atEOF {
|
if atEOF {
|
||||||
return dataLen, data, nil
|
return len(data), data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
@ -264,10 +308,12 @@ func str(value, defaultValue string) string {
|
|||||||
if value != "" {
|
if value != "" {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func combinedOutputErr(err error, out []byte) error {
|
func combinedOutputErr(err error, out []byte) error {
|
||||||
msg := strings.Split(string(out), "\n")
|
msg := strings.Split(string(out), "\n")
|
||||||
return fmt.Errorf("%v - %s", err, msg[0])
|
|
||||||
|
return fmt.Errorf("%w - %s", err, msg[0])
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,28 @@ func Test_parseTagsOutput(t *testing.T) {
|
|||||||
want []GitTag
|
want []GitTag
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"with date", "2020-05-01 18:00:00 -0300#1.0.0", []GitTag{{Name: "1.0.0", Date: date("2020-05-01 18:00:00 -0300")}}, false},
|
{
|
||||||
{"without date", "#1.0.0", []GitTag{{Name: "1.0.0", Date: time.Time{}}}, false},
|
"with date",
|
||||||
|
"2020-05-01 18:00:00 -0300#1.0.0",
|
||||||
|
[]GitTag{{Name: "1.0.0", Date: date("2020-05-01 18:00:00 -0300")}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"without date",
|
||||||
|
"#1.0.0",
|
||||||
|
[]GitTag{{Name: "1.0.0", Date: time.Time{}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := parseTagsOutput(tt.input)
|
got, err := parseTagsOutput(tt.input)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("parseTagsOutput() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("parseTagsOutput() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("parseTagsOutput() = %v, want %v", got, tt.want)
|
t.Errorf("parseTagsOutput() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
@ -35,5 +47,6 @@ func date(input string) time.Time {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
func version(v string) *semver.Version {
|
func version(v string) *semver.Version {
|
||||||
r, _ := semver.NewVersion(v)
|
r, _ := semver.NewVersion(v)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ func commitlog(ctype string, metadata map[string]string, author string) GitCommi
|
|||||||
if _, found := metadata[breakingChangeMetadataKey]; found {
|
if _, found := metadata[breakingChangeMetadataKey]; found {
|
||||||
breaking = true
|
breaking = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return GitCommitLog{
|
return GitCommitLog{
|
||||||
Message: CommitMessage{
|
Message: CommitMessage{
|
||||||
Type: ctype,
|
Type: ctype,
|
||||||
@ -27,7 +29,13 @@ func commitlog(ctype string, metadata map[string]string, author string) GitCommi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func releaseNote(version *semver.Version, tag string, date time.Time, sections []ReleaseNoteSection, authorsNames map[string]struct{}) ReleaseNote {
|
func releaseNote(
|
||||||
|
version *semver.Version,
|
||||||
|
tag string,
|
||||||
|
date time.Time,
|
||||||
|
sections []ReleaseNoteSection,
|
||||||
|
authorsNames map[string]struct{},
|
||||||
|
) ReleaseNote {
|
||||||
return ReleaseNote{
|
return ReleaseNote{
|
||||||
Version: version,
|
Version: version,
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
|
@ -30,10 +30,19 @@ func NewCommitMessage(ctype, scope, description, body, issue, breakingChanges st
|
|||||||
if issue != "" {
|
if issue != "" {
|
||||||
metadata[issueMetadataKey] = issue
|
metadata[issueMetadataKey] = issue
|
||||||
}
|
}
|
||||||
|
|
||||||
if breakingChanges != "" {
|
if breakingChanges != "" {
|
||||||
metadata[breakingChangeMetadataKey] = breakingChanges
|
metadata[breakingChangeMetadataKey] = breakingChanges
|
||||||
}
|
}
|
||||||
return CommitMessage{Type: ctype, Scope: scope, Description: description, Body: body, IsBreakingChange: breakingChanges != "", Metadata: metadata}
|
|
||||||
|
return CommitMessage{
|
||||||
|
Type: ctype,
|
||||||
|
Scope: scope,
|
||||||
|
Description: description,
|
||||||
|
Body: body,
|
||||||
|
IsBreakingChange: breakingChanges != "",
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue return issue from metadata.
|
// Issue return issue from metadata.
|
||||||
@ -53,7 +62,7 @@ type MessageProcessor interface {
|
|||||||
ValidateType(ctype string) error
|
ValidateType(ctype string) error
|
||||||
ValidateScope(scope string) error
|
ValidateScope(scope string) error
|
||||||
ValidateDescription(description string) error
|
ValidateDescription(description string) error
|
||||||
Enhance(branch string, message string) (string, error)
|
Enhance(branch, message string) (string, error)
|
||||||
IssueID(branch string) (string, error)
|
IssueID(branch string) (string, error)
|
||||||
Format(msg CommitMessage) (string, string, string)
|
Format(msg CommitMessage) (string, string, string)
|
||||||
Parse(subject, body string) (CommitMessage, error)
|
Parse(subject, body string) (CommitMessage, error)
|
||||||
@ -75,7 +84,8 @@ type MessageProcessorImpl struct {
|
|||||||
|
|
||||||
// SkipBranch check if branch should be ignored.
|
// SkipBranch check if branch should be ignored.
|
||||||
func (p MessageProcessorImpl) SkipBranch(branch string, detached bool) bool {
|
func (p MessageProcessorImpl) SkipBranch(branch string, detached bool) bool {
|
||||||
return contains(branch, p.branchesCfg.Skip) || (p.branchesCfg.SkipDetached != nil && *p.branchesCfg.SkipDetached && detached)
|
return contains(branch, p.branchesCfg.Skip) ||
|
||||||
|
(p.branchesCfg.SkipDetached != nil && *p.branchesCfg.SkipDetached && detached)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate commit message.
|
// Validate commit message.
|
||||||
@ -88,7 +98,7 @@ func (p MessageProcessorImpl) Validate(message string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !regexp.MustCompile(`^[a-z+]+(\(.+\))?!?: .+$`).MatchString(subject) {
|
if !regexp.MustCompile(`^[a-z+]+(\(.+\))?!?: .+$`).MatchString(subject) {
|
||||||
return fmt.Errorf("subject [%s] should be valid according with conventional commits", subject)
|
return fmt.Errorf("%w: subject [%s] not valid", errInvalidCommitMessage, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.ValidateType(msg.Type); err != nil {
|
if err := p.ValidateType(msg.Type); err != nil {
|
||||||
@ -99,40 +109,48 @@ func (p MessageProcessorImpl) Validate(message string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.ValidateDescription(msg.Description); err != nil {
|
return p.ValidateDescription(msg.Description)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateType check if commit type is valid.
|
// ValidateType check if commit type is valid.
|
||||||
func (p MessageProcessorImpl) ValidateType(ctype string) error {
|
func (p MessageProcessorImpl) ValidateType(ctype string) error {
|
||||||
if ctype == "" || !contains(ctype, p.messageCfg.Types) {
|
if ctype == "" || !contains(ctype, p.messageCfg.Types) {
|
||||||
return fmt.Errorf("message type should be one of [%v]", strings.Join(p.messageCfg.Types, ", "))
|
return fmt.Errorf(
|
||||||
|
"%w: type must be one of [%s]",
|
||||||
|
errInvalidCommitMessage,
|
||||||
|
strings.Join(p.messageCfg.Types, ", "),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateScope check if commit scope is valid.
|
// ValidateScope check if commit scope is valid.
|
||||||
func (p MessageProcessorImpl) ValidateScope(scope string) error {
|
func (p MessageProcessorImpl) ValidateScope(scope string) error {
|
||||||
if len(p.messageCfg.Scope.Values) > 0 && !contains(scope, p.messageCfg.Scope.Values) {
|
if len(p.messageCfg.Scope.Values) > 0 && !contains(scope, p.messageCfg.Scope.Values) {
|
||||||
return fmt.Errorf("message scope should one of [%v]", strings.Join(p.messageCfg.Scope.Values, ", "))
|
return fmt.Errorf(
|
||||||
|
"%w: scope must one of [%s]",
|
||||||
|
errInvalidCommitMessage,
|
||||||
|
strings.Join(p.messageCfg.Scope.Values, ", "),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDescription check if commit description is valid.
|
// ValidateDescription check if commit description is valid.
|
||||||
func (p MessageProcessorImpl) ValidateDescription(description string) error {
|
func (p MessageProcessorImpl) ValidateDescription(description string) error {
|
||||||
if !regexp.MustCompile("^[a-z]+.*$").MatchString(description) {
|
if !regexp.MustCompile("^[a-z]+.*$").MatchString(description) {
|
||||||
return fmt.Errorf("description [%s] should begins with lowercase letter", description)
|
return fmt.Errorf("%w: description [%s] must start with lowercase", errInvalidCommitMessage, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhance add metadata on commit message.
|
// Enhance add metadata on commit message.
|
||||||
func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) {
|
func (p MessageProcessorImpl) Enhance(branch, message string) (string, error) {
|
||||||
if p.branchesCfg.DisableIssue || p.messageCfg.IssueFooterConfig().Key == "" || hasIssueID(message, p.messageCfg.IssueFooterConfig()) {
|
if p.branchesCfg.DisableIssue || p.messageCfg.IssueFooterConfig().Key == "" ||
|
||||||
|
hasIssueID(message, p.messageCfg.IssueFooterConfig()) {
|
||||||
return "", nil // enhance disabled
|
return "", nil // enhance disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +158,9 @@ func (p MessageProcessorImpl) Enhance(branch string, message string) (string, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if issue == "" {
|
if issue == "" {
|
||||||
return "", fmt.Errorf("could not find issue id using configured regex")
|
return "", errIssueIDNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
footer := formatIssueFooter(p.messageCfg.IssueFooterConfig(), issue)
|
footer := formatIssueFooter(p.messageCfg.IssueFooterConfig(), issue)
|
||||||
@ -156,9 +175,11 @@ func formatIssueFooter(cfg CommitMessageFooterConfig, issue string) string {
|
|||||||
if !strings.HasPrefix(issue, cfg.AddValuePrefix) {
|
if !strings.HasPrefix(issue, cfg.AddValuePrefix) {
|
||||||
issue = cfg.AddValuePrefix + issue
|
issue = cfg.AddValuePrefix + issue
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.UseHash {
|
if cfg.UseHash {
|
||||||
return fmt.Sprintf("%s #%s", cfg.Key, strings.TrimPrefix(issue, "#"))
|
return fmt.Sprintf("%s #%s", cfg.Key, strings.TrimPrefix(issue, "#"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s: %s", cfg.Key, issue)
|
return fmt.Sprintf("%s: %s", cfg.Key, issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,25 +190,30 @@ func (p MessageProcessorImpl) IssueID(branch string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rstr := fmt.Sprintf("^%s(%s)%s$", p.branchesCfg.Prefix, p.messageCfg.Issue.Regex, p.branchesCfg.Suffix)
|
rstr := fmt.Sprintf("^%s(%s)%s$", p.branchesCfg.Prefix, p.messageCfg.Issue.Regex, p.branchesCfg.Suffix)
|
||||||
|
|
||||||
r, err := regexp.Compile(rstr)
|
r, err := regexp.Compile(rstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not compile issue regex: %s, error: %v", rstr, err.Error())
|
return "", fmt.Errorf("%w: %s: %v", errInvalidIssueRegex, rstr, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := r.FindStringSubmatch(branch)
|
groups := r.FindStringSubmatch(branch)
|
||||||
if len(groups) != 4 {
|
if len(groups) != 4 { //nolint:gomnd
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups[2], nil
|
return groups[2], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format a commit message returning header, body and footer.
|
// Format a commit message returning header, body and footer.
|
||||||
func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) {
|
func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) {
|
||||||
var header strings.Builder
|
var header strings.Builder
|
||||||
|
|
||||||
header.WriteString(msg.Type)
|
header.WriteString(msg.Type)
|
||||||
|
|
||||||
if msg.Scope != "" {
|
if msg.Scope != "" {
|
||||||
header.WriteString("(" + msg.Scope + ")")
|
header.WriteString("(" + msg.Scope + ")")
|
||||||
}
|
}
|
||||||
|
|
||||||
header.WriteString(": ")
|
header.WriteString(": ")
|
||||||
header.WriteString(msg.Description)
|
header.WriteString(msg.Description)
|
||||||
|
|
||||||
@ -195,10 +221,12 @@ func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string)
|
|||||||
if msg.BreakingMessage() != "" {
|
if msg.BreakingMessage() != "" {
|
||||||
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeFooterKey, msg.BreakingMessage()))
|
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeFooterKey, msg.BreakingMessage()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if issue, exists := msg.Metadata[issueMetadataKey]; exists && p.messageCfg.IssueFooterConfig().Key != "" {
|
if issue, exists := msg.Metadata[issueMetadataKey]; exists && p.messageCfg.IssueFooterConfig().Key != "" {
|
||||||
if footer.Len() > 0 {
|
if footer.Len() > 0 {
|
||||||
footer.WriteString("\n")
|
footer.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.WriteString(formatIssueFooter(p.messageCfg.IssueFooterConfig(), issue))
|
footer.WriteString(formatIssueFooter(p.messageCfg.IssueFooterConfig(), issue))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,17 +249,20 @@ func (p MessageProcessorImpl) Parse(subject, body string) (CommitMessage, error)
|
|||||||
commitType, scope, description, hasBreakingChange := parseSubjectMessage(preparedSubject)
|
commitType, scope, description, hasBreakingChange := parseSubjectMessage(preparedSubject)
|
||||||
|
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
|
|
||||||
for key, mdCfg := range p.messageCfg.Footer {
|
for key, mdCfg := range p.messageCfg.Footer {
|
||||||
if mdCfg.Key != "" {
|
if mdCfg.Key != "" {
|
||||||
prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...)
|
prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...)
|
||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
if tagValue := extractFooterMetadata(prefix, commitBody, mdCfg.UseHash); tagValue != "" {
|
if tagValue := extractFooterMetadata(prefix, commitBody, mdCfg.UseHash); tagValue != "" {
|
||||||
metadata[key] = tagValue
|
metadata[key] = tagValue
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagValue := extractFooterMetadata(breakingChangeFooterKey, commitBody, false); tagValue != "" {
|
if tagValue := extractFooterMetadata(breakingChangeFooterKey, commitBody, false); tagValue != "" {
|
||||||
metadata[breakingChangeMetadataKey] = tagValue
|
metadata[breakingChangeMetadataKey] = tagValue
|
||||||
hasBreakingChange = true
|
hasBreakingChange = true
|
||||||
@ -254,18 +285,23 @@ func (p MessageProcessorImpl) prepareHeader(header string) (string, error) {
|
|||||||
|
|
||||||
regex, err := regexp.Compile(p.messageCfg.HeaderSelector)
|
regex, err := regexp.Compile(p.messageCfg.HeaderSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid regex on header-selector %s, error: %s", p.messageCfg.HeaderSelector, err.Error())
|
return "", fmt.Errorf("%w: %s: %s", errInvalidHeaderRegex, p.messageCfg.HeaderSelector, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
index := regex.SubexpIndex(messageRegexGroupName)
|
index := regex.SubexpIndex(messageRegexGroupName)
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return "", fmt.Errorf("could not find %s regex group on header-selector regex", messageRegexGroupName)
|
return "", fmt.Errorf("%w: could not find group %s", errInvalidHeaderRegex, messageRegexGroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
match := regex.FindStringSubmatch(header)
|
match := regex.FindStringSubmatch(header)
|
||||||
|
|
||||||
if match == nil || len(match) < index {
|
if match == nil || len(match) < index {
|
||||||
return "", fmt.Errorf("could not find %s regex group in match result for '%s'", messageRegexGroupName, header)
|
return "", fmt.Errorf(
|
||||||
|
"%w: could not find group %s in match result for '%s'",
|
||||||
|
errInvalidHeaderRegex,
|
||||||
|
messageRegexGroupName,
|
||||||
|
header,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return match[index], nil
|
return match[index], nil
|
||||||
@ -273,10 +309,12 @@ func (p MessageProcessorImpl) prepareHeader(header string) (string, error) {
|
|||||||
|
|
||||||
func parseSubjectMessage(message string) (string, string, string, bool) {
|
func parseSubjectMessage(message string) (string, string, string, bool) {
|
||||||
regex := regexp.MustCompile(`([a-z]+)(\((.*)\))?(!)?: (.*)`)
|
regex := regexp.MustCompile(`([a-z]+)(\((.*)\))?(!)?: (.*)`)
|
||||||
|
|
||||||
result := regex.FindStringSubmatch(message)
|
result := regex.FindStringSubmatch(message)
|
||||||
if len(result) != 6 {
|
if len(result) != 6 { //nolint:gomnd
|
||||||
return "", "", message, false
|
return "", "", message, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[1], result[3], strings.TrimSpace(result[5]), result[4] == "!"
|
return result[1], result[3], strings.TrimSpace(result[5]), result[4] == "!"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,9 +327,10 @@ func extractFooterMetadata(key, text string, useHash bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := regex.FindStringSubmatch(text)
|
result := regex.FindStringSubmatch(text)
|
||||||
if len(result) < 2 {
|
if len(result) < 2 { //nolint:gomnd
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[1]
|
return result[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +339,7 @@ func hasFooter(message string) bool {
|
|||||||
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(message))
|
scanner := bufio.NewScanner(strings.NewReader(message))
|
||||||
lines := 0
|
lines := 0
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if lines > 0 && r.MatchString(scanner.Text()) {
|
if lines > 0 && r.MatchString(scanner.Text()) {
|
||||||
return true
|
return true
|
||||||
@ -317,6 +357,7 @@ func hasIssueID(message string, issueConfig CommitMessageFooterConfig) bool {
|
|||||||
} else {
|
} else {
|
||||||
r = regexp.MustCompile(fmt.Sprintf("(?m)^%s: .+$", issueConfig.Key))
|
r = regexp.MustCompile(fmt.Sprintf("(?m)^%s: .+$", issueConfig.Key))
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.MatchString(message)
|
return r.MatchString(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +367,7 @@ func contains(value string, content []string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +378,15 @@ func splitCommitMessageContent(content string) (string, string) {
|
|||||||
subject := scanner.Text()
|
subject := scanner.Text()
|
||||||
|
|
||||||
var body strings.Builder
|
var body strings.Builder
|
||||||
|
|
||||||
first := true
|
first := true
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if !first {
|
if !first {
|
||||||
body.WriteString("\n")
|
body.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
body.WriteString(scanner.Text())
|
body.WriteString(scanner.Text())
|
||||||
|
|
||||||
first = false
|
first = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,23 +146,55 @@ func TestMessageProcessorImpl_Validate(t *testing.T) {
|
|||||||
message string
|
message string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"single line valid message", ccfg, "feat: add something", false},
|
{
|
||||||
{"single line valid message with scope", ccfg, "feat(scope): add something", false},
|
"single line valid message",
|
||||||
|
ccfg,
|
||||||
|
"feat: add something", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single line valid message with scope",
|
||||||
|
ccfg,
|
||||||
|
"feat(scope): add something", false,
|
||||||
|
},
|
||||||
{"single line valid scope from list", ccfgWithScope, "feat(scope): add something", false},
|
{"single line valid scope from list", ccfgWithScope, "feat(scope): add something", false},
|
||||||
{"single line invalid scope from list", ccfgWithScope, "feat(invalid): add something", true},
|
{"single line invalid scope from list", ccfgWithScope, "feat(invalid): add something", true},
|
||||||
{"single line invalid type message", ccfg, "something: add something", true},
|
{
|
||||||
{"single line invalid type message", ccfg, "feat?: add something", true},
|
"single line invalid type message",
|
||||||
|
ccfg,
|
||||||
|
"something: add something", true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single line invalid type message",
|
||||||
|
ccfg,
|
||||||
|
"feat?: add something", true,
|
||||||
|
},
|
||||||
|
|
||||||
{"multi line valid message", ccfg, `feat: add something
|
{
|
||||||
|
"multi line valid message",
|
||||||
|
ccfg,
|
||||||
|
`feat: add something
|
||||||
|
|
||||||
team: x`, false},
|
team: x`, false,
|
||||||
|
},
|
||||||
|
|
||||||
{"multi line invalid message", ccfg, `feat add something
|
{
|
||||||
|
"multi line invalid message",
|
||||||
|
ccfg,
|
||||||
|
`feat add something
|
||||||
|
|
||||||
team: x`, true},
|
team: x`, true,
|
||||||
|
},
|
||||||
|
|
||||||
{"support ! for breaking change", ccfg, "feat!: add something", false},
|
{
|
||||||
{"support ! with scope for breaking change", ccfg, "feat(scope)!: add something", false},
|
"support ! for breaking change",
|
||||||
|
ccfg,
|
||||||
|
"feat!: add something", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support ! with scope for breaking change",
|
||||||
|
ccfg,
|
||||||
|
"feat(scope)!: add something", false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -181,9 +213,21 @@ func TestMessageProcessorImpl_ValidateType(t *testing.T) {
|
|||||||
ctype string
|
ctype string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"valid type", ccfg, "feat", false},
|
{
|
||||||
{"invalid type", ccfg, "aaa", true},
|
"valid type",
|
||||||
{"empty type", ccfg, "", true},
|
ccfg,
|
||||||
|
"feat", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid type",
|
||||||
|
ccfg,
|
||||||
|
"aaa", true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty type",
|
||||||
|
ccfg,
|
||||||
|
"", true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -202,7 +246,11 @@ func TestMessageProcessorImpl_ValidateScope(t *testing.T) {
|
|||||||
scope string
|
scope string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"any scope", ccfg, "aaa", false},
|
{
|
||||||
|
"any scope",
|
||||||
|
ccfg,
|
||||||
|
"aaa", false,
|
||||||
|
},
|
||||||
{"valid scope with scope list", ccfgWithScope, "scope", false},
|
{"valid scope with scope list", ccfgWithScope, "scope", false},
|
||||||
{"invalid scope with scope list", ccfgWithScope, "aaa", true},
|
{"invalid scope with scope list", ccfgWithScope, "aaa", true},
|
||||||
}
|
}
|
||||||
@ -223,11 +271,31 @@ func TestMessageProcessorImpl_ValidateDescription(t *testing.T) {
|
|||||||
description string
|
description string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"empty description", ccfg, "", true},
|
{
|
||||||
{"sigle letter description", ccfg, "a", false},
|
"empty description",
|
||||||
{"number description", ccfg, "1", true},
|
ccfg,
|
||||||
{"valid description", ccfg, "add some feature", false},
|
"", true,
|
||||||
{"invalid capital letter description", ccfg, "Add some feature", true},
|
},
|
||||||
|
{
|
||||||
|
"sigle letter description",
|
||||||
|
ccfg,
|
||||||
|
"a", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"number description",
|
||||||
|
ccfg,
|
||||||
|
"1", true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid description",
|
||||||
|
ccfg,
|
||||||
|
"add some feature", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid capital letter description",
|
||||||
|
ccfg,
|
||||||
|
"Add some feature", true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -248,24 +316,73 @@ func TestMessageProcessorImpl_Enhance(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"issue on branch name", ccfg, "JIRA-123", "fix: fix something", "\njira: JIRA-123", false},
|
{
|
||||||
{"issue on branch name with description", ccfg, "JIRA-123-some-description", "fix: fix something", "\njira: JIRA-123", false},
|
"issue on branch name",
|
||||||
{"issue on branch name with prefix", ccfg, "feature/JIRA-123", "fix: fix something", "\njira: JIRA-123", false},
|
ccfg,
|
||||||
{"with footer", ccfg, "JIRA-123", fullMessage, "jira: JIRA-123", false},
|
"JIRA-123", "fix: fix something", "\njira: JIRA-123", false,
|
||||||
{"with issue on footer", ccfg, "JIRA-123", fullMessageWithJira, "", false},
|
},
|
||||||
{"issue on branch name with prefix and description", ccfg, "feature/JIRA-123-some-description", "fix: fix something", "\njira: JIRA-123", false},
|
{
|
||||||
{"no issue on branch name", ccfg, "branch", "fix: fix something", "", true},
|
"issue on branch name with description",
|
||||||
{"unexpected branch name", ccfg, "feature /JIRA-123", "fix: fix something", "", true},
|
ccfg,
|
||||||
{"issue on branch name using hash", ccfgHash, "JIRA-123-some-description", "fix: fix something", "\njira #JIRA-123", false},
|
"JIRA-123-some-description", "fix: fix something", "\njira: JIRA-123", false,
|
||||||
{"numeric issue on branch name", ccfgGitIssue, "#13", "fix: fix something", "\nissue: #13", false},
|
},
|
||||||
{"numeric issue on branch name without hash", ccfgGitIssue, "13", "fix: fix something", "\nissue: #13", false},
|
{
|
||||||
{"numeric issue on branch name with description without hash", ccfgGitIssue, "13-some-fix", "fix: fix something", "\nissue: #13", false},
|
"issue on branch name with prefix",
|
||||||
|
ccfg,
|
||||||
|
"feature/JIRA-123", "fix: fix something", "\njira: JIRA-123", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with footer",
|
||||||
|
ccfg,
|
||||||
|
"JIRA-123", fullMessage, "jira: JIRA-123", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with issue on footer",
|
||||||
|
ccfg,
|
||||||
|
"JIRA-123", fullMessageWithJira, "", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issue on branch name with prefix and description",
|
||||||
|
ccfg,
|
||||||
|
"feature/JIRA-123-some-description", "fix: fix something", "\njira: JIRA-123", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no issue on branch name",
|
||||||
|
ccfg,
|
||||||
|
"branch", "fix: fix something", "", true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unexpected branch name",
|
||||||
|
ccfg,
|
||||||
|
"feature /JIRA-123", "fix: fix something", "", true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issue on branch name using hash",
|
||||||
|
ccfgHash,
|
||||||
|
"JIRA-123-some-description", "fix: fix something", "\njira #JIRA-123", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"numeric issue on branch name",
|
||||||
|
ccfgGitIssue,
|
||||||
|
"#13", "fix: fix something", "\nissue: #13", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"numeric issue on branch name without hash",
|
||||||
|
ccfgGitIssue,
|
||||||
|
"13", "fix: fix something", "\nissue: #13", false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"numeric issue on branch name with description without hash",
|
||||||
|
ccfgGitIssue,
|
||||||
|
"13-some-fix", "fix: fix something", "\nissue: #13", false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := NewMessageProcessor(tt.cfg, newBranchCfg(false)).Enhance(tt.branch, tt.message)
|
got, err := NewMessageProcessor(tt.cfg, newBranchCfg(false)).Enhance(tt.branch, tt.message)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("MessageProcessorImpl.Enhance() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("MessageProcessorImpl.Enhance() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
@ -296,6 +413,7 @@ func TestMessageProcessorImpl_IssueID(t *testing.T) {
|
|||||||
got, err := p.IssueID(tt.branch)
|
got, err := p.IssueID(tt.branch)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("MessageProcessorImpl.IssueID() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("MessageProcessorImpl.IssueID() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
@ -378,8 +496,10 @@ var completeBody = `some descriptions
|
|||||||
jira: JIRA-123
|
jira: JIRA-123
|
||||||
BREAKING CHANGE: this change breaks everything`
|
BREAKING CHANGE: this change breaks everything`
|
||||||
|
|
||||||
var bodyWithCarriage = "some description\r\nmore description\r\n\r\njira: JIRA-123\r"
|
var (
|
||||||
var expectedBodyWithCarriage = "some description\nmore description\n\njira: JIRA-123"
|
bodyWithCarriage = "some description\r\nmore description\r\n\r\njira: JIRA-123\r"
|
||||||
|
expectedBodyWithCarriage = "some description\nmore description\n\njira: JIRA-123"
|
||||||
|
)
|
||||||
|
|
||||||
var issueOnlyBody = `some descriptions
|
var issueOnlyBody = `some descriptions
|
||||||
|
|
||||||
@ -402,20 +522,145 @@ func TestMessageProcessorImpl_Parse(t *testing.T) {
|
|||||||
body string
|
body string
|
||||||
want CommitMessage
|
want CommitMessage
|
||||||
}{
|
}{
|
||||||
{"simple message", ccfg, "feat: something awesome", "", CommitMessage{Type: "feat", Scope: "", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
|
{
|
||||||
{"message with scope", ccfg, "feat(scope): something awesome", "", CommitMessage{Type: "feat", Scope: "scope", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
|
"simple message",
|
||||||
{"unmapped type", ccfg, "unkn: something unknown", "", CommitMessage{Type: "unkn", Scope: "", Description: "something unknown", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
|
ccfg,
|
||||||
{"jira and breaking change metadata", ccfg, "feat: something new", completeBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: completeBody, IsBreakingChange: true, Metadata: map[string]string{issueMetadataKey: "JIRA-123", breakingChangeMetadataKey: "this change breaks everything"}}},
|
"feat: something awesome", "",
|
||||||
{"jira only metadata", ccfg, "feat: something new", issueOnlyBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueOnlyBody, IsBreakingChange: false, Metadata: map[string]string{issueMetadataKey: "JIRA-456"}}},
|
CommitMessage{
|
||||||
{"jira synonyms metadata", ccfg, "feat: something new", issueSynonymsBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueSynonymsBody, IsBreakingChange: false, Metadata: map[string]string{issueMetadataKey: "JIRA-789"}}},
|
Type: "feat",
|
||||||
{"breaking change with exclamation mark", ccfg, "feat!: something new", "", CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: "", IsBreakingChange: true, Metadata: map[string]string{}}},
|
Scope: "",
|
||||||
{"hash metadata", ccfg, "feat: something new", hashMetadataBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: hashMetadataBody, IsBreakingChange: false, Metadata: map[string]string{issueMetadataKey: "JIRA-999", "refs": "#123"}}},
|
Description: "something awesome",
|
||||||
{"empty issue cfg", ccfgEmptyIssue, "feat: something new", hashMetadataBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: hashMetadataBody, IsBreakingChange: false, Metadata: map[string]string{}}},
|
Body: "",
|
||||||
{"carriage return on body", ccfg, "feat: something new", bodyWithCarriage, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: expectedBodyWithCarriage, IsBreakingChange: false, Metadata: map[string]string{issueMetadataKey: "JIRA-123"}}},
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message with scope",
|
||||||
|
ccfg,
|
||||||
|
"feat(scope): something awesome", "",
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "scope",
|
||||||
|
Description: "something awesome",
|
||||||
|
Body: "",
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unmapped type",
|
||||||
|
ccfg,
|
||||||
|
"unkn: something unknown", "",
|
||||||
|
CommitMessage{
|
||||||
|
Type: "unkn",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something unknown",
|
||||||
|
Body: "",
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jira and breaking change metadata",
|
||||||
|
ccfg,
|
||||||
|
"feat: something new", completeBody,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: completeBody,
|
||||||
|
IsBreakingChange: true,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
issueMetadataKey: "JIRA-123",
|
||||||
|
breakingChangeMetadataKey: "this change breaks everything",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jira only metadata",
|
||||||
|
ccfg,
|
||||||
|
"feat: something new", issueOnlyBody,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: issueOnlyBody,
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{issueMetadataKey: "JIRA-456"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jira synonyms metadata",
|
||||||
|
ccfg,
|
||||||
|
"feat: something new", issueSynonymsBody,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: issueSynonymsBody,
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{issueMetadataKey: "JIRA-789"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"breaking change with exclamation mark",
|
||||||
|
ccfg,
|
||||||
|
"feat!: something new", "",
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: "",
|
||||||
|
IsBreakingChange: true,
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hash metadata",
|
||||||
|
ccfg,
|
||||||
|
"feat: something new", hashMetadataBody,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: hashMetadataBody,
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{issueMetadataKey: "JIRA-999", "refs": "#123"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty issue cfg",
|
||||||
|
ccfgEmptyIssue,
|
||||||
|
"feat: something new", hashMetadataBody,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: hashMetadataBody,
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"carriage return on body",
|
||||||
|
ccfg,
|
||||||
|
"feat: something new", bodyWithCarriage,
|
||||||
|
CommitMessage{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "",
|
||||||
|
Description: "something new",
|
||||||
|
Body: expectedBodyWithCarriage,
|
||||||
|
IsBreakingChange: false,
|
||||||
|
Metadata: map[string]string{issueMetadataKey: "JIRA-123"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got, err := NewMessageProcessor(tt.cfg, newBranchCfg(false)).Parse(tt.subject, tt.body); !reflect.DeepEqual(got, tt.want) && err == nil {
|
if got, err := NewMessageProcessor(
|
||||||
|
tt.cfg, newBranchCfg(false),
|
||||||
|
).Parse(tt.subject, tt.body); !reflect.DeepEqual(got, tt.want) && err == nil {
|
||||||
t.Errorf("MessageProcessorImpl.Parse() = [%+v], want [%+v]", got, tt.want)
|
t.Errorf("MessageProcessorImpl.Parse() = [%+v], want [%+v]", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -431,18 +676,102 @@ func TestMessageProcessorImpl_Format(t *testing.T) {
|
|||||||
wantBody string
|
wantBody string
|
||||||
wantFooter string
|
wantFooter string
|
||||||
}{
|
}{
|
||||||
{"simple message", ccfg, NewCommitMessage("feat", "", "something", "", "", ""), "feat: something", "", ""},
|
{
|
||||||
{"with issue", ccfg, NewCommitMessage("feat", "", "something", "", "JIRA-123", ""), "feat: something", "", "jira: JIRA-123"},
|
"simple message",
|
||||||
{"with issue using hash", ccfgHash, NewCommitMessage("feat", "", "something", "", "JIRA-123", ""), "feat: something", "", "jira #JIRA-123"},
|
ccfg,
|
||||||
{"with issue using double hash", ccfgHash, NewCommitMessage("feat", "", "something", "", "#JIRA-123", ""), "feat: something", "", "jira #JIRA-123"},
|
NewCommitMessage("feat", "", "something", "", "", ""),
|
||||||
{"with breaking change", ccfg, NewCommitMessage("feat", "", "something", "", "", "breaks"), "feat: something", "", "BREAKING CHANGE: breaks"},
|
"feat: something",
|
||||||
{"with scope", ccfg, NewCommitMessage("feat", "scope", "something", "", "", ""), "feat(scope): something", "", ""},
|
"",
|
||||||
{"with body", ccfg, NewCommitMessage("feat", "", "something", "body", "", ""), "feat: something", "body", ""},
|
"",
|
||||||
{"with multiline body", ccfg, NewCommitMessage("feat", "", "something", multilineBody, "", ""), "feat: something", multilineBody, ""},
|
},
|
||||||
{"full message", ccfg, NewCommitMessage("feat", "scope", "something", multilineBody, "JIRA-123", "breaks"), "feat(scope): something", multilineBody, fullFooter},
|
{
|
||||||
{"config without issue key", ccfgEmptyIssue, NewCommitMessage("feat", "", "something", "", "JIRA-123", ""), "feat: something", "", ""},
|
"with issue",
|
||||||
{"with issue and issue prefix", ccfgGitIssue, NewCommitMessage("feat", "", "something", "", "123", ""), "feat: something", "", "issue: #123"},
|
ccfg,
|
||||||
{"with #issue and issue prefix", ccfgGitIssue, NewCommitMessage("feat", "", "something", "", "#123", ""), "feat: something", "", "issue: #123"},
|
NewCommitMessage("feat", "", "something", "", "JIRA-123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"jira: JIRA-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with issue using hash",
|
||||||
|
ccfgHash,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "JIRA-123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"jira #JIRA-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with issue using double hash",
|
||||||
|
ccfgHash,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "#JIRA-123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"jira #JIRA-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with breaking change",
|
||||||
|
ccfg,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "", "breaks"),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"BREAKING CHANGE: breaks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with scope",
|
||||||
|
ccfg,
|
||||||
|
NewCommitMessage("feat", "scope", "something", "", "", ""),
|
||||||
|
"feat(scope): something",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with body",
|
||||||
|
ccfg,
|
||||||
|
NewCommitMessage("feat", "", "something", "body", "", ""),
|
||||||
|
"feat: something",
|
||||||
|
"body",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with multiline body",
|
||||||
|
ccfg,
|
||||||
|
NewCommitMessage("feat", "", "something", multilineBody, "", ""),
|
||||||
|
"feat: something",
|
||||||
|
multilineBody,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"full message",
|
||||||
|
ccfg,
|
||||||
|
NewCommitMessage("feat", "scope", "something", multilineBody, "JIRA-123", "breaks"),
|
||||||
|
"feat(scope): something",
|
||||||
|
multilineBody,
|
||||||
|
fullFooter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config without issue key",
|
||||||
|
ccfgEmptyIssue,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "JIRA-123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with issue and issue prefix",
|
||||||
|
ccfgGitIssue,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"issue: #123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with #issue and issue prefix",
|
||||||
|
ccfgGitIssue,
|
||||||
|
NewCommitMessage("feat", "", "something", "", "#123", ""),
|
||||||
|
"feat: something",
|
||||||
|
"",
|
||||||
|
"issue: #123",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -532,14 +861,61 @@ func Test_prepareHeader(t *testing.T) {
|
|||||||
wantHeader string
|
wantHeader string
|
||||||
wantError bool
|
wantError bool
|
||||||
}{
|
}{
|
||||||
{"conventional without selector", "", "feat: something", "feat: something", false},
|
{
|
||||||
{"conventional with scope without selector", "", "feat(scope): something", "feat(scope): something", false},
|
"conventional without selector",
|
||||||
{"non-conventional without selector", "", "something", "something", false},
|
"",
|
||||||
{"matching conventional with selector with group", "Merged PR (\\d+): (?P<header>.*)", "Merged PR 123: feat: something", "feat: something", false},
|
"feat: something",
|
||||||
{"matching non-conventional with selector with group", "Merged PR (\\d+): (?P<header>.*)", "Merged PR 123: something", "something", false},
|
"feat: something",
|
||||||
{"matching non-conventional with selector without group", "Merged PR (\\d+): (.*)", "Merged PR 123: something", "", true},
|
false,
|
||||||
{"non-matching non-conventional with selector with group", "Merged PR (\\d+): (?P<header>.*)", "something", "", true},
|
},
|
||||||
{"matching non-conventional with invalid regex", "Merged PR (\\d+): (?<header>.*)", "Merged PR 123: something", "", true},
|
{
|
||||||
|
"conventional with scope without selector",
|
||||||
|
"",
|
||||||
|
"feat(scope): something",
|
||||||
|
"feat(scope): something",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-conventional without selector",
|
||||||
|
"",
|
||||||
|
"something", "something",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matching conventional with selector with group",
|
||||||
|
"Merged PR (\\d+): (?P<header>.*)",
|
||||||
|
"Merged PR 123: feat: something",
|
||||||
|
"feat: something",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matching non-conventional with selector with group",
|
||||||
|
"Merged PR (\\d+): (?P<header>.*)",
|
||||||
|
"Merged PR 123: something",
|
||||||
|
"something",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matching non-conventional with selector without group",
|
||||||
|
"Merged PR (\\d+): (.*)",
|
||||||
|
"Merged PR 123: something",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-matching non-conventional with selector with group",
|
||||||
|
"Merged PR (\\d+): (?P<header>.*)",
|
||||||
|
"something",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matching non-conventional with invalid regex",
|
||||||
|
"Merged PR (\\d+): (?<header>.*)",
|
||||||
|
"Merged PR 123: something",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -22,22 +22,32 @@ func NewReleaseNoteProcessor(cfg ReleaseNotesConfig) *ReleaseNoteProcessorImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create create a release note based on commits.
|
// Create create a release note based on commits.
|
||||||
func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, date time.Time, commits []GitCommitLog) ReleaseNote {
|
func (p ReleaseNoteProcessorImpl) Create(
|
||||||
|
version *semver.Version,
|
||||||
|
tag string,
|
||||||
|
date time.Time,
|
||||||
|
commits []GitCommitLog,
|
||||||
|
) ReleaseNote {
|
||||||
mapping := commitSectionMapping(p.cfg.Sections)
|
mapping := commitSectionMapping(p.cfg.Sections)
|
||||||
|
|
||||||
sections := make(map[string]ReleaseNoteCommitsSection)
|
sections := make(map[string]ReleaseNoteCommitsSection)
|
||||||
authors := make(map[string]struct{})
|
authors := make(map[string]struct{})
|
||||||
|
|
||||||
var breakingChanges []string
|
var breakingChanges []string
|
||||||
|
|
||||||
for _, commit := range commits {
|
for _, commit := range commits {
|
||||||
authors[commit.AuthorName] = struct{}{}
|
authors[commit.AuthorName] = struct{}{}
|
||||||
|
|
||||||
if sectionCfg, exists := mapping[commit.Message.Type]; exists {
|
if sectionCfg, exists := mapping[commit.Message.Type]; exists {
|
||||||
section, sexists := sections[sectionCfg.Name]
|
section, sexists := sections[sectionCfg.Name]
|
||||||
if !sexists {
|
if !sexists {
|
||||||
section = ReleaseNoteCommitsSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
|
section = ReleaseNoteCommitsSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.Items = append(section.Items, commit)
|
section.Items = append(section.Items, commit)
|
||||||
sections[sectionCfg.Name] = section
|
sections[sectionCfg.Name] = section
|
||||||
}
|
}
|
||||||
|
|
||||||
if commit.Message.BreakingMessage() != "" {
|
if commit.Message.BreakingMessage() != "" {
|
||||||
// TODO: if no message found, should use description instead?
|
// TODO: if no message found, should use description instead?
|
||||||
breakingChanges = append(breakingChanges, commit.Message.BreakingMessage())
|
breakingChanges = append(breakingChanges, commit.Message.BreakingMessage())
|
||||||
@ -48,10 +58,20 @@ func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, da
|
|||||||
if bcCfg := p.cfg.sectionConfig(ReleaseNotesSectionTypeBreakingChanges); bcCfg != nil && len(breakingChanges) > 0 {
|
if bcCfg := p.cfg.sectionConfig(ReleaseNotesSectionTypeBreakingChanges); bcCfg != nil && len(breakingChanges) > 0 {
|
||||||
breakingChangeSection = ReleaseNoteBreakingChangeSection{Name: bcCfg.Name, Messages: breakingChanges}
|
breakingChangeSection = ReleaseNoteBreakingChangeSection{Name: bcCfg.Name, Messages: breakingChanges}
|
||||||
}
|
}
|
||||||
return ReleaseNote{Version: version, Tag: tag, Date: date.Truncate(time.Minute), Sections: p.toReleaseNoteSections(sections, breakingChangeSection), AuthorsNames: authors}
|
|
||||||
|
return ReleaseNote{
|
||||||
|
Version: version,
|
||||||
|
Tag: tag,
|
||||||
|
Date: date.Truncate(time.Minute),
|
||||||
|
Sections: p.toReleaseNoteSections(sections, breakingChangeSection),
|
||||||
|
AuthorsNames: authors,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[string]ReleaseNoteCommitsSection, breakingChange ReleaseNoteBreakingChangeSection) []ReleaseNoteSection {
|
func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(
|
||||||
|
commitSections map[string]ReleaseNoteCommitsSection,
|
||||||
|
breakingChange ReleaseNoteBreakingChangeSection,
|
||||||
|
) []ReleaseNoteSection {
|
||||||
hasBreaking := 0
|
hasBreaking := 0
|
||||||
if breakingChange.Name != "" {
|
if breakingChange.Name != "" {
|
||||||
hasBreaking = 1
|
hasBreaking = 1
|
||||||
@ -59,11 +79,13 @@ func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[strin
|
|||||||
|
|
||||||
sections := make([]ReleaseNoteSection, len(commitSections)+hasBreaking)
|
sections := make([]ReleaseNoteSection, len(commitSections)+hasBreaking)
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for _, cfg := range p.cfg.Sections {
|
for _, cfg := range p.cfg.Sections {
|
||||||
if cfg.SectionType == ReleaseNotesSectionTypeBreakingChanges && hasBreaking > 0 {
|
if cfg.SectionType == ReleaseNotesSectionTypeBreakingChanges && hasBreaking > 0 {
|
||||||
sections[i] = breakingChange
|
sections[i] = breakingChange
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, exists := commitSections[cfg.Name]; cfg.SectionType == ReleaseNotesSectionTypeCommits && exists {
|
if s, exists := commitSections[cfg.Name]; cfg.SectionType == ReleaseNotesSectionTypeCommits && exists {
|
||||||
sections[i] = s
|
sections[i] = s
|
||||||
i++
|
i++
|
||||||
@ -75,6 +97,7 @@ func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[strin
|
|||||||
|
|
||||||
func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]ReleaseNotesSectionConfig {
|
func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]ReleaseNotesSectionConfig {
|
||||||
mapping := make(map[string]ReleaseNotesSectionConfig)
|
mapping := make(map[string]ReleaseNotesSectionConfig)
|
||||||
|
|
||||||
for _, section := range sections {
|
for _, section := range sections {
|
||||||
if section.SectionType == ReleaseNotesSectionTypeCommits {
|
if section.SectionType == ReleaseNotesSectionTypeCommits {
|
||||||
for _, commitType := range section.CommitTypes {
|
for _, commitType := range section.CommitTypes {
|
||||||
@ -82,6 +105,7 @@ func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]Relea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,15 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
|
|||||||
tag: "v1.0.0",
|
tag: "v1.0.0",
|
||||||
date: date,
|
date: date,
|
||||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a")},
|
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a")},
|
||||||
want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, map[string]struct{}{"a": {}}),
|
want: releaseNote(
|
||||||
|
semver.MustParse("1.0.0"),
|
||||||
|
"v1.0.0",
|
||||||
|
date,
|
||||||
|
[]ReleaseNoteSection{
|
||||||
|
newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")}),
|
||||||
|
},
|
||||||
|
map[string]struct{}{"a": {}},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unmapped tag",
|
name: "unmapped tag",
|
||||||
@ -33,28 +41,71 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
|
|||||||
tag: "v1.0.0",
|
tag: "v1.0.0",
|
||||||
date: date,
|
date: date,
|
||||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{}, "a")},
|
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{}, "a")},
|
||||||
want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, map[string]struct{}{"a": {}}),
|
want: releaseNote(
|
||||||
|
semver.MustParse("1.0.0"),
|
||||||
|
"v1.0.0",
|
||||||
|
date,
|
||||||
|
[]ReleaseNoteSection{
|
||||||
|
newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")}),
|
||||||
|
},
|
||||||
|
map[string]struct{}{"a": {}},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "breaking changes tag",
|
name: "breaking changes tag",
|
||||||
version: semver.MustParse("1.0.0"),
|
version: semver.MustParse("1.0.0"),
|
||||||
tag: "v1.0.0",
|
tag: "v1.0.0",
|
||||||
date: date,
|
date: date,
|
||||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{"breaking-change": "breaks"}, "a")},
|
commits: []GitCommitLog{
|
||||||
want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")}), ReleaseNoteBreakingChangeSection{Name: "Breaking Changes", Messages: []string{"breaks"}}}, map[string]struct{}{"a": {}}),
|
commitlog("t1", map[string]string{}, "a"),
|
||||||
|
commitlog("unmapped", map[string]string{"breaking-change": "breaks"}, "a"),
|
||||||
|
},
|
||||||
|
want: releaseNote(
|
||||||
|
semver.MustParse("1.0.0"),
|
||||||
|
"v1.0.0",
|
||||||
|
date,
|
||||||
|
[]ReleaseNoteSection{
|
||||||
|
newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")}),
|
||||||
|
ReleaseNoteBreakingChangeSection{Name: "Breaking Changes", Messages: []string{"breaks"}},
|
||||||
|
},
|
||||||
|
map[string]struct{}{"a": {}},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple authors",
|
name: "multiple authors",
|
||||||
version: semver.MustParse("1.0.0"),
|
version: semver.MustParse("1.0.0"),
|
||||||
tag: "v1.0.0",
|
tag: "v1.0.0",
|
||||||
date: date,
|
date: date,
|
||||||
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")},
|
commits: []GitCommitLog{
|
||||||
want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")})}, map[string]struct{}{"author1": {}, "author2": {}, "author3": {}}),
|
commitlog("t1", map[string]string{}, "author3"),
|
||||||
|
commitlog("t1", map[string]string{}, "author2"),
|
||||||
|
commitlog("t1", map[string]string{}, "author1"),
|
||||||
|
},
|
||||||
|
want: releaseNote(
|
||||||
|
semver.MustParse("1.0.0"),
|
||||||
|
"v1.0.0",
|
||||||
|
date,
|
||||||
|
[]ReleaseNoteSection{
|
||||||
|
newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{
|
||||||
|
commitlog("t1", map[string]string{}, "author3"),
|
||||||
|
commitlog("t1", map[string]string{}, "author2"),
|
||||||
|
commitlog("t1", map[string]string{}, "author1"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
map[string]struct{}{"author1": {}, "author2": {}, "author3": {}},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
p := NewReleaseNoteProcessor(ReleaseNotesConfig{Sections: []ReleaseNotesSectionConfig{{Name: "Tag 1", SectionType: "commits", CommitTypes: []string{"t1"}}, {Name: "Tag 2", SectionType: "commits", CommitTypes: []string{"t2"}}, {Name: "Breaking Changes", SectionType: "breaking-changes"}}})
|
p := NewReleaseNoteProcessor(
|
||||||
|
ReleaseNotesConfig{
|
||||||
|
Sections: []ReleaseNotesSectionConfig{
|
||||||
|
{Name: "Tag 1", SectionType: "commits", CommitTypes: []string{"t1"}},
|
||||||
|
{Name: "Tag 2", SectionType: "commits", CommitTypes: []string{"t2"}},
|
||||||
|
{Name: "Breaking Changes", SectionType: "breaking-changes"},
|
||||||
|
},
|
||||||
|
})
|
||||||
if got := p.Create(tt.version, tt.tag, tt.date, tt.commits); !reflect.DeepEqual(got, tt.want) {
|
if got := p.Create(tt.version, tt.tag, tt.date, tt.commits); !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("ReleaseNoteProcessorImpl.Create() = %v, want %v", got, tt.want)
|
t.Errorf("ReleaseNoteProcessorImpl.Create() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
|
14
sv/semver.go
14
sv/semver.go
@ -14,6 +14,7 @@ const (
|
|||||||
// IsValidVersion return true when a version is valid.
|
// IsValidVersion return true when a version is valid.
|
||||||
func IsValidVersion(value string) bool {
|
func IsValidVersion(value string) bool {
|
||||||
_, err := semver.NewVersion(value)
|
_, err := semver.NewVersion(value)
|
||||||
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ func ToVersion(value string) (*semver.Version, error) {
|
|||||||
if version == "" {
|
if version == "" {
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
return semver.NewVersion(version)
|
return semver.NewVersion(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +54,9 @@ func NewSemVerCommitsProcessor(vcfg VersioningConfig, mcfg CommitMessageConfig)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NextVersion calculates next version based on commit log.
|
// NextVersion calculates next version based on commit log.
|
||||||
func (p SemVerCommitsProcessorImpl) NextVersion(version *semver.Version, commits []GitCommitLog) (*semver.Version, bool) {
|
func (p SemVerCommitsProcessorImpl) NextVersion(
|
||||||
|
version *semver.Version, commits []GitCommitLog,
|
||||||
|
) (*semver.Version, bool) {
|
||||||
versionToUpdate := none
|
versionToUpdate := none
|
||||||
for _, commit := range commits {
|
for _, commit := range commits {
|
||||||
if v := p.versionTypeToUpdate(commit); v > versionToUpdate {
|
if v := p.versionTypeToUpdate(commit); v > versionToUpdate {
|
||||||
@ -64,7 +68,9 @@ func (p SemVerCommitsProcessorImpl) NextVersion(version *semver.Version, commits
|
|||||||
if version == nil {
|
if version == nil {
|
||||||
return nil, updated
|
return nil, updated
|
||||||
}
|
}
|
||||||
|
|
||||||
newVersion := updateVersion(*version, versionToUpdate)
|
newVersion := updateVersion(*version, versionToUpdate)
|
||||||
|
|
||||||
return &newVersion, updated
|
return &newVersion, updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,18 +91,23 @@ func (p SemVerCommitsProcessorImpl) versionTypeToUpdate(commit GitCommitLog) ver
|
|||||||
if commit.Message.IsBreakingChange {
|
if commit.Message.IsBreakingChange {
|
||||||
return major
|
return major
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := p.MajorVersionTypes[commit.Message.Type]; exists {
|
if _, exists := p.MajorVersionTypes[commit.Message.Type]; exists {
|
||||||
return major
|
return major
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := p.MinorVersionTypes[commit.Message.Type]; exists {
|
if _, exists := p.MinorVersionTypes[commit.Message.Type]; exists {
|
||||||
return minor
|
return minor
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := p.PatchVersionTypes[commit.Message.Type]; exists {
|
if _, exists := p.PatchVersionTypes[commit.Message.Type]; exists {
|
||||||
return patch
|
return patch
|
||||||
}
|
}
|
||||||
|
|
||||||
if !contains(commit.Message.Type, p.KnownTypes) && p.IncludeUnknownTypeAsPatch {
|
if !contains(commit.Message.Type, p.KnownTypes) && p.IncludeUnknownTypeAsPatch {
|
||||||
return patch
|
return patch
|
||||||
}
|
}
|
||||||
|
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,5 +116,6 @@ func toMap(values []string) map[string]struct{} {
|
|||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
result[v] = struct{}{}
|
result[v] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,104 @@ func TestSemVerCommitsProcessorImpl_NextVersion(t *testing.T) {
|
|||||||
want *semver.Version
|
want *semver.Version
|
||||||
wantUpdated bool
|
wantUpdated bool
|
||||||
}{
|
}{
|
||||||
{"no update", true, version("0.0.0"), []GitCommitLog{}, version("0.0.0"), false},
|
{
|
||||||
{"no update without version", true, nil, []GitCommitLog{}, nil, false},
|
"no update",
|
||||||
{"no update on unknown type", true, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{}, "a")}, version("0.0.0"), false},
|
true,
|
||||||
{"no update on unmapped known type", false, version("0.0.0"), []GitCommitLog{commitlog("none", map[string]string{}, "a")}, version("0.0.0"), false},
|
version("0.0.0"),
|
||||||
{"update patch on unknown type", false, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{}, "a")}, version("0.0.1"), true},
|
[]GitCommitLog{},
|
||||||
{"patch update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a")}, version("0.0.1"), true},
|
version("0.0.0"),
|
||||||
{"patch update without version", false, nil, []GitCommitLog{commitlog("patch", map[string]string{}, "a")}, nil, true},
|
false,
|
||||||
{"minor update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("minor", map[string]string{}, "a")}, version("0.1.0"), true},
|
},
|
||||||
{"major update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("major", map[string]string{}, "a")}, version("1.0.0"), true},
|
{
|
||||||
{"breaking change update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("patch", map[string]string{"breaking-change": "break"}, "a")}, version("1.0.0"), true},
|
"no update without version",
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
[]GitCommitLog{},
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no update on unknown type",
|
||||||
|
true,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{commitlog("a", map[string]string{}, "a")},
|
||||||
|
version("0.0.0"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no update on unmapped known type",
|
||||||
|
false,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{commitlog("none", map[string]string{}, "a")},
|
||||||
|
version("0.0.0"),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update patch on unknown type",
|
||||||
|
false,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{commitlog("a", map[string]string{}, "a")},
|
||||||
|
version("0.0.1"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patch update",
|
||||||
|
false, version("0.0.0"),
|
||||||
|
[]GitCommitLog{commitlog("patch", map[string]string{}, "a")},
|
||||||
|
version("0.0.1"), true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patch update without version",
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
[]GitCommitLog{commitlog("patch", map[string]string{}, "a")},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minor update",
|
||||||
|
false,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{
|
||||||
|
commitlog("patch", map[string]string{}, "a"),
|
||||||
|
commitlog("minor", map[string]string{}, "a"),
|
||||||
|
},
|
||||||
|
version("0.1.0"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"major update",
|
||||||
|
false,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{
|
||||||
|
commitlog("patch", map[string]string{}, "a"),
|
||||||
|
commitlog("major", map[string]string{}, "a"),
|
||||||
|
},
|
||||||
|
version("1.0.0"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"breaking change update",
|
||||||
|
false,
|
||||||
|
version("0.0.0"),
|
||||||
|
[]GitCommitLog{
|
||||||
|
commitlog("patch", map[string]string{}, "a"),
|
||||||
|
commitlog("patch", map[string]string{"breaking-change": "break"}, "a"),
|
||||||
|
},
|
||||||
|
version("1.0.0"),
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
p := NewSemVerCommitsProcessor(VersioningConfig{UpdateMajor: []string{"major"}, UpdateMinor: []string{"minor"}, UpdatePatch: []string{"patch"}, IgnoreUnknown: tt.ignoreUnknown}, CommitMessageConfig{Types: []string{"major", "minor", "patch", "none"}})
|
p := NewSemVerCommitsProcessor(
|
||||||
|
VersioningConfig{
|
||||||
|
UpdateMajor: []string{"major"},
|
||||||
|
UpdateMinor: []string{"minor"},
|
||||||
|
UpdatePatch: []string{"patch"},
|
||||||
|
IgnoreUnknown: tt.ignoreUnknown,
|
||||||
|
},
|
||||||
|
CommitMessageConfig{Types: []string{"major", "minor", "patch", "none"}})
|
||||||
got, gotUpdated := p.NextVersion(tt.version, tt.commits)
|
got, gotUpdated := p.NextVersion(tt.version, tt.commits)
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("SemVerCommitsProcessorImpl.NextVersion() Version = %v, want %v", got, tt.want)
|
t.Errorf("SemVerCommitsProcessorImpl.NextVersion() Version = %v, want %v", got, tt.want)
|
||||||
@ -57,6 +141,7 @@ func TestToVersion(t *testing.T) {
|
|||||||
got, err := ToVersion(tt.input)
|
got, err := ToVersion(tt.input)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("ToVersion() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("ToVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
Loading…
Reference in New Issue
Block a user