Skip to content

Commit

Permalink
Add support for version ranges
Browse files Browse the repository at this point in the history
Fixes #62
  • Loading branch information
joshuacc committed Nov 10, 2022
1 parent bf231eb commit 5b48586
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 14 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
)

require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
github.com/VividCortex/ewma v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
Expand Down
77 changes: 70 additions & 7 deletions src/core/packages_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"os"
"sort"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/otiai10/copy"
Expand Down Expand Up @@ -72,6 +74,15 @@ func (pr *packagesRepository) GetPackageDependencies(dep ResolvedDependency) (*D
}

func (pr *packagesRepository) GetResolvedDependencySHA(dep Dependency) (string, error) {
if dep.Version().Kind() == SemVerRange {
exactDep, err := pr.getVersionMatchingSemVerRange(dep)
if err != nil {
return "", err
}

dep = exactDep
}

err := pr.ensurePackageIsReady(dep.Name(), dep.Version().Value())
if err != nil {
return "", err
Expand All @@ -87,6 +98,45 @@ func (pr *packagesRepository) GetResolvedDependencySHA(dep Dependency) (string,
return ref.Hash().String(), nil
}

func (pr *packagesRepository) getVersionMatchingSemVerRange(dep Dependency) (Dependency, error) {
repo, _, err := pr.ensurePackageIsUpToDate(dep.Name())
if err != nil {
return dep, err
}

// Get the latest tag that matches the semver range
constraint, err := semver.NewConstraint(dep.Version().Value())
if err != nil {
return nil, err
}
matchingTags := make([]*semver.Version, 0)
tagIter, err := repo.Tags()
if err != nil {
return nil, err
}
err = tagIter.ForEach(func(ref *plumbing.Reference) error {
tagName := strings.TrimPrefix(ref.Name().String(), "refs/tags/")
tagVersion, err := semver.NewVersion(tagName)
if err == nil && constraint.Check(tagVersion) {
matchingTags = append(matchingTags, tagVersion)
}
return nil
})
if err != nil {
return nil, err
}

sort.Sort(semver.Collection(matchingTags))

if len(matchingTags) == 0 {
return nil, errors.New("No matching tags found for " + dep.Name())
}

latestMatchingTag := matchingTags[len(matchingTags)-1]

return NewDependency(dep.Name(), NewVersion(SemVerExact, latestMatchingTag.String())), nil
}

func (pr *packagesRepository) ClearCache() error {
return pr.removeAll(pr.getCacheDir())
}
Expand All @@ -99,17 +149,17 @@ func (pr *packagesRepository) getPackageCacheDir(depName string) string {
return pr.getCacheDir() + `\` + depName
}

func (pr *packagesRepository) ensurePackageIsReady(depName string, depVersionString string) error {
func (pr *packagesRepository) ensurePackageIsUpToDate(depName string) (*git.Repository, bool, error) {
packageCacheDir := pr.getPackageCacheDir(depName)

err := os.MkdirAll(packageCacheDir, os.ModePerm)
if err != nil {
return errors.New("Error creating package cache directory")
return nil, false, errors.New("Error creating package cache directory")
}

packageCloneAlreadyExisted, err := utils.FileExists(packageCacheDir + `\.git`)
if err != nil {
return errors.New("Error checking if package was cloned")
return nil, false, errors.New("Error checking if package was cloned")
}

if !packageCloneAlreadyExisted {
Expand All @@ -123,22 +173,35 @@ func (pr *packagesRepository) ensurePackageIsReady(depName string, depVersionStr
if err.Error() == "authentication required" {
message = "Error downloading package " + depName + ". Are you sure that package exists?"
}
return errors.New(message)
return nil, packageCloneAlreadyExisted, errors.New(message)
}
}

// Checkout the specified version
repo, err := git.PlainOpen(packageCacheDir)
if err != nil {
return errors.New("Error opening package")
return nil, packageCloneAlreadyExisted, errors.New("Error opening package")
}

err = repo.Fetch(&git.FetchOptions{})
if err != nil && err.Error() != "already up-to-date" {
return nil, packageCloneAlreadyExisted, errors.New("Error fetching package")
}

return repo, packageCloneAlreadyExisted, nil
}

func (pr *packagesRepository) ensurePackageIsReady(depName string, depVersionString string) error {
repo, previouslyCloned, err := pr.ensurePackageIsUpToDate(depName)
if err != nil {
return err
}

worktree, err := repo.Worktree()
if err != nil {
return errors.New("Error getting worktree")
}

if packageCloneAlreadyExisted {
if previouslyCloned {
errorMessage := "Problem fetching latest updates to package " + depName + ". Continuing from local cache."

branches, err := repo.Branches()
Expand Down
24 changes: 24 additions & 0 deletions src/core/version.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package core

import (
"ahkpm/src/invariant"
"ahkpm/src/utils"
"errors"
"regexp"
"strings"
)

Expand All @@ -21,6 +23,7 @@ type version struct {
type VersionKind string

const (
SemVerRange VersionKind = "Semantic Version Range"
SemVerExact VersionKind = "Semantic Version"
Branch VersionKind = "Branch"
Tag VersionKind = "Tag"
Expand Down Expand Up @@ -52,13 +55,34 @@ func VersionFromSpecifier(versionSpecifier string) (Version, error) {
} else if strings.HasPrefix(versionSpecifier, "commit:") {
v.kind = Commit
v.value = strings.TrimPrefix(versionSpecifier, "commit:")
} else if utils.IsSemVerRange(versionSpecifier) {
v.kind = SemVerRange
v.value = getLegibleRange(versionSpecifier)
} else {
return v, errors.New("Invalid version specifier " + versionSpecifier)
}

return v, nil
}

// Check to see if the range is of form "1" or "1.2" to convert them
// to equivalents "1.x.x" and "1.2.x" respectively. The goal is to make
// the range more explicit and readable to the user before saving them.
func getLegibleRange(versionSpecifier string) string {
isSimpleRange, err := regexp.Match(`^\d+\.?(\d+)?$`, []byte(versionSpecifier))
invariant.AssertNoError(err)
if isSimpleRange {
if strings.Contains(versionSpecifier, ".") {
// If it's of form "1.2", convert it to "1.2.x"
return versionSpecifier + ".x"
} else {
// If it's of form "1", convert it to "1.x.x"
return versionSpecifier + ".x.x"
}
}
return versionSpecifier
}

// Represents the Version as a valid version specifier string.
func (v version) String() string {
if v.kind == Branch || v.kind == Tag || v.kind == Commit {
Expand Down
15 changes: 8 additions & 7 deletions src/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import (
"fmt"
"os"
"os/exec"
"regexp"

"github.com/Masterminds/semver/v3"
)

func IsSemVer(value string) bool {
// This regular expression is taken from semver.org
isMatch, err := regexp.MatchString("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", value)
if err != nil {
Exit("Error validating semver:")
}
_, err := semver.StrictNewVersion(value)
return err == nil
}

return isMatch
func IsSemVerRange(value string) bool {
_, err := semver.NewConstraint(value)
return err == nil
}

func Exit(msg string) {
Expand Down
13 changes: 13 additions & 0 deletions src/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,18 @@ func TestIsSemver(t *testing.T) {
assert.True(t, IsSemVer("1.2.3"))
assert.True(t, IsSemVer("1.2.3-beta.1"))
assert.True(t, IsSemVer("1.2.3-beta.1+build.1"))
assert.False(t, IsSemVer("1"))
assert.False(t, IsSemVer("^1.2.3"))
assert.False(t, IsSemVer("foobar"))
}

func TestIsSemverRange(t *testing.T) {
assert.True(t, IsSemVerRange("1.2.3"))
assert.True(t, IsSemVerRange("1.2.3-beta.1"))
assert.True(t, IsSemVerRange("1.2.3-beta.1+build.1"))
assert.True(t, IsSemVerRange("1.2.3-beta.1+build.1 || 1.2.3-beta.2"))
assert.True(t, IsSemVerRange("1.2.3-beta.1+build.1 || 1.2.3-beta.2 || 1.2.3-beta.3"))
assert.True(t, IsSemVerRange("^1.2.3"))
assert.True(t, IsSemVerRange("~1.2.3"))
assert.False(t, IsSemVerRange("foobar"))
}

0 comments on commit 5b48586

Please sign in to comment.