Skip to content

Commit

Permalink
Merge pull request #18 from NeowayLabs/add-commit-lint-option
Browse files Browse the repository at this point in the history
type: [feat], message: Add commit lint option.
  • Loading branch information
esequielvirtuoso authored Mar 5, 2024
2 parents f5afdc2 + 0ccb779 commit 4c031f7
Show file tree
Hide file tree
Showing 16 changed files with 997 additions and 273 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@ githooks:
@echo "git hooks copied"

shell: modcache imagedev
$(run) sh
$(run) sh
105 changes: 90 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ stages:

semantic-release:
stage: semantic-release
only:
refs:
- master
before_script:
- docker pull registry.com/dataplatform/semantic-release:latest
variables:
SEMANTIC_RELEASE_VERSION: latest
dependencies: []
except:
- master
before_script:
- docker pull registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION
script:
- 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}

- 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}
```
If your project is a Python project you can add the flag `-setup-py true` to update the release version in this file too.
Expand Down Expand Up @@ -77,6 +78,73 @@ setup(
)
```

### Adding pre-commit message CLI

The `pre-commit` will validate your commit messages before the commit being accepted.
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.

**Requirements**
- [Golang installation](https://go.dev/doc/install)


Clone pre-commit project and install it in you SO.

```
git clone git@github.com:NeowayLabs/pre-commit.git
```
```
make install
```
**How to use it?**
After adding new changes with the `git add` command, you can run `commit .` on any git project root path and follow CLI steps.
```
commit .
```
### How to use `rebase` to rename commit message?
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.
With your local repository up to date with your remote branch, run the following command.
Note: `N` is the number of commits you pushed to your branch.
```
git rebase -i HEAD~N
```
Edit the document and replace the first word `pick` to `r` or `reword`. Save it with `ctrl+o` and close it with `ctrl+x`;
Force push it with `-f` tag as follows:
```
git push -f origin my-branch-name
```
### How to add commit lint stage to Gitlab?
You must add a new stage to `gitlab-ci.yml` file adding two new arguments to semantic-release script.
- `-commit-lint=true` to run commit-lint logic;
- `-branch-name=${CI_COMMIT_REF_NAME}` so that semantic-release can validate only the commits of the referenced branch.
```yaml
stages:
- commit-lint
commit-lint:
stage: commit-int
variables:
SEMANTIC_RELEASE_VERSION: latest
dependencies: []
before_script:
- docker pull registry.com/dataplatform/semantic-release:$SEMANTIC_RELEASE_VERSION
script:
- 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}
```

### If you need more information about the semantic release CLI usage you can run the following command.

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


```
type: [type here].
message: Commit message here.
type(scope?): Commit message here.
```

I.e.
```
feat(fibonacci): Added new function to print the Fibonacci sequece.
```

The scope is optional, so you can also use the fllowing message standard.

```
type: Commit message here.
```

I.e.
```
type: [feat]
message: Added new function to print the Fibonacci sequece.
feat: Added new function to print the Fibonacci sequece.
```

### 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.
### If you want to complete a Merge Request without triggering the versioning process then you can use the skip type tags as follows.

- type: [skip]
- type: [skip v]
- type: [skip versioning]
- skip

## Adding new tests

Expand Down
59 changes: 41 additions & 18 deletions cmd/semantic-release/semantic-release.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"

commitmessage "github.com/NeowayLabs/semantic-release/src/commit-message"
committype "github.com/NeowayLabs/semantic-release/src/commit-type"
"github.com/NeowayLabs/semantic-release/src/files"
"github.com/NeowayLabs/semantic-release/src/git"
"github.com/NeowayLabs/semantic-release/src/log"
Expand Down Expand Up @@ -37,6 +39,8 @@ func main() {
helpCmd := flag.NewFlagSet("help", flag.ExitOnError)
helpCommitCmd := flag.NewFlagSet("help-cmt", flag.ExitOnError)

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

semantic := newSemantic(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile)

if err := semantic.GenerateNewRelease(); err != nil {
logger.Error(err.Error())
os.Exit(1)
semantic := newSemantic(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile, branchName)

if *commitLint {
if *branchName == "" {
logger.Error(colorRed + "\nThe argument -branch-name must be set when --commit-lint is true.\n\n" + colorReset)
}

logger.Info(colorYellow + "\nSemantic Version commit lint started...\n\n" + colorReset)
err := semantic.CommitLint()
if err != nil {
printCommitTypes()
printCommitMessageExample()
os.Exit(1)
}
} else {
if err := semantic.GenerateNewRelease(); err != nil {
logger.Error(err.Error())
os.Exit(1)
}
}

logger.Info(colorYellow + "\nDone!" + colorReset)

case "help":
printMainCommands()
helpCmd.PrintDefaults()
Expand Down Expand Up @@ -112,7 +129,6 @@ func addFilesToUpgradeList(upgradePyFile *bool, repositoryRootPath string) Upgra
}

func validateIncomingParams(logger *log.Log, upgradeVersionCmd *flag.FlagSet, gitHost, groupName, projectName, username, password *string, upgradePyFile *bool) {

if *gitHost == "" {
logger.Info(colorRed + "Oops! Git host name must be specified." + colorReset + "[docker run neowaylabs/semantic-release up " + colorYellow + "-git-host gitHostNameHere]" + colorReset)
os.Exit(1)
Expand Down Expand Up @@ -158,44 +174,51 @@ func printCommitTypes() {
fmt.Println(colorYellow + "\n\t* [build]" + colorReset + ": Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)")
fmt.Println(colorYellow + "\t* [ci]" + colorReset + ": Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)")
fmt.Println(colorYellow + "\t* [docs]" + colorReset + ": Documentation only changes")
fmt.Println(colorYellow + "\t* [documentation]" + colorReset + ": ||")
fmt.Println(colorYellow + "\t* [feat]" + colorReset + ": A new feature")
fmt.Println(colorYellow + "\t* [feature]" + colorReset + ": ||")
fmt.Println(colorYellow + "\t* [fix]" + colorReset + ": A bug fix")
fmt.Println(colorYellow + "\t* [perf]" + colorReset + ": A code change that improves performance")
fmt.Println(colorYellow + "\t* [performance]" + colorReset + ": ||")
fmt.Println(colorYellow + "\t* [refactor]" + colorReset + ": A code change that neither fixes a bug nor adds a feature")
fmt.Println(colorYellow + "\t* [style]" + colorReset + ": Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)")
fmt.Println(colorYellow + "\t* [test]" + colorReset + ": Adding missing tests or correcting existing tests")
fmt.Println(colorYellow + "\t* [skip]" + colorReset + ": Skip versioning")
fmt.Println(colorYellow + "\t* [skip versioning]" + colorReset + ": Skip versioning")
fmt.Println(colorYellow + "\t* [breaking change]" + colorReset + ": Change that will require other changes in dependant applications")
fmt.Println(colorYellow + "\t* [breaking changes]" + colorReset + ": Changes that will require other changes in dependant applications")
fmt.Println(colorYellow + "\t* [bc]" + colorReset + ": Changes that will require other changes in dependant applications")
fmt.Println(colorYellow + "\t* [breaking]" + colorReset + ": ||")
fmt.Println(colorYellow + "\t* [breaking change]" + colorReset + ": ||")
}

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

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.")
fmt.Println("\n\tNote 1: The (scope) is optional. Semantic-release accepts the following pattern: \"type: Commit subject message here\".")
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.")
}

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

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

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

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

filesVersionControl := files.New(logger, timer.PrintElapsedTime, *gitHost, repositoryRootPath, *groupName, *projectName)
commitTypeManager := committype.New(logger)
commitMessageManager := commitmessage.New(logger, commitTypeManager)

filesVersionControl := files.New(logger, timer.PrintElapsedTime, *gitHost, repositoryRootPath, *groupName, *projectName, commitMessageManager)

versionControl := v.NewVersionControl(logger, timer.PrintElapsedTime)
versionControl := v.NewVersionControl(logger, timer.PrintElapsedTime, commitTypeManager)

return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl)
return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl, commitMessageManager, commitTypeManager)
}
107 changes: 107 additions & 0 deletions src/commit-message/commit_message_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package commitmessage

import (
"errors"
"fmt"
"strings"
)

type Logger interface {
Info(s string, args ...interface{})
Error(s string, args ...interface{})
Warn(s string, args ...interface{})
}

type CommitType interface {
GetAll() []string
GetMajorUpgrade() []string
GetMinorUpgrade() []string
GetPatchUpgrade() []string
GetSkipVersioning() []string
GetCommitChangeType(commitMessage string) (string, error)
IndexNotFound(index int) bool
}

type CommitMessage struct {
log Logger
commitType CommitType
}

func (f *CommitMessage) isMessageLongerThanLimit(message string) bool {
return len(message) >= 150
}

func (f *CommitMessage) upperFirstLetterOfSentence(text string) string {
return fmt.Sprintf("%s%s", strings.ToUpper(text[:1]), text[1:])
}

// prettifyCommitMessage aims to keep a short message based on the commit message, removing extra information such as commit type.
// Args:
//
// commitMessage (string): Full commit message.
//
// Returns:
//
// string: Returns a commit message with limmited number of characters.
// err: Error whenever unexpected issues happen.
func (f *CommitMessage) PrettifyCommitMessage(commitMessage string) (string, error) {
splitedMessage := strings.Split(commitMessage, "\n")

message := ""
for _, row := range splitedMessage {
index := strings.Index(row, ":")

if f.commitType.IndexNotFound(index) || row == "" {
continue
}

commitTypeScope := strings.ToLower(row[:index])

for _, changeType := range f.commitType.GetAll() {
if strings.Contains(commitTypeScope, changeType) {
message = strings.TrimSpace(strings.Replace(row[index:], ":", "", 1))
}
}
}

if message == "" {
return "", errors.New("commit message is empty")
}

if f.isMessageLongerThanLimit(message) {
message = fmt.Sprintf("%s...", message[:150])
}

return f.upperFirstLetterOfSentence(message), nil
}

func (f *CommitMessage) IsValidMessage(message string) bool {
index := strings.Index(message, ":")

if f.commitType.IndexNotFound(index) {
f.log.Error("commit message out of pattern")
return false
}

if message == "" || message[index:] == ":" {
f.log.Error("commit message cannot be empty")
return false
}

_, err := f.commitType.GetCommitChangeType(message)
if err != nil {
if err.Error() == "change type not found" {
f.log.Error("change type not found")
}
return false
}

return true
}

func New(log Logger, commitType CommitType) *CommitMessage {
return &CommitMessage{
log: log,
commitType: commitType,
}
}
Loading

0 comments on commit 4c031f7

Please sign in to comment.