Skip to content

Commit 3b65632

Browse files
feat: support ignore patterns
Fixes: #8, #10
1 parent 01ce160 commit 3b65632

4 files changed

Lines changed: 143 additions & 16 deletions

File tree

config/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"os"
99
"path/filepath"
10+
"regexp"
1011

1112
"golang.org/x/mod/semver"
1213
yaml "gopkg.in/yaml.v2"
@@ -48,6 +49,9 @@ func Parse(confPath string) (*lint.Config, error) {
4849
conf.MinVersion = internal.Version()
4950
}
5051

52+
// Always set the built-in default patterns
53+
conf.DefaultIgnorePatterns = DefaultIgnorePatterns()
54+
5155
if conf.Formatter == "" {
5256
return nil, errors.New("config error: formatter is empty")
5357
}
@@ -124,6 +128,15 @@ func Validate(conf *lint.Config) []error {
124128
errs = append(errs, err)
125129
}
126130
}
131+
132+
// Validate ignore patterns (both default and user-defined)
133+
for _, pattern := range conf.EffectiveIgnorePatterns() {
134+
_, err := regexp.Compile(pattern)
135+
if err != nil {
136+
errs = append(errs, fmt.Errorf("invalid ignore pattern %q: %w", pattern, err))
137+
}
138+
}
139+
127140
return errs
128141
}
129142

config/default.go

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,51 @@ import (
77
"github.com/conventionalcommit/commitlint/rule"
88
)
99

10+
const (
11+
DefaultTypeCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
12+
DefaultScopeCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/,"
13+
)
14+
15+
// DefaultIgnorePatterns returns the default list of ignore patterns
16+
// These patterns match commit messages auto-generated by git commands
17+
// like merge, revert, fixup, squash, etc.
18+
func DefaultIgnorePatterns() []string {
19+
return []string{
20+
// GitHub / GitLab merge
21+
`^Merge pull request #\d+`,
22+
`^Merge .+ into .+`,
23+
`^Merge branch '.+'`,
24+
`^Merge tag '.+'`,
25+
`^Merge remote-tracking branch '.+'`,
26+
27+
// Azure DevOps / Bitbucket merge
28+
`^Merged .+ (in|into) .+`,
29+
`^Merged PR #?\d+`,
30+
31+
// Revert and Reapply
32+
`^(R|r)evert `,
33+
`^(R|r)eapply `,
34+
35+
// Fixup, Amend, Squash (git commit --fixup/--squash)
36+
`^(amend|fixup|squash)! `,
37+
38+
// Automatic merges
39+
`^Automatic merge`,
40+
`^Auto-merged .+ into .+`,
41+
42+
// Initial commit
43+
`^Initial commit$`,
44+
}
45+
}
46+
47+
// DefaultTypeEnums returns the default list of type enums
48+
func DefaultTypeEnums() []interface{} {
49+
return []interface{}{
50+
"feat", "fix", "docs", "style", "refactor", "perf",
51+
"test", "build", "ci", "chore", "revert",
52+
}
53+
}
54+
1055
// NewDefault returns default config
1156
func NewDefault() *lint.Config {
1257
// Enabled Rules
@@ -47,10 +92,7 @@ func NewDefault() *lint.Config {
4792

4893
// Types Enum Rule
4994
(&rule.TypeEnumRule{}).Name(): {
50-
Argument: []interface{}{
51-
"feat", "fix", "docs", "style", "refactor", "perf",
52-
"test", "build", "ci", "chore", "revert",
53-
},
95+
Argument: DefaultTypeEnums(),
5496
},
5597

5698
// Scope Enum Rule
@@ -113,12 +155,12 @@ func NewDefault() *lint.Config {
113155

114156
// Type Charset Rule
115157
(&rule.TypeCharsetRule{}).Name(): {
116-
Argument: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
158+
Argument: DefaultTypeCharset,
117159
},
118160

119161
// Scope Charset Rule
120162
(&rule.ScopeCharsetRule{}).Name(): {
121-
Argument: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/,",
163+
Argument: DefaultScopeCharset,
122164
},
123165

124166
// Footer Enum Rule
@@ -128,16 +170,17 @@ func NewDefault() *lint.Config {
128170

129171
// Footer Type Enum Rule
130172
(&rule.FooterTypeEnumRule{}).Name(): {
131-
Argument: []map[interface{}]interface{}{},
173+
Argument: []interface{}{},
132174
},
133175
}
134176

135177
def := &lint.Config{
136-
MinVersion: internal.Version(),
137-
Formatter: (&formatter.DefaultFormatter{}).Name(),
138-
Rules: rules,
139-
Severity: severity,
140-
Settings: settings,
178+
MinVersion: internal.Version(),
179+
Formatter: (&formatter.DefaultFormatter{}).Name(),
180+
Rules: rules,
181+
Severity: severity,
182+
Settings: settings,
183+
DefaultIgnorePatterns: DefaultIgnorePatterns(),
141184
}
142185
return def
143186
}

lint/config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@ type Config struct {
3333

3434
// Settings is rule name to rule settings
3535
Settings map[string]RuleSetting `yaml:"settings"`
36+
37+
// DisableDefaultIgnores disables the built-in ignore patterns
38+
// (merge, revert, fixup, squash, etc.) when set to true.
39+
DisableDefaultIgnores bool `yaml:"disable-default-ignores,omitempty"`
40+
41+
// IgnorePatterns is a list of user-defined regex patterns.
42+
// If the first line of the commit message matches any pattern,
43+
// linting is skipped. These are added on top of the default
44+
// patterns (unless DisableDefaultIgnores is true).
45+
IgnorePatterns []string `yaml:"ignores,omitempty"`
46+
47+
// DefaultIgnorePatterns holds the built-in patterns (set by config package).
48+
// Not serialized to YAML - users never set this directly.
49+
DefaultIgnorePatterns []string `yaml:"-"`
50+
}
51+
52+
// EffectiveIgnorePatterns returns the combined list of patterns the linter should use.
53+
// If DisableDefaultIgnores is true, only user-defined patterns are returned.
54+
func (c *Config) EffectiveIgnorePatterns() []string {
55+
if c.DisableDefaultIgnores {
56+
return c.IgnorePatterns
57+
}
58+
combined := make([]string, 0, len(c.DefaultIgnorePatterns)+len(c.IgnorePatterns))
59+
combined = append(combined, c.DefaultIgnorePatterns...)
60+
combined = append(combined, c.IgnorePatterns...)
61+
return combined
3662
}
3763

3864
// GetRule returns RuleConfig for given rule name

lint/linter.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
11
// Package lint provides a simple linter for conventional commits
22
package lint
33

4+
import (
5+
"fmt"
6+
"regexp"
7+
"strings"
8+
)
9+
410
// Linter is linter for commit message
511
type Linter struct {
612
conf *Config
713
rules []Rule
814

9-
parser Parser
15+
parser Parser
16+
ignorePatterns []*regexp.Regexp
1017
}
1118

1219
// New returns a new Linter instance with given config and rules
1320
func New(conf *Config, rules []Rule) (*Linter, error) {
21+
compiled, err := compilePatterns(conf.EffectiveIgnorePatterns())
22+
if err != nil {
23+
return nil, err
24+
}
25+
1426
l := &Linter{
15-
conf: conf,
16-
rules: rules,
17-
parser: newParser(),
27+
conf: conf,
28+
rules: rules,
29+
parser: newParser(),
30+
ignorePatterns: compiled,
1831
}
1932
return l, nil
2033
}
2134

2235
// ParseAndLint checks the given commitMsg string against rules
2336
func (l *Linter) ParseAndLint(commitMsg string) (*Result, error) {
37+
if l.isIgnored(commitMsg) {
38+
return newResult(commitMsg), nil
39+
}
40+
2441
msg, err := l.parser.Parse(commitMsg)
2542
if err != nil {
2643
issues := l.parserErrorRule(commitMsg, err)
@@ -29,6 +46,34 @@ func (l *Linter) ParseAndLint(commitMsg string) (*Result, error) {
2946
return l.Lint(msg)
3047
}
3148

49+
// isIgnored checks if the first line of the commit message
50+
// matches any of the configured ignore patterns
51+
func (l *Linter) isIgnored(commitMsg string) bool {
52+
if len(l.ignorePatterns) == 0 {
53+
return false
54+
}
55+
56+
firstLine := strings.Split(commitMsg, "\n")[0]
57+
for _, re := range l.ignorePatterns {
58+
if re.MatchString(firstLine) {
59+
return true
60+
}
61+
}
62+
return false
63+
}
64+
65+
func compilePatterns(patterns []string) ([]*regexp.Regexp, error) {
66+
compiled := make([]*regexp.Regexp, 0, len(patterns))
67+
for _, p := range patterns {
68+
re, err := regexp.Compile(p)
69+
if err != nil {
70+
return nil, fmt.Errorf("invalid ignore pattern %q: %w", p, err)
71+
}
72+
compiled = append(compiled, re)
73+
}
74+
return compiled, nil
75+
}
76+
3277
// Lint checks the given Commit against rules
3378
func (l *Linter) Lint(msg Commit) (*Result, error) {
3479
issues := make([]*Issue, 0, len(l.rules))

0 commit comments

Comments
 (0)