Skip to content

Commit

Permalink
Merge pull request #33 from pantheon-systems/semver-build-meta
Browse files Browse the repository at this point in the history
  • Loading branch information
joemiller authored May 20, 2020
2 parents 685ac3f + 104cc71 commit 9623602
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 270 deletions.
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ AutoTag

Automatically increment version tags to a git repo based on commit messages.

* [AutoTag](#autotag)
* [Dependencies](#dependencies)
* [Installing](#installing)
* [Pre-built binaries](#pre-built-binaries)
* [Docker images](#docker-images)
* [One-liner](#one-liner)
* [Usage](#usage)
* [Scheme: Autotag (default)](#scheme-autotag-default)
* [Scheme: Conventional Commits](#scheme-conventional-commits)
* [Pre-Release Tags](#pre-release-tags)
* [Build metadata](#build-metadata)
* [Examples](#examples)
* [Goreleaser](#goreleaser)
* [Build from Source](#build-from-source)
* [Release information](#release-information)

Dependencies
------------

Expand All @@ -23,14 +39,14 @@ Installing
### Pre-built binaries

| OS | Arch | binary |
| ----- | ----- | ------------------- |
|-------|-------|---------------------|
| macOS | amd64 | [autotag][releases] |
| Linux | amd64 | [autotag][releases] |

### Docker images

| Arch | Images |
| ----- | ---------------------------------------------------------------- |
|-------|------------------------------------------------------------------|
| amd64 | `quay.io/pantheon-public/autotag:latest`, `vX.Y.Z`, `vX.Y`, `vX` |

[releases]: https://github.com/pantheon-systems/autotag/releases/latest
Expand Down Expand Up @@ -99,7 +115,8 @@ If no keywords are specified a **Patch** bump is applied.

### Scheme: Conventional Commits

Specify the [Conventional Commits](TODO) v1.0.0 scheme by passing `--scheme=conventional` to `autotag`.
Specify the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#examples) v1.0.0
scheme by passing `--scheme=conventional` to `autotag`.

Conventional Commits implements SemVer style versioning `vMajor.Minor.Patch` similar to the
autotag scheme, but with a different commit message format.
Expand Down Expand Up @@ -139,6 +156,17 @@ If no keywords are specified a **Patch** bump is applied.
* Use `-T/--pre-release-timestmap=` to append **timestamp** to the version. Allowed timetstamp
formats are `datetime` (YYYYMMDDHHMMSS) or `epoch` (UNIX epoch timestamp in seconds).

### Build metadata

Optional SemVer build metadata can be appended to the version string after a `+` character using the `-m/--build-metadata` flag. eg: `v1.2.3+foo`

Build metadata is subject to the rules outlined in the [Semver](https://semver.org/#spec-item-10)
spec.

A common uses might be the current git reference: `git rev-parse --short HEAD`.

Multiple metadata items should be seperated by a `.`, eg: `foo.bar`

Examples
--------

Expand All @@ -162,6 +190,18 @@ $ autotag -p pre -T epoch

$ autotag -p rc -T datetime
3.2.1-rc.20170706054528

$ autotag -m g$(git rev-parse --short HEAD)
3.2.1+ge92b825

$ autotag -p dev -m g$(git rev-parse --short HEAD)
3.2.1-dev+ge92b825

$ autotag -m $(date +%Y%M%d)
3.2.1-dev+20200518

$ autotag -m g$(git rev-parse --short HEAD).$(date +%s)
3.2.1+g11492a8.1589860151
```

For additional help information use the `-h/--help` flag:
Expand Down Expand Up @@ -223,10 +263,12 @@ git clone [email protected]:pantheon-systems/autotag.git
cd autotag
make test
make build
```

Release information
-------------------

Autotag itself uses `autotag` to increment releases. The default [autotag](#scheme-autotag-default) scheme is used for version selection.
Autotag itself uses `autotag` to increment releases. The default [autotag](#scheme-autotag-default)
scheme is used for version selection.
84 changes: 78 additions & 6 deletions autotag.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import (
"github.com/hashicorp/go-version"
)

const (
// datetimeTsLayout is the YYYYMMDDHHMMSS time format
datetimeTsLayout = "20060102150405"
)

var (
// autotag commit message scheme:
majorRex = regexp.MustCompile(`(?i)\[major\]|\#major`)
Expand All @@ -27,10 +32,16 @@ var (
// https://regex101.com/r/XciTmT/2
conventionalCommitRex = regexp.MustCompile(`^\s*(?P<type>\w+)(?P<scope>(?:\([^()\r\n]*\)|\()?(?P<breaking>!)?)(?P<subject>:.*)?`)

// versionRex matches semver style versions, eg: `v1.0.0`
// versionRex matches semVer style versions, eg: `v1.0.0`
versionRex = regexp.MustCompile(`^v([\d]+\.?.*)`)

// semVerBuildMetaRex validates SemVer build metadata strings according to
// https://semver.org/#spec-item-10
semVerBuildMetaRex = regexp.MustCompile(`^[0-9A-Za-z-\.]+$`)
)

var timeNow = time.Now

// GitRepoConfig is the configuration needed to create a new *GitRepo.
type GitRepoConfig struct {
// Repo is the path to the root of the git repository.
Expand Down Expand Up @@ -70,6 +81,15 @@ type GitRepoConfig struct {
// v1.2.3-pre.1499308568
PreReleaseTimestampLayout string

// BuildMetadata is an optional string appended by a plus sign and a series of dot separated
// identifiers immediately following the patch or pre-release version. Identifiers MUST comprise
// only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Build metadata
// MUST be ignored when determining version precedence. Thus two versions that differ only in the
// build metadata, have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700,
// 1.0.0-beta+exp.sha.5114f85
// https://semver.org/#spec-item-10
BuildMetadata string

// Scheme is the versioning scheme to use when determining the version of the next
// tag. If not specified the default "autotag" is used.
//
Expand Down Expand Up @@ -98,14 +118,19 @@ type GitRepo struct {

preReleaseName string
preReleaseTimestampLayout string
buildMetadata string

scheme string
}

// NewRepo is a constructor for a repo object, parsing the tags that exist
func NewRepo(cfg GitRepoConfig) (*GitRepo, error) {
if cfg.Branch == "" {
return nil, fmt.Errorf("must specify a branch")
if err := validateConfig(cfg); err != nil {
return nil, err
}

if cfg.PreReleaseTimestampLayout == "datetime" {
cfg.PreReleaseTimestampLayout = datetimeTsLayout
}

gitDirPath, err := generateGitDirPath(cfg.RepoPath)
Expand All @@ -125,6 +150,7 @@ func NewRepo(cfg GitRepoConfig) (*GitRepo, error) {
branch: cfg.Branch,
preReleaseName: cfg.PreReleaseName,
preReleaseTimestampLayout: cfg.PreReleaseTimestampLayout,
buildMetadata: cfg.BuildMetadata,
scheme: cfg.Scheme,
}

Expand All @@ -140,6 +166,32 @@ func NewRepo(cfg GitRepoConfig) (*GitRepo, error) {
return r, nil
}

func validateConfig(cfg GitRepoConfig) error {
if cfg.Branch == "" {
return fmt.Errorf("must specify a branch")
}

if cfg.BuildMetadata != "" && !validateSemVerBuildMetadata(cfg.BuildMetadata) {
return fmt.Errorf("'%s' is not valid SemVer build metadata", cfg.BuildMetadata)
}

switch cfg.PreReleaseName {
case "", "alpha", "beta", "pre", "rc", "dev":
// nothing -- valid values
default:
return fmt.Errorf("pre-release-name '%s' is not valid; must be (alpha|beta|pre|rc|dev)", cfg.PreReleaseName)
}

switch cfg.PreReleaseTimestampLayout {
case "", "datetime", "epoch":
// nothing -- valid values
default:
return fmt.Errorf("pre-release-timestamp '%s' is not valid; must be (datetime|epoch)", cfg.PreReleaseTimestampLayout)
}

return nil
}

func generateGitDirPath(repoPath string) (string, error) {
absolutePath, err := filepath.Abs(repoPath)

Expand Down Expand Up @@ -229,7 +281,7 @@ func parseVersion(v string) (*version.Version, error) {
return nVersion, nil
}

// LatestVersion Reports the Lattest version of the given repo
// LatestVersion Reports the Latest version of the given repo
// TODO:(jnelson) this could be more intelligent, looking for a nil new and reporting the latest version found if we refactor autobump at some point Mon Sep 14 13:05:49 2015
func (r *GitRepo) LatestVersion() string {
return r.newVersion.String()
Expand Down Expand Up @@ -273,7 +325,7 @@ func preReleaseVersion(v *version.Version, name, tsLayout string) (*version.Vers

var (
timestamp string
currentTime = time.Now().UTC()
currentTime = timeNow().UTC()
)

if tsLayout == "epoch" {
Expand Down Expand Up @@ -335,13 +387,20 @@ func (r *GitRepo) calcVersion() error {
}
}

// if we want this to be a PreRelease tag, we need to enhance the format a bit
// append pre-release-name and/or pre-release-timestamp to the version
if len(r.preReleaseName) > 0 || len(r.preReleaseTimestampLayout) > 0 {
if r.newVersion, err = preReleaseVersion(r.newVersion, r.preReleaseName, r.preReleaseTimestampLayout); err != nil {
return err
}
}

// append optional build metadata
if r.buildMetadata != "" {
if r.newVersion, err = version.NewVersion(fmt.Sprintf("%s+%s", r.newVersion.String(), r.buildMetadata)); err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -461,3 +520,16 @@ func findNamedMatches(regex *regexp.Regexp, str string) map[string]string {
}
return results
}

// validateSemVerBuildMetadata validates SemVer build metadata strings according to
// https://semver.org/#spec-item-10
func validateSemVerBuildMetadata(meta string) bool {
metas := strings.Split(meta, ".")

for _, s := range metas {
if !semVerBuildMetaRex.MatchString(s) {
return false
}
}
return true
}
55 changes: 5 additions & 50 deletions autotag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,65 +18,18 @@ type Options struct {
RepoPath string `short:"r" long:"repo" description:"Path to the repo" default:"./" `
PreReleaseName string `short:"p" long:"pre-release-name" description:"create a pre-release tag with this name (can be: alpha|beta|pre|rc|dev)"`
PreReleaseTimestamp string `short:"T" long:"pre-release-timestamp" description:"create a pre-release tag and append a timestamp (can be: datetime|epoch)"`
BuildMetadata string `short:"m" long:"build-metadata" description:"optional SemVer build metadata to append to the version with '+' character"`
Scheme string `short:"s" long:"scheme" description:"The commit message scheme to use (can be: autotag|conventional)" default:"autotag"`
}

var opts Options

const (
// epochTsLayout is the UNIX epoch time format
epochTsLayout = "epoch"

// datetimeTsLayout is the YYYYMMDDHHMMSS time format
datetimeTsLayout = "20060102150405"
)

func timestampLayoutFromOpts() string {
switch opts.PreReleaseTimestamp {
case "epoch":
return epochTsLayout
case "datetime":
return datetimeTsLayout
default:
return ""
}
}

func init() {
_, err := flags.Parse(&opts)
if err != nil {
log.Println(err)
os.Exit(1)
}

if err := validateOpts(); err != nil {
log.SetOutput(os.Stderr)
log.Fatalf("error validating flags: %s\n", err.Error())
}
}

func validateOpts() error {
switch opts.PreReleaseName {
case "", "alpha", "beta", "pre", "rc", "dev":
// nothing -- valid values
default:
return fmt.Errorf("-p/--pre-release-name was %q; want (alpha|beta|pre|rc|dev)", opts.PreReleaseName)
}

switch opts.PreReleaseTimestamp {
case "", "datetime", "epoch":
// nothing -- valid values
default:
return fmt.Errorf("-T/--pre-release-timestamp was %q; want (datetime|epoch)", opts.PreReleaseTimestamp)
}

switch opts.Scheme {
case "", "autotag", "conventional":
// nothing -- valid values
default:
return fmt.Errorf("-s/--scheme was %q; want (autotag|conventional)", opts.Scheme)
}

return nil
}

func main() {
Expand All @@ -89,9 +42,11 @@ func main() {
RepoPath: opts.RepoPath,
Branch: opts.Branch,
PreReleaseName: opts.PreReleaseName,
PreReleaseTimestampLayout: timestampLayoutFromOpts(),
PreReleaseTimestampLayout: opts.PreReleaseTimestamp,
BuildMetadata: opts.BuildMetadata,
Scheme: opts.Scheme,
})
log.Println("FUCK1")

if err != nil {
fmt.Println("Error initializing: ", err)
Expand Down
Loading

0 comments on commit 9623602

Please sign in to comment.