diff --git a/.conform.yaml b/.conform.yaml index 912f9a4a..f0cd7225 100644 --- a/.conform.yaml +++ b/.conform.yaml @@ -1,25 +1,26 @@ policies: -- type: commit - spec: - headerLength: 89 - dco: true - gpg: false - imperative: true - conventional: - types: - - chore - - docs - - perf - - refactor - - style - - test - scopes: - - policy -- type: license - spec: - includeSuffixes: - - .go - header: | - /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + - type: commit + spec: + headerLength: 89 + dco: true + gpg: false + imperative: true + maximumOfOneCommit: true + conventional: + types: + - chore + - docs + - perf + - refactor + - style + - test + scopes: + - policy + - type: license + spec: + includeSuffixes: + - .go + header: | + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ diff --git a/internal/git/git.go b/internal/git/git.go index e8f503a2..b40c4d11 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -14,6 +14,7 @@ import ( "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" + "gopkg.in/src-d/go-git.v4/plumbing/storer" ) // Git is a helper for git. @@ -134,3 +135,38 @@ func (g *Git) SHA() (sha string, err error) { return sha, nil } + +// AheadBehind returns the number of commits that HEAD is ahead and behind +// relative to the specified ref. +func (g *Git) AheadBehind(ref string) (ahead int, behind int, err error) { + ref1, err := g.repo.Reference(plumbing.ReferenceName(ref), false) + if err != nil { + return 0, 0, err + } + + ref2, err := g.repo.Head() + if err != nil { + return 0, 0, err + } + + commit2, err := object.GetCommit(g.repo.Storer, ref2.Hash()) + if err != nil { + return 0, 0, nil + } + + var count int + iter := object.NewCommitPreorderIter(commit2, nil, nil) + err = iter.ForEach(func(comm *object.Commit) error { + if comm.Hash != ref1.Hash() { + count++ + return nil + } + + return storer.ErrStop + }) + if err != nil { + return 0, 0, nil + } + + return count, 0, nil +} diff --git a/internal/policy/commit/check_number_of_commits.go b/internal/policy/commit/check_number_of_commits.go new file mode 100644 index 00000000..3c98201d --- /dev/null +++ b/internal/policy/commit/check_number_of_commits.go @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package commit + +import ( + "fmt" + + "github.com/autonomy/conform/internal/git" + "github.com/autonomy/conform/internal/policy" + "github.com/pkg/errors" +) + +// NumberOfCommits enforces a maximum number of charcters on the commit +// header. +type NumberOfCommits struct { + ref string + ahead int + errors []error +} + +// Name returns the name of the check. +func (h NumberOfCommits) Name() string { + return "Number of Commits" +} + +// Message returns to check message. +func (h NumberOfCommits) Message() string { + return fmt.Sprintf("HEAD is %d commit(s) ahead of %s", h.ahead, h.ref) +} + +// Errors returns any violations of the check. +func (h NumberOfCommits) Errors() []error { + return h.errors +} + +// ValidateNumberOfCommits checks the header length. +func (c Commit) ValidateNumberOfCommits(g *git.Git, ref string) policy.Check { + check := &NumberOfCommits{ + ref: ref, + } + + var err error + check.ahead, _, err = g.AheadBehind(ref) + if err != nil { + check.errors = append(check.errors, err) + return check + } + + if check.ahead > 1 { + check.errors = append(check.errors, errors.Errorf("HEAD is %d commit(s) ahead of %s", check.ahead, ref)) + return check + } + + return check +} diff --git a/internal/policy/commit/commit.go b/internal/policy/commit/commit.go index ae8078b0..f9d7d171 100644 --- a/internal/policy/commit/commit.go +++ b/internal/policy/commit/commit.go @@ -26,6 +26,9 @@ type Commit struct { // Imperative enforces the use of imperative verbs as the first word of a // commit message. Imperative bool `mapstructure:"imperative"` + // MaximumOfOneCommit enforces that the current commit is only one commit + // ahead of a specified ref. + MaximumOfOneCommit bool `mapstructure:"maximumOfOneCommit"` // Conventional is the user specified settings for conventional commits. Conventional *Conventional `mapstructure:"conventional"` @@ -73,6 +76,7 @@ func (c *Commit) Compliance(options *policy.Options) (*policy.Report, error) { if c.GPG { report.AddCheck(c.ValidateGPGSign(g)) } + if c.Imperative { report.AddCheck(c.ValidateImperative()) } @@ -81,6 +85,10 @@ func (c *Commit) Compliance(options *policy.Options) (*policy.Report, error) { report.AddCheck(c.ValidateConventionalCommit()) } + if c.MaximumOfOneCommit { + report.AddCheck(c.ValidateNumberOfCommits(g, "refs/heads/master")) + } + return report, nil }