mirror of
https://github.com/lukaszraczylo/semver-generator.git
synced 2026-06-05 22:49:25 +00:00
Improve calculation logic, add ability to strip prefixes.
This commit is contained in:
+22
-12
@@ -26,26 +26,36 @@ type Force struct {
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
Wording Wording
|
||||
Force Force
|
||||
Blacklist []string
|
||||
Wording Wording
|
||||
Force Force
|
||||
Blacklist []string
|
||||
TagPrefixes []string // Prefixes to strip from tags before parsing (e.g., "app-", "infra-", "v")
|
||||
}
|
||||
|
||||
// ReadConfig reads the configuration from a file
|
||||
func ReadConfig(file string) (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
|
||||
viper.SetConfigFile(file)
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("fatal error config file: %s", err)
|
||||
return config, err
|
||||
}
|
||||
|
||||
viper.UnmarshalKey("wording", &config.Wording)
|
||||
viper.UnmarshalKey("force", &config.Force)
|
||||
viper.UnmarshalKey("blacklist", &config.Blacklist)
|
||||
|
||||
|
||||
if err := viper.UnmarshalKey("wording", &config.Wording); err != nil {
|
||||
return config, fmt.Errorf("error parsing wording config: %w", err)
|
||||
}
|
||||
if err := viper.UnmarshalKey("force", &config.Force); err != nil {
|
||||
return config, fmt.Errorf("error parsing force config: %w", err)
|
||||
}
|
||||
if err := viper.UnmarshalKey("blacklist", &config.Blacklist); err != nil {
|
||||
return config, fmt.Errorf("error parsing blacklist config: %w", err)
|
||||
}
|
||||
if err := viper.UnmarshalKey("tag_prefixes", &config.TagPrefixes); err != nil {
|
||||
return config, fmt.Errorf("error parsing tag_prefixes config: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -55,14 +65,14 @@ func ApplyForcedVersioning(force Force, semver *SemVer) {
|
||||
Debug("Forced versioning (MAJOR)", map[string]interface{}{"major": force.Major})
|
||||
semver.Major = force.Major
|
||||
}
|
||||
|
||||
|
||||
if force.Minor > 0 {
|
||||
Debug("Forced versioning (MINOR)", map[string]interface{}{"minor": force.Minor})
|
||||
semver.Minor = force.Minor
|
||||
}
|
||||
|
||||
|
||||
if force.Patch > 0 {
|
||||
Debug("Forced versioning (PATCH)", map[string]interface{}{"patch": force.Patch})
|
||||
semver.Patch = force.Patch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-32
@@ -29,14 +29,14 @@ type TagDetails struct {
|
||||
|
||||
// GitRepository represents a git repository
|
||||
type GitRepository struct {
|
||||
Handler *git.Repository
|
||||
Name string
|
||||
Branch string
|
||||
LocalPath string
|
||||
UseLocal bool
|
||||
Commits []CommitDetails
|
||||
Tags []TagDetails
|
||||
StartCommit string
|
||||
Handler *git.Repository
|
||||
Name string
|
||||
Branch string
|
||||
LocalPath string
|
||||
UseLocal bool
|
||||
Commits []CommitDetails
|
||||
Tags []TagDetails
|
||||
StartCommit string
|
||||
}
|
||||
|
||||
// PrepareRepository prepares the git repository for use
|
||||
@@ -47,15 +47,15 @@ func PrepareRepository(repo *GitRepository) error {
|
||||
u, err := url.Parse(repo.Name)
|
||||
if err != nil {
|
||||
Error("Unable to parse repository URL", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"url": repo.Name,
|
||||
"error": err.Error(),
|
||||
"url": repo.Name,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
repo.LocalPath = fmt.Sprintf("/tmp/semver/%s/%s", u.Path, repo.Branch)
|
||||
os.RemoveAll(repo.LocalPath)
|
||||
|
||||
_ = os.RemoveAll(repo.LocalPath) // Ignore error - directory may not exist
|
||||
|
||||
repo.Handler, err = git.PlainClone(repo.LocalPath, false, &git.CloneOptions{
|
||||
URL: repo.Name,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(repo.Branch),
|
||||
@@ -66,11 +66,11 @@ func PrepareRepository(repo *GitRepository) error {
|
||||
},
|
||||
Tags: git.AllTags,
|
||||
})
|
||||
|
||||
|
||||
if err != nil {
|
||||
Error("Unable to clone repository", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"url": repo.Name,
|
||||
"error": err.Error(),
|
||||
"url": repo.Name,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -79,14 +79,20 @@ func PrepareRepository(repo *GitRepository) error {
|
||||
repo.Handler, err = git.PlainOpen(repo.LocalPath)
|
||||
if err != nil {
|
||||
Error("Unable to open local repository", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"path": repo.LocalPath,
|
||||
"error": err.Error(),
|
||||
"path": repo.LocalPath,
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
os.Chdir(repo.LocalPath)
|
||||
|
||||
if err := os.Chdir(repo.LocalPath); err != nil {
|
||||
Error("Unable to change directory", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"path": repo.LocalPath,
|
||||
})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -105,14 +111,14 @@ func ListCommits(repo *GitRepository) ([]CommitDetails, error) {
|
||||
if err != nil {
|
||||
return []CommitDetails{}, err
|
||||
}
|
||||
|
||||
|
||||
commitsList, err := repo.Handler.Log(&git.LogOptions{From: ref.Hash()})
|
||||
if err != nil {
|
||||
return []CommitDetails{}, err
|
||||
}
|
||||
|
||||
var tmpResults []CommitDetails
|
||||
commitsList.ForEach(func(c *object.Commit) error {
|
||||
if err := commitsList.ForEach(func(c *object.Commit) error {
|
||||
tmpResults = append(tmpResults, CommitDetails{
|
||||
Hash: c.Hash.String(),
|
||||
Author: c.Author.String(),
|
||||
@@ -123,17 +129,19 @@ func ListCommits(repo *GitRepository) ([]CommitDetails, error) {
|
||||
return tmpResults[i].Timestamp.Unix() < tmpResults[j].Timestamp.Unix()
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return []CommitDetails{}, err
|
||||
}
|
||||
|
||||
Debug("Listing commits", map[string]interface{}{"commits": tmpResults})
|
||||
|
||||
|
||||
// Filter commits starting from the specified commit if provided
|
||||
if repo.StartCommit != "" {
|
||||
for commitId, cmt := range tmpResults {
|
||||
if cmt.Hash == repo.StartCommit {
|
||||
Debug("Found commit match", map[string]interface{}{
|
||||
"commit": cmt.Hash,
|
||||
"index": commitId,
|
||||
"index": commitId,
|
||||
})
|
||||
repo.Commits = tmpResults[commitId:]
|
||||
break
|
||||
@@ -150,32 +158,32 @@ func ListCommits(repo *GitRepository) ([]CommitDetails, error) {
|
||||
// ListExistingTags lists all tags in the repository
|
||||
func ListExistingTags(repo *GitRepository) {
|
||||
Debug("Listing existing tags", nil)
|
||||
|
||||
|
||||
// Check if Handler is nil to avoid panic
|
||||
if repo.Handler == nil {
|
||||
Debug("Repository handler is nil, skipping tag listing", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
refs, err := repo.Handler.Tags()
|
||||
if err != nil {
|
||||
Error("Unable to list tags", map[string]interface{}{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if err := refs.ForEach(func(ref *plumbing.Reference) error {
|
||||
repo.Tags = append(repo.Tags, TagDetails{
|
||||
Name: ref.Name().Short(),
|
||||
Hash: ref.Hash().String(),
|
||||
})
|
||||
|
||||
|
||||
Debug("Found tag", map[string]interface{}{
|
||||
"tag": ref.Name().Short(),
|
||||
"tag": ref.Name().Short(),
|
||||
"hash": ref.Hash().String(),
|
||||
})
|
||||
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
Error("Error iterating tags", map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-8
@@ -219,20 +219,23 @@ func downloadBinary(url string) (string, error) {
|
||||
if strings.HasSuffix(url, ".tar.gz") {
|
||||
// For tar.gz, we need to extract the binary
|
||||
if err := extractTarGz(resp.Body, tempFile); err != nil {
|
||||
tempFile.Close()
|
||||
os.Remove(tempPath)
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempPath)
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
// Direct binary download
|
||||
if _, err := io.Copy(tempFile, resp.Body); err != nil {
|
||||
tempFile.Close()
|
||||
os.Remove(tempPath)
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempPath)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
tempFile.Close()
|
||||
if err := tempFile.Close(); err != nil {
|
||||
_ = os.Remove(tempPath)
|
||||
return "", err
|
||||
}
|
||||
return tempPath, nil
|
||||
}
|
||||
|
||||
@@ -250,10 +253,12 @@ func extractTarGz(r io.Reader, destFile *os.File) error {
|
||||
defer os.Remove(archivePath)
|
||||
|
||||
if _, err := io.Copy(archiveFile, r); err != nil {
|
||||
archiveFile.Close()
|
||||
_ = archiveFile.Close()
|
||||
return err
|
||||
}
|
||||
if err := archiveFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
archiveFile.Close()
|
||||
|
||||
// Extract using tar command
|
||||
extractDir, err := os.MkdirTemp("", "semver-generator-extract-*")
|
||||
@@ -336,6 +341,7 @@ var runCommandFunc = func(cmdStr string) error {
|
||||
// replaceBinary replaces the current binary with the new one
|
||||
func replaceBinary(newBinary, currentBinary string) error {
|
||||
// Make the new binary executable
|
||||
// #nosec G302 -- 0755 is required for executable binaries
|
||||
if err := os.Chmod(newBinary, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -350,13 +356,17 @@ func replaceBinary(newBinary, currentBinary string) error {
|
||||
}
|
||||
|
||||
// copyFile copies a file from src to dst
|
||||
// Note: This function is only called internally with controlled paths from
|
||||
// os.CreateTemp and os.Executable, not with user-supplied paths.
|
||||
func copyFile(src, dst string) error {
|
||||
// #nosec G304 -- src is from os.CreateTemp, not user input
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
// #nosec G304 -- dst is from os.Executable, not user input
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -368,6 +378,7 @@ func copyFile(src, dst string) error {
|
||||
}
|
||||
|
||||
// Make executable
|
||||
// #nosec G302 -- 0755 is required for executable binaries
|
||||
return os.Chmod(dst, 0755)
|
||||
}
|
||||
|
||||
@@ -409,7 +420,10 @@ func parseVersionParts(v string) []int {
|
||||
|
||||
for _, p := range parts {
|
||||
var num int
|
||||
fmt.Sscanf(p, "%d", &num)
|
||||
if _, err := fmt.Sscanf(p, "%d", &num); err != nil {
|
||||
// If parsing fails, use 0 for this part
|
||||
num = 0
|
||||
}
|
||||
result = append(result, num)
|
||||
}
|
||||
|
||||
|
||||
+13
-12
@@ -13,6 +13,7 @@ func CalculateSemver(
|
||||
initialSemver SemVer,
|
||||
respectExisting bool,
|
||||
strictMode bool,
|
||||
tagPrefixes []string,
|
||||
) SemVer {
|
||||
semver := initialSemver
|
||||
|
||||
@@ -22,10 +23,10 @@ func CalculateSemver(
|
||||
for _, tagHash := range tags {
|
||||
if commit.Hash == tagHash.Hash {
|
||||
Debug("Found existing tag", map[string]interface{}{
|
||||
"tag": tagHash.Name,
|
||||
"tag": tagHash.Name,
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
})
|
||||
semver = ParseExistingSemver(tagHash.Name, semver)
|
||||
semver = ParseExistingSemver(tagHash.Name, semver, tagPrefixes)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -35,7 +36,7 @@ func CalculateSemver(
|
||||
if !strictMode {
|
||||
semver.Patch++
|
||||
Debug("Incrementing patch (DEFAULT)", map[string]interface{}{
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"semver": FormatSemver(semver),
|
||||
})
|
||||
}
|
||||
@@ -55,44 +56,44 @@ func CalculateSemver(
|
||||
semver.EnableReleaseCandidate = false
|
||||
semver.Release = 0
|
||||
Debug("Incrementing major (WORDING)", map[string]interface{}{
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"semver": FormatSemver(semver),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if matchMinor {
|
||||
semver.Minor++
|
||||
semver.Patch = 1
|
||||
semver.EnableReleaseCandidate = false
|
||||
semver.Release = 0
|
||||
Debug("Incrementing minor (WORDING)", map[string]interface{}{
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"semver": FormatSemver(semver),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if matchReleaseCandidate {
|
||||
semver.Release++
|
||||
semver.Patch = 1
|
||||
semver.EnableReleaseCandidate = true
|
||||
Debug("Incrementing release candidate (WORDING)", map[string]interface{}{
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"semver": FormatSemver(semver),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if matchPatch {
|
||||
semver.Patch++
|
||||
Debug("Incrementing patch (WORDING)", map[string]interface{}{
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"commit": strings.TrimSuffix(commit.Message, "\n"),
|
||||
"semver": FormatSemver(semver),
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return semver
|
||||
}
|
||||
}
|
||||
|
||||
+52
-11
@@ -19,7 +19,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
// More sophisticated mock implementation for testing
|
||||
for _, h := range haystack {
|
||||
// Check for substring match to better simulate fuzzy search
|
||||
if h == needle || (len(h) >= 3 && len(needle) >= 3 &&
|
||||
if h == needle || (len(h) >= 3 && len(needle) >= 3 &&
|
||||
(h[:3] == needle[:3] || h[len(h)-3:] == needle[len(needle)-3:])) {
|
||||
return []string{h}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
|
||||
// Test data
|
||||
now := time.Now()
|
||||
|
||||
|
||||
// Common wording and blacklist for all tests
|
||||
wording := Wording{
|
||||
Patch: []string{"update", "fix", "initial"},
|
||||
@@ -49,6 +49,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver SemVer
|
||||
respectExisting bool
|
||||
strictMode bool
|
||||
tagPrefixes []string
|
||||
want SemVer
|
||||
}{
|
||||
{
|
||||
@@ -76,11 +77,12 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: true,
|
||||
strictMode: false,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 1, // Initial tag 2.0.0 + one patch increment
|
||||
Release: 1,
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 1, // Initial tag 2.0.0 + one patch increment
|
||||
Release: 1,
|
||||
EnableReleaseCandidate: true,
|
||||
},
|
||||
},
|
||||
@@ -109,11 +111,12 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: true,
|
||||
strictMode: true,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 1, // Initial tag 2.0.0 + patch from "update" keyword
|
||||
Release: 1,
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 1, // Initial tag 2.0.0 + patch from "update" keyword
|
||||
Release: 1,
|
||||
EnableReleaseCandidate: true,
|
||||
},
|
||||
},
|
||||
@@ -142,6 +145,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: false,
|
||||
strictMode: false,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 0,
|
||||
Minor: 1,
|
||||
@@ -173,6 +177,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{Major: 1},
|
||||
respectExisting: false,
|
||||
strictMode: true,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 1,
|
||||
Minor: 1,
|
||||
@@ -199,6 +204,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: false,
|
||||
strictMode: false,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 0,
|
||||
Minor: 0,
|
||||
@@ -225,6 +231,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: false,
|
||||
strictMode: false,
|
||||
tagPrefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 0,
|
||||
Minor: 0,
|
||||
@@ -233,6 +240,39 @@ func TestCalculateSemver(t *testing.T) {
|
||||
EnableReleaseCandidate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With prefixed tags should not be RC",
|
||||
commits: []CommitDetails{
|
||||
{
|
||||
Hash: "commit1",
|
||||
Message: "tagged commit",
|
||||
Timestamp: now.Add(-3 * time.Hour),
|
||||
},
|
||||
{
|
||||
Hash: "commit2",
|
||||
Message: "another commit",
|
||||
Timestamp: now.Add(-2 * time.Hour),
|
||||
},
|
||||
},
|
||||
tags: []TagDetails{
|
||||
{
|
||||
Name: "app-0.0.16",
|
||||
Hash: "commit1",
|
||||
},
|
||||
},
|
||||
wording: wording,
|
||||
blacklist: blacklist,
|
||||
initialSemver: SemVer{},
|
||||
respectExisting: true,
|
||||
strictMode: true, // Use strict mode for predictable results
|
||||
tagPrefixes: []string{"app-", "infra-"},
|
||||
want: SemVer{
|
||||
Major: 0,
|
||||
Minor: 0,
|
||||
Patch: 16, // From tag, no additional increments in strict mode
|
||||
EnableReleaseCandidate: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -245,6 +285,7 @@ func TestCalculateSemver(t *testing.T) {
|
||||
tt.initialSemver,
|
||||
tt.respectExisting,
|
||||
tt.strictMode,
|
||||
tt.tagPrefixes,
|
||||
)
|
||||
|
||||
assert.Equal(t, tt.want.Major, got.Major, "Major version mismatch")
|
||||
@@ -254,4 +295,4 @@ func TestCalculateSemver(t *testing.T) {
|
||||
assert.Equal(t, tt.want.EnableReleaseCandidate, got.EnableReleaseCandidate, "EnableReleaseCandidate mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+72
-22
@@ -51,52 +51,102 @@ func FormatSemver(semver SemVer) string {
|
||||
|
||||
var extractNumber = regexp.MustCompile("[0-9]+")
|
||||
|
||||
// StripTagPrefix removes configured prefixes from a tag name
|
||||
// The "v" prefix is always stripped by default (e.g., v1.2.3 -> 1.2.3)
|
||||
func StripTagPrefix(tagName string, prefixes []string) string {
|
||||
result := tagName
|
||||
|
||||
// Always strip "v" prefix by default
|
||||
if strings.HasPrefix(result, "v") && len(result) > 1 {
|
||||
// Only strip if followed by a digit (to avoid stripping "version-1.0.0")
|
||||
if result[1] >= '0' && result[1] <= '9' {
|
||||
result = result[1:]
|
||||
Debug("Stripped default 'v' prefix from tag", map[string]interface{}{
|
||||
"original": tagName,
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Then strip any user-configured prefixes
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(result, prefix) {
|
||||
result = strings.TrimPrefix(result, prefix)
|
||||
Debug("Stripped prefix from tag", map[string]interface{}{
|
||||
"original": tagName,
|
||||
"prefix": prefix,
|
||||
"result": result,
|
||||
})
|
||||
break // Only strip one prefix
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseExistingSemver parses a semantic version from a tag name
|
||||
func ParseExistingSemver(tagName string, currentSemver SemVer) SemVer {
|
||||
func ParseExistingSemver(tagName string, currentSemver SemVer, prefixes []string) SemVer {
|
||||
Debug("Parsing existing semver", map[string]interface{}{"tag": tagName})
|
||||
|
||||
tagNameParts := strings.Split(tagName, ".")
|
||||
|
||||
// Strip configured prefixes before parsing
|
||||
cleanTagName := StripTagPrefix(tagName, prefixes)
|
||||
|
||||
// Check for release candidate pattern (-rc.X) before splitting
|
||||
isReleaseCandidate := false
|
||||
rcVersion := 0
|
||||
if idx := strings.Index(cleanTagName, "-rc."); idx != -1 {
|
||||
isReleaseCandidate = true
|
||||
rcPart := cleanTagName[idx+4:] // Get everything after "-rc."
|
||||
rcMatches := extractNumber.FindAllString(rcPart, 1)
|
||||
if len(rcMatches) > 0 {
|
||||
rcVersion, _ = strconv.Atoi(rcMatches[0])
|
||||
}
|
||||
// Remove the RC suffix for version parsing
|
||||
cleanTagName = cleanTagName[:idx]
|
||||
Debug("Detected release candidate", map[string]interface{}{
|
||||
"rc_version": rcVersion,
|
||||
"clean_tag_name": cleanTagName,
|
||||
})
|
||||
}
|
||||
|
||||
tagNameParts := strings.Split(cleanTagName, ".")
|
||||
if len(tagNameParts) < 3 {
|
||||
Debug("Unable to parse incompatible semver (non x.y.z)", map[string]interface{}{"tag": tagName})
|
||||
return currentSemver
|
||||
}
|
||||
|
||||
|
||||
semanticVersion := SemVer{}
|
||||
|
||||
|
||||
// Extract major version
|
||||
majorMatches := extractNumber.FindAllString(tagNameParts[0], -1)
|
||||
if len(majorMatches) > 0 {
|
||||
semanticVersion.Major, _ = strconv.Atoi(majorMatches[0])
|
||||
}
|
||||
|
||||
|
||||
// Extract minor version
|
||||
minorMatches := extractNumber.FindAllString(tagNameParts[1], -1)
|
||||
if len(minorMatches) > 0 {
|
||||
semanticVersion.Minor, _ = strconv.Atoi(minorMatches[0])
|
||||
}
|
||||
|
||||
|
||||
// Extract patch version
|
||||
patchMatches := extractNumber.FindAllString(tagNameParts[2], -1)
|
||||
if len(patchMatches) > 0 {
|
||||
semanticVersion.Patch, _ = strconv.Atoi(patchMatches[0])
|
||||
}
|
||||
|
||||
// Extract release candidate version if present
|
||||
if len(tagNameParts) > 3 {
|
||||
releaseMatches := extractNumber.FindAllString(tagNameParts[3], -1)
|
||||
if len(releaseMatches) > 0 {
|
||||
semanticVersion.Release, _ = strconv.Atoi(releaseMatches[0])
|
||||
semanticVersion.EnableReleaseCandidate = true
|
||||
}
|
||||
|
||||
// Set release candidate if detected
|
||||
if isReleaseCandidate {
|
||||
semanticVersion.Release = rcVersion
|
||||
semanticVersion.EnableReleaseCandidate = true
|
||||
}
|
||||
|
||||
|
||||
return semanticVersion
|
||||
}
|
||||
|
||||
// CheckMatches checks if any of the targets match the content
|
||||
func CheckMatches(content []string, targets []string, blacklist []string) bool {
|
||||
contentStr := strings.Join(content, " ")
|
||||
|
||||
|
||||
// First check if any target matches
|
||||
hasMatch := false
|
||||
for _, tgt := range targets {
|
||||
@@ -104,8 +154,8 @@ func CheckMatches(content []string, targets []string, blacklist []string) bool {
|
||||
if len(matches) > 0 {
|
||||
hasMatch = true
|
||||
Debug("Found match", map[string]interface{}{
|
||||
"target": tgt,
|
||||
"match": strings.Join(matches, ","),
|
||||
"target": tgt,
|
||||
"match": strings.Join(matches, ","),
|
||||
"content": contentStr,
|
||||
})
|
||||
break
|
||||
@@ -117,14 +167,14 @@ func CheckMatches(content []string, targets []string, blacklist []string) bool {
|
||||
for _, blacklistTerm := range blacklist {
|
||||
if strings.Contains(strings.ToLower(contentStr), strings.ToLower(blacklistTerm)) {
|
||||
Debug("Blacklisted term detected, ignoring commit", map[string]interface{}{
|
||||
"content": contentStr,
|
||||
"content": contentStr,
|
||||
"blacklist_term": blacklistTerm,
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return hasMatch
|
||||
}
|
||||
|
||||
@@ -132,4 +182,4 @@ func CheckMatches(content []string, targets []string, blacklist []string) bool {
|
||||
var FuzzyFind = func(needle string, haystack []string) []string {
|
||||
// This will be replaced with the actual implementation in main.go
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+64
-11
@@ -58,15 +58,17 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
InitLogger(false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tagName string
|
||||
name string
|
||||
tagName string
|
||||
currentSemver SemVer
|
||||
want SemVer
|
||||
prefixes []string
|
||||
want SemVer
|
||||
}{
|
||||
{
|
||||
name: "Standard semver",
|
||||
tagName: "1.2.3",
|
||||
name: "Standard semver",
|
||||
tagName: "1.2.3",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 1,
|
||||
Minor: 2,
|
||||
@@ -74,9 +76,10 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With v prefix",
|
||||
tagName: "v2.3.4",
|
||||
name: "With v prefix configured",
|
||||
tagName: "v2.3.4",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{"v"},
|
||||
want: SemVer{
|
||||
Major: 2,
|
||||
Minor: 3,
|
||||
@@ -84,9 +87,32 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With release candidate",
|
||||
tagName: "3.4.5-rc.2",
|
||||
name: "With app- prefix configured",
|
||||
tagName: "app-1.2.3",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{"app-", "infra-"},
|
||||
want: SemVer{
|
||||
Major: 1,
|
||||
Minor: 2,
|
||||
Patch: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With prefix but not in config - should still parse numbers",
|
||||
tagName: "v2.3.4",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{}, // v not in prefixes
|
||||
want: SemVer{
|
||||
Major: 2,
|
||||
Minor: 3,
|
||||
Patch: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With release candidate",
|
||||
tagName: "3.4.5-rc.2",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 3,
|
||||
Minor: 4,
|
||||
@@ -95,6 +121,31 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
EnableReleaseCandidate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With prefix and release candidate",
|
||||
tagName: "app-1.0.0-rc.3",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{"app-"},
|
||||
want: SemVer{
|
||||
Major: 1,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
Release: 3,
|
||||
EnableReleaseCandidate: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Prefixed tag without RC should NOT be RC",
|
||||
tagName: "app-0.0.16",
|
||||
currentSemver: SemVer{},
|
||||
prefixes: []string{"app-"},
|
||||
want: SemVer{
|
||||
Major: 0,
|
||||
Minor: 0,
|
||||
Patch: 16,
|
||||
EnableReleaseCandidate: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid format",
|
||||
tagName: "not-a-semver",
|
||||
@@ -103,6 +154,7 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
Minor: 1,
|
||||
Patch: 1,
|
||||
},
|
||||
prefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 1,
|
||||
Minor: 1,
|
||||
@@ -117,6 +169,7 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
Minor: 5,
|
||||
Patch: 5,
|
||||
},
|
||||
prefixes: []string{},
|
||||
want: SemVer{
|
||||
Major: 5,
|
||||
Minor: 5,
|
||||
@@ -127,7 +180,7 @@ func TestParseExistingSemver(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseExistingSemver(tt.tagName, tt.currentSemver)
|
||||
got := ParseExistingSemver(tt.tagName, tt.currentSemver, tt.prefixes)
|
||||
assert.Equal(t, tt.want.Major, got.Major, "Major version mismatch")
|
||||
assert.Equal(t, tt.want.Minor, got.Minor, "Minor version mismatch")
|
||||
assert.Equal(t, tt.want.Patch, got.Patch, "Patch version mismatch")
|
||||
@@ -196,4 +249,4 @@ func TestCheckMatches(t *testing.T) {
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user