Skip to content

Commit 29c5ef5

Browse files
committed
feat: add support for git autosquash commits
`fixup!`, `squash!`, and `amend!`` messages are now accepted by default, with a new `rejectAutoSquash` option to explicitly disallow them.
1 parent 53b0619 commit 29c5ef5

3 files changed

Lines changed: 86 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ policies:
8484
scopes:
8585
- "scope"
8686
descriptionLength: 72
87+
rejectAutoSquash: false
8788
- type: license
8889
spec:
8990
skipPaths:

internal/policy/commit/check_conventional_commit.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ type Conventional struct {
1919
Types []string `mapstructure:"types"`
2020
Scopes []string `mapstructure:"scopes"`
2121
DescriptionLength int `mapstructure:"descriptionLength"`
22+
RejectAutoSquash bool `mapstructure:"rejectAutoSquash"`
2223
}
2324

2425
// HeaderRegex is the regular expression used for Conventional Commits 1.0.0.
2526
var HeaderRegex = regexp.MustCompile(`^(\w*)(\(([^)]+)\))?(!)?:\s{1}(.*)($|\n{2})`)
2627

28+
// AutoSquashHeaderRegex matches git autosquash commit messages.
29+
var AutoSquashHeaderRegex = regexp.MustCompile(`^(fixup|squash|amend)!\s+.+`)
30+
2731
const (
2832
// TypeFeat is a commit of the type fix patches a bug in your codebase
2933
// (this correlates with MINOR in semantic versioning).
@@ -62,7 +66,19 @@ func (c ConventionalCommitCheck) Errors() []error {
6266
// ValidateConventionalCommit returns the commit type.
6367
func (c Commit) ValidateConventionalCommit() policy.Check { //nolint:ireturn
6468
check := &ConventionalCommitCheck{}
65-
groups := parseHeader(c.msg)
69+
header := firstHeaderLine(c.msg)
70+
71+
if isAutoSquashMessage(header) {
72+
if !c.Conventional.RejectAutoSquash {
73+
return check
74+
}
75+
76+
check.errors = append(check.errors, errors.Errorf("Auto-squash commit format is disabled: %q", c.msg))
77+
78+
return check
79+
}
80+
81+
groups := parseHeader(header)
6682

6783
if len(groups) != 7 {
6884
check.errors = append(check.errors, errors.Errorf("Invalid conventional commits format: %q", c.msg))
@@ -124,12 +140,17 @@ func (c Commit) ValidateConventionalCommit() policy.Check { //nolint:ireturn
124140
return check
125141
}
126142

127-
func parseHeader(msg string) []string {
128-
// To circumvent any policy violation due to the leading \n that GitHub
129-
// prefixes to the commit message on a squash merge, we remove it from the
130-
// message.
131-
header := strings.Split(strings.TrimPrefix(msg, "\n"), "\n")[0]
143+
func parseHeader(header string) []string {
132144
groups := HeaderRegex.FindStringSubmatch(header)
133145

134146
return groups
135147
}
148+
149+
func isAutoSquashMessage(header string) bool {
150+
return AutoSquashHeaderRegex.MatchString(header)
151+
}
152+
153+
func firstHeaderLine(msg string) string {
154+
// GitHub can prefix squash-merge commit messages with a leading newline.
155+
return strings.Split(strings.TrimPrefix(msg, "\n"), "\n")[0]
156+
}

internal/policy/commit/commit_test.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func TestConventionalCommitPolicy(t *testing.T) {
2222
Name string
2323
CreateCommit func(t *testing.T) error
2424
ExpectValid bool
25+
Conventional *Conventional
2526
}
2627

2728
for _, test := range []testDesc{
@@ -60,6 +61,27 @@ func TestConventionalCommitPolicy(t *testing.T) {
6061
CreateCommit: createInvalidEmptyCommit,
6162
ExpectValid: false,
6263
},
64+
{
65+
Name: "Fixup",
66+
CreateCommit: createFixupCommit,
67+
ExpectValid: true,
68+
},
69+
{
70+
Name: "FixupRejected",
71+
CreateCommit: createFixupCommit,
72+
ExpectValid: false,
73+
Conventional: rejectAutoSquashConventional(),
74+
},
75+
{
76+
Name: "Squash",
77+
CreateCommit: createSquashCommit,
78+
ExpectValid: true,
79+
},
80+
{
81+
Name: "Amend",
82+
CreateCommit: createAmendCommit,
83+
ExpectValid: true,
84+
},
6385
} {
6486
func(test testDesc) {
6587
t.Run(test.Name, func(tt *testing.T) {
@@ -77,7 +99,7 @@ func TestConventionalCommitPolicy(t *testing.T) {
7799
tt.Error(err)
78100
}
79101

80-
report, err := runCompliance()
102+
report, err := runComplianceWithConventional(test.Conventional)
81103
if err != nil {
82104
t.Error(err)
83105
}
@@ -363,16 +385,30 @@ func runComplianceRange(id1, id2 string) (*policy.Report, error) {
363385
}
364386

365387
func runCompliance() (*policy.Report, error) {
366-
c := &Commit{
367-
Conventional: &Conventional{
388+
return runComplianceWithConventional(nil)
389+
}
390+
391+
func runComplianceWithConventional(conventional *Conventional) (*policy.Report, error) {
392+
if conventional == nil {
393+
conventional = &Conventional{
368394
Types: []string{"type"},
369395
Scopes: []string{"scope", "^valid"},
370-
},
396+
}
371397
}
372398

399+
c := &Commit{Conventional: conventional}
400+
373401
return c.Compliance(&policy.Options{})
374402
}
375403

404+
func rejectAutoSquashConventional() *Conventional {
405+
return &Conventional{
406+
Types: []string{"type"},
407+
Scopes: []string{"scope", "^valid"},
408+
RejectAutoSquash: true,
409+
}
410+
}
411+
376412
func initRepo(t *testing.T) error {
377413
_, err := exec.CommandContext(t.Context(), "git", "init").Output()
378414
if err != nil {
@@ -442,3 +478,21 @@ func createInvalidCommitRegex(t *testing.T) error {
442478

443479
return err
444480
}
481+
482+
func createFixupCommit(t *testing.T) error {
483+
_, err := exec.CommandContext(t.Context(), "git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "fixup! deadbeef").Output()
484+
485+
return err
486+
}
487+
488+
func createSquashCommit(t *testing.T) error {
489+
_, err := exec.CommandContext(t.Context(), "git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "squash! deadbeef").Output()
490+
491+
return err
492+
}
493+
494+
func createAmendCommit(t *testing.T) error {
495+
_, err := exec.CommandContext(t.Context(), "git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "amend! deadbeef").Output()
496+
497+
return err
498+
}

0 commit comments

Comments
 (0)