diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af768c..5b34154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +v1.1.0: + - generate release from changelog (@mateus.mello) + v1.0.5 - Fix isSetNewVersion function logic (@esequiel.virtuoso) diff --git a/Makefile b/Makefile index 0b17f1a..01d5f17 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ check: modcache imagedev $(run) go test -tags unit -timeout 20s -race -coverprofile=$(cov) ./... check-integration: imagedev start-env - $(runcompose) --entrypoint "./hack/check-integration.sh" semantic-release + $(runcompose) --entrypoint "./hack/check-integration.sh" semantic-release $(testfile) $(testname) coverage: modcache check $(run) go tool cover -html=$(cov) -o=$(covhtml) diff --git a/README.md b/README.md index f47e702..6d14da6 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ semantic-release: ``` +Also, you can add `-changelog "true"` argument to set semantic-release to get most recent version from CHANGELOG.md file instead of get it from gitlab tags. + If your project is a Python project you can add the flag `-setup-py true` to update the release version in this file too. Note: The version must be placed in a variable called `__version__` as follows: diff --git a/cmd/semantic-release/semantic-release b/cmd/semantic-release/semantic-release deleted file mode 100755 index ea1d057..0000000 Binary files a/cmd/semantic-release/semantic-release and /dev/null differ diff --git a/cmd/semantic-release/semantic-release.go b/cmd/semantic-release/semantic-release.go index 97601fe..d1ed963 100644 --- a/cmd/semantic-release/semantic-release.go +++ b/cmd/semantic-release/semantic-release.go @@ -44,6 +44,8 @@ func main() { username := upgradeVersionCmd.String("username", "", "Git username. (required)") password := upgradeVersionCmd.String("password", "", "Git password. (required)") logLevel := upgradeVersionCmd.String("log-level", "debug", "Log level.") + var useChangelogFile bool + upgradeVersionCmd.BoolVar(&useChangelogFile, "changelog", false, "Use changelog file to generate input version. (default false)") if len(os.Args) < 2 { printWelcomeMessage() @@ -67,7 +69,7 @@ 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) + semantic := newSemantic(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile, useChangelogFile) if err := semantic.GenerateNewRelease(); err != nil { logger.Error(err.Error()) @@ -180,7 +182,7 @@ func printCommitMessageExample() { 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.") } -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, changelog bool) *semantic.Semantic { validateIncomingParams(logger, upgradeVersionCmd, gitHost, groupName, projectName, username, password, upgradePyFile) @@ -197,5 +199,5 @@ func newSemantic(logger *log.Log, upgradeVersionCmd *flag.FlagSet, gitHost, grou versionControl := v.NewVersionControl(logger, timer.PrintElapsedTime) - return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl) + return semantic.New(logger, repositoryRootPath, addFilesToUpgradeList(upgradePyFile, repositoryRootPath), repoVersionControl, filesVersionControl, versionControl, changelog) } diff --git a/src/git/git.go b/src/git/git.go index 8092f87..451aa77 100644 --- a/src/git/git.go +++ b/src/git/git.go @@ -1,9 +1,11 @@ package git import ( + "bufio" "errors" "fmt" "os" + "path/filepath" "regexp" "strconv" "strings" @@ -117,6 +119,49 @@ func (g *GitVersioning) GetCurrentVersion() string { return g.mostRecentTag } +func (g *GitVersioning) GetChangelog() (*os.File, error) { + + changelog, err := os.Open(filepath.Join(g.destinationDirectory, "CHANGELOG")) + if err != nil { + return nil, fmt.Errorf("error to open file in GetChangelog / %w", err) + } + + return changelog, nil +} + +func (g *GitVersioning) GetChangelogChanges() (string, error) { + changelog, err := os.Open(filepath.Join(g.destinationDirectory, "CHANGELOG")) + if err != nil { + return "", err + } + + scanner := bufio.NewScanner(changelog) + // optionally, resize scanner's capacity for lines over 64K, see next example + change := "" + for scanner.Scan() { + line := scanner.Text() + finishedNewChanges := "## v" + if strings.Contains(line, finishedNewChanges) { + break + } + + if isValidChangeMessage(line) { + if change != "" { + return "", fmt.Errorf("more than one change in the release / actual change: %s / another found: %s", change, line) + } + change = line + } + } + + return change, nil +} + +func isValidChangeMessage(message string) bool { + //regex validate this message: "type: [fix], message: testing message" + r := regexp.MustCompile(`type:\s\[[A-Za-z0-9]+\], message:\s[A-Za-z0-9]+`) + return r.FindString(message) != "" +} + func (g *GitVersioning) UpgradeRemoteRepository(newVersion string) error { // consider 1.0.0 as the start tag of a repository when it does not have tags yet if newVersion == "0.1.0" || newVersion == "0.0.1" { diff --git a/src/git/git_integration_test.go b/src/git/git_integration_test.go index 7b1a845..ced4f44 100644 --- a/src/git/git_integration_test.go +++ b/src/git/git_integration_test.go @@ -364,3 +364,81 @@ func TestNewGitGetCurrentVersionFromRepoWithDisorderedTags(t *testing.T) { func getDestinationDirectory(repo string) string { return fmt.Sprintf("%s/%s", os.Getenv("HOME"), repo) } + +// func TestGetChangelogWorks(t *testing.T) { +// f := getValidSetup() +// defer f.cleanLocalRepo(t) +// f.gitLabVersioning.url = greatNumbersTagProject +// f.gitLabVersioning.destinationDirectory = fmt.Sprintf("%s/%s", os.Getenv("HOME"), "get-changelog") +// repo, err := f.newGitService() +// file, err := os.Create(fmt.Sprintf("%s/%s/%s", os.Getenv("HOME"), "get-changelog", "CHANGELOG")) +// if err != nil { +// fmt.Println("Error to create file", err) +// return +// } +// defer file.Close() +// tests.AssertNoError(t, err) +// fmt.Fprintf(file, "type [fix], message: testing message") + +// result, err := repo.GetChangelog() +// tests.AssertNil(t, err) +// tests.AssertNotNil(t, result) +// } + +// func TestGetChangelogChangesWorks(t *testing.T) { +// f := getValidSetup() +// f.gitLabVersioning.url = greatNumbersTagProject +// f.gitLabVersioning.destinationDirectory = fmt.Sprintf("%s/%s", os.Getenv("HOME"), "update-changelog") +// repo, err := f.newGitService() +// file, err := os.Create(fmt.Sprintf("%s/%s/%s", os.Getenv("HOME"), "update-changelog", "CHANGELOG")) +// if err != nil { +// fmt.Println("Error to create file", err) +// return +// } +// defer file.Close() +// tests.AssertNoError(t, err) +// changelogSample := getChangelogSample() +// fmt.Fprintf(file, changelogSample) + +// result, err := repo.GetChangelogChanges() +// tests.AssertNil(t, err) +// tests.AssertNotNil(t, result) +// tests.AssertEqualValues(t, "type: [fix], message: testing message", result) +// f.cleanLocalRepo(t) +// } + +// func TestGetChangelogChangesError(t *testing.T) { +// f := getValidSetup() +// f.gitLabVersioning.url = greatNumbersTagProject +// f.gitLabVersioning.destinationDirectory = fmt.Sprintf("%s/%s", os.Getenv("HOME"), "update-changelog") +// repo, err := f.newGitService() +// file, err := os.Create(fmt.Sprintf("%s/%s/%s", os.Getenv("HOME"), "update-changelog", "CHANGELOG")) +// if err != nil { +// fmt.Println("Error to create file", err) +// return +// } +// defer file.Close() +// tests.AssertNoError(t, err) +// changelogSample := getChangelogSampleWithTwoChanges() +// fmt.Fprintf(file, changelogSample) + +// result, err := repo.GetChangelogChanges() +// tests.AssertEmpty(t, result) +// tests.AssertNotNil(t, err) +// f.cleanLocalRepo(t) +// } + +// func getChangelogSample() string { +// return `type: [fix], message: testing message + +// ## v1.9.9 +// type: [fix], message: v199` +// } + +// func getChangelogSampleWithTwoChanges() string { +// return `type: [fix], message: testing message +// type: [feat], message: feat message + +// ## v1.9.9 +// type: [fix], message: v199` +// } diff --git a/src/semantic/semantic.go b/src/semantic/semantic.go index 85f2fb2..4fd6778 100644 --- a/src/semantic/semantic.go +++ b/src/semantic/semantic.go @@ -24,6 +24,7 @@ type RepositoryVersionControl interface { GetChangeMessage() string GetCurrentVersion() string UpgradeRemoteRepository(newVersion string) error + GetChangelogChanges() (string, error) } type VersionControl interface { @@ -54,6 +55,7 @@ type Semantic struct { repoVersionControl RepositoryVersionControl versionControl VersionControl filesVersionControl FilesVersionControl + changelog bool } func (s *Semantic) GenerateNewRelease() error { @@ -65,6 +67,15 @@ func (s *Semantic) GenerateNewRelease() error { CurrentVersion: s.repoVersionControl.GetCurrentVersion(), } + if s.changelog { + //TODO if the user pass as parameter to generate version from changelog should set changesInfo.message + newChangeMessage, err := s.repoVersionControl.GetChangelogChanges() + if err != nil { + return fmt.Errorf("error to get changelog message / %w", err) + } + changesInfo.Message = newChangeMessage + } + if s.versionControl.MustSkipVersioning(changesInfo.Message) { s.log.Info(colorCyan + "Semantic Release has been skiped by commit message tag [skip]" + colorReset) return nil @@ -110,7 +121,7 @@ func (s *Semantic) GenerateNewRelease() error { return nil } -func New(log Logger, rootPath string, filesToUpdateVariable interface{}, repoVersionControl RepositoryVersionControl, filesVersionControl FilesVersionControl, versionControl VersionControl) *Semantic { +func New(log Logger, rootPath string, filesToUpdateVariable interface{}, repoVersionControl RepositoryVersionControl, filesVersionControl FilesVersionControl, versionControl VersionControl, changelog bool) *Semantic { return &Semantic{ log: log, rootPath: rootPath, @@ -118,5 +129,6 @@ func New(log Logger, rootPath string, filesToUpdateVariable interface{}, repoVer repoVersionControl: repoVersionControl, filesVersionControl: filesVersionControl, versionControl: versionControl, + changelog: changelog, } } diff --git a/src/semantic/semantic_test.go b/src/semantic/semantic_test.go index 0b7eebc..9c41a6c 100644 --- a/src/semantic/semantic_test.go +++ b/src/semantic/semantic_test.go @@ -43,6 +43,11 @@ func (r *RepositoryVersionControlMock) UpgradeRemoteRepository(newVersion string return r.errUpgradeRemoteRepo } +func (r *RepositoryVersionControlMock) GetChangelogChanges() (string, error) { + change := "" + return change, nil +} + type VersionControlMock struct { newVersion string errGetNewVersion error @@ -92,8 +97,8 @@ func (f *fixture) NewSemantic() *semantic.Semantic { if err != nil { errors.New("error while getting new log") } - - return semantic.New(logger, f.rootPath, f.filesToUpdateVariable, f.repoVersionMock, f.filesVersionMock, f.versionControlMock) + useChangelog := false + return semantic.New(logger, f.rootPath, f.filesToUpdateVariable, f.repoVersionMock, f.filesVersionMock, f.versionControlMock, useChangelog) } type upgradeFilesMock struct { diff --git a/src/version/version_test.go b/src/version/version_test.go index 7a1fe46..5acfdd1 100644 --- a/src/version/version_test.go +++ b/src/version/version_test.go @@ -85,6 +85,20 @@ func TestGetNewVersionPatchSuccess(t *testing.T) { tests.AssertEqualValues(t, "1.0.1", actualVersion) } +func TestGetNewVersionPatchSuccessLot(t *testing.T) { + f := setup() + testcases := map[string]string{ + "type:[fix]": "1.0.0", + } + expected := "1.0.1" + for k, v := range testcases { + actualVersion, actualErr := f.versionControl.GetNewVersion(k, v) + tests.AssertNoError(t, actualErr) + tests.AssertEqualValues(t, expected, actualVersion) + } + +} + func TestMustSkipVersioningFalse(t *testing.T) { f := setup() actualMustSkip := f.versionControl.MustSkipVersioning("type: [fix]")