Skip to content

Commit 3788f0c

Browse files
authored
added my take on the code changes needed (#2)
1 parent 9993827 commit 3788f0c

File tree

3 files changed

+231
-58
lines changed

3 files changed

+231
-58
lines changed

logcategories.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// LogsByCategory - Type to hold logs by each's category
9+
type LogsByCategory struct {
10+
CI []string
11+
FIX []string
12+
REFACTOR []string
13+
FEATURE []string
14+
DOCS []string
15+
OTHER []string
16+
}
17+
18+
// printCategory will output all items inside a Log slice and a title
19+
func printCategory(output *strings.Builder, title string, logs []string) {
20+
if len(logs) > 0 {
21+
output.WriteString(fmt.Sprintf("\n\n## %s \n", title))
22+
for _, item := range logs {
23+
output.WriteString(item + "\n")
24+
}
25+
}
26+
}
27+
28+
// GenerateMarkdown - Generate markdown output for the collected commits
29+
func (logContainer *LogsByCategory) GenerateMarkdown() string {
30+
var output strings.Builder
31+
output.WriteString("# Changelog \n")
32+
33+
printCategory(&output, "CI Changes", logContainer.CI)
34+
printCategory(&output, "Fixes", logContainer.FIX)
35+
printCategory(&output, "Performance Fixes", logContainer.REFACTOR)
36+
printCategory(&output, "Feature Fixes", logContainer.FEATURE)
37+
printCategory(&output, "Doc Updates", logContainer.DOCS)
38+
printCategory(&output, "Other Changes", logContainer.OTHER)
39+
40+
return output.String()
41+
}
42+
43+
// AddCommitLog will take a commitHash and a commitMessage and append them to their
44+
// apropriate Slice
45+
func (logContainer *LogsByCategory) AddCommitLog(commitHash, commitMessage string) {
46+
message := fmt.Sprintf("%s - %s", commitHash, normalizeCommit(commitMessage))
47+
48+
switch {
49+
case strings.Contains(commitMessage, "ci:"):
50+
logContainer.CI = append(logContainer.CI, message)
51+
case strings.Contains(commitMessage, "fix:"):
52+
logContainer.FIX = append(logContainer.FIX, message)
53+
case strings.Contains(commitMessage, "refactor:"):
54+
logContainer.REFACTOR = append(logContainer.REFACTOR, message)
55+
// Golang Switch allows multiple values in cases with , separation
56+
case strings.Contains(commitMessage, "feat:"), strings.Contains(commitMessage, "feature:"):
57+
logContainer.FEATURE = append(logContainer.FEATURE, message)
58+
case strings.Contains(commitMessage, "docs:"):
59+
logContainer.DOCS = append(logContainer.DOCS, message)
60+
61+
default:
62+
logContainer.OTHER = append(logContainer.OTHER, message)
63+
}
64+
}

main.go

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,68 @@
1-
// SPDX-License-Identifier: MIT
2-
31
package main
42

53
import (
4+
"flag"
65
"fmt"
76
"log"
87
"os"
98
"strings"
10-
11-
"github.com/go-git/go-git/v5"
12-
"github.com/go-git/go-git/v5/plumbing/object"
139
)
1410

15-
// ErrMessage - simple interface around error with a custom message
16-
type ErrMessage struct {
17-
message string
18-
err error
19-
}
20-
2111
func main() {
2212

23-
if len(os.Args) < 2 {
24-
fmt.Println("Usage:\n > commitlog path/to/repo")
25-
os.Exit(0)
26-
}
27-
28-
path := os.Args[1]
29-
30-
err := CommitLog(path)
13+
var path string
14+
// Read user input
15+
flag.StringVar(&path, "path", "", "A filepath to a folder containing a github repository")
16+
// Parse Flags
17+
flag.Parse()
3118

32-
if err.err != nil {
33-
log.Fatal(err.message, err.err)
19+
// Make sure user has inserted the needed flags
20+
if path == "" {
21+
flag.Usage()
22+
os.Exit(0)
3423
}
35-
}
36-
37-
// CommitLog - Generate commit log
38-
func CommitLog(path string) ErrMessage {
39-
currentRepository := openRepository(path)
40-
41-
baseCommitReference, err := currentRepository.Head()
4224

25+
repo, err := Open(path)
4326
if err != nil {
44-
return ErrMessage{"Unable to get repository HEAD:", err}
27+
log.Fatal(err)
4528
}
4629

47-
cIter, err := currentRepository.Log(&git.LogOptions{From: baseCommitReference.Hash()})
48-
30+
commits, err := repo.GetCommits()
4931
if err != nil {
50-
return ErrMessage{"Unable to get repository commits:", err}
32+
33+
log.Fatal(err)
5134
}
5235

53-
var commits []*object.Commit
36+
logContainer := new(LogsByCategory)
5437

55-
err = cIter.ForEach(func(c *object.Commit) error {
56-
commits = append(commits, c)
57-
return nil
58-
})
38+
// we no longer need to fetch latestTag here to compare tillLatest.
5939

60-
if err != nil {
61-
return ErrMessage{"Error getting commits : ", err}
62-
}
40+
// itterate all commits and add them to the log based on hash and Message
41+
for _, c := range commits {
6342

64-
logContainer := logsByCategory{}
43+
logContainer.AddCommitLog(c.Hash.String(), c.Message)
6544

66-
for _, c := range commits {
67-
normalizedHash := c.Hash.String() + " - " + normalizeCommit(c.Message)
68-
switch strings.SplitN(strings.TrimSpace(c.Message), ":", 2)[0] {
69-
case "ci":
70-
logContainer.CI = append(logContainer.CI, normalizedHash)
71-
case "fix":
72-
logContainer.FIX = append(logContainer.FIX, normalizedHash)
73-
case "refactor":
74-
logContainer.REFACTOR = append(logContainer.REFACTOR, normalizedHash)
75-
case "feat", "feature":
76-
logContainer.FEATURE = append(logContainer.FEATURE, normalizedHash)
77-
case "docs":
78-
logContainer.DOCS = append(logContainer.DOCS, normalizedHash)
79-
default:
80-
logContainer.OTHER = append(logContainer.OTHER, normalizedHash)
45+
nearestTag, err := repo.IsCommitNearest(c)
46+
if err != nil {
47+
log.Fatal(err)
8148
}
82-
if isCommitToNearestTag(currentRepository, c) {
49+
if nearestTag {
8350
break
8451
}
8552
}
8653
fmt.Println(logContainer.ToMarkdown())
8754

88-
return ErrMessage{}
55+
fmt.Println(logContainer.GenerateMarkdown())
56+
57+
}
58+
59+
func normalizeCommit(commitMessage string) string {
60+
var message string
61+
for i, msg := range strings.Split(commitMessage, "\n") {
62+
if i == 0 {
63+
message = msg
64+
break
65+
}
66+
}
67+
return strings.TrimSuffix(message, "\n")
8968
}

repository.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-git/go-git/v5"
7+
"github.com/go-git/go-git/v5/plumbing"
8+
"github.com/go-git/go-git/v5/plumbing/object"
9+
)
10+
11+
// Repository is a struct that holds an Opened github repository and the reference
12+
type Repository struct {
13+
path string
14+
repo *git.Repository
15+
ref *plumbing.Reference
16+
}
17+
18+
// Open will open up a git repository
19+
// and load the Head reference
20+
func Open(path string) (*Repository, error) {
21+
// Open the github repository
22+
r, err := git.PlainOpen(path)
23+
if err != nil {
24+
return nil, fmt.Errorf("%v:%w", "Error opening Repository: ", err)
25+
}
26+
27+
// Grab the Git HEAD reference
28+
ref, err := r.Head()
29+
if err != nil {
30+
return nil, fmt.Errorf("%v:%w", "Unable to get repository HEAD:", err)
31+
}
32+
return &Repository{
33+
path: path,
34+
repo: r,
35+
ref: ref,
36+
}, nil
37+
}
38+
39+
// GetCommits will extract commit history from the git repository
40+
func (r *Repository) GetCommits() ([]*object.Commit, error) {
41+
// Get the Commit history
42+
cIter, err := r.repo.Log(&git.LogOptions{From: r.ref.Hash()})
43+
44+
if err != nil {
45+
return nil, fmt.Errorf("%v:%w", "Unable to get repository commits:", err)
46+
}
47+
48+
var commits []*object.Commit
49+
50+
err = cIter.ForEach(func(c *object.Commit) error {
51+
commits = append(commits, c)
52+
return nil
53+
})
54+
55+
if err != nil {
56+
return nil, fmt.Errorf("%v:%w", "Error getting commits :", err)
57+
}
58+
return commits, nil
59+
}
60+
61+
// GetLatestTag is used to check the latestTag
62+
// it will return a reference to the LatestTag and the PreviousTag
63+
func (r *Repository) GetLatestTag() (*plumbing.Reference, *plumbing.Reference, error) {
64+
tagRefs, err := r.repo.Tags()
65+
if err != nil {
66+
return nil, nil, err
67+
}
68+
69+
var latestTagCommit *object.Commit
70+
var latestTagName *plumbing.Reference
71+
var previousTag *plumbing.Reference
72+
var previousTagReturn *plumbing.Reference
73+
74+
err = tagRefs.ForEach(func(tagRef *plumbing.Reference) error {
75+
revision := plumbing.Revision(tagRef.Name().String())
76+
77+
tagCommitHash, err := r.repo.ResolveRevision(revision)
78+
if err != nil {
79+
return err
80+
}
81+
82+
commit, err := r.repo.CommitObject(*tagCommitHash)
83+
if err != nil {
84+
return err
85+
}
86+
87+
if latestTagCommit == nil {
88+
latestTagCommit = commit
89+
latestTagName = tagRef
90+
previousTagReturn = previousTag
91+
}
92+
93+
if commit.Committer.When.After(latestTagCommit.Committer.When) {
94+
latestTagCommit = commit
95+
latestTagName = tagRef
96+
previousTagReturn = previousTag
97+
}
98+
99+
previousTag = tagRef
100+
101+
return nil
102+
})
103+
if err != nil {
104+
return nil, nil, err
105+
}
106+
107+
return latestTagName, previousTagReturn, nil
108+
}
109+
110+
// IsCommitNearest will check if a commit tag Hash is equal to the current repository HEAD tag
111+
// If the Hashes matches, it will return true
112+
func (r *Repository) IsCommitNearest(commit *object.Commit) (bool, error) {
113+
latestTag, previousTag, err := r.GetLatestTag()
114+
115+
if err != nil {
116+
return false, fmt.Errorf("%v:%w", "Couldn't get latest tag...", err)
117+
}
118+
119+
if latestTag != nil {
120+
// Compare latest tag Hash with the repository HEAD hash
121+
// Hash() returns a Slice which can be compared without converting to string
122+
// Noticed by /OfficialTomCruise on reddit comments
123+
if latestTag.Hash() == r.ref.Hash() {
124+
return true, nil
125+
}
126+
return previousTag.Hash() == commit.Hash, nil
127+
128+
}
129+
return false, nil
130+
}

0 commit comments

Comments
 (0)