Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow generate semantic version from -commit-headlines CLI flag #113

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Just run `jx-release-version` in your project's top directory, and it should jus
It accepts the following CLI flags:
- `-dir`: the location on the filesystem of your project's top directory - default to the current working directory.
- `-previous-version`: the [strategy to use to read the previous version](#reading-the-previous-version). Can also be set using the `PREVIOUS_VERSION` environment variable. Default to `auto`.
- `-commit-headlines`: the [commit headlines to use to generate the next semantic version](#pass-commit-headlines). Can also be set using the `COMMIT_HEADLINES` environment variable. Default to ``.
- `-next-version`: the [strategy to use to calculate the next version](#calculating—the-next-version). Can also be set using the `NEXT_VERSION` environment variable. Default to `auto`.
- `-output-format`: the [output format of the next release version](#output-format). Can also be set using the `OUTPUT_FORMAT` environment variable. Default to `{{.Major}}.{{.Minor}}.{{.Patch}}`.
- `-tag`: if enabled, [a new tag will be created](#tag). Can also be set using the `TAG` environment variable with the `"TRUE"` value.
Expand Down Expand Up @@ -111,13 +112,18 @@ The `semantic` strategy finds all commits between the previous version's git tag
- at least 1 commit with a `feat:` prefix, then it will bump the minor component of the version
- otherwise it will bump the patch component of the version

Note that if it can't find a tag for the previous version, it will fail.
Note that if it can't find a tag for the previous version, it will fail, except if you use the `-commit-headlines` flags to generate semantic next version from a single/multiline string instead of repository commits/tags.

**Usage**:
- `jx-release-version -next-version=semantic`
- if you want to strip any prerelease information from the build before performing the version bump you can use:
- `jx-release-version -next-version=semantic:strip-prerelease`

#### Pass commit headlines
If you want to retrieve a semantic version without using tags or commits from a repository, you can manually set the previous version and the commit headlines to use:
- `jx-release-version -previous-version=1.2.3 -commit-headlines="feat: a feature"`


### From file

The `from-file` strategy will read the next version from a file. Supported formats are:
Expand Down
12 changes: 8 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var (
debug bool
dir string
previousVersion string
commitHeadlines string
nextVersion string
outputFormat string
tag bool
Expand All @@ -47,6 +48,7 @@ func init() {
wd, _ := os.Getwd()
flag.StringVar(&options.dir, "dir", wd, "The directory that contains the git repository. Default to the current working directory.")
flag.StringVar(&options.previousVersion, "previous-version", getEnvWithDefault("PREVIOUS_VERSION", "auto"), "The strategy to detect the previous version: auto, from-tag, from-file or manual. Default to the PREVIOUS_VERSION env var.")
flag.StringVar(&options.commitHeadlines, "commit-headlines", getEnvWithDefault("COMMIT_HEADLINES", ""), "The commit headline(s) to use for semantic next version instead of the commit()s of a repository. Default to empty.")
flag.StringVar(&options.nextVersion, "next-version", getEnvWithDefault("NEXT_VERSION", "auto"), "The strategy to calculate the next version: auto, semantic, from-file, increment or manual. Default to the NEXT_VERSION env var.")
flag.StringVar(&options.outputFormat, "output-format", getEnvWithDefault("OUTPUT_FORMAT", "{{.Major}}.{{.Minor}}.{{.Patch}}"), "The output format of the next version. Default to the OUTPUT_FORMAT env var.")
flag.BoolVar(&options.debug, "debug", os.Getenv("JX_LOG_LEVEL") == "debug", "Print debug logs. Enabled by default if the JX_LOG_LEVEL env var is set to 'debug'.")
Expand Down Expand Up @@ -167,14 +169,16 @@ func versionBumper() strategy.VersionBumper {
case "auto", "":
versionBumper = auto.Strategy{
SemanticStrategy: semantic.Strategy{
Dir: options.dir,
StripPrerelease: strings.Contains(strategyArg, "strip-prerelease"),
Dir: options.dir,
StripPrerelease: strings.Contains(strategyArg, "strip-prerelease"),
CommitHeadlinesString: options.commitHeadlines,
},
}
case "semantic":
versionBumper = semantic.Strategy{
Dir: options.dir,
StripPrerelease: strings.Contains(strategyArg, "strip-prerelease"),
Dir: options.dir,
StripPrerelease: strings.Contains(strategyArg, "strip-prerelease"),
CommitHeadlinesString: options.commitHeadlines,
}
case "from-file":
versionBumper = fromfile.Strategy{
Expand Down
77 changes: 58 additions & 19 deletions pkg/strategy/semantic/semantic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"regexp"

"github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5"
Expand All @@ -19,35 +20,45 @@ var (
)

type Strategy struct {
Dir string
StripPrerelease bool
Dir string
StripPrerelease bool
CommitHeadlinesString string
}

func (s Strategy) BumpVersion(previous semver.Version) (*semver.Version, error) {
var (
dir = s.Dir
err error
dir = s.Dir
err error
commitHeadlinesString = s.CommitHeadlinesString
summary *conventionalCommitsSummary
)
if dir == "" {
dir, err = os.Getwd()
if commitHeadlinesString != "" {
summary, err = s.parseCommitHeadlines(commitHeadlinesString)
if err != nil {
return nil, fmt.Errorf("failed to get current working directory: %w", err)
return nil, err
}
} else {
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get current working directory: %w", err)
}
}
}

repo, err := git.PlainOpen(dir)
if err != nil {
return nil, fmt.Errorf("failed to open git repository at %q: %w", dir, err)
}
repo, err := git.PlainOpen(dir)
if err != nil {
return nil, fmt.Errorf("failed to open git repository at %q: %w", dir, err)
}

tagCommit, err := s.extractTagCommit(repo, previous.String())
if err != nil {
return nil, err
}
tagCommit, err := s.extractTagCommit(repo, previous.String())
if err != nil {
return nil, err
}

summary, err := s.parseCommitsSince(repo, tagCommit)
if err != nil {
return nil, err
summary, err = s.parseCommitsSince(repo, tagCommit)
if err != nil {
return nil, err
}
}

if s.StripPrerelease {
Expand Down Expand Up @@ -170,3 +181,31 @@ func (s Strategy) parseCommitsSince(repo *git.Repository, firstCommit *object.Co
log.Logger().Debugf("Summary of conventional commits since %s: %#v", firstCommit.Committer.When, summary)
return &summary, nil
}

func (s Strategy) parseCommitHeadlines(commitHeadlinesString string) (*conventionalCommitsSummary, error) {
summary := conventionalCommitsSummary{
types: map[string]bool{},
}

log.Logger().Debugf("Iterating over all commits headline passed as a string")

commitHeadlines := regexp.MustCompile("\r?\n").Split(commitHeadlinesString, -1)

for index, commitHeadline := range commitHeadlines {
log.Logger().Debugf("Parsing commit headline number %d with message %s", index, commitHeadline)
c, err := cc.Parse(commitHeadline)
if err != nil {
log.Logger().WithError(err).Debugf("Skipping non-conventional commit headline number %d", index)
continue
}

summary.conventionalCommitsCount++
summary.types[c.Header.Type] = true
if len(c.BreakingMessage()) > 0 {
summary.breakingChanges = true
}
}

log.Logger().Debugf("Summary of conventional commits: %#v", summary)
return &summary, nil
}
42 changes: 42 additions & 0 deletions pkg/strategy/semantic/semantic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,48 @@ func TestBumpVersion(t *testing.T) {
previous: *semver.MustParse("1.1.0"),
expected: semver.MustParse("2.0.0"),
},
{
name: "feat from commit headline",
strategy: Strategy{
CommitHeadlinesString: "feat: a feature",
},
previous: *semver.MustParse("2.0.0"),
expected: semver.MustParse("2.1.0"),
},
{
name: "feat from commit headlines",
strategy: Strategy{
CommitHeadlinesString: `chore: a chore
feat: a feature`,
},
previous: *semver.MustParse("2.0.0"),
expected: semver.MustParse("2.1.0"),
},
{
name: "breaking change from commit headline",
strategy: Strategy{
CommitHeadlinesString: "feat!: a breaking feature",
},
previous: *semver.MustParse("1.1.0"),
expected: semver.MustParse("2.0.0"),
},
{
name: "breaking change from commit headlines",
strategy: Strategy{
CommitHeadlinesString: `chore: a chore
feat!: a breaking feature`,
},
previous: *semver.MustParse("1.1.0"),
expected: semver.MustParse("2.0.0"),
},
{
name: "patch from unrecognized commit headline",
strategy: Strategy{
CommitHeadlinesString: "nothing",
},
previous: *semver.MustParse("1.1.0"),
expected: semver.MustParse("1.1.1"),
},
}

// the git repo is stored as a tar.gz archive to make it easy to commit
Expand Down