chore(ci): add golangci-lint action and apply linting changes (#120)

BREAKING CHANGE: `JiraIssueId` has been renamed to `JiraIssueID`. This impacts the value for `pattern_maps` in `config.yml`.

* chore(ci): add golangci-lint action

* chore(lint): address errcheck lint failures

* chore(lint): address misspell lint failures

* chore(lint): address gocritic lint failures

* chore(lint): address golint lint failures

* chore(lint): address structcheck lint failures

* chore(lint): address gosimple lint failures

* chore(lint): address gofmt lint failures

* chore(ci): port to official golangci-lint github action

* Update golangci configuration for better coverage

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* fix: file is not goimports-ed

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* fix: golint and exported functions comments

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* chore(lint): address gosec G304 warning

* chore(lint): address uparam warnings

* chore(lint): address scopelint lint failures

* fix: cyclomatic complexity

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* chore(lint): address prealloc warning, noting that we are warning for now

* chore(lint): address govet and errorlint failures

* chore: clean up defer logic when checking errors

Co-authored-by: Khosrow Moossavi <khos2ow@gmail.com>
This commit is contained in:
Derek Smith 2021-03-16 21:24:36 -05:00 committed by GitHub
parent 2c3d3f400e
commit ae3382b7c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 391 additions and 177 deletions

18
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: lint
on:
pull_request:
types: ['opened', 'synchronize']
push:
branches:
- master
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.38

155
.golangci.yml Normal file
View file

@ -0,0 +1,155 @@
# https://golangci-lint.run/usage/configuration/
run:
timeout: 10m
deadline: 5m
tests: true
output:
format: tab
linters-settings:
govet:
# report about shadowed variables
check-shadowing: true
golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: github.com/git-chglog/git-chglog
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 5
lll:
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
prealloc:
# XXX: we don't recommend using this linter before doing performance profiling.
# For most programs usage of prealloc will be a premature optimization.
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
misspell:
locale: US
linters:
enable:
- megacheck
- govet
- gocyclo
- gocritic
- interfacer
- goconst
- goimports
- gofmt # We enable this as well as goimports for its simplify mode.
- prealloc
- golint
- unconvert
- misspell
- nakedret
- dupl
- depguard
presets:
- bugs
- unused
fast: false
issues:
# Excluding configuration per-path and per-linter
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test(ing)?\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- scopelint
- unparam
# These are performance optimisations rather than style issues per se.
# They warn when function arguments or range values copy a lot of memory
# rather than using a pointer.
- text: "(hugeParam|rangeValCopy):"
linters:
- gocritic
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: false
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
new: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View file

@ -20,3 +20,7 @@ install:
.PHONY: changelog .PHONY: changelog
changelog: changelog:
@git-chglog --next-tag $(tag) $(tag) @git-chglog --next-tag $(tag) $(tag)
.PHONY: lint
lint:
@golangci-lint run

View file

@ -522,7 +522,7 @@ The following is a sample pattern:
pattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$" pattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$"
pattern_maps: pattern_maps:
- Type - Type
- JiraIssueId - JiraIssueID
- Subject - Subject
``` ```
@ -646,6 +646,8 @@ We alway welcome your contributions :clap:
1. Fork (https://github.com/git-chglog/git-chglog) :tada: 1. Fork (https://github.com/git-chglog/git-chglog) :tada:
1. Create a feature branch :coffee: 1. Create a feature branch :coffee:
1. Run test suite with the `$ make test` command and confirm that it passes :zap: 1. Run test suite with the `$ make test` command and confirm that it passes :zap:
1. Run linters with the `$ make lint` command and confirm it passes :broom:
- The project uses [golangci-lint](https://golangci-lint.run/usage/install/#local-installation)
1. Commit your changes :memo: 1. Commit your changes :memo:
1. Rebase your local changes against the `master` branch :bulb: 1. Rebase your local changes against the `master` branch :bulb:
1. Create new Pull Request :love_letter: 1. Create new Pull Request :love_letter:

View file

@ -5,13 +5,14 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template" "text/template"
"time" "time"
gitcmd "github.com/tsuyoshiwada/go-gitcmd" "github.com/tsuyoshiwada/go-gitcmd"
) )
// Options is an option used to process commits // Options is an option used to process commits
@ -37,7 +38,7 @@ type Options struct {
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`) NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
JiraUsername string JiraUsername string
JiraToken string JiraToken string
JiraUrl string JiraURL string
JiraTypeMaps map[string]string JiraTypeMaps map[string]string
JiraIssueDescriptionPattern string JiraIssueDescriptionPattern string
Paths []string // Path filter Paths []string // Path filter
@ -138,7 +139,11 @@ func (gen *Generator) Generate(w io.Writer, query string) error {
if err != nil { if err != nil {
return err return err
} }
defer back() defer func() {
if err = back(); err != nil {
log.Fatal(err)
}
}()
tags, first, err := gen.getTags(query) tags, first, err := gen.getTags(query)
if err != nil { if err != nil {
@ -269,7 +274,7 @@ func (gen *Generator) getTags(query string) ([]*Tag, string, error) {
// Assign the date with `readVersions()` // Assign the date with `readVersions()`
tags = append([]*Tag{ tags = append([]*Tag{
&Tag{ {
Name: next, Name: next,
Subject: next, Subject: next,
Previous: previous, Previous: previous,
@ -318,30 +323,18 @@ func (gen *Generator) render(w io.Writer, unreleased *Unreleased, versions []*Ve
"datetime": func(layout string, input time.Time) string { "datetime": func(layout string, input time.Time) string {
return input.Format(layout) return input.Format(layout)
}, },
// check whether substs is withing s // check whether substr is within s
"contains": func(s, substr string) bool { "contains": strings.Contains,
return strings.Contains(s, substr)
},
// check whether s begins with prefix // check whether s begins with prefix
"hasPrefix": func(s, prefix string) bool { "hasPrefix": strings.HasPrefix,
return strings.HasPrefix(s, prefix)
},
// check whether s ends with suffix // check whether s ends with suffix
"hasSuffix": func(s, suffix string) bool { "hasSuffix": strings.HasSuffix,
return strings.HasSuffix(s, suffix)
},
// replace the first n instances of old with new // replace the first n instances of old with new
"replace": func(s, old, new string, n int) string { "replace": strings.Replace,
return strings.Replace(s, old, new, n)
},
// lower case a string // lower case a string
"lower": func(s string) string { "lower": strings.ToLower,
return strings.ToLower(s)
},
// upper case a string // upper case a string
"upper": func(s string) string { "upper": strings.ToUpper,
return strings.ToUpper(s)
},
// upper case the first character of a string // upper case the first character of a string
"upperFirst": func(s string) string { "upperFirst": func(s string) string {
if len(s) > 0 { if len(s) > 0 {

View file

@ -32,17 +32,17 @@ func TestMain(m *testing.M) {
func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) { func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) {
testDir := filepath.Join(cwd, testRepoRoot, dir) testDir := filepath.Join(cwd, testRepoRoot, dir)
os.RemoveAll(testDir) _ = os.RemoveAll(testDir)
os.MkdirAll(testDir, os.ModePerm) _ = os.MkdirAll(testDir, os.ModePerm)
os.Chdir(testDir) _ = os.Chdir(testDir)
loc, _ := time.LoadLocation("UTC") loc, _ := time.LoadLocation("UTC")
time.Local = loc time.Local = loc
git := gitcmd.New(nil) git := gitcmd.New(nil)
git.Exec("init") _, _ = git.Exec("init")
git.Exec("config", "user.name", "test_user") _, _ = git.Exec("config", "user.name", "test_user")
git.Exec("config", "user.email", "test@example.com") _, _ = git.Exec("config", "user.email", "test@example.com")
var commit = func(date, subject, body string) { var commit = func(date, subject, body string) {
msg := subject msg := subject
@ -51,21 +51,21 @@ func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) {
} }
t, _ := time.Parse(internalTimeFormat, date) t, _ := time.Parse(internalTimeFormat, date)
d := t.Format("Mon Jan 2 15:04:05 2006 +0000") d := t.Format("Mon Jan 2 15:04:05 2006 +0000")
git.Exec("commit", "--allow-empty", "--date", d, "-m", msg) _, _ = git.Exec("commit", "--allow-empty", "--date", d, "-m", msg)
} }
var tag = func(name string) { var tag = func(name string) {
git.Exec("tag", name) _, _ = git.Exec("tag", name)
} }
setupRepo(commit, tag, git) setupRepo(commit, tag, git)
os.Chdir(cwd) _ = os.Chdir(cwd)
} }
func cleanup() { func cleanup() {
os.Chdir(cwd) _ = os.Chdir(cwd)
os.RemoveAll(filepath.Join(cwd, testRepoRoot)) _ = os.RemoveAll(filepath.Join(cwd, testRepoRoot))
} }
func TestGeneratorNotFoundTags(t *testing.T) { func TestGeneratorNotFoundTags(t *testing.T) {
@ -193,7 +193,7 @@ func TestGeneratorWithTypeScopeSubject(t *testing.T) {
tag("1.1.0") tag("1.1.0")
commit("2018-01-03 00:00:00", "feat(context): Online breaking change", "BREAKING CHANGE: Online breaking change message.") commit("2018-01-03 00:00:00", "feat(context): Online breaking change", "BREAKING CHANGE: Online breaking change message.")
commit("2018-01-03 00:01:00", "feat(router): Muliple breaking change", `This is body, commit("2018-01-03 00:01:00", "feat(router): Multiple breaking change", `This is body,
BREAKING CHANGE: BREAKING CHANGE:
Multiple Multiple
@ -216,7 +216,7 @@ change message.`)
}, },
Options: &Options{ Options: &Options{
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
"fix", "fix",
}, },
@ -256,7 +256,7 @@ change message.`)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")
output := strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1) output := strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err) assert.Nil(err)
assert.Equal(`<a name="unreleased"></a> assert.Equal(`<a name="unreleased"></a>
@ -270,7 +270,7 @@ change message.`)
## [2.0.0-beta.0] - 2018-01-03 ## [2.0.0-beta.0] - 2018-01-03
### Features ### Features
- **context:** Online breaking change - **context:** Online breaking change
- **router:** Muliple breaking change - **router:** Multiple breaking change
### BREAKING CHANGE ### BREAKING CHANGE
@ -331,7 +331,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
Options: &Options{ Options: &Options{
NextTag: "3.0.0", NextTag: "3.0.0",
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
}, },
}, },
@ -352,7 +352,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")
output := strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1) output := strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err) assert.Nil(err)
assert.Equal(`<a name="unreleased"></a> assert.Equal(`<a name="unreleased"></a>
@ -383,7 +383,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
buf = &bytes.Buffer{} buf = &bytes.Buffer{}
err = gen.Generate(buf, "3.0.0") err = gen.Generate(buf, "3.0.0")
output = strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1) output = strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err) assert.Nil(err)
assert.Equal(`<a name="unreleased"></a> assert.Equal(`<a name="unreleased"></a>
@ -424,7 +424,7 @@ func TestGeneratorWithTagFiler(t *testing.T) {
Options: &Options{ Options: &Options{
TagFilterPattern: "^v", TagFilterPattern: "^v",
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
}, },
}, },

View file

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/fatih/color" "github.com/fatih/color"
chglog "github.com/git-chglog/git-chglog" chglog "github.com/git-chglog/git-chglog"
) )

View file

@ -8,8 +8,9 @@ import (
"regexp" "regexp"
"testing" "testing"
chglog "github.com/git-chglog/git-chglog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
chglog "github.com/git-chglog/git-chglog"
) )
func TestCLIForStdout(t *testing.T) { func TestCLIForStdout(t *testing.T) {
@ -36,7 +37,7 @@ func TestCLIForStdout(t *testing.T) {
if config.Bin != "/custom/bin/git" { if config.Bin != "/custom/bin/git" {
return errors.New("") return errors.New("")
} }
w.Write([]byte("success!!")) _, _ = w.Write([]byte("success!!"))
return nil return nil
}, },
} }
@ -104,7 +105,7 @@ func TestCLIForFile(t *testing.T) {
if filepath.ToSlash(config.Bin) != "/custom/bin/git" { if filepath.ToSlash(config.Bin) != "/custom/bin/git" {
return errors.New("") return errors.New("")
} }
w.Write([]byte("success!!")) _, _ = w.Write([]byte("success!!"))
return nil return nil
}, },
} }

View file

@ -4,8 +4,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
chglog "github.com/git-chglog/git-chglog"
"github.com/imdario/mergo" "github.com/imdario/mergo"
chglog "github.com/git-chglog/git-chglog"
) )
// Info ... // Info ...
@ -49,6 +50,7 @@ type NoteOptions struct {
Keywords []string `yaml:"keywords"` Keywords []string `yaml:"keywords"`
} }
// JiraClientInfoOptions ...
type JiraClientInfoOptions struct { type JiraClientInfoOptions struct {
Username string `yaml:"username"` Username string `yaml:"username"`
Token string `yaml:"token"` Token string `yaml:"token"`
@ -286,7 +288,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
Template: orValue(ctx.Template, config.Template), Template: orValue(ctx.Template, config.Template),
Info: &chglog.Info{ Info: &chglog.Info{
Title: info.Title, Title: info.Title,
RepositoryURL: orValue(ctx.RepositoryUrl, info.RepositoryURL), RepositoryURL: orValue(ctx.RepositoryURL, info.RepositoryURL),
}, },
Options: &chglog.Options{ Options: &chglog.Options{
NextTag: ctx.NextTag, NextTag: ctx.NextTag,
@ -309,7 +311,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
NoteKeywords: opts.Notes.Keywords, NoteKeywords: opts.Notes.Keywords,
JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username), JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username),
JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token), JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token),
JiraUrl: orValue(ctx.JiraUrl, opts.Jira.ClintInfo.URL), JiraURL: orValue(ctx.JiraURL, opts.Jira.ClintInfo.URL),
JiraTypeMaps: opts.Jira.Issue.TypeMaps, JiraTypeMaps: opts.Jira.Issue.TypeMaps,
JiraIssueDescriptionPattern: opts.Jira.Issue.DescriptionPattern, JiraIssueDescriptionPattern: opts.Jira.Issue.DescriptionPattern,
}, },

View file

@ -2,8 +2,9 @@ package main
import ( import (
"io/ioutil" "io/ioutil"
"path/filepath"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// ConfigLoader ... // ConfigLoader ...
@ -20,7 +21,8 @@ func NewConfigLoader() ConfigLoader {
} }
func (loader *configLoaderImpl) Load(path string) (*Config, error) { func (loader *configLoaderImpl) Load(path string) (*Config, error) {
bytes, err := ioutil.ReadFile(path) fp := filepath.Clean(path)
bytes, err := ioutil.ReadFile(fp)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -11,7 +11,7 @@ type CLIContext struct {
Stderr io.Writer Stderr io.Writer
ConfigPath string ConfigPath string
Template string Template string
RepositoryUrl string RepositoryURL string
OutputPath string OutputPath string
Silent bool Silent bool
NoColor bool NoColor bool
@ -22,7 +22,7 @@ type CLIContext struct {
TagFilterPattern string TagFilterPattern string
JiraUsername string JiraUsername string
JiraToken string JiraToken string
JiraUrl string JiraURL string
Paths []string Paths []string
} }

View file

@ -18,7 +18,7 @@ func (t *customTemplateBuilderImpl) Build(ans *Answer) (string, error) {
tpl += t.versionHeader(ans.Style, ans.Template) tpl += t.versionHeader(ans.Style, ans.Template)
// commits // commits
tpl += t.commits(ans.Template, ans.CommitMessageFormat) tpl += t.commits(ans.CommitMessageFormat)
// revert // revert
if ans.IncludeReverts { if ans.IncludeReverts {
@ -75,7 +75,7 @@ func (*customTemplateBuilderImpl) versionHeader(style, template string) string {
return tpl return tpl
} }
func (*customTemplateBuilderImpl) commits(template, format string) string { func (*customTemplateBuilderImpl) commits(format string) string {
var ( var (
header string header string
body string body string

View file

@ -2,17 +2,16 @@ package main
import ( import (
"fmt" "fmt"
"github.com/git-chglog/git-chglog"
"path/filepath" "path/filepath"
"github.com/fatih/color" "github.com/fatih/color"
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
chglog "github.com/git-chglog/git-chglog"
) )
// Initializer ... // Initializer ...
type Initializer struct { type Initializer struct {
ctx *InitContext ctx *InitContext
client gitcmd.Client
fs FileSystem fs FileSystem
logger *chglog.Logger logger *chglog.Logger
questioner Questioner questioner Questioner

View file

@ -3,14 +3,17 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
gitcmd "github.com/tsuyoshiwada/go-gitcmd" "github.com/tsuyoshiwada/go-gitcmd"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
// CreateApp creates and initializes CLI application
// with description, flags, version, etc.
func CreateApp(actionFunc cli.ActionFunc) *cli.App { func CreateApp(actionFunc cli.ActionFunc) *cli.App {
ttl := color.New(color.FgYellow).SprintFunc() ttl := color.New(color.FgYellow).SprintFunc()
@ -182,6 +185,8 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
return app return app
} }
// AppAction is a callback function to create initializer
// and CLIContext and ultimately run the application.
func AppAction(c *cli.Context) error { func AppAction(c *cli.Context) error {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
@ -219,7 +224,7 @@ func AppAction(c *cli.Context) error {
Stderr: colorable.NewColorableStderr(), Stderr: colorable.NewColorableStderr(),
ConfigPath: c.String("config"), ConfigPath: c.String("config"),
Template: c.String("template"), Template: c.String("template"),
RepositoryUrl: c.String("repository-url"), RepositoryURL: c.String("repository-url"),
OutputPath: c.String("output"), OutputPath: c.String("output"),
Silent: c.Bool("silent"), Silent: c.Bool("silent"),
NoColor: c.Bool("no-color"), NoColor: c.Bool("no-color"),
@ -230,7 +235,7 @@ func AppAction(c *cli.Context) error {
TagFilterPattern: c.String("tag-filter-pattern"), TagFilterPattern: c.String("tag-filter-pattern"),
JiraUsername: c.String("jira-username"), JiraUsername: c.String("jira-username"),
JiraToken: c.String("jira-token"), JiraToken: c.String("jira-token"),
JiraUrl: c.String("jira-url"), JiraURL: c.String("jira-url"),
Paths: c.StringSlice("path"), Paths: c.StringSlice("path"),
}, },
fs, fs,
@ -245,5 +250,8 @@ func AppAction(c *cli.Context) error {
func main() { func main() {
app := CreateApp(AppAction) app := CreateApp(AppAction)
app.Run(os.Args) err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} }

View file

@ -1,14 +1,16 @@
package main package main
import ( import (
"log"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"testing"
) )
var gAssert *assert.Assertions var gAssert *assert.Assertions
func mock_app_action(c *cli.Context) error { func mockAppAction(c *cli.Context) error {
assert := gAssert assert := gAssert
assert.Equal("c.yml", c.String("config")) assert.Equal("c.yml", c.String("config"))
assert.Equal("^v", c.String("tag-filter-pattern")) assert.Equal("^v", c.String("tag-filter-pattern"))
@ -25,7 +27,7 @@ func TestCreateApp(t *testing.T) {
assert.True(true) assert.True(true)
gAssert = assert gAssert = assert
app := CreateApp(mock_app_action) app := CreateApp(mockAppAction)
args := []string{ args := []string{
"git-chglog", "git-chglog",
"--silent", "--silent",
@ -36,5 +38,8 @@ func TestCreateApp(t *testing.T) {
"--next-tag", "v5", "--next-tag", "v5",
"--tag-filter-pattern", "^v", "--tag-filter-pattern", "^v",
} }
app.Run(args) err := app.Run(args)
if err != nil {
log.Fatal(err)
}
} }

View file

@ -3,8 +3,9 @@ package main
import ( import (
"testing" "testing"
chglog "github.com/git-chglog/git-chglog"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
chglog "github.com/git-chglog/git-chglog"
) )
func TestProcessorFactory(t *testing.T) { func TestProcessorFactory(t *testing.T) {

View file

@ -6,8 +6,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
gitcmd "github.com/tsuyoshiwada/go-gitcmd" "github.com/AlecAivazis/survey/v2"
survey "github.com/AlecAivazis/survey/v2" "github.com/tsuyoshiwada/go-gitcmd"
) )
// Answer ... // Answer ...
@ -52,11 +52,12 @@ func (q *questionerImpl) Ask() (*Answer, error) {
t := q.fs.Exists(tpl) t := q.fs.Exists(tpl)
msg := "" msg := ""
if c && t { switch {
case c && t:
msg = fmt.Sprintf("\"%s\" and \"%s\" already exists. Do you want to overwrite?", config, tpl) msg = fmt.Sprintf("\"%s\" and \"%s\" already exists. Do you want to overwrite?", config, tpl)
} else if c { case c:
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", config) msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", config)
} else if t { case t:
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", tpl) msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", tpl)
} }

View file

@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
var reSSH = regexp.MustCompile("^\\w+@([\\w\\.\\-]+):([\\w\\.\\-]+)\\/([\\w\\.\\-]+)$") var reSSH = regexp.MustCompile(`^\w+@([\w\.\-]+):([\w\.\-]+)\/([\w\.\-]+)$`)
func remoteOriginURLToHTTP(rawurl string) string { func remoteOriginURLToHTTP(rawurl string) string {
if rawurl == "" { if rawurl == "" {

View file

@ -74,7 +74,7 @@ func (f *CommitMessageFormat) PatternMapString() string {
return fmt.Sprintf("\n%s", strings.Join(arr, "\n")) return fmt.Sprintf("\n%s", strings.Join(arr, "\n"))
} }
// FilterTypeString ... // FilterTypesString ...
func (f *CommitMessageFormat) FilterTypesString() string { func (f *CommitMessageFormat) FilterTypesString() string {
if len(f.typeSamples) == 0 { if len(f.typeSamples) == 0 {
return " []" return " []"

View file

@ -127,7 +127,11 @@ func (e *commitExtractor) commitGroupTitle(commit *Commit) (string, string) {
return raw, ttl return raw, ttl
} }
func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) { func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) { //nolint:gocyclo
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
// Be wary when adding branches, and look for functionality that could
// be reasonably moved into an injected dependency.
order := make(map[string]int) order := make(map[string]int)
if e.opts.CommitGroupSortBy == "Custom" { if e.opts.CommitGroupSortBy == "Custom" {
for i, t := range e.opts.CommitGroupTitleOrder { for i, t := range e.opts.CommitGroupTitleOrder {
@ -136,6 +140,9 @@ func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
} }
// groups // groups
// TODO(khos2ow): move the inline sort function to
// conceret implementation of sort.Interface in order
// to reduce cyclomatic complaxity.
sort.Slice(groups, func(i, j int) bool { sort.Slice(groups, func(i, j int) bool {
if e.opts.CommitGroupSortBy == "Custom" { if e.opts.CommitGroupSortBy == "Custom" {
return order[groups[i].RawTitle] < order[groups[j].RawTitle] return order[groups[i].RawTitle] < order[groups[j].RawTitle]
@ -165,6 +172,11 @@ func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
// commits // commits
for _, group := range groups { for _, group := range groups {
group := group // pin group to avoid potential bugs with passing group to lower functions
// TODO(khos2ow): move the inline sort function to
// conceret implementation of sort.Interface in order
// to reduce cyclomatic complaxity.
sort.Slice(group.Commits, func(i, j int) bool { sort.Slice(group.Commits, func(i, j int) bool {
var ( var (
a, b interface{} a, b interface{}
@ -198,6 +210,7 @@ func (e *commitExtractor) sortNoteGroups(groups []*NoteGroup) {
// notes // notes
for _, group := range groups { for _, group := range groups {
group := group // pin group to avoid potential bugs with passing group to lower functions
sort.Slice(group.Notes, func(i, j int) bool { sort.Slice(group.Notes, func(i, j int) bool {
return strings.ToLower(group.Notes[i].Title) < strings.ToLower(group.Notes[j].Title) return strings.ToLower(group.Notes[i].Title) < strings.ToLower(group.Notes[j].Title)
}) })

View file

@ -20,14 +20,14 @@ func TestCommitExtractor(t *testing.T) {
fixtures := []*Commit{ fixtures := []*Commit{
// [0] // [0]
&Commit{ {
Type: "foo", Type: "foo",
Scope: "c", Scope: "c",
Header: "1", Header: "1",
Notes: []*Note{}, Notes: []*Note{},
}, },
// [1] // [1]
&Commit{ {
Type: "foo", Type: "foo",
Scope: "b", Scope: "b",
Header: "2", Header: "2",
@ -37,7 +37,7 @@ func TestCommitExtractor(t *testing.T) {
}, },
}, },
// [2] // [2]
&Commit{ {
Type: "bar", Type: "bar",
Scope: "d", Scope: "d",
Header: "3", Header: "3",
@ -47,7 +47,7 @@ func TestCommitExtractor(t *testing.T) {
}, },
}, },
// [3] // [3]
&Commit{ {
Type: "foo", Type: "foo",
Scope: "a", Scope: "a",
Header: "4", Header: "4",
@ -56,7 +56,7 @@ func TestCommitExtractor(t *testing.T) {
}, },
}, },
// [4] // [4]
&Commit{ {
Type: "", Type: "",
Scope: "", Scope: "",
Header: "Merge1", Header: "Merge1",
@ -67,7 +67,7 @@ func TestCommitExtractor(t *testing.T) {
}, },
}, },
// [5] // [5]
&Commit{ {
Type: "", Type: "",
Scope: "", Scope: "",
Header: "Revert1", Header: "Revert1",
@ -81,14 +81,14 @@ func TestCommitExtractor(t *testing.T) {
commitGroups, mergeCommits, revertCommits, noteGroups := extractor.Extract(fixtures) commitGroups, mergeCommits, revertCommits, noteGroups := extractor.Extract(fixtures)
assert.Equal([]*CommitGroup{ assert.Equal([]*CommitGroup{
&CommitGroup{ {
RawTitle: "bar", RawTitle: "bar",
Title: "BAR", Title: "BAR",
Commits: []*Commit{ Commits: []*Commit{
fixtures[2], fixtures[2],
}, },
}, },
&CommitGroup{ {
RawTitle: "foo", RawTitle: "foo",
Title: "Foo", Title: "Foo",
Commits: []*Commit{ Commits: []*Commit{
@ -108,26 +108,26 @@ func TestCommitExtractor(t *testing.T) {
}, revertCommits) }, revertCommits)
assert.Equal([]*NoteGroup{ assert.Equal([]*NoteGroup{
&NoteGroup{ {
Title: "note1-title", Title: "note1-title",
Notes: []*Note{ Notes: []*Note{
fixtures[1].Notes[0], fixtures[1].Notes[0],
fixtures[2].Notes[0], fixtures[2].Notes[0],
}, },
}, },
&NoteGroup{ {
Title: "note2-title", Title: "note2-title",
Notes: []*Note{ Notes: []*Note{
fixtures[1].Notes[1], fixtures[1].Notes[1],
}, },
}, },
&NoteGroup{ {
Title: "note3-title", Title: "note3-title",
Notes: []*Note{ Notes: []*Note{
fixtures[2].Notes[1], fixtures[2].Notes[1],
}, },
}, },
&NoteGroup{ {
Title: "note4-title", Title: "note4-title",
Notes: []*Note{ Notes: []*Note{
fixtures[3].Notes[0], fixtures[3].Notes[0],

View file

@ -4,7 +4,11 @@ import (
"strings" "strings"
) )
func commitFilter(commits []*Commit, filters map[string][]string, noCaseSensitive bool) []*Commit { func commitFilter(commits []*Commit, filters map[string][]string, noCaseSensitive bool) []*Commit { //nolint:gocyclo
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
// Be wary when adding branches, and look for functionality that could
// be reasonably moved into an injected dependency.
res := []*Commit{} res := []*Commit{}
for _, commit := range commits { for _, commit := range commits {

View file

@ -18,27 +18,27 @@ func TestCommitFilter(t *testing.T) {
} }
fixtures := []*Commit{ fixtures := []*Commit{
&Commit{ {
Type: "foo", Type: "foo",
Scope: "hoge", Scope: "hoge",
Subject: "1", Subject: "1",
}, },
&Commit{ {
Type: "foo", Type: "foo",
Scope: "fuga", Scope: "fuga",
Subject: "2", Subject: "2",
}, },
&Commit{ {
Type: "bar", Type: "bar",
Scope: "hoge", Scope: "hoge",
Subject: "3", Subject: "3",
}, },
&Commit{ {
Type: "bar", Type: "bar",
Scope: "fuga", Scope: "fuga",
Subject: "4", Subject: "4",
}, },
&Commit{ {
Type: "Bar", Type: "Bar",
Scope: "hogera", Scope: "hogera",
Subject: "5", Subject: "5",

View file

@ -80,7 +80,7 @@ func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"), reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"), reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"), reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
reMention: regexp.MustCompile("@([\\w-]+)"), reMention: regexp.MustCompile(`@([\w-]+)`),
reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern), reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern),
} }
} }
@ -223,8 +223,8 @@ func (p *commitParser) processHeader(commit *Commit, input string) {
commit.Mentions = p.parseMentions(input) commit.Mentions = p.parseMentions(input)
// Jira // Jira
if commit.JiraIssueId != "" { if commit.JiraIssueID != "" {
p.processJiraIssue(commit, commit.JiraIssueId) p.processJiraIssue(commit, commit.JiraIssueID)
} }
} }
@ -364,10 +364,10 @@ func (p *commitParser) uniqMentions(mentions []string) []string {
return arr return arr
} }
func (p *commitParser) processJiraIssue(commit *Commit, issueId string) { func (p *commitParser) processJiraIssue(commit *Commit, issueID string) {
issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueId) issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueID)
if err != nil { if err != nil {
p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueId, err)) p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueID, err))
return return
} }
commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name] commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name]

View file

@ -33,7 +33,7 @@ func TestCommitParserParse(t *testing.T) {
mock, nil, &Config{ mock, nil, &Config{
Options: &Options{ Options: &Options{
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
"fix", "fix",
"perf", "perf",
@ -79,7 +79,7 @@ func TestCommitParserParse(t *testing.T) {
commits, err := parser.Parse("HEAD") commits, err := parser.Parse("HEAD")
assert.Nil(err) assert.Nil(err)
assert.Equal([]*Commit{ assert.Equal([]*Commit{
&Commit{ {
Hash: &Hash{ Hash: &Hash{
Long: "65cf1add9735dcc4810dda3312b0792236c97c4e", Long: "65cf1add9735dcc4810dda3312b0792236c97c4e",
Short: "65cf1add", Short: "65cf1add",
@ -97,7 +97,7 @@ func TestCommitParserParse(t *testing.T) {
Merge: nil, Merge: nil,
Revert: nil, Revert: nil,
Refs: []*Ref{ Refs: []*Ref{
&Ref{ {
Action: "", Action: "",
Ref: "123", Ref: "123",
Source: "", Source: "",
@ -111,7 +111,7 @@ func TestCommitParserParse(t *testing.T) {
Subject: "Add new feature #123", Subject: "Add new feature #123",
Body: "", Body: "",
}, },
&Commit{ {
Hash: &Hash{ Hash: &Hash{
Long: "14ef0b6d386c5432af9292eab3c8314fa3001bc7", Long: "14ef0b6d386c5432af9292eab3c8314fa3001bc7",
Short: "14ef0b6d", Short: "14ef0b6d",
@ -132,24 +132,24 @@ func TestCommitParserParse(t *testing.T) {
}, },
Revert: nil, Revert: nil,
Refs: []*Ref{ Refs: []*Ref{
&Ref{ {
Action: "", Action: "",
Ref: "3", Ref: "3",
Source: "", Source: "",
}, },
&Ref{ {
Action: "Fixes", Action: "Fixes",
Ref: "3", Ref: "3",
Source: "", Source: "",
}, },
&Ref{ {
Action: "Closes", Action: "Closes",
Ref: "1", Ref: "1",
Source: "", Source: "",
}, },
}, },
Notes: []*Note{ Notes: []*Note{
&Note{ {
Title: "BREAKING CHANGE", Title: "BREAKING CHANGE",
Body: "This is breaking point message.", Body: "This is breaking point message.",
}, },
@ -167,7 +167,7 @@ Closes #1
BREAKING CHANGE: This is breaking point message.`, BREAKING CHANGE: This is breaking point message.`,
}, },
&Commit{ {
Hash: &Hash{ Hash: &Hash{
Long: "809a8280ffd0dadb0f4e7ba9fc835e63c37d6af6", Long: "809a8280ffd0dadb0f4e7ba9fc835e63c37d6af6",
Short: "809a8280", Short: "809a8280",
@ -201,7 +201,7 @@ BREAKING CHANGE: This is breaking point message.`,
@hogefuga @hogefuga
@FooBarBaz`, @FooBarBaz`,
}, },
&Commit{ {
Hash: &Hash{ Hash: &Hash{
Long: "74824d6bd1470b901ec7123d13a76a1b8938d8d0", Long: "74824d6bd1470b901ec7123d13a76a1b8938d8d0",
Short: "74824d6b", Short: "74824d6b",
@ -219,19 +219,19 @@ BREAKING CHANGE: This is breaking point message.`,
Merge: nil, Merge: nil,
Revert: nil, Revert: nil,
Refs: []*Ref{ Refs: []*Ref{
&Ref{ {
Action: "Fixes", Action: "Fixes",
Ref: "123", Ref: "123",
Source: "", Source: "",
}, },
&Ref{ {
Action: "Closes", Action: "Closes",
Ref: "456", Ref: "456",
Source: "username/repository", Source: "username/repository",
}, },
}, },
Notes: []*Note{ Notes: []*Note{
&Note{ {
Title: "BREAKING CHANGE", Title: "BREAKING CHANGE",
Body: fmt.Sprintf(`This is multiline breaking change note. Body: fmt.Sprintf(`This is multiline breaking change note.
It is treated as the body of the Note until a mention or reference appears. It is treated as the body of the Note until a mention or reference appears.
@ -281,7 +281,7 @@ class MyController extends Controller {
Fixes #123 Fixes #123
Closes username/repository#456`, "```", "```"), Closes username/repository#456`, "```", "```"),
}, },
&Commit{ {
Hash: &Hash{ Hash: &Hash{
Long: "123456789735dcc4810dda3312b0792236c97c4e", Long: "123456789735dcc4810dda3312b0792236c97c4e",
Short: "12345678", Short: "12345678",
@ -381,7 +381,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
mock, mockJiraClient{}, &Config{ mock, mockJiraClient{}, &Config{
Options: &Options{ Options: &Options{
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
"fix", "fix",
"perf", "perf",
@ -391,7 +391,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$", HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$",
HeaderPatternMaps: []string{ HeaderPatternMaps: []string{
"Type", "Type",
"JiraIssueId", "JiraIssueID",
"Subject", "Subject",
}, },
JiraTypeMaps: map[string]string{ JiraTypeMaps: map[string]string{
@ -403,7 +403,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
commits, err := parser.Parse("HEAD") commits, err := parser.Parse("HEAD")
assert.Nil(err) assert.Nil(err)
commit := commits[0] commit := commits[0]
assert.Equal(commit.JiraIssueId, "JIRA-1111") assert.Equal(commit.JiraIssueID, "JIRA-1111")
assert.Equal(commit.JiraIssue.Type, "Story") assert.Equal(commit.JiraIssue.Type, "Story")
assert.Equal(commit.JiraIssue.Summary, "summary of JIRA-1111") assert.Equal(commit.JiraIssue.Summary, "summary of JIRA-1111")
assert.Equal(commit.JiraIssue.Description, "description of JIRA-1111") assert.Equal(commit.JiraIssue.Description, "description of JIRA-1111")

View file

@ -19,7 +19,7 @@ func Example() {
}, },
Options: &Options{ Options: &Options{
CommitFilters: map[string][]string{ CommitFilters: map[string][]string{
"Type": []string{ "Type": {
"feat", "feat",
"fix", "fix",
}, },

View file

@ -52,7 +52,7 @@ type NoteGroup struct {
Notes []*Note Notes []*Note
} }
// JiraIssue // JiraIssue is information about a jira ticket (type, summary, description, and labels)
type JiraIssue struct { type JiraIssue struct {
Type string Type string
Summary string Summary string
@ -69,13 +69,13 @@ type Commit struct {
Revert *Revert // If it is not a revert commit, `nil` is assigned Revert *Revert // If it is not a revert commit, `nil` is assigned
Refs []*Ref Refs []*Ref
Notes []*Note Notes []*Note
Mentions []string // Name of the user included in the commit header or body Mentions []string // Name of the user included in the commit header or body
JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned
Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`) Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
Type string // (e.g. `feat`) Type string // (e.g. `feat`)
Scope string // (e.g. `core`) Scope string // (e.g. `core`)
Subject string // (e.g. `Add new feature`) Subject string // (e.g. `Add new feature`)
JiraIssueId string // (e.g. `RNWY-310`) JiraIssueID string // (e.g. `RNWY-310`)
Body string Body string
} }

View file

@ -4,6 +4,7 @@ import (
agjira "github.com/andygrunwald/go-jira" agjira "github.com/andygrunwald/go-jira"
) )
// JiraClient is an HTTP client for Jira
type JiraClient interface { type JiraClient interface {
GetJiraIssue(id string) (*agjira.Issue, error) GetJiraIssue(id string) (*agjira.Issue, error)
} }
@ -14,11 +15,12 @@ type jiraClient struct {
url string url string
} }
// NewJiraClient returns an instance of JiraClient
func NewJiraClient(config *Config) JiraClient { func NewJiraClient(config *Config) JiraClient {
return jiraClient{ return jiraClient{
username: config.Options.JiraUsername, username: config.Options.JiraUsername,
token: config.Options.JiraToken, token: config.Options.JiraToken,
url: config.Options.JiraUrl, url: config.Options.JiraURL,
} }
} }

View file

@ -1,14 +1,15 @@
package chglog package chglog
import ( import (
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestJira(t *testing.T) { func TestJira(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
config := &Config { config := &Config{
Options: &Options{ Options: &Options{
Processor: nil, Processor: nil,
NextTag: "", NextTag: "",
@ -29,7 +30,7 @@ func TestJira(t *testing.T) {
NoteKeywords: nil, NoteKeywords: nil,
JiraUsername: "uuu", JiraUsername: "uuu",
JiraToken: "ppp", JiraToken: "ppp",
JiraUrl: "http://jira.com", JiraURL: "http://jira.com",
JiraTypeMaps: nil, JiraTypeMaps: nil,
JiraIssueDescriptionPattern: "", JiraIssueDescriptionPattern: "",
}, },

View file

@ -7,7 +7,7 @@ import (
"regexp" "regexp"
"github.com/fatih/color" "github.com/fatih/color"
emoji "github.com/kyokomi/emoji/v2" "github.com/kyokomi/emoji/v2"
) )
// Logger ... // Logger ...
@ -26,7 +26,7 @@ func NewLogger(stdout, stderr io.Writer, silent, noEmoji bool) *Logger {
stderr: stderr, stderr: stderr,
silent: silent, silent: silent,
noEmoji: noEmoji, noEmoji: noEmoji,
reEmoji: regexp.MustCompile(":[\\w\\+_\\-]+:\\s?"), reEmoji: regexp.MustCompile(`:[\w\+_\-]+:\s?`),
} }
} }

View file

@ -6,8 +6,8 @@ import (
"testing" "testing"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/stretchr/testify/assert"
emoji "github.com/kyokomi/emoji/v2" emoji "github.com/kyokomi/emoji/v2"
"github.com/stretchr/testify/assert"
) )
func TestLoggerLogSilent(t *testing.T) { func TestLoggerLogSilent(t *testing.T) {
@ -40,7 +40,7 @@ func TestLoggerLogNoEmoji(t *testing.T) {
stderr := &bytes.Buffer{} stderr := &bytes.Buffer{}
logger := NewLogger(stdout, stderr, false, true) logger := NewLogger(stdout, stderr, false, true)
logger.Log(":+1:Hello, World! :)") logger.Log(":+1:Hello, World! :)")
assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String()) assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String()) //nolint:gosimple
} }
func TestLoggerError(t *testing.T) { func TestLoggerError(t *testing.T) {

View file

@ -33,8 +33,8 @@ func (p *GitHubProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/") p.Host = strings.TrimRight(p.Host, "/")
} }
p.reMention = regexp.MustCompile("@(\\w+)") p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile("(?i)(#|gh-)(\\d+)") p.reIssue = regexp.MustCompile(`(?i)(#|gh-)(\d+)`)
} }
// ProcessCommit ... // ProcessCommit ...
@ -88,8 +88,8 @@ func (p *GitLabProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/") p.Host = strings.TrimRight(p.Host, "/")
} }
p.reMention = regexp.MustCompile("@(\\w+)") p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile("(?i)#(\\d+)") p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
} }
// ProcessCommit ... // ProcessCommit ...
@ -143,8 +143,8 @@ func (p *BitbucketProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/") p.Host = strings.TrimRight(p.Host, "/")
} }
p.reMention = regexp.MustCompile("@(\\w+)") p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile("(?i)#(\\d+)") p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
} }
// ProcessCommit ... // ProcessCommit ...

View file

@ -27,7 +27,7 @@ func TestGitHubProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789) multiline [#789](https://example.com/issues/789)
[@foo](https://github.com/foo), [@bar](https://github.com/bar)`, [@foo](https://github.com/foo), [@bar](https://github.com/bar)`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 [#11](https://example.com/issues/11) Body: `issue1 [#11](https://example.com/issues/11)
issue2 [#22](https://example.com/issues/22) issue2 [#22](https://example.com/issues/22)
[gh-56](https://example.com/issues/56) hoge fuga`, [gh-56](https://example.com/issues/56) hoge fuga`,
@ -42,7 +42,7 @@ issue2 [#22](https://example.com/issues/22)
multiline #789 multiline #789
@foo, @bar`, @foo, @bar`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 #11 Body: `issue1 #11
issue2 #22 issue2 #22
gh-56 hoge fuga`, gh-56 hoge fuga`,
@ -89,7 +89,7 @@ func TestGitLabProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789) multiline [#789](https://example.com/issues/789)
[@foo](https://gitlab.com/foo), [@bar](https://gitlab.com/bar)`, [@foo](https://gitlab.com/foo), [@bar](https://gitlab.com/bar)`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 [#11](https://example.com/issues/11) Body: `issue1 [#11](https://example.com/issues/11)
issue2 [#22](https://example.com/issues/22) issue2 [#22](https://example.com/issues/22)
gh-56 hoge fuga`, gh-56 hoge fuga`,
@ -104,7 +104,7 @@ gh-56 hoge fuga`,
multiline #789 multiline #789
@foo, @bar`, @foo, @bar`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 #11 Body: `issue1 #11
issue2 #22 issue2 #22
gh-56 hoge fuga`, gh-56 hoge fuga`,
@ -151,7 +151,7 @@ func TestBitbucketProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789/) multiline [#789](https://example.com/issues/789/)
[@foo](https://bitbucket.org/foo/), [@bar](https://bitbucket.org/bar/)`, [@foo](https://bitbucket.org/foo/), [@bar](https://bitbucket.org/bar/)`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 [#11](https://example.com/issues/11/) Body: `issue1 [#11](https://example.com/issues/11/)
issue2 [#22](https://example.com/issues/22/) issue2 [#22](https://example.com/issues/22/)
gh-56 hoge fuga`, gh-56 hoge fuga`,
@ -166,7 +166,7 @@ gh-56 hoge fuga`,
multiline #789 multiline #789
@foo, @bar`, @foo, @bar`,
Notes: []*Note{ Notes: []*Note{
&Note{ {
Body: `issue1 #11 Body: `issue1 #11
issue2 #22 issue2 #22
gh-56 hoge fuga`, gh-56 hoge fuga`,

View file

@ -12,7 +12,6 @@ import (
type tagReader struct { type tagReader struct {
client gitcmd.Client client gitcmd.Client
format string
separator string separator string
reFilter *regexp.Regexp reFilter *regexp.Regexp
} }
@ -36,7 +35,7 @@ func (r *tagReader) ReadAll() ([]*Tag, error) {
tags := []*Tag{} tags := []*Tag{}
if err != nil { if err != nil {
return tags, fmt.Errorf("failed to get git-tag: %s", err.Error()) return tags, fmt.Errorf("failed to get git-tag: %w", err)
} }
lines := strings.Split(out, "\n") lines := strings.Split(out, "\n")

View file

@ -33,7 +33,7 @@ func TestTagReader(t *testing.T) {
assert.Equal( assert.Equal(
[]*Tag{ []*Tag{
&Tag{ {
Name: "hoge_fuga", Name: "hoge_fuga",
Subject: "Invalid semver tag name", Subject: "Invalid semver tag name",
Date: time.Date(2018, 3, 12, 12, 30, 10, 0, time.UTC), Date: time.Date(2018, 3, 12, 12, 30, 10, 0, time.UTC),
@ -44,7 +44,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC), Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
}, },
}, },
&Tag{ {
Name: "5.0.0-rc.0", Name: "5.0.0-rc.0",
Subject: "Release 5.0.0-rc.0", Subject: "Release 5.0.0-rc.0",
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC), Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
@ -59,7 +59,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC), Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
}, },
}, },
&Tag{ {
Name: "4.4.4", Name: "4.4.4",
Subject: "Release 4.4.4", Subject: "Release 4.4.4",
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC), Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
@ -74,7 +74,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC), Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
}, },
}, },
&Tag{ {
Name: "4.4.3", Name: "4.4.3",
Subject: "This is tag subject", Subject: "This is tag subject",
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC), Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
@ -89,7 +89,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
}, },
}, },
&Tag{ {
Name: "v2.0.4-beta.1", Name: "v2.0.4-beta.1",
Subject: "Release v2.0.4-beta.1", Subject: "Release v2.0.4-beta.1",
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
@ -104,11 +104,11 @@ func TestTagReader(t *testing.T) {
actual, actual,
) )
actual_filtered, err_filtered := newTagReader(client, "^v").ReadAll() actualFiltered, errFiltered := newTagReader(client, "^v").ReadAll()
assert.Nil(err_filtered) assert.Nil(errFiltered)
assert.Equal( assert.Equal(
[]*Tag{ []*Tag{
&Tag{ {
Name: "v2.0.4-beta.1", Name: "v2.0.4-beta.1",
Subject: "Release v2.0.4-beta.1", Subject: "Release v2.0.4-beta.1",
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
@ -116,6 +116,6 @@ func TestTagReader(t *testing.T) {
Previous: nil, Previous: nil,
}, },
}, },
actual_filtered, actualFiltered,
) )
} }

View file

@ -19,14 +19,16 @@ func (s *tagSelector) Select(tags []*Tag, query string) ([]*Tag, string, error)
case 2: case 2:
old := tokens[0] old := tokens[0]
new := tokens[1] new := tokens[1]
if old == "" && new == "" { switch {
case old == "" && new == "":
return nil, "", nil return nil, "", nil
} else if old == "" { case old == "":
return s.selectBeforeTags(tags, new) return s.selectBeforeTags(tags, new)
} else if new == "" { case new == "":
return s.selectAfterTags(tags, old) return s.selectAfterTags(tags, old)
default:
return s.selectRangeTags(tags, tokens[0], tokens[1])
} }
return s.selectRangeTags(tags, tokens[0], tokens[1])
} }
return nil, "", errFailedQueryParse return nil, "", errFailedQueryParse
@ -76,7 +78,8 @@ func (*tagSelector) selectBeforeTags(tags []*Tag, token string) ([]*Tag, string,
} }
func (*tagSelector) selectAfterTags(tags []*Tag, token string) ([]*Tag, string, error) { func (*tagSelector) selectAfterTags(tags []*Tag, token string) ([]*Tag, string, error) {
var ( // NOTE(clok): the res slice can range in size based on the token passed in.
var ( //nolint:prealloc
res []*Tag res []*Tag
from string from string
) )

View file

@ -12,51 +12,51 @@ func TestTagSelector(t *testing.T) {
selector := newTagSelector() selector := newTagSelector()
fixtures := []*Tag{ fixtures := []*Tag{
&Tag{Name: "2.2.12-rc.12"}, {Name: "2.2.12-rc.12"},
&Tag{Name: "2.1.0"}, {Name: "2.1.0"},
&Tag{Name: "v2.0.0-beta.1"}, {Name: "v2.0.0-beta.1"},
&Tag{Name: "v1.2.9"}, {Name: "v1.2.9"},
&Tag{Name: "v1.0.0"}, {Name: "v1.0.0"},
} }
table := map[string][]string{ table := map[string][]string{
// Single // Single
"2.2.12-rc.12": []string{ "2.2.12-rc.12": {
"2.2.12-rc.12", "2.2.12-rc.12",
"2.1.0", "2.1.0",
}, },
"v2.0.0-beta.1": []string{ "v2.0.0-beta.1": {
"v2.0.0-beta.1", "v2.0.0-beta.1",
"v1.2.9", "v1.2.9",
}, },
"v1.0.0": []string{ "v1.0.0": {
"v1.0.0", "v1.0.0",
"", "",
}, },
// ~ <tag> // ~ <tag>
"..2.1.0": []string{ "..2.1.0": {
"2.1.0", "2.1.0",
"v2.0.0-beta.1", "v2.0.0-beta.1",
"v1.2.9", "v1.2.9",
"v1.0.0", "v1.0.0",
"", "",
}, },
"..v1.0.0": []string{ "..v1.0.0": {
"v1.0.0", "v1.0.0",
"", "",
}, },
// <tag> ~ // <tag> ~
"v2.0.0-beta.1..": []string{ "v2.0.0-beta.1..": {
"2.2.12-rc.12", "2.2.12-rc.12",
"2.1.0", "2.1.0",
"v2.0.0-beta.1", "v2.0.0-beta.1",
"v1.2.9", "v1.2.9",
}, },
"2.2.12-rc.12..": []string{ "2.2.12-rc.12..": {
"2.2.12-rc.12", "2.2.12-rc.12",
"2.1.0", "2.1.0",
}, },
"v1.0.0..": []string{ "v1.0.0..": {
"2.2.12-rc.12", "2.2.12-rc.12",
"2.1.0", "2.1.0",
"v2.0.0-beta.1", "v2.0.0-beta.1",
@ -65,7 +65,7 @@ func TestTagSelector(t *testing.T) {
"", "",
}, },
// <tag> ~ <tag> // <tag> ~ <tag>
"v1.0.0..2.2.12-rc.12": []string{ "v1.0.0..2.2.12-rc.12": {
"2.2.12-rc.12", "2.2.12-rc.12",
"2.1.0", "2.1.0",
"v2.0.0-beta.1", "v2.0.0-beta.1",
@ -73,13 +73,13 @@ func TestTagSelector(t *testing.T) {
"v1.0.0", "v1.0.0",
"", "",
}, },
"v1.0.0..v2.0.0-beta.1": []string{ "v1.0.0..v2.0.0-beta.1": {
"v2.0.0-beta.1", "v2.0.0-beta.1",
"v1.2.9", "v1.2.9",
"v1.0.0", "v1.0.0",
"", "",
}, },
"v1.2.9..2.1.0": []string{ "v1.2.9..2.1.0": {
"2.1.0", "2.1.0",
"v2.0.0-beta.1", "v2.0.0-beta.1",
"v1.2.9", "v1.2.9",