From 3b0c098b16fb7e93b79c5cb259fbcc3725e9bb7b Mon Sep 17 00:00:00 2001
From: Christoph Witzko <github@christophwitzko.com>
Date: Tue, 4 Aug 2020 11:32:34 +0200
Subject: [PATCH] refactor: extract commit analyzer

---
 cmd/semantic-release/main.go       |  6 +++-
 pkg/analyzer/commit/commit.go      | 44 ++++++++++++++++++++++++++
 pkg/analyzer/commit/commit_test.go | 51 ++++++++++++++++++++++++++++++
 pkg/provider/github/github.go      |  9 ++++--
 pkg/provider/github/github_test.go | 21 ++----------
 pkg/provider/gitlab/gitlab.go      |  9 ++++--
 pkg/provider/gitlab/gitlab_test.go | 21 ++----------
 pkg/provider/repository.go         |  2 +-
 pkg/semrel/commit.go               | 30 +++---------------
 9 files changed, 123 insertions(+), 70 deletions(-)
 create mode 100644 pkg/analyzer/commit/commit.go
 create mode 100644 pkg/analyzer/commit/commit_test.go

diff --git a/cmd/semantic-release/main.go b/cmd/semantic-release/main.go
index 85d63e2..daa5a9f 100644
--- a/cmd/semantic-release/main.go
+++ b/cmd/semantic-release/main.go
@@ -9,6 +9,7 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/go-semantic-release/semantic-release/pkg/analyzer/commit"
 	"github.com/go-semantic-release/semantic-release/pkg/condition"
 	"github.com/go-semantic-release/semantic-release/pkg/config"
 	"github.com/go-semantic-release/semantic-release/pkg/generator/changelog"
@@ -128,9 +129,12 @@ func cliHandler(c *cli.Context) error {
 	}
 
 	logger.Println("getting commits...")
-	commits, err := repo.GetCommits(currentSha)
+	rawCommits, err := repo.GetCommits(currentSha)
 	exitIfError(err)
 
+	commitAnalyzer := &commit.DefaultAnalyzer{}
+	commits := commitAnalyzer.Analyze(rawCommits)
+
 	logger.Println("calculating new version...")
 	newVer := semrel.GetNewVersion(conf, commits, release)
 	if newVer == nil {
diff --git a/pkg/analyzer/commit/commit.go b/pkg/analyzer/commit/commit.go
new file mode 100644
index 0000000..9f58f96
--- /dev/null
+++ b/pkg/analyzer/commit/commit.go
@@ -0,0 +1,44 @@
+package commit
+
+import (
+	"regexp"
+	"strings"
+
+	"github.com/go-semantic-release/semantic-release/pkg/semrel"
+)
+
+type Analyzer interface {
+	Analyze([]*semrel.RawCommit) []*semrel.Commit
+}
+
+var commitPattern = regexp.MustCompile(`^(\w*)(?:\((.*)\))?\: (.*)$`)
+var breakingPattern = regexp.MustCompile("BREAKING CHANGES?")
+
+type DefaultAnalyzer struct{}
+
+func (da *DefaultAnalyzer) analyzeSingleCommit(rawCommit *semrel.RawCommit) *semrel.Commit {
+	c := new(semrel.Commit)
+	c.SHA = rawCommit.SHA
+	c.Raw = strings.Split(rawCommit.RawMessage, "\n")
+	found := commitPattern.FindAllStringSubmatch(c.Raw[0], -1)
+	if len(found) < 1 {
+		return c
+	}
+	c.Type = strings.ToLower(found[0][1])
+	c.Scope = found[0][2]
+	c.Message = found[0][3]
+	c.Change = semrel.Change{
+		Major: breakingPattern.MatchString(rawCommit.RawMessage),
+		Minor: c.Type == "feat",
+		Patch: c.Type == "fix",
+	}
+	return c
+}
+
+func (da *DefaultAnalyzer) Analyze(rawCommits []*semrel.RawCommit) []*semrel.Commit {
+	ret := make([]*semrel.Commit, len(rawCommits))
+	for i, c := range rawCommits {
+		ret[i] = da.analyzeSingleCommit(c)
+	}
+	return ret
+}
diff --git a/pkg/analyzer/commit/commit_test.go b/pkg/analyzer/commit/commit_test.go
new file mode 100644
index 0000000..c776ee0
--- /dev/null
+++ b/pkg/analyzer/commit/commit_test.go
@@ -0,0 +1,51 @@
+package commit
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/go-semantic-release/semantic-release/pkg/semrel"
+	"github.com/stretchr/testify/require"
+)
+
+func compareCommit(c *semrel.Commit, t, s string, change semrel.Change) bool {
+	if c.Type != t || c.Scope != s {
+		return false
+	}
+	if c.Change.Major != change.Major ||
+		c.Change.Minor != change.Minor ||
+		c.Change.Patch != change.Patch {
+		return false
+	}
+	return true
+}
+
+func createRawCommit(sha, message string) *semrel.RawCommit {
+	return &semrel.RawCommit{
+		SHA:        sha,
+		RawMessage: message,
+	}
+}
+
+func TestDefaultAnalyzer(t *testing.T) {
+	testCases := []struct {
+		RawCommit *semrel.RawCommit
+		Type      string
+		Scope     string
+		Change    semrel.Change
+	}{
+		{
+			createRawCommit("a", "feat: new feature"),
+			"feat",
+			"",
+			semrel.Change{Major: false, Minor: true, Patch: false},
+		},
+	}
+
+	defaultAnalyzer := &DefaultAnalyzer{}
+	for _, tc := range testCases {
+		t.Run(fmt.Sprintf("AnalyzeCommitMessage: %s", tc.RawCommit.RawMessage), func(t *testing.T) {
+			require.True(t, compareCommit(defaultAnalyzer.analyzeSingleCommit(tc.RawCommit), tc.Type, tc.Scope, tc.Change))
+		})
+	}
+}
diff --git a/pkg/provider/github/github.go b/pkg/provider/github/github.go
index 8755488..c53120c 100644
--- a/pkg/provider/github/github.go
+++ b/pkg/provider/github/github.go
@@ -57,7 +57,7 @@ func (repo *GitHubRepository) GetInfo() (*provider.RepositoryInfo, error) {
 	}, nil
 }
 
-func (repo *GitHubRepository) GetCommits(sha string) ([]*semrel.Commit, error) {
+func (repo *GitHubRepository) GetCommits(sha string) ([]*semrel.RawCommit, error) {
 	opts := &github.CommitsListOptions{
 		SHA:         sha,
 		ListOptions: github.ListOptions{PerPage: 100},
@@ -66,9 +66,12 @@ func (repo *GitHubRepository) GetCommits(sha string) ([]*semrel.Commit, error) {
 	if err != nil {
 		return nil, err
 	}
-	ret := make([]*semrel.Commit, len(commits))
+	ret := make([]*semrel.RawCommit, len(commits))
 	for i, commit := range commits {
-		ret[i] = semrel.NewCommit(commit.GetSHA(), commit.Commit.GetMessage())
+		ret[i] = &semrel.RawCommit{
+			SHA:        commit.GetSHA(),
+			RawMessage: commit.Commit.GetMessage(),
+		}
 	}
 	return ret, nil
 }
diff --git a/pkg/provider/github/github_test.go b/pkg/provider/github/github_test.go
index cc53286..99a33a2 100644
--- a/pkg/provider/github/github_test.go
+++ b/pkg/provider/github/github_test.go
@@ -12,7 +12,6 @@ import (
 
 	"github.com/Masterminds/semver"
 	"github.com/go-semantic-release/semantic-release/pkg/provider"
-	"github.com/go-semantic-release/semantic-release/pkg/semrel"
 	"github.com/google/go-github/v30/github"
 	"github.com/stretchr/testify/require"
 )
@@ -136,18 +135,6 @@ func TestGithubGetInfo(t *testing.T) {
 	require.True(t, repoInfo.Private)
 }
 
-func compareCommit(c *semrel.Commit, t, s string, change semrel.Change) bool {
-	if c.Type != t || c.Scope != s {
-		return false
-	}
-	if c.Change.Major != change.Major ||
-		c.Change.Minor != change.Minor ||
-		c.Change.Patch != change.Patch {
-		return false
-	}
-	return true
-}
-
 func TestGithubGetCommits(t *testing.T) {
 	repo, ts := getNewGithubTestRepo(t)
 	defer ts.Close()
@@ -155,11 +142,9 @@ func TestGithubGetCommits(t *testing.T) {
 	require.NoError(t, err)
 	require.Len(t, commits, 4)
 
-	if !compareCommit(commits[0], "feat", "app", semrel.Change{Major: false, Minor: true, Patch: false}) ||
-		!compareCommit(commits[1], "fix", "", semrel.Change{Major: false, Minor: false, Patch: true}) ||
-		!compareCommit(commits[2], "", "", semrel.Change{Major: false, Minor: false, Patch: false}) ||
-		!compareCommit(commits[3], "chore", "", semrel.Change{Major: true, Minor: false, Patch: false}) {
-		t.Fatal("invalid commits")
+	for i, c := range commits {
+		require.Equal(t, c.SHA, GITHUB_COMMITS[i].GetSHA())
+		require.Equal(t, c.RawMessage, GITHUB_COMMITS[i].Commit.GetMessage())
 	}
 }
 
diff --git a/pkg/provider/gitlab/gitlab.go b/pkg/provider/gitlab/gitlab.go
index 89df154..9f3c7a4 100644
--- a/pkg/provider/gitlab/gitlab.go
+++ b/pkg/provider/gitlab/gitlab.go
@@ -62,7 +62,7 @@ func (repo *GitLabRepository) GetInfo() (*provider.RepositoryInfo, error) {
 	}, nil
 }
 
-func (repo *GitLabRepository) GetCommits(sha string) ([]*semrel.Commit, error) {
+func (repo *GitLabRepository) GetCommits(sha string) ([]*semrel.RawCommit, error) {
 	opts := &gitlab.ListCommitsOptions{
 		ListOptions: gitlab.ListOptions{
 			Page:    1,
@@ -72,7 +72,7 @@ func (repo *GitLabRepository) GetCommits(sha string) ([]*semrel.Commit, error) {
 		All:     gitlab.Bool(true),
 	}
 
-	allCommits := make([]*semrel.Commit, 0)
+	allCommits := make([]*semrel.RawCommit, 0)
 
 	for {
 		commits, resp, err := repo.client.Commits.ListCommits(repo.projectID, opts)
@@ -82,7 +82,10 @@ func (repo *GitLabRepository) GetCommits(sha string) ([]*semrel.Commit, error) {
 		}
 
 		for _, commit := range commits {
-			allCommits = append(allCommits, semrel.NewCommit(commit.ID, commit.Message))
+			allCommits = append(allCommits, &semrel.RawCommit{
+				SHA:        commit.ID,
+				RawMessage: commit.Message,
+			})
 		}
 
 		if resp.CurrentPage >= resp.TotalPages {
diff --git a/pkg/provider/gitlab/gitlab_test.go b/pkg/provider/gitlab/gitlab_test.go
index 7ad9a5f..b953565 100644
--- a/pkg/provider/gitlab/gitlab_test.go
+++ b/pkg/provider/gitlab/gitlab_test.go
@@ -12,7 +12,6 @@ import (
 
 	"github.com/Masterminds/semver"
 	"github.com/go-semantic-release/semantic-release/pkg/provider"
-	"github.com/go-semantic-release/semantic-release/pkg/semrel"
 	"github.com/stretchr/testify/require"
 	"github.com/xanzy/go-gitlab"
 )
@@ -125,18 +124,6 @@ func TestGitlabGetInfo(t *testing.T) {
 	require.True(t, repoInfo.Private)
 }
 
-func compareCommit(c *semrel.Commit, t, s string, change semrel.Change) bool {
-	if c.Type != t || c.Scope != s {
-		return false
-	}
-	if c.Change.Major != change.Major ||
-		c.Change.Minor != change.Minor ||
-		c.Change.Patch != change.Patch {
-		return false
-	}
-	return true
-}
-
 func TestGitlabGetCommits(t *testing.T) {
 	repo, ts := getNewGitlabTestRepo(t)
 	defer ts.Close()
@@ -144,11 +131,9 @@ func TestGitlabGetCommits(t *testing.T) {
 	require.NoError(t, err)
 	require.Len(t, commits, 4)
 
-	if !compareCommit(commits[0], "feat", "app", semrel.Change{Major: false, Minor: true, Patch: false}) ||
-		!compareCommit(commits[1], "fix", "", semrel.Change{Major: false, Minor: false, Patch: true}) ||
-		!compareCommit(commits[2], "", "", semrel.Change{Major: false, Minor: false, Patch: false}) ||
-		!compareCommit(commits[3], "chore", "", semrel.Change{Major: true, Minor: false, Patch: false}) {
-		t.Fatal("invalid commits")
+	for i, c := range commits {
+		require.Equal(t, c.SHA, GITLAB_COMMITS[i].ID)
+		require.Equal(t, c.RawMessage, GITLAB_COMMITS[i].Message)
 	}
 }
 
diff --git a/pkg/provider/repository.go b/pkg/provider/repository.go
index 5f7ef91..ce8f837 100644
--- a/pkg/provider/repository.go
+++ b/pkg/provider/repository.go
@@ -24,7 +24,7 @@ type RepositoryRelease struct {
 
 type Repository interface {
 	GetInfo() (*RepositoryInfo, error)
-	GetCommits(sha string) ([]*semrel.Commit, error)
+	GetCommits(sha string) ([]*semrel.RawCommit, error)
 	GetReleases(re *regexp.Regexp) (semrel.Releases, error)
 	CreateRelease(*RepositoryRelease) error
 	Provider() string
diff --git a/pkg/semrel/commit.go b/pkg/semrel/commit.go
index 771eb79..9beb584 100644
--- a/pkg/semrel/commit.go
+++ b/pkg/semrel/commit.go
@@ -1,12 +1,9 @@
 package semrel
 
-import (
-	"regexp"
-	"strings"
-)
-
-var commitPattern = regexp.MustCompile(`^(\w*)(?:\((.*)\))?\: (.*)$`)
-var breakingPattern = regexp.MustCompile("BREAKING CHANGES?")
+type RawCommit struct {
+	SHA        string
+	RawMessage string
+}
 
 type Change struct {
 	Major, Minor, Patch bool
@@ -20,22 +17,3 @@ type Commit struct {
 	Message string
 	Change  Change
 }
-
-func NewCommit(sha, msg string) *Commit {
-	c := new(Commit)
-	c.SHA = sha
-	c.Raw = strings.Split(msg, "\n")
-	found := commitPattern.FindAllStringSubmatch(c.Raw[0], -1)
-	if len(found) < 1 {
-		return c
-	}
-	c.Type = strings.ToLower(found[0][1])
-	c.Scope = found[0][2]
-	c.Message = found[0][3]
-	c.Change = Change{
-		Major: breakingPattern.MatchString(msg),
-		Minor: c.Type == "feat",
-		Patch: c.Type == "fix",
-	}
-	return c
-}
-- 
GitLab