SemVer working with new Masterminds/semver library
diff --git a/cmd/flatten.go b/cmd/flatten.go
index 746c083..51655af 100644
--- a/cmd/flatten.go
+++ b/cmd/flatten.go
@@ -3,8 +3,10 @@
import (
"os"
"path"
+ "strings"
"github.com/Masterminds/cookoo"
+ "github.com/Masterminds/semver"
"github.com/kylelemons/go-gypsy/yaml"
)
@@ -16,7 +18,7 @@
// Params:
// - packages ([]string): The packages to read. If this is empty, it reads all
// packages.
-// - force (bool): force git updates.
+// - force (bool): force vcs updates.
// - conf (*Config): The configuration.
//
// Returns:
@@ -87,15 +89,15 @@
Debug("----> Scanning %s", imp)
base := path.Join(f.top, imp)
mod := []string{}
- if m, ok := mergeGlide(base, imp, f.deps); ok {
+ if m, ok := mergeGlide(base, imp, f.deps, f.top); ok {
mod = m
- } else if m, ok = mergeGodep(base, imp, f.deps); ok {
+ } else if m, ok = mergeGodep(base, imp, f.deps, f.top); ok {
mod = m
- } else if m, ok = mergeGPM(base, imp, f.deps); ok {
+ } else if m, ok = mergeGPM(base, imp, f.deps, f.top); ok {
mod = m
- } else if m, ok = mergeGb(base, imp, f.deps); ok {
+ } else if m, ok = mergeGb(base, imp, f.deps, f.top); ok {
mod = m
- } else if m, ok = mergeGuess(base, imp, f.deps); ok {
+ } else if m, ok = mergeGuess(base, imp, f.deps, f.top); ok {
mod = m
}
@@ -164,7 +166,7 @@
}
}
-func mergeGlide(dir, name string, deps map[string]*Dependency) ([]string, bool) {
+func mergeGlide(dir, name string, deps map[string]*Dependency, vend string) ([]string, bool) {
gp := path.Join(dir, "glide.yaml")
if _, err := os.Stat(gp); err != nil {
return []string{}, false
@@ -183,14 +185,14 @@
Info("Found glide.yaml in %s", gp)
- return mergeDeps(deps, conf.Imports), true
+ return mergeDeps(deps, conf.Imports, vend), true
}
// listGodep appends Godeps entries to the deps.
//
// It returns true if any dependencies were found (even if not added because
// they are duplicates).
-func mergeGodep(dir, name string, deps map[string]*Dependency) ([]string, bool) {
+func mergeGodep(dir, name string, deps map[string]*Dependency, vend string) ([]string, bool) {
Debug("Looking in %s/Godeps/ for a Godeps.json file.\n", dir)
d, err := parseGodepGodeps(dir)
if err != nil {
@@ -201,28 +203,28 @@
}
Info("Found Godeps.json file for %q", name)
- return mergeDeps(deps, d), true
+ return mergeDeps(deps, d, vend), true
}
// listGb merges GB dependencies into the deps.
-func mergeGb(dir, pkg string, deps map[string]*Dependency) ([]string, bool) {
+func mergeGb(dir, pkg string, deps map[string]*Dependency, vend string) ([]string, bool) {
Debug("Looking in %s/vendor/ for a manifest file.\n", dir)
d, err := parseGbManifest(dir)
if err != nil || len(d) == 0 {
return []string{}, false
}
Info("Found gb manifest file for %q", pkg)
- return mergeDeps(deps, d), true
+ return mergeDeps(deps, d, vend), true
}
// mergeGPM merges GPM Godeps files into deps.
-func mergeGPM(dir, pkg string, deps map[string]*Dependency) ([]string, bool) {
+func mergeGPM(dir, pkg string, deps map[string]*Dependency, vend string) ([]string, bool) {
d, err := parseGPMGodeps(dir)
if err != nil || len(d) == 0 {
return []string{}, false
}
Info("Found GPM file for %q", pkg)
- return mergeDeps(deps, d), true
+ return mergeDeps(deps, d, vend), true
}
// mergeGuess guesses dependencies and merges.
@@ -230,7 +232,7 @@
// This always returns true because it always handles the job of searching
// for dependencies. So generally it should be the last merge strategy
// that you try.
-func mergeGuess(dir, pkg string, deps map[string]*Dependency) ([]string, bool) {
+func mergeGuess(dir, pkg string, deps map[string]*Dependency, vend string) ([]string, bool) {
/*
Info("Scanning %s for dependencies.", pkg)
buildContext, err := GetBuildContext()
@@ -286,7 +288,7 @@
}
// mergeDeps merges any dependency array into deps.
-func mergeDeps(orig map[string]*Dependency, add []*Dependency) []string {
+func mergeDeps(orig map[string]*Dependency, add []*Dependency, vend string) []string {
mod := []string{}
for _, dd := range add {
// Add it unless it's already there.
@@ -300,11 +302,115 @@
existing.Reference = dd.Reference
mod = append(mod, dd.Name)
} else if dd.Reference != "" && existing.Reference != "" && dd.Reference != existing.Reference {
- // We can detect version conflicts, but we can't really do
- // anything to correct, since we don't know the intentions of the
- // authors.
- Warn("Conflict: %s ref is %s, but also asked for %s", existing.Name, existing.Reference, dd.Reference)
- Info("Keeping %s %s", existing.Name, existing.Reference)
+ // Check if one is a version and the other is a constraint. If the
+ // version is in the constraint use that.
+ dest := path.Join(vend, dd.Name)
+ repo, err := existing.GetRepo(dest)
+ if err != nil {
+ Warn("Unable to access repo for %s\n", existing.Name)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ eIsRef := repo.IsReference(existing.Reference)
+ ddIsRef := repo.IsReference(dd.Reference)
+
+ // Both are references and different ones.
+ if eIsRef && ddIsRef {
+ Warn("Conflict: %s ref is %s, but also asked for %s\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ } else if eIsRef {
+ // Test ddIsRef is a constraint and if eIsRef is a semver
+ // within that
+ con, err := semver.NewConstraint(dd.Reference)
+ if err != nil {
+ Warn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dd.Name, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ ver, err := semver.NewVersion(existing.Reference)
+ if err != nil {
+ // The existing version is not a semantic version.
+ Warn("Conflict: %s version is %s, but also asked for %s\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ if con.Check(ver) {
+ Info("Keeping %s %s because it fits constraint '%s'", existing.Name, existing.Reference, dd.Reference)
+ } else {
+ Warn("Conflict: %s version is %s but does not meet constraint '%s'\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ }
+
+ } else if ddIsRef {
+ // Test eIsRef is a constraint and if ddIsRef is a semver
+ // within that
+ con, err := semver.NewConstraint(existing.Reference)
+ if err != nil {
+ Warn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", existing.Name, existing.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ ver, err := semver.NewVersion(dd.Reference)
+ if err != nil {
+ // The dd version is not a semantic version.
+ Warn("Conflict: %s version is %s, but also asked for %s\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ if con.Check(ver) {
+ // Use the specific version if noted instead of the existing
+ // constraint.
+ existing.Reference = dd.Reference
+ mod = append(mod, dd.Name)
+ Info("Using %s %s because it fits constraint '%s'", existing.Name, dd.Reference, existing.Reference)
+ } else {
+ Warn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ }
+ } else {
+ // Neither is a vcs reference and both could be semantic version
+ // constraints that are different.
+
+ _, err := semver.NewConstraint(dd.Reference)
+ if err != nil {
+ // dd.Reference is not a reference or a valid constraint.
+ Warn("Version %s %s is not a reference or valid semantic version constraint\n", dd.Name, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ continue
+ }
+
+ _, err = semver.NewConstraint(existing.Reference)
+ if err != nil {
+ // existing.Reference is not a reference or a valid constraint.
+ // We really should never end up here.
+ Warn("Version %s %s is not a reference or valid semantic version constraint\n", existing.Name, existing.Reference)
+
+ existing.Reference = dd.Reference
+ mod = append(mod, dd.Name)
+ Info("Using %s %s because it is a valid version", existing.Name, existing.Reference)
+ continue
+ }
+
+ // Both versions are constraints. Try to merge them.
+ // If either comparison has an || skip merging. That's complicated.
+ ddor := strings.Index(dd.Reference, "||")
+ eor := strings.Index(existing.Reference, "||")
+ if ddor == -1 && eor == -1 {
+ // Add the comparisons together.
+ newRef := existing.Reference + ", " + dd.Reference
+ existing.Reference = newRef
+ mod = append(mod, dd.Name)
+ Info("Combining %s semantic version constraints %s and %s", existing.Name, existing.Reference, dd.Reference)
+ } else {
+ Warn("Conflict: %s version is %s, but also asked for %s\n", existing.Name, existing.Reference, dd.Reference)
+ Info("Keeping %s %s", existing.Name, existing.Reference)
+ }
+ }
}
}
return mod
diff --git a/cmd/get_imports.go b/cmd/get_imports.go
index b730877..538c2ca 100644
--- a/cmd/get_imports.go
+++ b/cmd/get_imports.go
@@ -3,6 +3,7 @@
import (
"encoding/xml"
"fmt"
+ "sort"
//"log"
"io"
"net/http"
@@ -14,8 +15,8 @@
"strings"
"github.com/Masterminds/cookoo"
+ "github.com/Masterminds/semver"
v "github.com/Masterminds/vcs"
- "github.com/hashicorp/go-version"
)
func init() {
@@ -370,7 +371,7 @@
// Create the constraing first to make sure it's valid before
// working on the repo.
- constraint, err := version.NewConstraint(ver)
+ constraint, err := semver.NewConstraint(ver)
// Make sure the constriant is valid. At this point it's not a valid
// reference so if it's not a valid constrint we can exit early.
@@ -385,25 +386,25 @@
return err
}
- // Filter the references to just the semantic versions.
- semverMap := getSemVers(refs)
+ // Convert and filter the list to semver.Version instances
+ semvers := getSemVers(refs)
// Sort semver list
- var sv []string
- for k := range semverMap {
- sv = append(sv, k)
- }
- sorted := getSortedSemVerList(sv)
- for _, v := range sorted {
+ sort.Sort(sort.Reverse(semver.Collection(semvers)))
+ found := false
+ for _, v := range semvers {
if constraint.Check(v) {
-
+ found = true
// If the constrint passes get the original reference
- ver = semverMap[v.String()]
+ ver = v.Original()
break
}
}
-
- Info("Detected semantic version. Setting version for %s to %s.\n", dep.Name, ver)
+ if found {
+ Info("Detected semantic version. Setting version for %s to %s.\n", dep.Name, ver)
+ } else {
+ Warn("Unable to find semantic version for constraint %s %s\n", dep.Name, ver)
+ }
}
if err := repo.UpdateVersion(ver); err != nil {
Error("Failed to set version to %s: %s\n", dep.Reference, err)
diff --git a/cmd/semver.go b/cmd/semver.go
index 5be74aa..566f431 100644
--- a/cmd/semver.go
+++ b/cmd/semver.go
@@ -1,50 +1,18 @@
package cmd
import (
- "errors"
- "regexp"
- "sort"
-
+ "github.com/Masterminds/semver"
"github.com/Masterminds/vcs"
- "github.com/hashicorp/go-version"
)
-// The SemVer handling by github.com/hashicorp/go-version provides the ability
-// to work with versions
-
-// The compiled regular expression used to test the validity of a version.
-var versionRegexp *regexp.Regexp
-
-const versionRegexpRaw string = `v?(([0-9]+(\.[0-9]+){0,2})` +
- `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
- `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?)` +
- `?`
-
-func init() {
- versionRegexp = regexp.MustCompile("^" + versionRegexpRaw + "$")
-}
-
-// Filter the leading v from the version. Returns an error if there
-// was an issue including if the version was not SemVer
-// returns:
-// - the semantic verions (stripping any leading v if present)
-// - error if there was one
-func filterVersion(v string) (string, error) {
- matches := versionRegexp.FindStringSubmatch(v)
- if matches == nil || matches[1] == "" {
- return "", errors.New("No SemVer found.")
- }
- return matches[1], nil
-}
-
// Filter a list of versions to only included semantic versions. The response
// is a mapping of the original version to the semantic version.
-func getSemVers(refs []string) map[string]string {
- sv := map[string]string{}
+func getSemVers(refs []string) []*semver.Version {
+ sv := []*semver.Version{}
for _, r := range refs {
- nv, err := filterVersion(r)
+ v, err := semver.NewVersion(r)
if err == nil {
- sv[nv] = r
+ sv = append(sv, v)
}
}
@@ -78,16 +46,3 @@
}
return false, nil
}
-
-func getSortedSemVerList(v []string) []*version.Version {
- versions := make([]*version.Version, len(v))
- for i, raw := range v {
- v, err := version.NewVersion(raw)
- if err == nil {
- versions[i] = v
- }
- }
-
- sort.Sort(sort.Reverse(version.Collection(versions)))
- return versions
-}
diff --git a/cmd/semver_test.go b/cmd/semver_test.go
deleted file mode 100644
index 5f9a15b..0000000
--- a/cmd/semver_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package cmd
-
-import "testing"
-
-func TestFilterVersion(t *testing.T) {
- cases := []struct {
- version string
- semver string
- err bool
- }{
- {"1.2.3", "1.2.3", false},
- {"1.0", "1.0", false},
- {"1", "1", false},
- {"1.2.beta", "", true},
- {"foo", "", true},
- {"1.2-5", "1.2-5", false},
- {"1.2-beta.5", "1.2-beta.5", false},
- {"\n1.2", "", true},
- {"1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata", false},
- {"1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen", false},
- {"1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen", false},
- {"1.2.3.4", "", true},
- {"v1.2.3", "1.2.3", false},
- {"foo1.2.3", "", true},
- {"v1.0", "1.0", false},
- {"v1", "1", false},
- {"v1.2.beta", "", true},
- {"v1.2-5", "1.2-5", false},
- {"v1.2-beta.5", "1.2-beta.5", false},
- }
-
- for _, tc := range cases {
- fv, err := filterVersion(tc.version)
- if tc.err && err == nil {
- t.Errorf("expected error for version: %s", tc.version)
- } else if !tc.err && err != nil {
- t.Errorf("error for version %s: %s", tc.version, err)
- }
-
- if tc.semver != fv {
- t.Errorf("expected version '%s' does not match actual version '%s'", tc.semver, fv)
- }
- }
-}
-
-func TestGetSemVers(t *testing.T) {
- versions := []string{
- "1.2.3",
- "1.0",
- "1",
- "1.2.beta",
- "foo",
- "1.2-5",
- "1.2-beta.5",
- "\n1.2",
- "1.2.0-x.Y.0+metadata",
- "1.2.0-x.Y.0+metadata-width-hypen",
- "1.2.3-rc1-with-hypen",
- "1.2.3.4",
- "v1.2.3",
- "foo1.2.3",
- "v1.0",
- "v1",
- "v1.2.beta",
- "v1.2-5",
- "v1.2-beta.5",
- }
-
- pass := map[string]string{
- "1.2.3": "1.2.3",
- "1.0": "1.0",
- "1": "1",
- "1.2-5": "1.2-5",
- "1.2-beta.5": "1.2-beta.5",
- "1.2.0-x.Y.0+metadata": "1.2.0-x.Y.0+metadata",
- "1.2.0-x.Y.0+metadata-width-hypen": "1.2.0-x.Y.0+metadata-width-hypen",
- "1.2.3-rc1-with-hypen": "1.2.3-rc1-with-hypen",
- "v1.2.3": "1.2.3",
- "v1.0": "1.0",
- "v1": "1",
- "v1.2-5": "1.2-5",
- "v1.2-beta.5": "1.2-beta.5",
- }
-
- sv := getSemVers(versions)
- for k, v := range sv {
- temp, ok := pass[v]
- if !ok {
- t.Errorf("GetSemVers found %s in error", k)
- }
- if k != temp {
- t.Errorf("GetSemVers found %s but expected %s", k, temp)
- }
- }
-}
-
-func TestGetSortedSemVerList(t *testing.T) {
- versions := []string{
- "1.2.3",
- "2.1",
- "2",
- "1.2-beta.5",
- "1.0",
- "2.0.3",
- }
-
- pass := []string{
- "2.1.0",
- "2.0.3",
- "2.0.0",
- "1.2.3",
- "1.2.0-beta.5",
- "1.0.0",
- }
-
- sorted := getSortedSemVerList(versions)
- for k, v := range sorted {
- if pass[k] != v.String() {
- t.Errorf("Sorting expected %s but got %s", pass[k], v)
- }
- }
-}
diff --git a/glide.yaml b/glide.yaml
index b11af42..d9a3721 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -11,5 +11,6 @@
subpackages:
- .
- package: github.com/Masterminds/vcs
+ ref: ^1.0.0
- package: github.com/codegangsta/cli
- - package: github.com/hashicorp/go-version
+ - package: github.com/Masterminds/semver