Skip to content

Commit

Permalink
feat: auto version with git tags (#418)
Browse files Browse the repository at this point in the history
This PR adds the ability to get latest version from `git-tags` by giving
the remote url tested with awscli.hcl locally. Underneath it runs `git
ls-remote`. Authentication depends on running environment's git
configuration. Works for private repository too.

```hcl
description = "The AWS Command Line Interface (CLI) is a unified tool to manage your AWS services."
test = "aws --version"
repository = "https://github.com/aws/aws-cli"

// ...

version "2.0.40" "2.1.26" "2.2.4" "2.4.2" "2.4.4" "2.4.7" "2.5.8" "2.6.4" "2.7.35"
        "2.13.26" "2.17.51" "2.18.6" {
  auto-version {
    git-tags = "git@github.com:aws/aws-cli.git"
  }
}
```

```console
╰─ go run ./cmd/hermit manifest auto-version awscli.hcl                                                                ─╯
info: Auto-versioned /Users/ylai/Development/hermit/awscli.hcl to 2.18.6
info:awscli: Updating 24 checksums...
info:awscli:   ed83a5f0d0885416d0c1599cc654974de7c96a446f9d7e829ce6cefc2de08b25 https://awscli.amazonaws.com/AWSCLIV2-2.0.40.pkg
info:awscli:   77d2a34db00cddc5aa198e6b03312140a7708a4b6000561879c94362c4ab34e2 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.4.4.zip
info:awscli:   92601956e6041fa9d7ca5b39cecc40d79073dac7db31e1ce76cb822cbb63eb30 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.6.4.zip
info:awscli:   678459c011edd570219895354ac9c9d1ba1faf49bd98cd3948a51cb25bc2e620 https://awscli.amazonaws.com/AWSCLIV2-2.17.51.pkg
info:awscli:   e27a8f66df1d6cea2672fbcf3856fce598cd780a8d25e4953798713d7da7e5a1 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.0.40.zip
info:awscli:   532b7d04edc94a6d6e5188726f3f519921463c618fc3453d54903f2a40d49987 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.1.26.zip
info:awscli:   94b35fdc1e013d07f7797a8ef8fceb399efc7f98d5a091c33f3ba31f7a7e05df https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.2.4.zip
info:awscli:   51b5db1fcf49ed674878ac79feceb7bf86a09e2f5d528e043c9bca4ed670dd81 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.5.8.zip
info:awscli:   497063433689d2b267046b2a0925c67786226a2c7c1c9682a00aaecae7d491fa https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.13.26.zip
info:awscli:   c75d1b6d0bec0193fed140736de172faf391a2e36e6da1ecd65ceb940a1a2c09 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.18.6.zip
info:awscli:   5d1c6adebe3da7beca37f937b63f942c73d10d03d6aed19934d331555a753bac https://awscli.amazonaws.com/AWSCLIV2-2.13.26.pkg
info:awscli:   7de020871379b2c7aa5eca75b76b39a58c649156e55d81fc8abea4c08bc3b11c https://awscli.amazonaws.com/AWSCLIV2-2.1.26.pkg
info:awscli:   852e7821d13f98e9633abfdbdfa7b3c7419ee186adf47196a1938dde0fdb3d46 https://awscli.amazonaws.com/AWSCLIV2-2.2.4.pkg
info:awscli:   bc880d80bdfacd94d1735509fe822b3f17975d1331399ba4dc12548696123e39 https://awscli.amazonaws.com/AWSCLIV2-2.4.2.pkg
info:awscli:   7fb4b1928d0b6fbe97c48e7b87787cff2287dad296b6fe0a56eddc6937d70f14 https://awscli.amazonaws.com/AWSCLIV2-2.4.4.pkg
info:awscli:   925810ba09815ef53997b901c76bd448c3caa593b5da1ccad79d17946ec94ab4 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.4.7.zip
info:awscli:   3673e6491f5f6ce285724197f2903738f7a63d75f968ae23fcb563de9aa95e9b https://awscli.amazonaws.com/AWSCLIV2-2.7.35.pkg
info:awscli:   a863ece08af5e2b77fe486428985b06c2c64da597fc6202de50fabfb4ceaa5c8 https://awscli.amazonaws.com/AWSCLIV2-2.18.6.pkg
info:awscli:   52dd2fd8c2537260c37d348824c96913359bb6927366c96cf948a0bc77e8b86f https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.4.2.zip
info:awscli:   5fbb1b718f0bb2ca23e48c76ddd3d02778c91dcc439c79317c95f101b4f629ca https://awscli.amazonaws.com/AWSCLIV2-2.4.7.pkg
info:awscli:   7e6e92ed83e9a9ae32313b76baa855b7d30452f8dee6aa6906fac9cae23107d2 https://awscli.amazonaws.com/AWSCLIV2-2.5.8.pkg
info:awscli:   0da0d44f6dff4287a702a3a38b7d7613473d51e524f2a47a511cff9f1c6bf34e https://awscli.amazonaws.com/AWSCLIV2-2.6.4.pkg
info:awscli:   104579773a39fad7505343ceb5df0e40014994f8f6d8cc1610de00f954121039 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.7.35.zip
info:awscli:   507413a75ded890308445aeb8765668c9e34eabafd84af422cff65ced7ef0db1 https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.17.51.zip
```
  • Loading branch information
lyonlai authored Oct 17, 2024
1 parent 9816ee0 commit 71e3426
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
2 changes: 2 additions & 0 deletions manifest/autoversion/autoversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ blocks:
latestVersion, err = gitHub(ghClient, block.autoVersion)
case block.autoVersion.HTML != nil:
latestVersion, err = htmlAutoVersion(httpClient, block.autoVersion)
case block.autoVersion.GitTags != "":
latestVersion, err = gitTagsAutoVersion(block.autoVersion)
default:
return "", errors.Errorf("%s: expected either github-release or html", block.version.Pos)
}
Expand Down
69 changes: 69 additions & 0 deletions manifest/autoversion/git_tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package autoversion

import (
"bufio"
"github.com/cashapp/hermit/errors"
"github.com/cashapp/hermit/manifest"
"os/exec"
"regexp"
"sort"
"strings"
)

func gitTagsAutoVersion(autoVersion *manifest.AutoVersionBlock) (string, error) {
versionRe, err := regexp.Compile(autoVersion.VersionPattern)
if err != nil {
return "", errors.WithStack(err)
}
if len(versionRe.SubexpNames()) != 2 {
return "", errors.Errorf("%s: version pattern %s must have exactly one named capture group", autoVersion.GitTags, autoVersion.VersionPattern)
}

remoteURL := autoVersion.GitTags

// --tags return all refs/tags/*
// using --refs to remove duplicated tag lines ended with ^{}
// output format of refs is
// <oid> TAB <ref> LF
// source: https://git-scm.com/docs/git-ls-remote
out, err := exec.Command("git", "ls-remote", "--tags", "--refs", remoteURL).Output()
if err != nil {
return "", errors.Wrapf(err, "error listing tags for %s", remoteURL)
}

versions := make(manifest.Versions, 0)

scanner := bufio.NewScanner(strings.NewReader(string(out)))
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
parts := strings.Split(line, "\t")
if len(parts) < 2 {
return "", errors.Wrapf(err, "error parsing tags from line :%s for %s", line, remoteURL)
}

// clean up the prefix
v := strings.ReplaceAll(parts[1], "refs/tags/", "")
// use version patterns to find the valid version
groups := versionRe.FindStringSubmatch(v)
if len(groups) != 2 {
// ignore invalid version according to config
if autoVersion.IgnoreInvalidVersions {
continue
}
return "", errors.Errorf("error parsing tags from line :%s for %s", line, remoteURL)
}

versions = append(versions, manifest.ParseVersion(groups[1]))
}

sort.Sort(versions)

if len(versions) == 0 {
return "", errors.Errorf("no tags found for %s", remoteURL)
}

return versions[len(versions)-1].String(), nil
}
59 changes: 59 additions & 0 deletions manifest/autoversion/git_tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package autoversion

import (
"fmt"
"github.com/alecthomas/assert/v2"
"github.com/cashapp/hermit/errors"
"github.com/cashapp/hermit/manifest"
"os"
"os/exec"
"path"
"testing"
)

func Test_GitTagsAutoVersion(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "")
defer os.RemoveAll(tmpDir)
assert.NoError(t, err, "could not create temp dir")

err = runCommandInDir(tmpDir, "git", "init", ".")
assert.NoError(t, err)

err = os.WriteFile(path.Join(tmpDir, "README.md"), []byte("readme"), 0600)
assert.NoError(t, err)

err = runCommandInDir(tmpDir, "git", "config", "--local", "user.email", "test@example.com")
assert.NoError(t, err)
err = runCommandInDir(tmpDir, "git", "config", "--local", "user.name", "test")
assert.NoError(t, err)
err = runCommandInDir(tmpDir, "git", "add", ".")
assert.NoError(t, err)

err = runCommandInDir(tmpDir, "git", "commit", "-m", "initial commit")
assert.NoError(t, err)

err = runCommandInDir(tmpDir, "git", "tag", "v0.0.1")
assert.NoError(t, err)

err = runCommandInDir(tmpDir, "git", "tag", "v0.0.2")
assert.NoError(t, err)

latest, err := gitTagsAutoVersion(&manifest.AutoVersionBlock{
GitTags: tmpDir,
VersionPattern: "v?(.*)",
})

assert.NoError(t, err)
assert.Equal(t, latest, "0.0.2")
}

func runCommandInDir(dir string, cmd string, args ...string) error { //nolint: unparam
command := exec.Command(cmd, args...)
command.Dir = dir
out, err := command.CombinedOutput()
if err != nil {
fmt.Println(string(out))
return errors.WithStack(err)
}
return nil
}
1 change: 1 addition & 0 deletions manifest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (c *Layer) match(arch string) bool {
type AutoVersionBlock struct {
GitHubRelease string `hcl:"github-release,optional" help:"GitHub <user>/<repo> to retrieve and update versions from the releases API."`
HTML *HTMLAutoVersionBlock `hcl:"html,block" help:"Extract version information from a HTML URL using XPath."`
GitTags string `hcl:"git-tags,optional" help:"Git remote URL to fetch git tags for version extraction."`

VersionPattern string `hcl:"version-pattern,optional" help:"Regex with one capture group to extract the version number from the origin." default:"v?(.*)"`
IgnoreInvalidVersions bool `hcl:"ignore-invalid-versions,optional" help:"Ignore tags that don't match the versin-pattern instead of failing. Does not apply to versions extracted using HTML URL"`
Expand Down

0 comments on commit 71e3426

Please sign in to comment.