Skip to content

Commit

Permalink
(#16) Add commit filters (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
llorllale committed Feb 24, 2019
1 parent db191c3 commit b52f151
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 62 deletions.
3 changes: 3 additions & 0 deletions check_coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ let THRESHOLD=80

let exit_code=0

# @todo #16 The coverage script is ignoring packages that are not tested
# at all (0% coverage). It should be fixed so that all packages are
# tested (except for main).
while read line; do
if [ "$(echo $line | grep coverage)" != "" ]; then
pkg=$(echo $line | sed 's/\s\+/ /g' | sed 's/%//' | cut -d ' ' -f 2)
Expand Down
23 changes: 17 additions & 6 deletions cmd/go-gitlint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@ import (
"os"

"github.com/llorllale/go-gitlint/internal/commits"
"github.com/llorllale/go-gitlint/internal/commits/filter"
"github.com/llorllale/go-gitlint/internal/commits/issues"
"github.com/llorllale/go-gitlint/internal/repo"
)

// @todo #4 The path should be passed in as a command line
// flag instead of being hard coded. All other configuration
// options should be passed in through CLI as well.
func main() {
commits.Printed(
commits.In(
repo.Filesystem("."),
os.Exit(
len(
issues.Printed(
os.Stdout, "\n",
issues.Collected(
[]func(*commits.Commit) issues.Issue{
filter.OfSubjectRegex(".{,1}"),
filter.OfBodyRegex(".{,1}"),
},
commits.In(
repo.Filesystem("."),
),
),
)(),
),
os.Stdout,
"\n",
)()
)
}
48 changes: 9 additions & 39 deletions internal/commits/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package commits

import (
"fmt"
"io"
"strings"

"github.com/llorllale/go-gitlint/internal/repo"
Expand All @@ -34,24 +32,24 @@ type Commits func() []*Commit

// Commit holds data for a single git commit.
type Commit struct {
hash string
message string
Hash string
Message string
}

// Hash is the commit's ID.
func (c *Commit) Hash() string {
return c.hash
// ID is the commit's hash.
func (c *Commit) ID() string {
return c.Hash
}

// Subject is the commit message's subject line.
func (c *Commit) Subject() string {
return strings.Split(c.message, "\n\n")[0]
return strings.Split(c.Message, "\n\n")[0]
}

// Body is the commit message's body.
func (c *Commit) Body() string {
body := ""
parts := strings.Split(c.message, "\n\n")
parts := strings.Split(c.Message, "\n\n")
if len(parts) > 1 {
body = strings.Join(parts[1:], "")
}
Expand All @@ -78,40 +76,12 @@ func In(repository repo.Repo) Commits {
commits = append(
commits,
&Commit{
hash: c.Hash.String(),
message: c.Message,
Hash: c.Hash.String(),
Message: c.Message,
},
)
return nil
})
return commits
}
}

// Printed prints the commits to the file.
func Printed(commits Commits, writer io.Writer, sep string) Commits {
return func() []*Commit {
input := commits()
for _, c := range input {
_, err := writer.Write(
[]byte(fmt.Sprintf("%s%s", &pretty{c}, sep)),
)
if err != nil {
panic(err)
}
}
return input
}
}

// a Stringer for pretty-printing the commit.
type pretty struct {
*Commit
}

func (p *pretty) String() string {
return fmt.Sprintf(
"hash: %s subject=%s body=%s",
p.Commit.Hash(), p.Commit.Subject(), p.Commit.Body(),
)
}
40 changes: 23 additions & 17 deletions internal/commits/commits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package commits

import (
"bytes"
"fmt"
"io/ioutil"
"os"
Expand All @@ -31,6 +30,29 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing/object"
)

func TestCommitID(t *testing.T) {
const ID = "test ID"
assert.Equal(t,
(&Commit{Hash: ID}).ID(), ID,
"Commit.ID() must return the commit's hash")
}

func TestCommitSubject(t *testing.T) {
const subject = "test subject"
assert.Equal(t,
(&Commit{Message: subject + "\n\ntest body"}).Subject(),
subject,
`Commit.Subject() must return the substring before the first \n\n`)
}

func TestCommitBody(t *testing.T) {
const body = "test body"
assert.Equal(t,
(&Commit{Message: "test subject\n\n" + body}).Body(),
body,
`Commit.Body() must return the substring after the first \n\n`)
}

func TestIn(t *testing.T) {
msgs := []string{"subject1\n\nbody1", "subject2\n\nbody2", "subject3\n\nbody3"}
r, cleanup := tmpRepo(msgs...)
Expand All @@ -45,22 +67,6 @@ func TestIn(t *testing.T) {
}
}

func TestPrinted(t *testing.T) {
commit := &Commit{
hash: "abc123",
message: "this is a test message",
}
const sep = " "
buffer := &bytes.Buffer{}
_ = Printed(
func() []*Commit { return []*Commit{commit} },
buffer,
sep,
)()
assert.Equal(t, (&pretty{commit}).String()+sep, string(buffer.Bytes()),
"commits.Printed() did not pretty-print the commit correctly")
}

// A git repo initialized and with one commit per each of the messages provided.
// This repo is created in a temporary directory; use the cleanup function
// to delete it afterwards.
Expand Down
65 changes: 65 additions & 0 deletions internal/commits/filter/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2019 George Aristy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filter

import (
"fmt"
"regexp"

"github.com/llorllale/go-gitlint/internal/commits"
"github.com/llorllale/go-gitlint/internal/commits/issues"
)

/*
Filters identify issues with a commit.
A filter returning a zero-valued Issue signals that it found no issue
with the commit.
*/

// OfSubjectRegex tests a commit's subject with the regex.
func OfSubjectRegex(regex string) func(*commits.Commit) issues.Issue {
return func(c *commits.Commit) issues.Issue {
var issue issues.Issue
matched, err := regexp.MatchString(regex, c.Subject())
if err != nil {
panic(err)
}
if !matched {
issue = issues.Issue{
Desc: fmt.Sprintf("subject does not match regex [%s]", regex),
Commit: *c,
}
}
return issue
}
}

// OfBodyRegex tests a commit's body with the regex.
func OfBodyRegex(regex string) func(*commits.Commit) issues.Issue {
return func(c *commits.Commit) issues.Issue {
var issue issues.Issue
matched, err := regexp.MatchString(regex, c.Body())
if err != nil {
panic(err)
}
if !matched {
issue = issues.Issue{
Desc: fmt.Sprintf("body does not conform to regex [%s]", regex),
Commit: *c,
}
}
return issue
}
}
66 changes: 66 additions & 0 deletions internal/commits/filter/filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019 George Aristy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filter

import (
"testing"

"github.com/llorllale/go-gitlint/internal/commits"
"github.com/stretchr/testify/assert"
)

func TestOfSubjectRegexMatch(t *testing.T) {
assert.Zero(t,
OfSubjectRegex(`\(#\d+\) [\w ]{10,50}`)(
&commits.Commit{
Message: "(#123) This is a good subject",
},
),
"filter.OfSubjectRegex() must match if the commit's subject matches the regex",
)
}

func TestOfSubjectRegexNonMatch(t *testing.T) {
assert.NotZero(t,
OfSubjectRegex(`\(#\d+\) [\w ]{,50}`)(
&commits.Commit{
Message: "I break all the rules!",
},
),
"filter.OfSubjectRegex() must not match if the commit's subject does not match the regex",
)
}

func TestOfBodyRegexMatch(t *testing.T) {
assert.Zero(t,
OfBodyRegex(`^.{10,20}$`)(
&commits.Commit{
Message: "subject\n\nBetween 10 and 20",
},
),
"filter.OfBodyRegex() must match if the commit's subject matches the regex",
)
}

func TestOfBodyRegexNonMatch(t *testing.T) {
assert.NotZero(t,
OfBodyRegex(`^.{10,20}$`)(
&commits.Commit{
Message: "subject\n\nMore than twenty characters!",
},
),
"filter.OfBodyRegex() must not match if the commit's subject does not match the regex",
)
}
67 changes: 67 additions & 0 deletions internal/commits/issues/issues.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2019 George Aristy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package issues

import (
"fmt"
"io"

"github.com/llorllale/go-gitlint/internal/commits"
)

// Issue is a problem found with a commit.
type Issue struct {
Desc string
Commit commits.Commit
}

func (i *Issue) String() string {
return fmt.Sprintf("Issue{Desc=%s Commit=%+v}", i.Desc, i.Commit)
}

// Issues is a collection of Issues.
type Issues func() []Issue

// Collected returns a collection of issues identified.
func Collected(filters []func(c *commits.Commit) Issue, cmts commits.Commits) Issues {
return func() []Issue {
issues := make([]Issue, 0)
for _, c := range cmts() {
for _, f := range filters {
if issue := f(c); issue != (Issue{}) {
issues = append(issues, issue)
break
}
}
}
return issues
}
}

// Printed prints the issues to the writer.
func Printed(w io.Writer, sep string, issues Issues) Issues {
return func() []Issue {
iss := issues()
for i := range iss {
_, err := w.Write(
[]byte(fmt.Sprintf("%s%s", iss[i].String(), sep)),
)
if err != nil {
panic(err)
}
}
return iss
}
}
Loading

1 comment on commit b52f151

@0pdd
Copy link

@0pdd 0pdd commented on b52f151 Feb 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 16-e0d2cf8f discovered in check_coverage.sh and submitted as #18. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.