diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 20e1ef1..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: "gomod"
- directory: "/"
- schedule:
- interval: "daily"
\ No newline at end of file
diff --git a/.github/settings.yml b/.github/settings.yml
new file mode 100644
index 0000000..1dae6d2
--- /dev/null
+++ b/.github/settings.yml
@@ -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
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 0a8f68d..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -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_*
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
deleted file mode 100644
index 9adf1c3..0000000
--- a/.github/workflows/pull-request.yml
+++ /dev/null
@@ -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
diff --git a/.gitignore b/.gitignore
index 9e67889..6e36e82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,6 @@
-# Binaries for programs and plugins
-bin/
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
+/dist
+/release
+/git-sv*
-# Test binary, build with `go test -c`
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
-
-*.sample
-todo
-
-# Additional generated artifacts
-artifacts/
-
-# Mac metadata
-
-.DS_Store
+coverage.out
+CHANGELOG.md
diff --git a/.gitsv/config.yml b/.gitsv/config.yml
new file mode 100644
index 0000000..7b9cae5
--- /dev/null
+++ b/.gitsv/config.yml
@@ -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]+"
diff --git a/.golangci.yml b/.golangci.yml
index 4db1c3e..7d44ee6 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,35 +1,109 @@
linters:
+ enable-all: false
+ disable-all: true
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
+ - tenv
+ - testableexamples
+ - thelper
+ - tparallel
+ - unconvert
+ - unparam
+ - usestdlibvars
+ # - wastedassign
+ - whitespace
+ - wsl
+ fast: false
run:
- skip-dirs:
- - build
- - artifacts
+ timeout: 3m
linters-settings:
tagliatelle:
case:
- use-field-name: true
rules:
- json: camel
yaml: kebab
- xml: camel
- bson: camel
- avro: snake
- mapstructure: kebab
+ gofumpt:
+ extra-rules: true
+ lang-version: "1.21"
issues:
exclude-rules:
- - path: _test\.go
+ - path: (.+)_test.go
linters:
- - gocyclo
- - errcheck
- - dupl
- gosec
- gochecknoglobals
- - testpackage
- - path: cmd/git-sv/main.go
- linters:
- - gochecknoglobals
- - funlen
+ - prealloc
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 0000000..b59a114
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,6 @@
+---
+default: True
+MD013: False
+MD041: False
+MD004:
+ style: dash
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..135c35d
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+*.tpl.md
+LICENSE
diff --git a/.sv4git.yml b/.sv4git.yml
deleted file mode 100644
index 97d57b4..0000000
--- a/.sv4git.yml
+++ /dev/null
@@ -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]+"
diff --git a/.woodpecker/build-container.yml b/.woodpecker/build-container.yml
new file mode 100644
index 0000000..7303444
--- /dev/null
+++ b/.woodpecker/build-container.yml
@@ -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
diff --git a/.woodpecker/build-package.yml b/.woodpecker/build-package.yml
new file mode 100644
index 0000000..1ca2b71
--- /dev/null
+++ b/.woodpecker/build-package.yml
@@ -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
diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml
new file mode 100644
index 0000000..8d8b85c
--- /dev/null
+++ b/.woodpecker/docs.yml
@@ -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
diff --git a/.woodpecker/notify.yml b/.woodpecker/notify.yml
new file mode 100644
index 0000000..a851904
--- /dev/null
+++ b/.woodpecker/notify.yml
@@ -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
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
new file mode 100644
index 0000000..abd3763
--- /dev/null
+++ b/.woodpecker/test.yml
@@ -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
diff --git a/Containerfile.multiarch b/Containerfile.multiarch
new file mode 100644
index 0000000..d8b3b42
--- /dev/null
+++ b/Containerfile.multiarch
@@ -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 "
+LABEL org.opencontainers.image.authors="Robert Kaussow "
+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"]
diff --git a/LICENSE b/LICENSE
index 64066c6..3812eb4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
MIT License
-Copyright (c) 2019 Beatriz Vieira
+Copyright (c) 2022 Robert Kaussow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice (including the next
+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
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
index dd6e9dd..ffa2f96 100644
--- a/Makefile
+++ b/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
-NO_COLOR=\033[0m
-ERROR_COLOR=\033[31;01m
-WARN_COLOR=\033[33;01m
+EXECUTABLE := git-sv
-PKGS = $(shell go list ./...)
-BIN = git-sv
+DIST := dist
+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")
-VERSION ?= dev-$(BUILD_TIME)
+GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION)
+GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION)
+XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
-BUILDOS ?= linux
-BUILDARCH ?= amd64
-BUILDENVS ?= CGO_ENABLED=0 GOOS=$(BUILDOS) GOARCH=$(BUILDARCH)
-BUILDFLAGS ?= -a -installsuffix cgo --ldflags '-X main.Version=$(VERSION) -extldflags "-lm -lstdc++ -static"'
+GENERATE ?=
+XGO_VERSION := go-1.21.x
+XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
-COMPRESS_TYPE ?= targz
+TARGETOS ?= linux
+TARGETARCH ?= amd64
+ifneq ("$(TARGETVARIANT)","")
+GOARM ?= $(subst v,,$(TARGETVARIANT))
+endif
+TAGS ?= netgo,osusergo
-usage: Makefile
- @echo $(ECHOFLAGS) "to use make call:"
- @echo $(ECHOFLAGS) " make "
- @echo $(ECHOFLAGS) ""
- @echo $(ECHOFLAGS) "list of available actions:"
- @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
-
-## 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)
+ifndef VERSION
+ ifneq ($(CI_COMMIT_TAG),)
+ VERSION ?= $(subst v,,$(CI_COMMIT_TAG))
+ else
+ VERSION ?= $(shell git rev-parse --short HEAD)
+ endif
endif
-## release-all: prepare linux, darwin and windows binary for release (requires sv4git)
-release-all:
- @rm -rf bin
+ifndef DATE
+ DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%z")
+endif
- VERSION=$(shell git sv nv) BUILDOS=linux BUILDARCH=amd64 make release
- 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
+LDFLAGS += -s -w -X "main.BuildVersion=$(VERSION)" -X "main.BuildDate=$(DATE)"
- VERSION=$(shell git sv nv) BUILDOS=linux BUILDARCH=arm64 make release
- VERSION=$(shell git sv nv) BUILDOS=darwin BUILDARCH=arm64 make release
- VERSION=$(shell git sv nv) COMPRESS_TYPE=zip BUILDOS=windows BUILDARCH=arm64 make release
+.PHONY: all
+all: clean build
+
+.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)
diff --git a/README.md b/README.md
index e10a01e..b5e4563 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,6 @@
-
-
sv4git
- A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs!
-
-
-
-
-
-
-
-
-
-
-
-
+# git-sv
+
+A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs.
## Getting Started
@@ -23,23 +11,23 @@
### Installing
- 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:
```bash
# 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.
-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
#### 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:
@@ -59,22 +47,22 @@ git sv cfg default
###### 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
-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.:
```bash
-.sv4git
+.gitsv
└── config.yml
```
###### 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
@@ -82,73 +70,85 @@ Create a `.sv4git.yml` file on the root of your repository, eg.: [.sv4git.yml](.
version: "1.1" #config version
versioning: # versioning bump
- update-major: [] # Commit types used to bump major.
- update-minor: [feat] # Commit types used to bump minor.
- 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);
- # if ignore-unknown=false bump patch, if ignore-unknown=true do not bump version
- ignore-unknown: false
+ update-major: [] # Commit types used to bump major.
+ update-minor: [feat] # Commit types used to bump minor.
+ 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);
+ # if ignore-unknown=false bump patch, if ignore-unknown=true do not bump version
+ ignore-unknown: false
tag:
- pattern: '%d.%d.%d' # Pattern used to create git tag.
- filter: '' # Enables you to filter for considerable tags using git pattern syntax
+ pattern: "%d.%d.%d" # Pattern used to create git tag.
+ filter: "" # Enables you to filter for considerable tags using git pattern syntax
release-notes:
- # Deprecated!!! please use 'sections' instead!
- # 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
- # respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
- headers:
- breaking-change: Breaking Changes
- feat: Features
- fix: Bug Fixes
-
- sections: # Array with each section of release note. Check template section for more information.
- - name: Features # Name used on section.
- 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.
- - name: Bug Fixes
- section-type: commits
- commit-types: [fix]
- - name: Breaking Changes
- section-type: breaking-changes
+ # Deprecated!!! please use 'sections' instead!
+ # 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
+ # respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
+ headers:
+ breaking-change: Breaking Changes
+ feat: Features
+ fix: Bug Fixes
+
+ sections: # Array with each section of release note. Check template section for more information.
+ - name: Features # Name used on section.
+ 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.
+ - name: Bug Fixes
+ section-type: commits
+ commit-types: [fix]
+ - name: Breaking Changes
+ section-type: breaking-changes
branches: # Git branches config.
- 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.
- 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-detached: false # Set true if a detached branch should be ignored on commit message validation.
+ 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.
+ 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-detached: false # Set true if a detached branch should be ignored on commit message validation.
commit-message:
- types: [build, ci, chore, docs, feat, fix, perf, refactor, revert, style, test] # Supported commit 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'.
- 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 : separator. If true, use # separator.
- add-value-prefix: '' # Add a prefix to issue value.
- issue:
- regex: '[A-Z]+-[0-9]+' # Regex for issue id.
+ types: [
+ build,
+ ci,
+ chore,
+ docs,
+ feat,
+ fix,
+ perf,
+ refactor,
+ revert,
+ style,
+ test,
+ ] # Supported commit 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'.
+ 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 : separator. If true, use # separator.
+ add-value-prefix: "" # Add a prefix to issue value.
+ issue:
+ regex: "[A-Z]+-[0-9]+" # Regex for issue id.
```
#### 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
-.sv4git
+.gitsv
└── templates
├── changelog-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
@@ -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.
-| section-type | ReleaseNoteSection |
-| -- | -- |
-| commits | ReleaseNoteCommitsSection |
+| section-type | ReleaseNoteSection |
+| ---------------- | -------------------------------- |
+| commits | ReleaseNoteCommitsSection |
| 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.
diff --git a/cmd/git-sv/config.go b/cmd/git-sv/config.go
index 755b7c7..ff5fd83 100644
--- a/cmd/git-sv/config.go
+++ b/cmd/git-sv/config.go
@@ -4,27 +4,27 @@ import (
"fmt"
"log"
"os"
- "os/exec"
"reflect"
- "strings"
- "github.com/bvieira/sv4git/v2/sv"
- "github.com/imdario/mergo"
+ "dario.cat/mergo"
"github.com/kelseyhightower/envconfig"
+ "github.com/thegeeklab/git-sv/v2/sv"
"gopkg.in/yaml.v3"
)
// EnvConfig env vars for cli configuration.
type EnvConfig struct {
- Home string `envconfig:"SV4GIT_HOME" default:""`
+ Home string `envconfig:"GITSV_HOME" default:""`
}
func loadEnvConfig() EnvConfig {
var c EnvConfig
+
err := envconfig.Process("", &c)
if err != nil {
log.Fatal("failed to load env config, error: ", err.Error())
}
+
return c
}
@@ -38,20 +38,6 @@ type Config struct {
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) {
content, rerr := os.ReadFile(filepath)
if rerr != nil {
@@ -59,9 +45,10 @@ func readConfig(filepath string) (Config, error) {
}
var cfg Config
+
cerr := yaml.Unmarshal(content, &cfg)
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
@@ -71,6 +58,7 @@ func defaultConfig() Config {
skipDetached := false
pattern := "%d.%d.%d"
filter := ""
+
return Config{
Version: "1.1",
Versioning: sv.VersioningConfig{
@@ -116,6 +104,7 @@ func merge(dst *Config, src Config) error {
dst.ReleaseNotes.Headers = src.ReleaseNotes.Headers
}
}
+
return err
}
@@ -127,6 +116,7 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
if dst.CanSet() && !src.IsNil() {
dst.Set(src)
}
+
return nil
}
}
@@ -136,9 +126,11 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
if dst.CanSet() && !src.IsNil() {
dst.Set(src)
}
+
return nil
}
}
+
return nil
}
@@ -146,6 +138,7 @@ func migrateConfig(cfg Config, filename string) Config {
if cfg.ReleaseNotes.Headers == nil {
return cfg
}
+
warnf("config 'release-notes.headers' on %s is deprecated, please use 'sections' instead!", filename)
return Config{
@@ -162,14 +155,29 @@ func migrateConfig(cfg Config, filename string) Config {
func migrateReleaseNotesConfig(headers map[string]string) []sv.ReleaseNotesSectionConfig {
order := []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}
+
var sections []sv.ReleaseNotesSectionConfig
+
for _, key := range order {
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 {
- sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeBreakingChanges})
+ sections = append(
+ sections,
+ sv.ReleaseNotesSectionConfig{
+ Name: name,
+ SectionType: sv.ReleaseNotesSectionTypeBreakingChanges,
+ })
}
+
return sections
}
diff --git a/cmd/git-sv/config_test.go b/cmd/git-sv/config_test.go
index 79d56a9..5b149fb 100644
--- a/cmd/git-sv/config_test.go
+++ b/cmd/git-sv/config_test.go
@@ -4,7 +4,7 @@ import (
"reflect"
"testing"
- "github.com/bvieira/sv4git/v2/sv"
+ "github.com/thegeeklab/git-sv/v2/sv"
)
func Test_merge(t *testing.T) {
@@ -20,24 +20,135 @@ func Test_merge(t *testing.T) {
want Config
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},
- {"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 pointer bool false", Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, false},
- {"overwrite pointer bool true", Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}}, 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},
+ {
+ "overwrite pointer bool false",
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
+ false,
+ },
+ {
+ "overwrite pointer bool true",
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolTrue}},
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
+ Config{Branches: sv.BranchesConfig{SkipDetached: &boolFalse}},
+ 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 {
t.Run(tt.name, func(t *testing.T) {
diff --git a/cmd/git-sv/handlers.go b/cmd/git-sv/handlers.go
index ec67bb1..2e31a47 100644
--- a/cmd/git-sv/handlers.go
+++ b/cmd/git-sv/handlers.go
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -10,19 +11,32 @@ import (
"time"
"github.com/Masterminds/semver/v3"
- "github.com/bvieira/sv4git/v2/sv"
+ "github.com/thegeeklab/git-sv/v2/sv"
"github.com/urfave/cli/v2"
"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 {
cfg := defaultConfig()
+
return func(c *cli.Context) error {
content, err := yaml.Marshal(&cfg)
if err != nil {
return err
}
+
fmt.Println(string(content))
+
return nil
}
}
@@ -33,7 +47,9 @@ func configShowHandler(cfg Config) func(c *cli.Context) error {
if err != nil {
return err
}
+
fmt.Println(string(content))
+
return nil
}
}
@@ -44,9 +60,11 @@ func currentVersionHandler(git sv.Git) func(c *cli.Context) error {
currentVer, err := sv.ToVersion(lastTag)
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())
+
return nil
}
}
@@ -57,30 +75,62 @@ func nextVersionHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) f
currentVer, err := sv.ToVersion(lastTag)
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, ""))
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)
+
fmt.Printf("%d.%d.%d\n", nextVer.Major(), nextVer.Minor(), nextVer.Patch())
+
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 {
return func(c *cli.Context) error {
- var commits []sv.GitCommitLog
- var err error
+ var (
+ commits []sv.GitCommitLog
+ err error
+ )
+
tagFlag := c.String("t")
rangeFlag := c.String("r")
startFlag := c.String("s")
endFlag := c.String("e")
+
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 != "" {
@@ -92,8 +142,9 @@ func commitLogHandler(git sv.Git) func(c *cli.Context) error {
}
commits, err = git.Log(r)
}
+
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 {
@@ -101,8 +152,10 @@ func commitLogHandler(git sv.Git) func(c *cli.Context) error {
if err != nil {
return err
}
+
fmt.Println(string(content))
}
+
return nil
}
}
@@ -112,6 +165,7 @@ func getTagCommits(git sv.Git, tag string) ([]sv.GitCommitLog, error) {
if err != nil {
return nil, err
}
+
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):
return sv.NewLogRange(sv.HashRange, startFlag, endFlag), nil
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 {
var date time.Time
rangeFlag := c.String("r")
+
lr, err := logRange(git, rangeFlag, c.String("s"), c.String("e"))
if err != nil {
return err
@@ -140,7 +224,7 @@ func commitNotesHandler(git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputF
commits, err := git.Log(lr)
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 {
@@ -149,20 +233,39 @@ func commitNotesHandler(git sv.Git, rnProcessor sv.ReleaseNoteProcessor, outputF
output, err := outputFormatter.FormatReleaseNote(rnProcessor.Create(nil, "", date, commits))
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)
+
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 {
- var commits []sv.GitCommitLog
- var rnVersion *semver.Version
- var tag string
- var date time.Time
- var err error
+ var (
+ commits []sv.GitCommitLog
+ rnVersion *semver.Version
+ tag string
+ date time.Time
+ err error
+ )
if tag = c.String("t"); 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)
+
output, err := outputFormatter.FormatReleaseNote(releasenote)
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)
+
return nil
}
}
@@ -190,12 +296,12 @@ func getTagVersionInfo(git sv.Git, tag string) (*semver.Version, time.Time, []sv
previousTag, currentTag, err := getTags(git, tag)
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))
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
@@ -209,13 +315,14 @@ func getTags(git sv.Git, tag string) (string, sv.GitTag, error) {
index := find(tag, tags)
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 := ""
if index > 0 {
previousTag = tags[index-1].Name
}
+
return previousTag, tags[index], nil
}
@@ -225,15 +332,18 @@ func find(tag string, tags []sv.GitTag) int {
return i
}
}
+
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()
commits, err := git.Log(sv.NewLogRange(sv.TagRange, lastTag, ""))
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)
@@ -248,20 +358,23 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c
currentVer, err := sv.ToVersion(lastTag)
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, ""))
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)
tagname, err := git.Tag(*nextVer)
+
fmt.Println(tagname)
+
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
}
}
@@ -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) {
if input == "" {
t, err := promptType(cfg.CommitMessage.Types)
+
return t.Type, err
}
+
return input, p.ValidateType(input)
}
@@ -278,6 +393,7 @@ func getCommitScope(cfg Config, p sv.MessageProcessor, input string, noScope boo
if input == "" && !noScope {
return promptScope(cfg.CommitMessage.Scope.Values)
}
+
return input, p.ValidateScope(input)
}
@@ -285,6 +401,7 @@ func getCommitDescription(p sv.MessageProcessor, input string) (string, error) {
if input == "" {
return promptSubject()
}
+
return input, p.ValidateDescription(input)
}
@@ -294,17 +411,21 @@ func getCommitBody(noBody bool) (string, error) {
}
var fullBody strings.Builder
+
for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() {
if err != nil {
return "", err
}
+
if fullBody.Len() > 0 {
fullBody.WriteString("\n")
}
+
if body != "" {
fullBody.WriteString(body)
}
}
+
return fullBody.String(), nil
}
@@ -338,6 +459,7 @@ func getCommitBreakingChange(noBreaking bool, input string) (string, error) {
if err != nil {
return "", err
}
+
if !hasBreakingChanges {
return "", nil
}
@@ -345,6 +467,51 @@ func getCommitBreakingChange(noBreaking bool, input string) (string, error) {
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 {
return func(c *cli.Context) error {
noBreaking := c.Bool("no-breaking")
@@ -386,22 +553,54 @@ func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor)
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)
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
}
}
-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 {
tags, err := git.Tags()
if err != nil {
return err
}
+
sort.Slice(tags, func(i, j int) bool {
return tags[i].Date.After(tags[j].Date)
})
@@ -418,10 +617,12 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
if uerr != nil {
return uerr
}
+
if updated {
releaseNotes = append(releaseNotes, rnProcessor.Create(rnVersion, "", date, commits))
}
}
+
for i, tag := range tags {
if !all && i >= size {
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))
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)
@@ -447,14 +648,35 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
output, err := formatter.FormatChangelog(releaseNotes)
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)
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 {
return func(c *cli.Context) error {
branch := git.Branch()
@@ -462,11 +684,13 @@ func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcess
if messageProcessor.SkipBranch(branch, derr == nil && detached) {
warnf("commit message validation skipped, branch in ignore list or detached...")
+
return nil
}
if source := c.String("source"); source == "merge" {
warnf("commit message validation skipped, ignoring source: %s...", source)
+
return nil
}
@@ -474,24 +698,26 @@ func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcess
commitMessage, err := readFile(filepath)
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 {
- return fmt.Errorf("invalid commit message, error: %s", err.Error())
+ return fmt.Errorf("%w: %s", errReadCommitMessage, err.Error())
}
msg, err := messageProcessor.Enhance(branch, commitMessage)
if err != nil {
warnf("could not enhance commit message, %s", err.Error())
+
return nil
}
+
if msg == "" {
return 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
@@ -503,17 +729,19 @@ func readFile(filepath string) (string, error) {
if err != nil {
return "", err
}
+
return string(f), nil
}
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 {
return err
}
defer f.Close()
_, err = f.WriteString(message)
+
return err
}
@@ -521,5 +749,6 @@ func str(value, defaultValue string) string {
if value != "" {
return value
}
+
return defaultValue
}
diff --git a/cmd/git-sv/main.go b/cmd/git-sv/main.go
index b2a4a02..00af40d 100644
--- a/cmd/git-sv/main.go
+++ b/cmd/git-sv/main.go
@@ -2,161 +2,146 @@ package main
import (
"embed"
+ "fmt"
"io/fs"
"log"
"os"
"path/filepath"
- "github.com/bvieira/sv4git/v2/sv"
+ "github.com/thegeeklab/git-sv/v2/sv"
"github.com/urfave/cli/v2"
)
-// Version for git-sv.
-var Version = "source"
+//nolint:gochecknoglobals
+var (
+ BuildVersion = "devel"
+ BuildDate = "00000000"
+)
const (
- configFilename = "config.yml"
- repoConfigFilename = ".sv4git.yml"
- configDir = ".sv4git"
+ configFilename = "config.yml"
+ configDir = ".gitsv"
)
-var (
- //go:embed resources/templates/*.tpl
- defaultTemplatesFS embed.FS
-)
+//go:embed resources/templates/*.tpl
+var defaultTemplatesFS embed.FS
func templateFS(filepath string) fs.FS {
if _, err := os.Stat(filepath); err != nil {
defaultTemplatesFS, _ := fs.Sub(defaultTemplatesFS, "resources/templates")
+
return defaultTemplatesFS
}
+
return os.DirFS(filepath)
}
func main() {
log.SetFlags(0)
- repoPath, rerr := getRepoPath()
- if rerr != nil {
- log.Fatal("failed to discovery repository top level, error: ", rerr)
+ wd, err := os.Getwd()
+ if err != nil {
+ log.Fatal("error while retrieving working directory: %w", err)
}
- cfg := loadCfg(repoPath)
+ cfg := loadCfg(wd)
messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches)
git := sv.NewGit(messageProcessor, cfg.Tag)
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning, cfg.CommitMessage)
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()
- app.Name = "sv"
- app.Version = Version
- app.Usage = "semantic version for git"
- app.Commands = []*cli.Command{
- {
- Name: "config",
- Aliases: []string{"cfg"},
- Usage: "cli configuration",
- Subcommands: []*cli.Command{
- {
- Name: "default",
- Usage: "show default config",
- Action: configDefaultHandler(),
- },
- {
- Name: "show",
- Usage: "show current config",
- Action: configShowHandler(cfg),
+ cli.VersionPrinter = func(c *cli.Context) {
+ fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate)
+ }
+
+ app := &cli.App{
+ Name: "git-sv",
+ Usage: "Semantic version for git.",
+ Version: BuildVersion,
+ Commands: []*cli.Command{
+ {
+ Name: "config",
+ Aliases: []string{"cfg"},
+ Usage: "cli configuration",
+ Subcommands: []*cli.Command{
+ {
+ Name: "default",
+ Usage: "show default config",
+ Action: configDefaultHandler(),
+ },
+ {
+ Name: "show",
+ Usage: "show current config",
+ Action: configShowHandler(cfg),
+ },
},
},
- },
- {
- Name: "current-version",
- Aliases: []string{"cv"},
- Usage: "get last released version from 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: "current-version",
+ Aliases: []string{"cv"},
+ Usage: "get last released version from git",
+ Action: currentVersionHandler(git),
},
- },
- {
- Name: "commit-notes",
- Aliases: []string{"cn"},
- Usage: "generate a commit notes according to range",
- 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: "next-version",
+ Aliases: []string{"nv"},
+ Usage: "generate the next version based on git commit messages",
+ Action: nextVersionHandler(git, semverProcessor),
},
- },
- {
- Name: "release-notes",
- Aliases: []string{"rn"},
- Usage: "generate release notes",
- Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
- Flags: []cli.Flag{&cli.StringFlag{Name: "t", Aliases: []string{"tag"}, Usage: "get release note from tag"}},
- },
- {
- 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-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: commitLogFlags(),
},
- },
- {
- 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: []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: "commit-notes",
+ Aliases: []string{"cn"},
+ Usage: "generate a commit notes according to range",
+ 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: commitNotesFlags(),
},
- },
- {
- 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: []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"},
+ {
+ Name: "release-notes",
+ Aliases: []string{"rn"},
+ Usage: "generate release notes",
+ Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor, outputFormatter),
+ Flags: releaseNotesFlags(),
+ },
+ {
+ Name: "changelog",
+ 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()
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 merr := merge(&cfg, migrateConfig(repoCfg, repoCfgFilepath)); merr != nil {
log.Fatal("failed to merge repo config, error: ", merr)
}
+
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
cfg.ReleaseNotes.Headers = repoCfg.ReleaseNotes.Headers
}
diff --git a/cmd/git-sv/prompt.go b/cmd/git-sv/prompt.go
index d967436..c8744c3 100644
--- a/cmd/git-sv/prompt.go
+++ b/cmd/git-sv/prompt.go
@@ -1,6 +1,7 @@
package main
import (
+ "errors"
"fmt"
"reflect"
"regexp"
@@ -14,22 +15,62 @@ type commitType struct {
Example string
}
+var errInvalidValue = errors.New("invalid value")
+
func promptType(types []string) (commitType, error) {
defaultTypes := map[string]commitType{
- "build": {Type: "build", Description: "changes that affect the build system or external dependencies", Example: "gradle, maven, go mod, npm"},
- "ci": {Type: "ci", Description: "changes to our CI configuration files and scripts", Example: "Circle, BrowserStack, SauceLabs"},
- "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"},
+ "build": {
+ Type: "build",
+ Description: "changes that affect the build system or external dependencies",
+ Example: "gradle, maven, go mod, npm",
+ },
+ "ci": {
+ Type: "ci",
+ Description: "changes to our CI configuration files and scripts",
+ Example: "Circle, BrowserStack, SauceLabs",
+ },
+ "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
+
for _, t := range types {
if v, exists := defaultTypes[t]; exists {
items = append(items, v)
@@ -53,6 +94,7 @@ func promptType(types []string) (commitType, error) {
if err != nil {
return commitType{}, err
}
+
return items[i], nil
}
@@ -62,8 +104,10 @@ func promptScope(values []string) (string, error) {
if err != nil {
return "", err
}
+
return values[selected], nil
}
+
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) {
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{
@@ -96,6 +140,7 @@ func promptSelect(label string, items interface{}, template *promptui.SelectTemp
}
index, _, err := prompt.Run()
+
return index, err
}
@@ -103,8 +148,9 @@ func promptText(label, regex, defaultValue string) (string, error) {
validate := func(input string) error {
regex := regexp.MustCompile(regex)
if !regex.MatchString(input) {
- return fmt.Errorf("invalid value, expected: %s", regex)
+ return fmt.Errorf("%w, expected: %s", errInvalidValue, regex)
}
+
return nil
}
@@ -122,5 +168,6 @@ func promptConfirm(label string) (bool, error) {
if err != nil {
return false, err
}
+
return r == "y", nil
}
diff --git a/cmd/git-sv/resources_test.go b/cmd/git-sv/resources_test.go
index 4d22540..4bbd0d4 100644
--- a/cmd/git-sv/resources_test.go
+++ b/cmd/git-sv/resources_test.go
@@ -14,9 +14,11 @@ func Test_checkTemplatesFiles(t *testing.T) {
got, err := defaultTemplatesFS.ReadFile(tt)
if err != nil {
t.Errorf("missing template error = %v", err)
+
return
}
- if len(got) <= 0 {
+
+ if len(got) == 0 {
t.Errorf("empty template")
}
})
diff --git a/go.mod b/go.mod
index b47eebb..75d6aa8 100644
--- a/go.mod
+++ b/go.mod
@@ -1,22 +1,23 @@
-module github.com/bvieira/sv4git/v2
+module github.com/thegeeklab/git-sv/v2
go 1.19
require (
- github.com/Masterminds/semver/v3 v3.2.0
- github.com/imdario/mergo v0.3.13
+ dario.cat/mergo v1.0.0
+ github.com/Masterminds/semver/v3 v3.2.1
github.com/kelseyhightower/envconfig v1.4.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
)
require (
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/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // 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
)
diff --git a/go.sum b/go.sum
index 4838524..f5c6dd3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
-github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
-github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
+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.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
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 v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
+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/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/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
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/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
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.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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
-github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
+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/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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..45d1c03
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": ["github>thegeeklab/renovate-presets:golang"]
+}
diff --git a/sv/config.go b/sv/config.go
index 5d76634..c504355 100644
--- a/sv/config.go
+++ b/sv/config.go
@@ -16,6 +16,7 @@ func (c CommitMessageConfig) IssueFooterConfig() CommitMessageFooterConfig {
if v, exists := c.Footer[issueMetadataKey]; exists {
return v
}
+
return CommitMessageFooterConfig{}
}
@@ -80,6 +81,7 @@ func (cfg ReleaseNotesConfig) sectionConfig(sectionType string) *ReleaseNotesSec
return §ionCfg
}
}
+
return nil
}
diff --git a/sv/errors.go b/sv/errors.go
new file mode 100644
index 0000000..ace87e8
--- /dev/null
+++ b/sv/errors.go
@@ -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")
+)
diff --git a/sv/formatter.go b/sv/formatter.go
index 3ec1a29..2f97e2f 100644
--- a/sv/formatter.go
+++ b/sv/formatter.go
@@ -39,6 +39,7 @@ func NewOutputFormatter(templatesFS fs.FS) *OutputFormatterImpl {
"getenv": os.Getenv,
}
tpls := template.Must(template.New("templates").Funcs(templateFNs).ParseFS(templatesFS, "*"))
+
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 {
return "", err
}
+
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 {
return "", err
}
+
return b.String(), nil
}
@@ -70,6 +73,7 @@ func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables
if releasenote.Version != nil {
release = "v" + releasenote.Version.String()
}
+
return releaseNoteTemplateVariables{
Release: release,
Tag: releasenote.Tag,
@@ -83,10 +87,13 @@ func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables
func toSortedArray(input map[string]struct{}) []string {
result := make([]string, len(input))
i := 0
+
for k := range input {
result[i] = k
i++
}
+
sort.Strings(result)
+
return result
}
diff --git a/sv/formatter_functions.go b/sv/formatter_functions.go
index 607b988..284090e 100644
--- a/sv/formatter_functions.go
+++ b/sv/formatter_functions.go
@@ -6,14 +6,16 @@ func timeFormat(t time.Time, format string) string {
if t.IsZero() {
return ""
}
+
return t.Format(format)
}
-func getSection(sections []ReleaseNoteSection, name string) ReleaseNoteSection {
+func getSection(sections []ReleaseNoteSection, name string) ReleaseNoteSection { //nolint:ireturn
for _, section := range sections {
if section.SectionName() == name {
return section
}
}
+
return nil
}
diff --git a/sv/formatter_functions_test.go b/sv/formatter_functions_test.go
index 83a2d92..a9d63cc 100644
--- a/sv/formatter_functions_test.go
+++ b/sv/formatter_functions_test.go
@@ -32,8 +32,20 @@ func Test_getSection(t *testing.T) {
sectionName string
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 {
t.Run(tt.name, func(t *testing.T) {
diff --git a/sv/formatter_test.go b/sv/formatter_test.go
index ea29235..e92cf18 100644
--- a/sv/formatter_test.go
+++ b/sv/formatter_test.go
@@ -73,6 +73,7 @@ func TestOutputFormatterImpl_FormatReleaseNote(t *testing.T) {
func emptyReleaseNote(tag string, date time.Time) ReleaseNote {
v, _ := semver.NewVersion(tag)
+
return ReleaseNote{
Version: v,
Tag: tag,
@@ -83,11 +84,24 @@ func emptyReleaseNote(tag string, date time.Time) ReleaseNote {
func fullReleaseNote(tag string, date time.Time) ReleaseNote {
v, _ := semver.NewVersion(tag)
sections := []ReleaseNoteSection{
- newReleaseNoteCommitsSection("Features", []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")}),
+ newReleaseNoteCommitsSection(
+ "Features",
+ []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"}},
}
+
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")},
{"releasenotes-md.tpl", releaseNotesVariables("v1.0.0")},
}
+
for _, tt := range tests {
t.Run(tt.template, func(t *testing.T) {
var b bytes.Buffer
err := tpls.ExecuteTemplate(&b, tt.template, tt.variables)
if err != nil {
t.Errorf("invalid template err = %v", err)
+
return
}
- if len(b.Bytes()) <= 0 {
+
+ if len(b.Bytes()) == 0 {
t.Errorf("empty template")
}
})
@@ -118,11 +135,20 @@ func Test_checkTemplatesExecution(t *testing.T) {
func releaseNotesVariables(release string) releaseNoteTemplateVariables {
return releaseNoteTemplateVariables{
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{
- newReleaseNoteCommitsSection("Features", []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")}),
+ newReleaseNoteCommitsSection("Features",
+ []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"}},
},
}
@@ -130,9 +156,10 @@ func releaseNotesVariables(release string) releaseNoteTemplateVariables {
func changelogVariables(releases ...string) []releaseNoteTemplateVariables {
var variables []releaseNoteTemplateVariables
+
for _, r := range releases {
variables = append(variables, releaseNotesVariables(r))
}
- return variables
+ return variables
}
diff --git a/sv/git.go b/sv/git.go
index 5926004..14b1d5b 100644
--- a/sv/git.go
+++ b/sv/git.go
@@ -3,7 +3,6 @@ package sv
import (
"bufio"
"bytes"
- "errors"
"fmt"
"os"
"os/exec"
@@ -83,17 +82,35 @@ func NewGit(messageProcessor MessageProcessor, cfg TagConfig) *GitImpl {
// LastTag get last tag, if no tag found, return empty.
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()
if err != nil {
return ""
}
+
return strings.TrimSpace(strings.Trim(string(out), "\n"))
}
// Log return git log.
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}
if lr.start != "" || lr.end != "" {
@@ -110,14 +127,17 @@ func (g GitImpl) Log(lr LogRange) ([]GitCommitLog, error) {
}
cmd := exec.Command("git", params...)
+
out, err := cmd.CombinedOutput()
if err != nil {
return nil, combinedOutputErr(err, out)
}
+
logs, parseErr := parseLogOutput(g.messageProcessor, string(out))
if parseErr != nil {
return nil, parseErr
}
+
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.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+
return cmd.Run()
}
@@ -143,45 +164,66 @@ func (g GitImpl) Tag(version semver.Version) (string, error) {
if out, err := pushCommand.CombinedOutput(); err != nil {
return tag, combinedOutputErr(err, out)
}
+
return tag, nil
}
// Tags list repository tags.
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()
if err != nil {
return nil, combinedOutputErr(err, out)
}
+
return parseTagsOutput(string(out))
}
// Branch get git branch.
func (GitImpl) Branch() string {
cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
+
out, err := cmd.CombinedOutput()
if err != nil {
return ""
}
+
return strings.TrimSpace(strings.Trim(string(out), "\n"))
}
// IsDetached check if is detached.
func (GitImpl) IsDetached() (bool, error) {
cmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
+
out, err := cmd.CombinedOutput()
- if output := string(out); err != nil { //-q: do not issue an error message if the 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 is not a symbolic ref, but a detached HEAD;
+ // instead exit with non-zero status silently.
+ if output := string(out); err != nil {
if output == "" {
return true, nil
}
- return false, errors.New(output)
+
+ return false, fmt.Errorf("%w: %s", errUnknownGitError, output)
}
+
return false, nil
}
func parseTagsOutput(input string) ([]GitTag, error) {
scanner := bufio.NewScanner(strings.NewReader(input))
+
var result []GitTag
+
for scanner.Scan() {
if line := strings.TrimSpace(scanner.Text()); line != "" {
values := strings.Split(line, "#")
@@ -189,31 +231,35 @@ func parseTagsOutput(input string) ([]GitTag, error) {
result = append(result, GitTag{Name: values[1], Date: date})
}
}
+
return result, nil
}
func parseLogOutput(messageProcessor MessageProcessor, log string) ([]GitCommitLog, error) {
scanner := bufio.NewScanner(strings.NewReader(log))
scanner.Split(splitAt([]byte(endLine)))
+
var logs []GitCommitLog
+
for scanner.Scan() {
if text := strings.TrimSpace(strings.Trim(scanner.Text(), "\"")); text != "" {
log, err := parseCommitLog(messageProcessor, text)
if err != nil {
return nil, err
}
+
logs = append(logs, log)
}
}
+
return logs, nil
}
func parseCommitLog(messageProcessor MessageProcessor, commit string) (GitCommitLog, error) {
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
-
timestamp, _ := strconv.Atoi(content[1])
- message, err := messageProcessor.Parse(content[4], content[5])
+ message, err := messageProcessor.Parse(content[4], content[5])
if err != nil {
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) {
- return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
- dataLen := len(data)
-
- if atEOF && dataLen == 0 {
+ return func(data []byte, atEOF bool) (advance int, token []byte, err error) { //nolint:nonamedreturns
+ if atEOF && len(data) == 0 {
return 0, nil, nil
}
@@ -240,7 +284,7 @@ func splitAt(b []byte) func(data []byte, atEOF bool) (advance int, token []byte,
}
if atEOF {
- return dataLen, data, nil
+ return len(data), data, nil
}
return 0, nil, nil
@@ -264,10 +308,12 @@ func str(value, defaultValue string) string {
if value != "" {
return value
}
+
return defaultValue
}
func combinedOutputErr(err error, out []byte) error {
msg := strings.Split(string(out), "\n")
- return fmt.Errorf("%v - %s", err, msg[0])
+
+ return fmt.Errorf("%w - %s", err, msg[0])
}
diff --git a/sv/git_test.go b/sv/git_test.go
index 8771d57..94680f9 100644
--- a/sv/git_test.go
+++ b/sv/git_test.go
@@ -13,16 +13,28 @@ func Test_parseTagsOutput(t *testing.T) {
want []GitTag
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 {
t.Run(tt.name, func(t *testing.T) {
got, err := parseTagsOutput(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("parseTagsOutput() error = %v, wantErr %v", err, tt.wantErr)
+
return
}
+
if !reflect.DeepEqual(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 {
panic(err)
}
+
return t
}
diff --git a/sv/helpers_test.go b/sv/helpers_test.go
index cc7b66e..ab8baef 100644
--- a/sv/helpers_test.go
+++ b/sv/helpers_test.go
@@ -8,6 +8,7 @@ import (
func version(v string) *semver.Version {
r, _ := semver.NewVersion(v)
+
return r
}
@@ -16,6 +17,7 @@ func commitlog(ctype string, metadata map[string]string, author string) GitCommi
if _, found := metadata[breakingChangeMetadataKey]; found {
breaking = true
}
+
return GitCommitLog{
Message: CommitMessage{
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{
Version: version,
Tag: tag,
diff --git a/sv/message.go b/sv/message.go
index 3344df8..c172540 100644
--- a/sv/message.go
+++ b/sv/message.go
@@ -30,10 +30,19 @@ func NewCommitMessage(ctype, scope, description, body, issue, breakingChanges st
if issue != "" {
metadata[issueMetadataKey] = issue
}
+
if 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.
@@ -53,7 +62,7 @@ type MessageProcessor interface {
ValidateType(ctype string) error
ValidateScope(scope string) error
ValidateDescription(description string) error
- Enhance(branch string, message string) (string, error)
+ Enhance(branch, message string) (string, error)
IssueID(branch string) (string, error)
Format(msg CommitMessage) (string, string, string)
Parse(subject, body string) (CommitMessage, error)
@@ -75,7 +84,8 @@ type MessageProcessorImpl struct {
// SkipBranch check if branch should be ignored.
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.
@@ -88,7 +98,7 @@ func (p MessageProcessorImpl) Validate(message string) error {
}
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 {
@@ -99,40 +109,48 @@ func (p MessageProcessorImpl) Validate(message string) error {
return err
}
- if err := p.ValidateDescription(msg.Description); err != nil {
- return err
- }
-
- return nil
+ return p.ValidateDescription(msg.Description)
}
// ValidateType check if commit type is valid.
func (p MessageProcessorImpl) ValidateType(ctype string) error {
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
}
// ValidateScope check if commit scope is valid.
func (p MessageProcessorImpl) ValidateScope(scope string) error {
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
}
// ValidateDescription check if commit description is valid.
func (p MessageProcessorImpl) ValidateDescription(description string) error {
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
}
// Enhance add metadata on commit message.
-func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) {
- if p.branchesCfg.DisableIssue || p.messageCfg.IssueFooterConfig().Key == "" || hasIssueID(message, p.messageCfg.IssueFooterConfig()) {
+func (p MessageProcessorImpl) Enhance(branch, message string) (string, error) {
+ if p.branchesCfg.DisableIssue || p.messageCfg.IssueFooterConfig().Key == "" ||
+ hasIssueID(message, p.messageCfg.IssueFooterConfig()) {
return "", nil // enhance disabled
}
@@ -140,8 +158,9 @@ func (p MessageProcessorImpl) Enhance(branch string, message string) (string, er
if err != nil {
return "", err
}
+
if issue == "" {
- return "", fmt.Errorf("could not find issue id using configured regex")
+ return "", errIssueIDNotFound
}
footer := formatIssueFooter(p.messageCfg.IssueFooterConfig(), issue)
@@ -156,9 +175,11 @@ func formatIssueFooter(cfg CommitMessageFooterConfig, issue string) string {
if !strings.HasPrefix(issue, cfg.AddValuePrefix) {
issue = cfg.AddValuePrefix + issue
}
+
if cfg.UseHash {
return fmt.Sprintf("%s #%s", cfg.Key, strings.TrimPrefix(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)
+
r, err := regexp.Compile(rstr)
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)
- if len(groups) != 4 {
+ if len(groups) != 4 { //nolint:gomnd
return "", nil
}
+
return groups[2], nil
}
// Format a commit message returning header, body and footer.
func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) {
var header strings.Builder
+
header.WriteString(msg.Type)
+
if msg.Scope != "" {
header.WriteString("(" + msg.Scope + ")")
}
+
header.WriteString(": ")
header.WriteString(msg.Description)
@@ -195,10 +221,12 @@ func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string)
if msg.BreakingMessage() != "" {
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeFooterKey, msg.BreakingMessage()))
}
+
if issue, exists := msg.Metadata[issueMetadataKey]; exists && p.messageCfg.IssueFooterConfig().Key != "" {
if footer.Len() > 0 {
footer.WriteString("\n")
}
+
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)
metadata := make(map[string]string)
+
for key, mdCfg := range p.messageCfg.Footer {
if mdCfg.Key != "" {
prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...)
for _, prefix := range prefixes {
if tagValue := extractFooterMetadata(prefix, commitBody, mdCfg.UseHash); tagValue != "" {
metadata[key] = tagValue
+
break
}
}
}
}
+
if tagValue := extractFooterMetadata(breakingChangeFooterKey, commitBody, false); tagValue != "" {
metadata[breakingChangeMetadataKey] = tagValue
hasBreakingChange = true
@@ -254,18 +285,23 @@ func (p MessageProcessorImpl) prepareHeader(header string) (string, error) {
regex, err := regexp.Compile(p.messageCfg.HeaderSelector)
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)
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)
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
@@ -273,10 +309,12 @@ func (p MessageProcessorImpl) prepareHeader(header string) (string, error) {
func parseSubjectMessage(message string) (string, string, string, bool) {
regex := regexp.MustCompile(`([a-z]+)(\((.*)\))?(!)?: (.*)`)
+
result := regex.FindStringSubmatch(message)
- if len(result) != 6 {
+ if len(result) != 6 { //nolint:gomnd
return "", "", message, false
}
+
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)
- if len(result) < 2 {
+ if len(result) < 2 { //nolint:gomnd
return ""
}
+
return result[1]
}
@@ -300,6 +339,7 @@ func hasFooter(message string) bool {
scanner := bufio.NewScanner(strings.NewReader(message))
lines := 0
+
for scanner.Scan() {
if lines > 0 && r.MatchString(scanner.Text()) {
return true
@@ -317,6 +357,7 @@ func hasIssueID(message string, issueConfig CommitMessageFooterConfig) bool {
} else {
r = regexp.MustCompile(fmt.Sprintf("(?m)^%s: .+$", issueConfig.Key))
}
+
return r.MatchString(message)
}
@@ -326,6 +367,7 @@ func contains(value string, content []string) bool {
return true
}
}
+
return false
}
@@ -336,12 +378,15 @@ func splitCommitMessageContent(content string) (string, string) {
subject := scanner.Text()
var body strings.Builder
+
first := true
for scanner.Scan() {
if !first {
body.WriteString("\n")
}
+
body.WriteString(scanner.Text())
+
first = false
}
diff --git a/sv/message_test.go b/sv/message_test.go
index 5fcd29f..49b01dc 100644
--- a/sv/message_test.go
+++ b/sv/message_test.go
@@ -146,23 +146,55 @@ func TestMessageProcessorImpl_Validate(t *testing.T) {
message string
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 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
-
- team: x`, false},
+ {
+ "multi line valid message",
+ ccfg,
+ `feat: add something
- {"multi line invalid message", ccfg, `feat add something
-
- team: x`, true},
+ team: x`, false,
+ },
- {"support ! for breaking change", ccfg, "feat!: add something", false},
- {"support ! with scope for breaking change", ccfg, "feat(scope)!: add something", false},
+ {
+ "multi line invalid message",
+ ccfg,
+ `feat add something
+
+ team: x`, true,
+ },
+
+ {
+ "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 {
t.Run(tt.name, func(t *testing.T) {
@@ -181,9 +213,21 @@ func TestMessageProcessorImpl_ValidateType(t *testing.T) {
ctype string
wantErr bool
}{
- {"valid type", ccfg, "feat", false},
- {"invalid type", ccfg, "aaa", true},
- {"empty type", ccfg, "", true},
+ {
+ "valid type",
+ ccfg,
+ "feat", false,
+ },
+ {
+ "invalid type",
+ ccfg,
+ "aaa", true,
+ },
+ {
+ "empty type",
+ ccfg,
+ "", true,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -202,7 +246,11 @@ func TestMessageProcessorImpl_ValidateScope(t *testing.T) {
scope string
wantErr bool
}{
- {"any scope", ccfg, "aaa", false},
+ {
+ "any scope",
+ ccfg,
+ "aaa", false,
+ },
{"valid scope with scope list", ccfgWithScope, "scope", false},
{"invalid scope with scope list", ccfgWithScope, "aaa", true},
}
@@ -223,11 +271,31 @@ func TestMessageProcessorImpl_ValidateDescription(t *testing.T) {
description string
wantErr bool
}{
- {"empty description", ccfg, "", 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},
+ {
+ "empty description",
+ ccfg,
+ "", 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 {
t.Run(tt.name, func(t *testing.T) {
@@ -248,24 +316,73 @@ func TestMessageProcessorImpl_Enhance(t *testing.T) {
want string
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 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},
+ {
+ "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 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 {
t.Run(tt.name, func(t *testing.T) {
got, err := NewMessageProcessor(tt.cfg, newBranchCfg(false)).Enhance(tt.branch, tt.message)
if (err != nil) != tt.wantErr {
t.Errorf("MessageProcessorImpl.Enhance() error = %v, wantErr %v", err, tt.wantErr)
+
return
}
if got != tt.want {
@@ -296,6 +413,7 @@ func TestMessageProcessorImpl_IssueID(t *testing.T) {
got, err := p.IssueID(tt.branch)
if (err != nil) != tt.wantErr {
t.Errorf("MessageProcessorImpl.IssueID() error = %v, wantErr %v", err, tt.wantErr)
+
return
}
if got != tt.want {
@@ -326,19 +444,19 @@ func Test_hasIssueID(t *testing.T) {
}{
{"single line without issue", "feat: something", cfgColon, false},
{"multi line without issue", `feat: something
-
+
yay`, cfgColon, false},
{"multi line without jira issue", `feat: something
-
+
jira1: JIRA-123`, cfgColon, false},
{"multi line with issue", `feat: something
-
+
jira: JIRA-123`, cfgColon, true},
{"multi line with issue and hash", `feat: something
-
+
jira #JIRA-123`, cfgHash, true},
{"empty config", `feat: something
-
+
jira #JIRA-123`, cfgEmpty, false},
}
for _, tt := range tests {
@@ -378,8 +496,10 @@ var completeBody = `some descriptions
jira: JIRA-123
BREAKING CHANGE: this change breaks everything`
-var bodyWithCarriage = "some description\r\nmore description\r\n\r\njira: JIRA-123\r"
-var expectedBodyWithCarriage = "some description\nmore description\n\njira: JIRA-123"
+var (
+ 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
@@ -402,20 +522,145 @@ func TestMessageProcessorImpl_Parse(t *testing.T) {
body string
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{}}},
- {"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"}}},
+ {
+ "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{},
+ },
+ },
+ {
+ "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 {
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)
}
})
@@ -431,18 +676,102 @@ func TestMessageProcessorImpl_Format(t *testing.T) {
wantBody string
wantFooter string
}{
- {"simple message", ccfg, NewCommitMessage("feat", "", "something", "", "", ""), "feat: something", "", ""},
- {"with issue", ccfg, 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"},
+ {
+ "simple message",
+ ccfg,
+ NewCommitMessage("feat", "", "something", "", "", ""),
+ "feat: something",
+ "",
+ "",
+ },
+ {
+ "with issue",
+ ccfg,
+ 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 {
t.Run(tt.name, func(t *testing.T) {
@@ -532,14 +861,61 @@ func Test_prepareHeader(t *testing.T) {
wantHeader string
wantError bool
}{
- {"conventional without selector", "", "feat: something", "feat: something", false},
- {"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.*)", "Merged PR 123: feat: something", "feat: something", false},
- {"matching non-conventional with selector with group", "Merged PR (\\d+): (?P.*)", "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.*)", "something", "", true},
- {"matching non-conventional with invalid regex", "Merged PR (\\d+): (?.*)", "Merged PR 123: something", "", true},
+ {
+ "conventional without selector",
+ "",
+ "feat: something",
+ "feat: something",
+ false,
+ },
+ {
+ "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.*)",
+ "Merged PR 123: feat: something",
+ "feat: something",
+ false,
+ },
+ {
+ "matching non-conventional with selector with group",
+ "Merged PR (\\d+): (?P.*)",
+ "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.*)",
+ "something",
+ "",
+ true,
+ },
+ {
+ "matching non-conventional with invalid regex",
+ "Merged PR (\\d+): (?.*)",
+ "Merged PR 123: something",
+ "",
+ true,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/sv/releasenotes.go b/sv/releasenotes.go
index f4a7d85..c07dfc2 100644
--- a/sv/releasenotes.go
+++ b/sv/releasenotes.go
@@ -22,22 +22,32 @@ func NewReleaseNoteProcessor(cfg ReleaseNotesConfig) *ReleaseNoteProcessorImpl {
}
// 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)
sections := make(map[string]ReleaseNoteCommitsSection)
authors := make(map[string]struct{})
+
var breakingChanges []string
+
for _, commit := range commits {
authors[commit.AuthorName] = struct{}{}
+
if sectionCfg, exists := mapping[commit.Message.Type]; exists {
section, sexists := sections[sectionCfg.Name]
if !sexists {
section = ReleaseNoteCommitsSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
}
+
section.Items = append(section.Items, commit)
sections[sectionCfg.Name] = section
}
+
if commit.Message.BreakingMessage() != "" {
// TODO: if no message found, should use description instead?
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 {
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
if breakingChange.Name != "" {
hasBreaking = 1
@@ -59,11 +79,13 @@ func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[strin
sections := make([]ReleaseNoteSection, len(commitSections)+hasBreaking)
i := 0
+
for _, cfg := range p.cfg.Sections {
if cfg.SectionType == ReleaseNotesSectionTypeBreakingChanges && hasBreaking > 0 {
sections[i] = breakingChange
i++
}
+
if s, exists := commitSections[cfg.Name]; cfg.SectionType == ReleaseNotesSectionTypeCommits && exists {
sections[i] = s
i++
@@ -75,6 +97,7 @@ func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[strin
func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]ReleaseNotesSectionConfig {
mapping := make(map[string]ReleaseNotesSectionConfig)
+
for _, section := range sections {
if section.SectionType == ReleaseNotesSectionTypeCommits {
for _, commitType := range section.CommitTypes {
@@ -82,6 +105,7 @@ func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]Relea
}
}
}
+
return mapping
}
diff --git a/sv/releasenotes_test.go b/sv/releasenotes_test.go
index af3029f..5614992 100644
--- a/sv/releasenotes_test.go
+++ b/sv/releasenotes_test.go
@@ -25,7 +25,15 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0",
date: date,
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",
@@ -33,28 +41,71 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0",
date: date,
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",
version: semver.MustParse("1.0.0"),
tag: "v1.0.0",
date: date,
- commits: []GitCommitLog{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": {}}),
+ commits: []GitCommitLog{
+ 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",
version: semver.MustParse("1.0.0"),
tag: "v1.0.0",
date: date,
- commits: []GitCommitLog{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": {}}),
+ commits: []GitCommitLog{
+ 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 {
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) {
t.Errorf("ReleaseNoteProcessorImpl.Create() = %v, want %v", got, tt.want)
}
diff --git a/sv/semver.go b/sv/semver.go
index ef70218..f90c9e1 100644
--- a/sv/semver.go
+++ b/sv/semver.go
@@ -14,6 +14,7 @@ const (
// IsValidVersion return true when a version is valid.
func IsValidVersion(value string) bool {
_, err := semver.NewVersion(value)
+
return err == nil
}
@@ -23,6 +24,7 @@ func ToVersion(value string) (*semver.Version, error) {
if version == "" {
version = "0.0.0"
}
+
return semver.NewVersion(version)
}
@@ -52,7 +54,9 @@ func NewSemVerCommitsProcessor(vcfg VersioningConfig, mcfg CommitMessageConfig)
}
// 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
for _, commit := range commits {
if v := p.versionTypeToUpdate(commit); v > versionToUpdate {
@@ -64,7 +68,9 @@ func (p SemVerCommitsProcessorImpl) NextVersion(version *semver.Version, commits
if version == nil {
return nil, updated
}
+
newVersion := updateVersion(*version, versionToUpdate)
+
return &newVersion, updated
}
@@ -85,18 +91,23 @@ func (p SemVerCommitsProcessorImpl) versionTypeToUpdate(commit GitCommitLog) ver
if commit.Message.IsBreakingChange {
return major
}
+
if _, exists := p.MajorVersionTypes[commit.Message.Type]; exists {
return major
}
+
if _, exists := p.MinorVersionTypes[commit.Message.Type]; exists {
return minor
}
+
if _, exists := p.PatchVersionTypes[commit.Message.Type]; exists {
return patch
}
+
if !contains(commit.Message.Type, p.KnownTypes) && p.IncludeUnknownTypeAsPatch {
return patch
}
+
return none
}
@@ -105,5 +116,6 @@ func toMap(values []string) map[string]struct{} {
for _, v := range values {
result[v] = struct{}{}
}
+
return result
}
diff --git a/sv/semver_test.go b/sv/semver_test.go
index 1940a39..34cefdf 100644
--- a/sv/semver_test.go
+++ b/sv/semver_test.go
@@ -16,20 +16,104 @@ func TestSemVerCommitsProcessorImpl_NextVersion(t *testing.T) {
want *semver.Version
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 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},
+ {
+ "no update",
+ true,
+ version("0.0.0"),
+ []GitCommitLog{},
+ version("0.0.0"),
+ false,
+ },
+ {
+ "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 {
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)
if !reflect.DeepEqual(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)
if (err != nil) != tt.wantErr {
t.Errorf("ToVersion() error = %v, wantErr %v", err, tt.wantErr)
+
return
}
if !reflect.DeepEqual(got, tt.want) {