Skip to content

Commit 4c031f7

Browse files
Merge pull request #18 from NeowayLabs/add-commit-lint-option
type: [feat], message: Add commit lint option.
2 parents f5afdc2 + 0ccb779 commit 4c031f7

16 files changed

+997
-273
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,4 @@ githooks:
123123
@echo "git hooks copied"
124124

125125
shell: modcache imagedev
126-
$(run) sh
126+
$(run) sh

README.md

+90-15
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ stages:
4040

4141
semantic-release:
4242
stage: semantic-release
43-
only:
44-
refs:
45-
- master
46-
before_script:
47-
- docker pull registry.com/dataplatform/semantic-release:latest
43+
variables:
44+
SEMANTIC_RELEASE_VERSION: latest
45+
dependencies: []
46+
except:
47+
- master
48+
before_script:
49+
- docker pull registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION
4850
script:
49-
- docker run registry.com/dataplatform/semantic-release:latest up -git-host ${CI_SERVER_HOST} -git-group ${CI_PROJECT_NAMESPACE} -git-project ${CI_PROJECT_NAME} -username ${PPD2_USERNAME} -password ${PPD2_ACCESS_TOKEN}
50-
51+
- docker run registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION up -git-host ${CI_SERVER_HOST} -git-group ${CI_PROJECT_NAMESPACE} -git-project ${CI_PROJECT_NAME} -username ${PPD2_USERNAME} -password ${PPD2_ACCESS_TOKEN}
5152
```
5253
5354
If your project is a Python project you can add the flag `-setup-py true` to update the release version in this file too.
@@ -77,6 +78,73 @@ setup(
7778
)
7879
```
7980

81+
### Adding pre-commit message CLI
82+
83+
The `pre-commit` will validate your commit messages before the commit being accepted.
84+
That will prevent you from having to rebase your commit history to adapt your commit messages to [semantic-release](https://github.com/NeowayLabs/semantic-release) standards.
85+
86+
**Requirements**
87+
- [Golang installation](https://go.dev/doc/install)
88+
89+
90+
Clone pre-commit project and install it in you SO.
91+
92+
```
93+
git clone [email protected]:NeowayLabs/pre-commit.git
94+
```
95+
96+
```
97+
make install
98+
```
99+
100+
**How to use it?**
101+
102+
After adding new changes with the `git add` command, you can run `commit .` on any git project root path and follow CLI steps.
103+
104+
```
105+
commit .
106+
```
107+
108+
### How to use `rebase` to rename commit message?
109+
110+
If the [commit lint ci step](#how-to-add-commit-lint-stage-to-gitlab) fail, you can rebase you commit history and fix the wrong commit messages.
111+
112+
With your local repository up to date with your remote branch, run the following command.
113+
Note: `N` is the number of commits you pushed to your branch.
114+
115+
```
116+
git rebase -i HEAD~N
117+
```
118+
119+
Edit the document and replace the first word `pick` to `r` or `reword`. Save it with `ctrl+o` and close it with `ctrl+x`;
120+
121+
Force push it with `-f` tag as follows:
122+
123+
```
124+
git push -f origin my-branch-name
125+
```
126+
127+
### How to add commit lint stage to Gitlab?
128+
129+
You must add a new stage to `gitlab-ci.yml` file adding two new arguments to semantic-release script.
130+
- `-commit-lint=true` to run commit-lint logic;
131+
- `-branch-name=${CI_COMMIT_REF_NAME}` so that semantic-release can validate only the commits of the referenced branch.
132+
133+
```yaml
134+
stages:
135+
- commit-lint
136+
137+
commit-lint:
138+
stage: commit-int
139+
variables:
140+
SEMANTIC_RELEASE_VERSION: latest
141+
dependencies: []
142+
before_script:
143+
- docker pull registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION
144+
script:
145+
- docker run registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION up -commit-lint=true -branch-name=${CI_COMMIT_REF_NAME} -git-host ${CI_SERVER_HOST} -git-group ${CI_PROJECT_NAMESPACE} -git-project ${CI_PROJECT_NAME} -username ${PPD2_USERNAME} -password ${PPD2_ACCESS_TOKEN}
146+
```
147+
80148
### If you need more information about the semantic release CLI usage you can run the following command.
81149

82150
```
@@ -93,21 +161,28 @@ So the semantic release can find out the commit type to define the upgrade type
93161

94162

95163
```
96-
type: [type here].
97-
message: Commit message here.
164+
type(scope?): Commit message here.
165+
```
166+
167+
I.e.
168+
```
169+
feat(fibonacci): Added new function to print the Fibonacci sequece.
170+
```
171+
172+
The scope is optional, so you can also use the fllowing message standard.
173+
174+
```
175+
type: Commit message here.
98176
```
99177

100178
I.e.
101179
```
102-
type: [feat]
103-
message: Added new function to print the Fibonacci sequece.
180+
feat: Added new function to print the Fibonacci sequece.
104181
```
105182

106-
### If you want to complete a Merge Request without triggering the versioning process then you can use one of the skip type tags as follows.
183+
### If you want to complete a Merge Request without triggering the versioning process then you can use the skip type tags as follows.
107184

108-
- type: [skip]
109-
- type: [skip v]
110-
- type: [skip versioning]
185+
- skip
111186

112187
## Adding new tests
113188

cmd/semantic-release/semantic-release.go

+41-18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"os"
77

8+
commitmessage "github.com/NeowayLabs/semantic-release/src/commit-message"
9+
committype "github.com/NeowayLabs/semantic-release/src/commit-type"
810
"github.com/NeowayLabs/semantic-release/src/files"
911
"github.com/NeowayLabs/semantic-release/src/git"
1012
"github.com/NeowayLabs/semantic-release/src/log"
@@ -37,6 +39,8 @@ func main() {
3739
helpCmd := flag.NewFlagSet("help", flag.ExitOnError)
3840
helpCommitCmd := flag.NewFlagSet("help-cmt", flag.ExitOnError)
3941

42+
commitLint := upgradeVersionCmd.Bool("commit-lint", false, "Only lint commit history if set as true. (default false)")
43+
branchName := upgradeVersionCmd.String("branch-name", "", "Branch name to be cloned.")
4044
gitHost := upgradeVersionCmd.String("git-host", "", "Git host name. I.e.: gitlab.integration-tests.com. (required)")
4145
groupName := upgradeVersionCmd.String("git-group", "", "Git group name. (required)")
4246
projectName := upgradeVersionCmd.String("git-project", "", "Git project name. (required)")
@@ -67,15 +71,28 @@ func main() {
6771
case "up":
6872
logger.Info(colorYellow + "\nSemantic Version just started the process...\n\n" + colorReset)
6973

70-
semantic := newSemantic(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile)
71-
72-
if err := semantic.GenerateNewRelease(); err != nil {
73-
logger.Error(err.Error())
74-
os.Exit(1)
74+
semantic := newSemantic(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile, branchName)
75+
76+
if *commitLint {
77+
if *branchName == "" {
78+
logger.Error(colorRed + "\nThe argument -branch-name must be set when --commit-lint is true.\n\n" + colorReset)
79+
}
80+
81+
logger.Info(colorYellow + "\nSemantic Version commit lint started...\n\n" + colorReset)
82+
err := semantic.CommitLint()
83+
if err != nil {
84+
printCommitTypes()
85+
printCommitMessageExample()
86+
os.Exit(1)
87+
}
88+
} else {
89+
if err := semantic.GenerateNewRelease(); err != nil {
90+
logger.Error(err.Error())
91+
os.Exit(1)
92+
}
7593
}
7694

7795
logger.Info(colorYellow + "\nDone!" + colorReset)
78-
7996
case "help":
8097
printMainCommands()
8198
helpCmd.PrintDefaults()
@@ -112,7 +129,6 @@ func addFilesToUpgradeList(upgradePyFile *bool, repositoryRootPath string) Upgra
112129
}
113130

114131
func validateIncomingParams(logger *log.Log, upgradeVersionCmd *flag.FlagSet, gitHost, groupName, projectName, username, password *string, upgradePyFile *bool) {
115-
116132
if *gitHost == "" {
117133
logger.Info(colorRed + "Oops! Git host name must be specified." + colorReset + "[docker run neowaylabs/semantic-release up " + colorYellow + "-git-host gitHostNameHere]" + colorReset)
118134
os.Exit(1)
@@ -158,44 +174,51 @@ func printCommitTypes() {
158174
fmt.Println(colorYellow + "\n\t* [build]" + colorReset + ": Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)")
159175
fmt.Println(colorYellow + "\t* [ci]" + colorReset + ": Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)")
160176
fmt.Println(colorYellow + "\t* [docs]" + colorReset + ": Documentation only changes")
177+
fmt.Println(colorYellow + "\t* [documentation]" + colorReset + ": ||")
161178
fmt.Println(colorYellow + "\t* [feat]" + colorReset + ": A new feature")
179+
fmt.Println(colorYellow + "\t* [feature]" + colorReset + ": ||")
162180
fmt.Println(colorYellow + "\t* [fix]" + colorReset + ": A bug fix")
163181
fmt.Println(colorYellow + "\t* [perf]" + colorReset + ": A code change that improves performance")
182+
fmt.Println(colorYellow + "\t* [performance]" + colorReset + ": ||")
164183
fmt.Println(colorYellow + "\t* [refactor]" + colorReset + ": A code change that neither fixes a bug nor adds a feature")
165184
fmt.Println(colorYellow + "\t* [style]" + colorReset + ": Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)")
166185
fmt.Println(colorYellow + "\t* [test]" + colorReset + ": Adding missing tests or correcting existing tests")
167186
fmt.Println(colorYellow + "\t* [skip]" + colorReset + ": Skip versioning")
168-
fmt.Println(colorYellow + "\t* [skip versioning]" + colorReset + ": Skip versioning")
169-
fmt.Println(colorYellow + "\t* [breaking change]" + colorReset + ": Change that will require other changes in dependant applications")
170-
fmt.Println(colorYellow + "\t* [breaking changes]" + colorReset + ": Changes that will require other changes in dependant applications")
187+
fmt.Println(colorYellow + "\t* [bc]" + colorReset + ": Changes that will require other changes in dependant applications")
188+
fmt.Println(colorYellow + "\t* [breaking]" + colorReset + ": ||")
189+
fmt.Println(colorYellow + "\t* [breaking change]" + colorReset + ": ||")
171190
}
172191

173192
func printCommitMessageExample() {
174193
fmt.Println(colorYellow + "\nCOMMIT MESSAGE PATTERN" + colorReset)
175194
fmt.Println("\nThe commit message must follow the pattern below.")
176-
fmt.Println("\n\ttype [commit type here], message: Commit subject here.")
195+
fmt.Println("\n\ttype(optional scope): Commit subject message here.")
177196
fmt.Println(colorYellow + "\n\tI.e." + colorReset)
178-
fmt.Println("\t\ttype [feat], message: Added new feature to handle postgresql database connection.")
197+
fmt.Println("\t\tfeat(config): Added new feature to handle configs.")
179198

180-
fmt.Println("\n\tNote: The maximum number of characters is 150. If the commit subject exceeds it, it will be cut, keeping only the first 150 characters.")
199+
fmt.Println("\n\tNote 1: The (scope) is optional. Semantic-release accepts the following pattern: \"type: Commit subject message here\".")
200+
fmt.Println("\n\tNote 2: The maximum number of characters is 150. If the commit subject exceeds it, it will be cut, keeping only the first 150 characters.")
181201
}
182202

183-
func newSemantic(logger *log.Log, upgradeVersionCmd *flag.FlagSet, gitHost, groupName, projectName, username, password *string, upgradePyFile *bool) *semantic.Semantic {
203+
func newSemantic(logger *log.Log, upgradeVersionCmd *flag.FlagSet, gitHost, groupName, projectName, username, password *string, upgradePyFile *bool, branchName *string) *semantic.Semantic {
184204

185205
validateIncomingParams(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile)
186206

187207
timer := time.New(logger)
188208
repositoryRootPath := fmt.Sprintf("%s/%s", homePath, *projectName)
189209

190210
url := fmt.Sprintf("https://%s:%s@%s/%s/%s.git", *username, *password, *gitHost, *groupName, *projectName)
191-
repoVersionControl, err := git.New(logger, timer.PrintElapsedTime, url, *username, *password, repositoryRootPath)
211+
repoVersionControl, err := git.New(logger, timer.PrintElapsedTime, url, *username, *password, repositoryRootPath, *branchName)
192212
if err != nil {
193213
logger.Fatal(err.Error())
194214
}
195215

196-
filesVersionControl := files.New(logger, timer.PrintElapsedTime, *gitHost, repositoryRootPath, *groupName, *projectName)
216+
commitTypeManager := committype.New(logger)
217+
commitMessageManager := commitmessage.New(logger, commitTypeManager)
218+
219+
filesVersionControl := files.New(logger, timer.PrintElapsedTime, *gitHost, repositoryRootPath, *groupName, *projectName, commitMessageManager)
197220

198-
versionControl := v.NewVersionControl(logger, timer.PrintElapsedTime)
221+
versionControl := v.NewVersionControl(logger, timer.PrintElapsedTime, commitTypeManager)
199222

200-
return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl)
223+
return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl, commitMessageManager, commitTypeManager)
201224
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package commitmessage
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
type Logger interface {
10+
Info(s string, args ...interface{})
11+
Error(s string, args ...interface{})
12+
Warn(s string, args ...interface{})
13+
}
14+
15+
type CommitType interface {
16+
GetAll() []string
17+
GetMajorUpgrade() []string
18+
GetMinorUpgrade() []string
19+
GetPatchUpgrade() []string
20+
GetSkipVersioning() []string
21+
GetCommitChangeType(commitMessage string) (string, error)
22+
IndexNotFound(index int) bool
23+
}
24+
25+
type CommitMessage struct {
26+
log Logger
27+
commitType CommitType
28+
}
29+
30+
func (f *CommitMessage) isMessageLongerThanLimit(message string) bool {
31+
return len(message) >= 150
32+
}
33+
34+
func (f *CommitMessage) upperFirstLetterOfSentence(text string) string {
35+
return fmt.Sprintf("%s%s", strings.ToUpper(text[:1]), text[1:])
36+
}
37+
38+
// prettifyCommitMessage aims to keep a short message based on the commit message, removing extra information such as commit type.
39+
// Args:
40+
//
41+
// commitMessage (string): Full commit message.
42+
//
43+
// Returns:
44+
//
45+
// string: Returns a commit message with limmited number of characters.
46+
// err: Error whenever unexpected issues happen.
47+
func (f *CommitMessage) PrettifyCommitMessage(commitMessage string) (string, error) {
48+
splitedMessage := strings.Split(commitMessage, "\n")
49+
50+
message := ""
51+
for _, row := range splitedMessage {
52+
index := strings.Index(row, ":")
53+
54+
if f.commitType.IndexNotFound(index) || row == "" {
55+
continue
56+
}
57+
58+
commitTypeScope := strings.ToLower(row[:index])
59+
60+
for _, changeType := range f.commitType.GetAll() {
61+
if strings.Contains(commitTypeScope, changeType) {
62+
message = strings.TrimSpace(strings.Replace(row[index:], ":", "", 1))
63+
}
64+
}
65+
}
66+
67+
if message == "" {
68+
return "", errors.New("commit message is empty")
69+
}
70+
71+
if f.isMessageLongerThanLimit(message) {
72+
message = fmt.Sprintf("%s...", message[:150])
73+
}
74+
75+
return f.upperFirstLetterOfSentence(message), nil
76+
}
77+
78+
func (f *CommitMessage) IsValidMessage(message string) bool {
79+
index := strings.Index(message, ":")
80+
81+
if f.commitType.IndexNotFound(index) {
82+
f.log.Error("commit message out of pattern")
83+
return false
84+
}
85+
86+
if message == "" || message[index:] == ":" {
87+
f.log.Error("commit message cannot be empty")
88+
return false
89+
}
90+
91+
_, err := f.commitType.GetCommitChangeType(message)
92+
if err != nil {
93+
if err.Error() == "change type not found" {
94+
f.log.Error("change type not found")
95+
}
96+
return false
97+
}
98+
99+
return true
100+
}
101+
102+
func New(log Logger, commitType CommitType) *CommitMessage {
103+
return &CommitMessage{
104+
log: log,
105+
commitType: commitType,
106+
}
107+
}

0 commit comments

Comments
 (0)