You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2020/01/24 00:12:13 UTC

[mynewt-newt] branch master updated (892b2bc -> 7b42efd)

This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git.


    from 892b2bc  Add missing license header
     new 617cbec  Only allow one version per repo dependency
     new 7b42efd  Simplify repo dependencies

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 newt/deprepo/deprepo.go       | 355 +++++++++++++++++++-----------------------
 newt/deprepo/graph.go         | 155 ++++++------------
 newt/deprepo/matrix.go        | 172 --------------------
 newt/downloader/downloader.go |  28 +++-
 newt/install/install.go       | 290 +++++-----------------------------
 newt/newtutil/repo_version.go | 140 ++---------------
 newt/project/project.go       |  10 +-
 newt/repo/repo.go             |   7 +-
 newt/repo/version.go          | 162 +++----------------
 9 files changed, 318 insertions(+), 1001 deletions(-)
 delete mode 100644 newt/deprepo/matrix.go


[mynewt-newt] 02/02: Simplify repo dependencies

Posted by cc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git

commit 7b42efd83e6a2373c526897d7d2c61c82a33274c
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Thu Jan 16 12:59:02 2020 -0800

    Simplify repo dependencies
    
    Remove two features from Mynewt's repo dependency support:
    
    1. The ability to depend on a range of repo versions, and
    2. Mandatory version.yml files.
    
    This change is described in more detail here:
    https://github.com/apache/mynewt-newt/pull/365
---
 newt/deprepo/deprepo.go       | 345 +++++++++++++++++++-----------------------
 newt/deprepo/graph.go         | 152 ++++++-------------
 newt/deprepo/matrix.go        | 172 ---------------------
 newt/downloader/downloader.go |  28 +++-
 newt/install/install.go       | 287 +++++------------------------------
 newt/newtutil/repo_version.go |  31 ++--
 newt/project/project.go       |   1 -
 newt/repo/repo.go             |   1 -
 newt/repo/version.go          | 147 ++----------------
 9 files changed, 299 insertions(+), 865 deletions(-)

diff --git a/newt/deprepo/deprepo.go b/newt/deprepo/deprepo.go
index 56c65ab..159bac0 100644
--- a/newt/deprepo/deprepo.go
+++ b/newt/deprepo/deprepo.go
@@ -25,6 +25,8 @@ import (
 	"sort"
 	"strings"
 
+	log "github.com/sirupsen/logrus"
+
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/newt/repo"
 	"mynewt.apache.org/newt/util"
@@ -39,10 +41,29 @@ type VersionMap map[string]newtutil.RepoVersion
 // [repo-name] => requirements-for-key-repo
 type RequirementMap map[string]newtutil.RepoVersion
 
-// Indicates an inability to find an acceptable version of a particular repo.
+type ConflictEntry struct {
+	Dependent   RVPair
+	DependeeVer newtutil.RepoVersion
+}
+
+// Indicates dependencies on different versions of the same repo.
 type Conflict struct {
-	RepoName string
-	Filters  []Filter
+	DependeeName string
+	Entries      []ConflictEntry
+}
+
+func (c *Conflict) SortEntries() {
+	sort.Slice(c.Entries, func(i int, j int) bool {
+		ci := c.Entries[i]
+		cj := c.Entries[j]
+
+		x := CompareRVPairs(ci.Dependent, cj.Dependent)
+		if x != 0 {
+			return x < 0
+		}
+
+		return newtutil.CompareRepoVersions(ci.DependeeVer, cj.DependeeVer) < 0
+	})
 }
 
 // Returns a sorted slice of all constituent repo names.
@@ -86,27 +107,6 @@ func (vm VersionMap) String() string {
 	return s
 }
 
-// Constructs a version matrix from the specified repos.  Each row in the
-// resulting matrix corresponds to a repo in the supplied slice.  Each row node
-// represents a single version of the repo.
-func BuildMatrix(repos []*repo.Repo, vm VersionMap) (Matrix, error) {
-	m := Matrix{}
-
-	for _, r := range repos {
-		if !r.IsLocal() {
-			vers, err := r.NormalizedVersions()
-			if err != nil {
-				return m, err
-			}
-			if err := m.AddRow(r.Name(), vers); err != nil {
-				return m, err
-			}
-		}
-	}
-
-	return m, nil
-}
-
 // Builds a repo dependency graph from the repo requirements expressed in the
 // `project.yml` file.
 func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
@@ -120,7 +120,7 @@ func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
 			return nil, err
 		}
 
-		if err := dg.AddRootDep(repoName, []newtutil.RepoVersion{normalizedReq}); err != nil {
+		if err := dg.AddRootDep(repoName, normalizedReq); err != nil {
 			return nil, err
 		}
 	}
@@ -151,86 +151,6 @@ func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
 	return dg, nil
 }
 
-// Prunes unusable repo versions from the specified matrix.
-func PruneMatrix(m *Matrix, repos RepoMap, rootReqs RequirementMap) error {
-	pruned := map[*repo.RepoDependency]struct{}{}
-
-	// Removes versions of the depended-on package that fail to satisfy the
-	// dependent's requirements.  Each of the depended-on package's
-	// dependencies are then recursively visited.
-	var recurse func(dependentName string, dep *repo.RepoDependency) error
-	recurse = func(dependentName string, dep *repo.RepoDependency) error {
-		// Don't prune the same dependency twice; prevents infinite
-		// recursion.
-		if _, ok := pruned[dep]; ok {
-			return nil
-		}
-		pruned[dep] = struct{}{}
-
-		// Remove versions of this depended-on package that don't satisfy the
-		// dependency's version requirements.
-		r := repos[dep.Name]
-		normalizedReq, err := r.NormalizeVerReq(dep.VerReqs)
-		if err != nil {
-			return err
-		}
-		filter := Filter{
-			Name: dependentName,
-			Req:  normalizedReq,
-		}
-		m.ApplyFilter(dep.Name, filter)
-
-		// If there is only one version of the depended-on package left in the
-		// matrix, we can recursively call this function on all its
-		// dependencies.
-		//
-		// We don't do it, but it is possible to prune when there is more than
-		// one version remaining.  To accomplish this, we would collect the
-		// requirements from all versions, find their union, and remove
-		// depended-on packages that satisfy none of the requirements in the
-		// union.  The actual implementation (only prune when one version
-		// remains) is a simplified implementation of this general procedure.
-		// In exchange for simplicity, some unusable versions remain in the
-		// matrix that could have been pruned.  These unsuable versions must be
-		// evaluated unnecessarily when the matrix is being searched for an
-		// acceptable version set.
-		row := m.FindRow(r.Name())
-		if row != nil && len(row.Vers) == 1 {
-			ver := row.Vers[0]
-			commit, err := r.CommitFromVer(ver)
-			if err != nil {
-				return err
-			}
-			depRepo := repos[dep.Name]
-			for _, ddep := range depRepo.CommitDepMap()[commit] {
-				name := fmt.Sprintf("%s,%s", depRepo.Name(), ver.String())
-				if err := recurse(name, ddep); err != nil {
-					return err
-				}
-			}
-		}
-
-		return nil
-	}
-
-	// Prune versions that are guaranteed to be unusable.  Any repo version
-	// which doesn't satisfy a requirement in `project.yml` is a
-	// known-bad-version and can be removed.  These repos' dependencies can
-	// then be pruned in turn.
-	for repoName, req := range rootReqs {
-		dep := &repo.RepoDependency{
-			Name:    repoName,
-			VerReqs: req,
-		}
-
-		if err := recurse("project.yml", dep); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 // PruneDepGraph removes all entries from a depgraph that aren't in the given
 // repo slice.  This is necessary when the user wants to upgrade only a subset
 // of repos in the project.
@@ -264,12 +184,16 @@ func ConflictError(conflicts []Conflict) error {
 		}
 
 		s += fmt.Sprintf("    Installation of repo \"%s\" is blocked:",
-			c.RepoName)
+			c.DependeeName)
 
 		lines := []string{}
-		for _, f := range c.Filters {
-			lines = append(lines, fmt.Sprintf("\n    %30s requires %s %s",
-				f.Name, c.RepoName, f.Req.String()))
+		for _, e := range c.Entries {
+			dependee := RVPair{
+				Name: c.DependeeName,
+				Ver:  e.DependeeVer,
+			}
+			lines = append(lines, fmt.Sprintf("\n    %30s requires %s",
+				e.Dependent.String(), dependee.String()))
 		}
 		sort.Strings(lines)
 		s += strings.Join(lines, "")
@@ -278,100 +202,149 @@ func ConflictError(conflicts []Conflict) error {
 	return util.NewNewtError("Repository conflicts:\n" + s)
 }
 
-// Searches a version matrix for a set of acceptable repo versions.  If there
-// isn't an acceptable set of versions, the set with the fewest conflicts is
-// returned.
+// ResolveRepoDeps calculates the set of repo versions a project should be
+// upgraded to.
 //
-// @param m                     Matrix containing all unpruned repo versions.
-// @param dg                    The repo dependency graph.
+// dg: The project's repo dependency graph.  This includes root dependencies
+// 	   (i.e., dependencies expressed in `project.yml`).
 //
-// @return VersionMap           The first perfect set of repo versions, or the
-//                                  closest match if there is no perfect set.
-// @return []string             nil if a perfect set was found, else the names
-//                                  of the repos that lack a suitable version
-//                                  in the returned version map.
-func findClosestMatch(m Matrix, dg DepGraph) (VersionMap, []string) {
-	// Tracks the best match seen so far.
-	type Best struct {
-		vm       VersionMap
-		failures []string
+// Returns a version map on success; a set of conflicts on failure.
+func ResolveRepoDeps(dg DepGraph) (VersionMap, []Conflict) {
+	// Represents an entry in the working set.
+	type WSNode struct {
+		highPrio  bool   // If true, always "wins" without conflict.
+		dependent RVPair // Repo that expresses this dependency.
+		dependee  RVPair // Repo that is depended on.
+	}
+
+	ws := map[string]WSNode{}        // Working set (key=dependent).
+	cm := map[string]*Conflict{}     // Conflict map (key=dependee).
+	visited := map[string]struct{}{} // Tracks which nodes have been visited.
+
+	// Updates (or inserts a new) conflict object into the conflict map (cm).
+	addConflict := func(dependent RVPair, dependee RVPair) {
+		c := cm[dependee.Name]
+		if c == nil {
+			wsn := ws[dependee.Name]
+
+			c = &Conflict{
+				DependeeName: repoNameString(dependee.Name),
+				Entries: []ConflictEntry{
+					ConflictEntry{
+						Dependent: RVPair{
+							Name: repoNameString(wsn.dependent.Name),
+							Ver:  wsn.dependent.Ver,
+						},
+						DependeeVer: wsn.dependee.Ver,
+					},
+				},
+			}
+			cm[dependee.Name] = c
+		}
+
+		c.Entries = append(c.Entries, ConflictEntry{
+			Dependent:   dependent,
+			DependeeVer: dependee.Ver,
+		})
 	}
-	var best Best
-
-	for {
-		vm := m.CurVersions()
-		badRepos := dg.conflictingRepos(vm)
-		if len(badRepos) == 0 {
-			// Found a perfect match.  Return it.
-			return vm, nil
+
+	// Attempts to add a single node to the working set.  In case of a
+	// conflict, the conflict map is updated instead.
+	addWsNode := func(dependent RVPair, dependee RVPair) {
+		old, ok := ws[dependee.Name]
+		if !ok {
+			// This is the first time we've seen this repo.
+			ws[dependee.Name] = WSNode{
+				highPrio:  false,
+				dependent: dependent,
+				dependee:  dependee,
+			}
+			return
 		}
 
-		if best.failures == nil || len(badRepos) < len(best.failures) {
-			best.vm = vm
-			best.failures = badRepos
+		if newtutil.CompareRepoVersions(old.dependee.Ver, dependee.Ver) == 0 {
+			// We have already seen this exact dependency.  Ignore the
+			// duplicate.
+			return
 		}
 
-		// Evaluate the next set of versions on the following iteration.
-		if !m.Increment() {
-			// All version sets evaluated.  Return the best match.
-			return best.vm, best.failures
+		// There is already a dependency for a different version of this repo.
+		// Handle the conflict.
+		if old.highPrio {
+			// Root commit dependencies take priority over all others.
+			log.Debugf("discarding repo dependency in favor "+
+				"of root override: dep=%s->%s root=%s->%s",
+				dependent.String(), dependee.String(),
+				old.dependent.String(), old.dependee.String())
+		} else {
+			addConflict(dependent, dependee)
 		}
 	}
-}
 
-// Finds the first set of repo versions which satisfies the dependency graph.
-// If there is no acceptable set, a slice of conflicts is returned instead.
-//
-// @param m                     Matrix containing all unpruned repo versions.
-// @param dg                    The repo dependency graph.
-// @return VersionMap           The first perfect set of repo versions, or nil
-//                                  if there is no perfect set.
-// @return []Conflict           nil if a perfect set was found, else the set of
-//                                  conflicts preventing a perfect match from
-//                                  being returned.
-func FindAcceptableVersions(m Matrix, dg DepGraph) (VersionMap, []Conflict) {
-	vm, failures := findClosestMatch(m, dg)
-	if len(failures) == 0 {
-		// No failures implies a perfect match was found.  Return it.
-		return vm, nil
+	// Adds one entry to the working set (ws) for each of the given repo's
+	// dependencies.
+	visit := func(rvp RVPair) {
+		nodes := dg[rvp]
+		for _, node := range nodes {
+			addWsNode(rvp, node)
+		}
+
+		visited[rvp.Name] = struct{}{}
 	}
 
-	// A perfect version set doesn't exist.  Generate the set of relevant
-	// conflicts and return it.
-	conflicts := make([]Conflict, len(failures))
+	// Insert all the root dependencies (dependencies in `project.yml`) into
+	// the working set.  A root dependency is "high priority" if it points to a
+	// git commit rather than a version number.
+	rootDeps := dg[RVPair{}]
+	for _, dep := range rootDeps {
+		ws[dep.Name] = WSNode{
+			highPrio:  dep.Ver.Commit != "",
+			dependent: RVPair{Name: rootDependencyName},
+			dependee:  dep,
+		}
+	}
 
-	// Build a reverse dependency graph.  This will make it easy to determine
-	// which version requirements are relevant to the failure.
-	rg := dg.Reverse()
-	for i, f := range failures {
-		conflict := Conflict{
-			RepoName: f,
+	// Repeatedly iterate through the working set, visiting each unvisited
+	// node.  Stop looping when an iteration produces no new nodes.
+	for len(visited) < len(ws) {
+		// Iterate the working set in a consistent order.  The order doesn't
+		// matter for well-defined projects.  For invalid projects, different
+		// iteration orders can result in different conflicts being reported.
+		names := make([]string, 0, len(ws))
+		for name, _ := range ws {
+			names = append(names, name)
 		}
-		for _, node := range rg[f] {
-			// Determine if this filter is responsible for any conflicts.
-			// Record the name of the filter if it applies.
-			var filterName string
-			if node.Name == rootDependencyName {
-				filterName = "project.yml"
-			} else {
-				// If the version of the repo in the closest-match version map
-				// is the same one that imposes this version requirement,
-				// include it in the conflict object.
-				if newtutil.CompareRepoVersions(vm[node.Name], node.Ver) == 0 {
-					filterName = fmt.Sprintf(
-						"%s,%s", node.Name, node.Ver.String())
-				}
-			}
+		sort.Strings(names)
 
-			if filterName != "" {
-				conflict.Filters = append(conflict.Filters, Filter{
-					Name: filterName,
-					Req:  node.VerReq,
-				})
+		for _, name := range names {
+			wsn := ws[name]
+			if _, ok := visited[name]; !ok {
+				visit(wsn.dependee)
 			}
 		}
-		conflicts[i] = conflict
 	}
 
-	return vm, conflicts
+	// It is an error if any conflicts were detected.
+	if len(cm) > 0 {
+		var cs []Conflict
+		for _, c := range cm {
+			c.SortEntries()
+			cs = append(cs, *c)
+		}
+
+		sort.Slice(cs, func(i int, j int) bool {
+			return strings.Compare(cs[i].DependeeName, cs[j].DependeeName) < 0
+		})
+
+		return nil, cs
+	}
+
+	// The working set now contains the target version of each repo in the
+	// project.
+	vm := VersionMap{}
+	for name, wsn := range ws {
+		vm[name] = wsn.dependee.Ver
+	}
+
+	return vm, nil
 }
diff --git a/newt/deprepo/graph.go b/newt/deprepo/graph.go
index 676b9dd..25564f7 100644
--- a/newt/deprepo/graph.go
+++ b/newt/deprepo/graph.go
@@ -34,40 +34,32 @@ import (
 	"mynewt.apache.org/newt/util"
 )
 
-// Describes a repo that depends on other repos.
-type Dependent struct {
-	Name string
-	Ver  newtutil.RepoVersion
-}
-
 const rootDependencyName = ""
 const rootRepoName = "project.yml"
 
 // Represents a top-level repo dependency (i.e., a repo specified in
 // `project.yml`).
-var rootDependent = Dependent{Name: rootDependencyName}
+var rootDependent = RVPair{Name: rootDependencyName}
 
-// A single node in a repo dependency graph.
-type DepGraphNode struct {
-	// Name of depended-on repo.
+// Represents a repo-name,version pair.
+type RVPair struct {
 	Name string
-	// Expresses the versions of the repo that satisfy this dependency.
-	VerReq newtutil.RepoVersion
+	Ver  newtutil.RepoVersion
 }
 
 // A repo dependency graph.
 // Key: A repo with dependencies.
 // Value: The corresponding list of dependencies.
-type DepGraph map[Dependent][]DepGraphNode
+type DepGraph map[RVPair][]RVPair
 
 // A single node in a repo reverse dependency graph.
 type RevdepGraphNode struct {
 	// The name of the dependent repo.
 	Name string
 	// The version of the dependent repo.
-	Ver newtutil.RepoVersion
-	// The dependent's version requirements that apply to the graph key.
-	VerReq newtutil.RepoVersion
+	DependentVer newtutil.RepoVersion
+	// The version of the dependee repo that is required.
+	DependeeVer newtutil.RepoVersion
 }
 
 // A repo reverse dependency graph.
@@ -75,20 +67,38 @@ type RevdepGraphNode struct {
 // Value: The corresponding list of dependencies.
 type RevdepGraph map[string][]RevdepGraphNode
 
-func repoNameVerString(repoName string, ver newtutil.RepoVersion) string {
+func repoNameString(repoName string) string {
 	if repoName == rootDependencyName {
 		return rootRepoName
 	} else {
-		return fmt.Sprintf("%s-%s", repoName, ver.String())
+		return repoName
+	}
+}
+
+func repoNameVerString(repoName string, ver newtutil.RepoVersion) string {
+	if repoName == rootDependencyName || repoName == rootRepoName {
+		return rootRepoName
+	} else {
+		return fmt.Sprintf("%s/%s", repoName, ver.String())
 	}
 }
 
-func (dep *Dependent) String() string {
-	return repoNameVerString(dep.Name, dep.Ver)
+func (rvp *RVPair) String() string {
+	return repoNameVerString(rvp.Name, rvp.Ver)
 }
 
-func (dgn *DepGraphNode) String() string {
-	return fmt.Sprintf("%s,%s", dgn.Name, dgn.VerReq.String())
+func CompareRVPairs(a RVPair, b RVPair) int {
+	x := strings.Compare(a.Name, b.Name)
+	if x != 0 {
+		return x
+	}
+
+	x = newtutil.CompareRepoVersions(a.Ver, b.Ver)
+	if x != 0 {
+		return x
+	}
+
+	return 0
 }
 
 func (dg DepGraph) String() string {
@@ -108,8 +118,8 @@ func (dg DepGraph) String() string {
 }
 
 func (rgn *RevdepGraphNode) String() string {
-	return fmt.Sprintf("%s,%s", repoNameVerString(rgn.Name, rgn.Ver),
-		rgn.VerReq.String())
+	return fmt.Sprintf("%s,%s", repoNameVerString(rgn.Name, rgn.DependentVer),
+		rgn.DependeeVer.String())
 }
 
 func (rg RevdepGraph) String() string {
@@ -137,7 +147,7 @@ func (rg RevdepGraph) String() string {
 func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
 	reqMap RequirementMap) error {
 
-	dep := Dependent{
+	dep := RVPair{
 		Name: repoName,
 		Ver:  repoVer,
 	}
@@ -149,9 +159,9 @@ func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
 	}
 
 	for depName, depReq := range reqMap {
-		dg[dep] = append(dg[dep], DepGraphNode{
-			Name:   depName,
-			VerReq: depReq,
+		dg[dep] = append(dg[dep], RVPair{
+			Name: depName,
+			Ver:  depReq,
 		})
 	}
 
@@ -159,9 +169,7 @@ func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
 }
 
 // Adds a root dependency (i.e., required repo specified in `project.yml`).
-func (dg DepGraph) AddRootDep(repoName string,
-	verReqs []newtutil.RepoVersion) error {
-
+func (dg DepGraph) AddRootDep(repoName string, ver newtutil.RepoVersion) error {
 	rootDeps := dg[rootDependent]
 	for _, d := range rootDeps {
 		if d.Name == repoName {
@@ -171,9 +179,9 @@ func (dg DepGraph) AddRootDep(repoName string,
 		}
 	}
 
-	dg[rootDependent] = append(dg[rootDependent], DepGraphNode{
-		Name:   repoName,
-		VerReq: verReqs[0],
+	dg[rootDependent] = append(dg[rootDependent], RVPair{
+		Name: repoName,
+		Ver:  ver,
 	})
 
 	return nil
@@ -191,12 +199,13 @@ func (dg DepGraph) Reverse() RevdepGraph {
 
 	for dependent, nodes := range dg {
 		for _, node := range nodes {
-			// Nothing depends on project.yml (""), so exclude it from the result.
+			// Nothing depends on project.yml (""), so exclude it from the
+			// result.
 			if node.Name != "" {
 				rg[node.Name] = append(rg[node.Name], RevdepGraphNode{
-					Name:   dependent.Name,
-					Ver:    dependent.Ver,
-					VerReq: node.VerReq,
+					Name:         dependent.Name,
+					DependentVer: dependent.Ver,
+					DependeeVer:  node.Ver,
 				})
 			}
 		}
@@ -204,70 +213,3 @@ func (dg DepGraph) Reverse() RevdepGraph {
 
 	return rg
 }
-
-// Identifies repos which cannot satisfy all their dependents.  For example, if
-// `project.yml` requires X1 and Y2, but Y2 requires X2, then X is a
-// conflicting repo (no overlap in requirement sets).
-//
-// Returns the names of all repos that cannot be included in the build.  For
-// example, if:
-//     * X depends on Z 1.0.0
-//     * Y depends on Z 2.0.0
-// then Z is included in the returned slice because it can't satisfy all its
-// dependents.  X and Y are *not* included (unless they also have depedents
-// that cannot be satisfied).
-func (dg DepGraph) conflictingRepos(vm VersionMap) []string {
-	badRepoMap := map[string]struct{}{}
-
-	// Create a reverse dependency graph.
-	rg := dg.Reverse()
-
-	for dependeeName, nodes := range rg {
-		dependeeVer, ok := vm[dependeeName]
-		if !ok {
-			// This version was pruned from the matrix.  It is unusable.
-			badRepoMap[dependeeName] = struct{}{}
-			continue
-		}
-
-		// Dependee is the repo being depended on.  We want to determine if it
-		// can be included in the project without violating any dependency
-		// requirements.
-		//
-		// For each repo that depends on dependee (i.e., for each dependent):
-		// 1. Determine which of the dependent's requirements apply to the
-		//    dependee (e.g., if we are evaluating v2 of the dependent, then
-		//    v1's requirements do not apply).
-		// 2. Check if the dependee satisfies all applicable requirements.
-		for _, node := range nodes {
-			var nodeApplies bool
-			if node.Name == rootDependencyName {
-				// project.yml requirements are always applicable.
-				nodeApplies = true
-			} else {
-				// Repo dependency requirements only apply if they are
-				// associated with the version of the dependent that we are
-				// evaluating.
-				dependentVer, ok := vm[node.Name]
-				if ok {
-					nodeApplies = newtutil.CompareRepoVersions(
-						dependentVer, node.Ver) == 0
-				}
-			}
-			if nodeApplies {
-				if !dependeeVer.Satisfies(node.VerReq) {
-					badRepoMap[dependeeName] = struct{}{}
-					break
-				}
-			}
-		}
-	}
-
-	badRepoSlice := make([]string, 0, len(badRepoMap))
-	for repoName, _ := range badRepoMap {
-		badRepoSlice = append(badRepoSlice, repoName)
-	}
-	sort.Strings(badRepoSlice)
-
-	return badRepoSlice
-}
diff --git a/newt/deprepo/matrix.go b/newt/deprepo/matrix.go
deleted file mode 100644
index ca7d99a..0000000
--- a/newt/deprepo/matrix.go
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// deprepo: Package for resolving repo dependencies.
-package deprepo
-
-import (
-	"fmt"
-	"strings"
-
-	"mynewt.apache.org/newt/newt/newtutil"
-	"mynewt.apache.org/newt/util"
-)
-
-// Eliminates non-matching version numbers when applied to a matrix row.
-type Filter struct {
-	Name string
-	Req  newtutil.RepoVersion
-}
-
-// Contains all versions of a single repo.  These version numbers are read from
-// the repo's `repository.yml` file.  Only normalized versions are included.
-type MatrixRow struct {
-	// The name of the repo that the row corresponds to.
-	RepoName string
-
-	// All normalized versions of the repo.
-	Vers []newtutil.RepoVersion
-
-	// Indicates the version of this repo currently being evaluated for
-	// conflicts.
-	VerIdx int
-
-	// All filters that have been applied to this row.  This is only used
-	// during reporting.
-	Filters []Filter
-}
-
-// Contains all versions of a set of repos.  Each row correponds to a single
-// repo.  Each element within a row represents a single version of the repo.
-//
-// The Matrix type serves two purposes:
-//
-// 1. Simple lookup: Provides a convenient means of determining whether a
-// specific version of a repo exists.
-//
-// 2. Requirements matching: The client can cycle through all combinations of
-// repo versions via the `Increment()` function.  Each combination can be
-// exported as a version map via the `CurVersions()` function.  By evaluating
-// each version map against a set of requirements, the client can find the set
-// of repo versions to upgrade to.
-type Matrix struct {
-	rows []MatrixRow
-}
-
-func (m *Matrix) String() string {
-	lines := make([]string, len(m.rows))
-
-	for i, row := range m.rows {
-		line := fmt.Sprintf("%s:", row.RepoName)
-		for _, v := range row.Vers {
-			line += fmt.Sprintf(" %s", v.String())
-		}
-
-		lines[i] = line
-	}
-
-	return strings.Join(lines, "\n")
-}
-
-// Adjusts the matrix to point to the next possible set of repo versions.
-//
-// @return bool                 true if the matrix points to a new set;
-//                              false if the matrix wrapped around to the first
-//                                  set.
-func (m *Matrix) Increment() bool {
-	for i := range m.rows {
-		row := &m.rows[i]
-
-		row.VerIdx++
-		if row.VerIdx < len(row.Vers) {
-			return true
-		}
-
-		// No more versions left for this repo; proceed to next.
-		row.VerIdx = 0
-	}
-
-	// All version combinations evaluated.
-	return false
-}
-
-func (m *Matrix) findRowIdx(repoName string) int {
-	for i, row := range m.rows {
-		if row.RepoName == repoName {
-			return i
-		}
-	}
-
-	return -1
-}
-
-func (m *Matrix) FindRow(repoName string) *MatrixRow {
-	idx := m.findRowIdx(repoName)
-	if idx == -1 {
-		return nil
-	}
-	return &m.rows[idx]
-}
-
-func (m *Matrix) AddRow(repoName string,
-	vers []newtutil.RepoVersion) error {
-
-	if m.findRowIdx(repoName) != -1 {
-		return util.FmtNewtError("Duplicate repo \"%s\" in repo matrix",
-			repoName)
-	}
-
-	m.rows = append(m.rows, MatrixRow{
-		RepoName: repoName,
-		Vers:     newtutil.SortedVersionsDesc(vers),
-	})
-
-	return nil
-}
-
-// Removes all non-matching versions of the specified repo from the matrix.
-func (m *Matrix) ApplyFilter(repoName string, filter Filter) {
-	rowIdx := m.findRowIdx(repoName)
-	if rowIdx == -1 {
-		return
-	}
-	row := &m.rows[rowIdx]
-
-	goodVers := []newtutil.RepoVersion{}
-	for _, v := range row.Vers {
-		if v.Satisfies(filter.Req) {
-			goodVers = append(goodVers, v)
-		}
-	}
-
-	row.Vers = goodVers
-	row.Filters = append(row.Filters, filter)
-}
-
-// Constructs a version map from the matrix's current state.
-func (m *Matrix) CurVersions() VersionMap {
-	vm := make(VersionMap, len(m.rows))
-	for _, row := range m.rows {
-		if len(row.Vers) > 0 {
-			vm[row.RepoName] = row.Vers[row.VerIdx]
-		}
-	}
-
-	return vm
-}
diff --git a/newt/downloader/downloader.go b/newt/downloader/downloader.go
index 53c0457..c36ee3c 100644
--- a/newt/downloader/downloader.go
+++ b/newt/downloader/downloader.go
@@ -578,20 +578,40 @@ func (gd *GenericDownloader) CommitsFor(
 
 	commit = fixupCommitString(commit)
 
-	var commits []string
+	cm := map[string]struct{}{}
 
-	// Add all commits that are equivalent to the specified string.
+	// Add all cm that are equivalent to the specified string.
 	for _, c := range gd.commits {
 		if commit == c.hash {
 			// User specified a hash; add the corresponding branch or tag name.
-			commits = append(commits, c.name)
+			cm[c.name] = struct{}{}
 		} else if commit == c.name {
 			// User specified a branch or tag; add the corresponding hash.
-			commits = append(commits, c.hash)
+			cm[c.hash] = struct{}{}
+		}
+	}
+
+	// If the user specified a hash, add the hash itself.
+	ct, err := gd.CommitType(path, commit)
+	if err != nil {
+		return nil, err
+	}
+
+	if ct == COMMIT_TYPE_HASH {
+		hash, err := gd.HashFor(path, commit)
+		if err != nil {
+			return nil, err
 		}
+		cm[hash] = struct{}{}
 	}
 
+	// Sort the list of commit strings.
+	var commits []string
+	for cstring, _ := range cm {
+		commits = append(commits, cstring)
+	}
 	sort.Strings(commits)
+
 	return commits, nil
 }
 
diff --git a/newt/install/install.go b/newt/install/install.go
index 4105f84..9085f28 100644
--- a/newt/install/install.go
+++ b/newt/install/install.go
@@ -36,16 +36,6 @@
 // "version specifiers".  Version specifiers map to "official releases", while
 // git commits typically map to "custom versions".
 //
-// Before newt can do anything with a repo requirement, it needs to extrapolate
-// two pieces of information:
-// 1. The normalized version number.
-// 2. The git commit.
-//
-// Newt needs the normalized version to determine the repo's dependencies, and
-// to ensure the version of the repo is compatible with the version of newt
-// being used.  Newt needs the git commit so that it knows how to checkout the
-// desired repo version.
-//
 // ### VERSION SPECIFIERS
 //
 // A repo's `repository.yml` file maps version specifiers to git commits in its
@@ -59,32 +49,6 @@
 // By performing a series of recursive lookups, newt converts a version
 // specifier to a normalized-version,git-commit pair.
 //
-// ### GIT COMMITS
-//
-// When newt encounters a git commit in the `project.yml` file, it already has
-// one piece of information that it needs: the git commit.  Newt uses the
-// following procedure to extrapolate its corresponding repo version:
-//
-// 1. If the repo at the commit contains a `version.yml` file, read the version
-//    from this file.
-// 2. Else, if the repo's `repository.yml` file maps the commit to a version
-//    number, use that version number.
-// 3. Else, warn the user and assume 0.0.0.
-//
-// The `version.yml` file is expected to be present in every commit in a repo.
-// It has the following form:
-//    repo.version: <normalized-version-number>
-//
-// For example, if commit 10 of repo X contains the following `version.yml`
-// file:
-//    repo.version: 1.10.0
-//
-// and commit 20 of repo X changes `version.yml` to:
-//    repo.version: 2.0.0
-//
-// then newt extrapolates 1.10.0 from commits 10 through 19 (inclusive).
-// Commit 20 and beyond correspond to 2.0.0.
-//
 // ### VERSION STRINGS
 //
 // Newt uses the following procedure when displaying a repo version to the
@@ -93,11 +57,8 @@
 // Official releases are expressed as a normalized version.
 //     e.g., 1.10.0
 //
-// Custom versions are expressed with the following form:
-//     <extrapolated-version>/<git-commit>
-//
-// E.g.,:
-//     0.0.0/0aae710654b48d9a84d54de771cc18427709df7d
+// Custom versions are expressed as a git hash.
+// 	   e.g., 0aae710654b48d9a84d54de771cc18427709df7d
 // ----------------------------------------------------------------------------
 
 package install
@@ -106,7 +67,6 @@ import (
 	"bufio"
 	"fmt"
 	"os"
-	"sort"
 	"strings"
 
 	log "github.com/sirupsen/logrus"
@@ -126,8 +86,7 @@ const (
 )
 
 // Determines the currently installed version of the specified repo.  If the
-// repo doesn't have a valid `version.yml` file, and it isn't using a commit
-// that maps to a version, 0.0.0 is returned.
+// repo isn't using a commit that maps to a version, 0.0.0 is returned.
 func detectVersion(r *repo.Repo) (newtutil.RepoVersion, error) {
 	ver, err := r.InstalledVersion()
 	if err != nil {
@@ -254,104 +213,6 @@ func (inst *Installer) ensureDepsInList(repos []*repo.Repo,
 	return deps
 }
 
-// Normalizes the installer's set of repo requirements.  Only the repos in the
-// specified slice are considered.
-//
-// A repo requirement takes one of two forms:
-//  * Version specifier (e.g., 1.3.0. or 0-dev).
-//  * Git commit (e.g., 1f48a3c or master).
-//
-// This function converts requirements from the second form to the first.  A
-// git commit is converted to a version number with this procedure:
-//
-// 1. If the specified commit contains a `version.yml` file, read the version
-//    from this file.
-// 2. Else, if the repo's `repository.yml` file maps the commit to a version
-//    number, use that version number.
-// 3. Else, assume 0.0.0.
-func (inst *Installer) inferReqVers(repos []*repo.Repo) error {
-	for _, r := range repos {
-		req, ok := inst.reqs[r.Name()]
-		if ok {
-			if req.Commit != "" {
-				ver, err := r.NonInstalledVersion(req.Commit)
-				if err != nil {
-					return err
-				}
-
-				if ver == nil {
-					util.OneTimeWarning(
-						"Could not detect version of requested repo "+
-							"%s:%s; assuming 0.0.0",
-						r.Name(), req.Commit)
-
-					ver = &req
-				}
-				req = *ver
-				req.Commit, err = r.HashFromVer(req)
-				if err != nil {
-					return err
-				}
-
-				inst.reqs[r.Name()] = req
-			}
-		}
-	}
-
-	return nil
-}
-
-// Determines if the `project.yml` file specifies a nonexistent repo version.
-// Only the repos in the specified slice are considered.
-//
-// @param repos                 The list of repos to consider during the check.
-// @param m                     A matrix containing all versions of the
-//                                  specified repos.
-//
-// @return error                Error if any repo requirement is invalid.
-func (inst *Installer) detectIllegalRepoReqs(
-	repos []*repo.Repo, m deprepo.Matrix) error {
-
-	var lines []string
-	for _, r := range repos {
-		req, ok := inst.reqs[r.Name()]
-		if ok {
-			row := m.FindRow(r.Name())
-			if row == nil {
-				return util.FmtNewtError(
-					"internal error; repo \"%s\" missing from matrix", r.Name())
-			}
-
-			r := inst.repos[r.Name()]
-			nreq, err := r.NormalizeVerReq(req)
-			if err != nil {
-				return err
-			}
-
-			anySatisfied := false
-			for _, ver := range row.Vers {
-				if ver.Satisfies(nreq) {
-					anySatisfied = true
-					break
-				}
-			}
-			if !anySatisfied {
-				line := fmt.Sprintf("    %s,%s", r.Name(), nreq.String())
-				lines = append(lines, line)
-			}
-		}
-	}
-
-	if len(lines) > 0 {
-		sort.Strings(lines)
-		return util.NewNewtError(
-			"project.yml file specifies nonexistent repo versions:\n" +
-				strings.Join(lines, "\n"))
-	}
-
-	return nil
-}
-
 // Indicates whether a repo should be upgraded to the specified version.  A
 // repo should be upgraded if it is not currently installed, or if a version
 // other than the desired one is installed.
@@ -399,7 +260,8 @@ func (inst *Installer) filterUpgradeList(
 
 	filtered := deprepo.VersionMap{}
 
-	for name, ver := range vm {
+	for _, name := range vm.SortedNames() {
+		ver := vm[name]
 		doUpgrade, err := inst.shouldUpgradeRepo(name, ver)
 		if err != nil {
 			return nil, err
@@ -526,22 +388,6 @@ func (inst *Installer) installPrompt(vm deprepo.VersionMap, op installOp,
 	}
 }
 
-// Filters out repos from a version map, keeping only those which are present
-// in the supplied slice.
-func filterVersionMap(
-	vm deprepo.VersionMap, keep []*repo.Repo) deprepo.VersionMap {
-
-	filtered := deprepo.VersionMap{}
-	for _, r := range keep {
-		name := r.Name()
-		if ver, ok := vm[name]; ok {
-			filtered[name] = ver
-		}
-	}
-
-	return filtered
-}
-
 // Creates a slice of repos, each corresponding to an element in the provided
 // version map.  The returned slice is sorted by repo name.
 func (inst *Installer) versionMapRepos(
@@ -564,34 +410,6 @@ func (inst *Installer) versionMapRepos(
 	return repos, nil
 }
 
-// assignCommits applies commit hashes to the selected version of each repo in
-// a version map.  In other words, it propagates `<...>-commit` requirements
-// from `project.yml` and `repository.yml` files.  If override is false,
-// attempting to set a commit for the same repo twice results in an error.
-func (inst *Installer) assignCommits(vm deprepo.VersionMap, repoName string,
-	req newtutil.RepoVersion, override bool) error {
-
-	curVer := vm[repoName]
-	if curVer.Satisfies(req) {
-		if req.Commit != "" {
-			prevCommit := vm[repoName].Commit
-			newCommit := req.Commit
-
-			if !override && prevCommit != "" && prevCommit != newCommit {
-				return util.FmtNewtError(
-					"repo %s: multiple commits: %s, %s",
-					repoName, vm[repoName].Commit, req.Commit)
-
-			}
-			ver := vm[repoName]
-			ver.Commit = req.Commit
-			vm[repoName] = ver
-		}
-	}
-
-	return nil
-}
-
 // Calculates a map of repos and version numbers that should be included in an
 // install or upgrade operation.
 func (inst *Installer) calcVersionMap(candidates []*repo.Repo) (
@@ -619,25 +437,23 @@ func (inst *Installer) calcVersionMap(candidates []*repo.Repo) (
 		}
 	}
 
-	m, err := deprepo.BuildMatrix(repoList, inst.vers)
-	if err != nil {
-		return nil, err
-	}
-
-	if err := inst.inferReqVers(repoList); err != nil {
-		return nil, err
-	}
-
-	// If the `project.yml` file specifies an invalid repo version, abort now.
-	if err := inst.detectIllegalRepoReqs(repoList, m); err != nil {
-		return nil, err
-	}
-
-	// Remove blocked repo versions from the table.
-	if err := deprepo.PruneMatrix(
-		&m, inst.repos, inst.reqs); err != nil {
+	// Ensure project.yml doesn't specify any invalid versions
+	for repoName, repoVer := range inst.reqs {
+		rvp := deprepo.RVPair{
+			Name: repoName,
+			Ver:  repoVer,
+		}
+		r := inst.repos[repoName]
+		if r == nil {
+			return nil, util.FmtNewtError(
+				"project.yml depends on an unknown repo: %s", rvp.String())
+		}
 
-		return nil, err
+		if !r.VersionIsValid(repoVer) {
+			return nil, util.FmtNewtError(
+				"project.yml depends on an unknown repo version: %s",
+				rvp.String())
+		}
 	}
 
 	// Construct a repo dependency graph from the `project.yml` version
@@ -647,54 +463,21 @@ func (inst *Installer) calcVersionMap(candidates []*repo.Repo) (
 		return nil, err
 	}
 
-	log.Debugf("Repo dependency graph:\n%s\n", dg.String())
-	log.Debugf("Repo reverse dependency graph:\n%s\n", dg.Reverse().String())
+	log.Debugf("repo dependency graph:\n%s\n", dg.String())
+	log.Debugf("repo reverse dependency graph:\n%s\n", dg.Reverse().String())
 
-	// Don't consider repos that the user excluded (if a repo list was
-	// specified).
-	deprepo.PruneDepGraph(dg, candidates)
+	// Don't consider repos that the user excluded (i.e., if a partial repo
+	// list was specified).
+	deprepo.PruneDepGraph(dg, repoList)
 
-	// Try to find a version set that satisfies the dependency graph.  If no
-	// such set exists, report the conflicts and abort.
-	vm, conflicts := deprepo.FindAcceptableVersions(m, dg)
+	vm, conflicts := deprepo.ResolveRepoDeps(dg)
 	if len(conflicts) > 0 {
 		return nil, deprepo.ConflictError(conflicts)
 	}
 
-	// If repos specify git commits in their repo-dependencies, ensure we get
-	// them.
-	rg := dg.Reverse()
-	for name, nodes := range rg {
-		for _, node := range nodes {
-			if newtutil.CompareRepoVersions(node.Ver, vm[node.Name]) == 0 {
-				// Don't consider project.yml dependencies here.  Those get
-				// assigned last so that they can override inter-repo
-				// dependencies.
-				if node.Name != "" {
-					if err := inst.assignCommits(vm, name, node.VerReq, false); err != nil {
-						return nil, err
-					}
-				}
-			}
-		}
-	}
-
-	// If project.yml specified any specific git commits, ensure we get them.
-	// Commits specified in project.yml override those from repo-dependencies.
-	for name, reqs := range inst.reqs {
-		if err := inst.assignCommits(vm, name, reqs, true); err != nil {
-			return nil, err
-		}
-	}
-
-	log.Debugf("repo version map after project.yml overrides:\n%s",
+	log.Debugf("repo version map:\n%s",
 		vm.String())
 
-	// Now that we know which repo versions we want, we can eliminate some
-	// false-positives from the repo list.
-	repoList = inst.ensureDepsInList(candidates, vm)
-	vm = filterVersionMap(vm, repoList)
-
 	return vm, nil
 }
 
@@ -918,18 +701,20 @@ func (inst *Installer) remoteRepoInfo(r *repo.Repo, vm *deprepo.VersionMap) {
 	ri := inst.gatherInfo(r, vm)
 	s := fmt.Sprintf("    * %s:", r.Name())
 
-	s += fmt.Sprintf(" %s,", ri.commitHash)
+	s += fmt.Sprintf(" %s", ri.commitHash)
 	if ri.installedVer == nil {
-		s += " (not installed)"
+		s += ", (not installed)"
 	} else if ri.errorText != "" {
-		s += fmt.Sprintf(" (unknown: %s)", ri.errorText)
+		s += fmt.Sprintf(", (unknown: %s)", ri.errorText)
 	} else {
-		s += fmt.Sprintf(" %s", ri.installedVer.String())
+		if ri.installedVer.Commit == "" {
+			s += fmt.Sprintf(", %s", ri.installedVer.String())
+		}
 		if ri.dirtyState != "" {
-			s += fmt.Sprintf(" (dirty: %s)", ri.dirtyState)
+			s += fmt.Sprintf(", (dirty: %s)", ri.dirtyState)
 		}
 		if ri.needsUpgrade {
-			s += " (needs upgrade)"
+			s += ", (needs upgrade)"
 		}
 	}
 	util.StatusMessage(util.VERBOSITY_DEFAULT, "%s\n", s)
diff --git a/newt/newtutil/repo_version.go b/newt/newtutil/repo_version.go
index 5e64b42..b8516db 100644
--- a/newt/newtutil/repo_version.go
+++ b/newt/newtutil/repo_version.go
@@ -71,31 +71,40 @@ func (v *RepoVersion) toComparable() RepoVersion {
 	return clone
 }
 
-func CompareRepoVersions(v1 RepoVersion, v2 RepoVersion) int64 {
+func CompareRepoVersions(v1 RepoVersion, v2 RepoVersion) int {
 	v1 = v1.toComparable()
 	v2 = v2.toComparable()
 
+	toInt := func(i64 int64) int {
+		if i64 < 0 {
+			return -1
+		} else if i64 > 0 {
+			return 1
+		} else {
+			return 0
+		}
+	}
+
 	if r := v1.Major - v2.Major; r != 0 {
-		return r
+		return toInt(r)
 	}
 
 	if r := v1.Minor - v2.Minor; r != 0 {
-		return r
+		return toInt(r)
 	}
 
 	if r := v1.Revision - v2.Revision; r != 0 {
-		return r
+		return toInt(r)
 	}
 
 	return 0
 }
 
-// XXX: Remove this
-func (v *RepoVersion) Satisfies(verReq RepoVersion) bool {
-	return CompareRepoVersions(verReq, *v) == 0
-}
-
 func (ver *RepoVersion) String() string {
+	if ver.Commit != "" {
+		return ver.Commit
+	}
+
 	s := fmt.Sprintf("%d", ver.Major)
 	if ver.Minor != VERSION_FLOATING {
 		s += fmt.Sprintf(".%d", ver.Minor)
@@ -108,10 +117,6 @@ func (ver *RepoVersion) String() string {
 		s += fmt.Sprintf("-%s", ver.Stability)
 	}
 
-	if ver.Commit != "" {
-		s += fmt.Sprintf("/%s", ver.Commit)
-	}
-
 	return s
 }
 
diff --git a/newt/project/project.go b/newt/project/project.go
index 42e4669..05beb1c 100644
--- a/newt/project/project.go
+++ b/newt/project/project.go
@@ -189,7 +189,6 @@ func (proj *Project) FindRepoPath(rname string) string {
 func (proj *Project) GetRepoVersion(
 	rname string) (*newtutil.RepoVersion, error) {
 
-	// First, try to read the repo's `version.yml` file.
 	r := proj.repos[rname]
 	if r == nil {
 		return nil, nil
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index 075e0bf..17d9e60 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -43,7 +43,6 @@ const REPO_NAME_LOCAL = "local"
 const REPO_DEFAULT_PERMS = 0755
 
 const REPO_FILE_NAME = "repository.yml"
-const REPO_VER_FILE_NAME = "version.yml"
 const REPOS_DIR = "repos"
 
 type Repo struct {
diff --git a/newt/repo/version.go b/newt/repo/version.go
index 561477e..5f0329b 100644
--- a/newt/repo/version.go
+++ b/newt/repo/version.go
@@ -26,14 +26,10 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
-	"mynewt.apache.org/newt/newt/config"
 	"mynewt.apache.org/newt/newt/newtutil"
 	"mynewt.apache.org/newt/util"
 )
 
-var versionYmlMissing = util.FmtNewtError("version.yml missing")
-var versionYmlBad = util.FmtNewtError("version.yml bad")
-
 func versString(vers []newtutil.RepoVersion) string {
 	s := "["
 
@@ -123,6 +119,16 @@ func (r *Repo) CommitFromVer(ver newtutil.RepoVersion) (string, error) {
 	return commit, nil
 }
 
+func (r *Repo) VersionIsValid(ver newtutil.RepoVersion) bool {
+	if ver.Commit == "" {
+		_, err := r.CommitFromVer(ver)
+		return err == nil
+	}
+
+	cs, _ := r.downloader.CommitsFor(r.Path(), ver.Commit)
+	return len(cs) > 0
+}
+
 // Determines whether the two specified commits refer to the same point in the
 // repo's history.
 func (r *Repo) CommitsEquivalent(c1 string, c2 string) (bool, error) {
@@ -302,113 +308,15 @@ func (r *Repo) VersionsEqual(v1 newtutil.RepoVersion,
 	return h1 == h2
 }
 
-// Parses the `version.yml` file at the specified path.  On success, the parsed
-// version is returned.
-func parseVersionYml(path string) (newtutil.RepoVersion, error) {
-	yc, err := config.ReadFile(path)
-	if err != nil {
-		if util.IsNotExist(err) {
-			return newtutil.RepoVersion{}, versionYmlMissing
-		} else {
-			return newtutil.RepoVersion{}, versionYmlBad
-		}
-	}
-
-	verString, err := yc.GetValString("repo.version", nil)
-	util.OneTimeWarningError(err)
-
-	if verString == "" {
-		return newtutil.RepoVersion{}, versionYmlBad
-	}
-
-	ver, err := newtutil.ParseRepoVersion(verString)
-	if err != nil || !ver.IsNormalized() {
-		return newtutil.RepoVersion{}, versionYmlBad
-	}
-
-	return ver, nil
-}
-
-// Reads and parses the `version.yml` file belonging to an installed repo.
-func (r *Repo) installedVersionYml() (*newtutil.RepoVersion, error) {
-	ver, err := parseVersionYml(r.Path() + "/" + REPO_VER_FILE_NAME)
-	if err != nil {
-		return nil, err
-	}
-
-	return &ver, nil
-}
-
-// Downloads and parses the `version.yml` file from the repo at the specified
-// commit.
-func (r *Repo) nonInstalledVersionYml(
-	commit string) (*newtutil.RepoVersion, error) {
-
-	filePath, err := r.downloadFile(commit, REPO_VER_FILE_NAME)
-	if err != nil {
-		// The download failed.  Determine if the commit string is bad or if
-		// the file just doesn't exist in that commit.
-		if _, e2 := r.downloader.CommitType(r.localPath, commit); e2 != nil {
-			// Bad commit string.
-			return nil, err
-		}
-
-		// The commit exists, but it doesn't contain a `version.yml` file.
-		// Assume the commit corresponds to version 0.0.0.
-		return nil, versionYmlMissing
-	}
-
-	ver, err := parseVersionYml(filePath)
-	if err != nil {
-		return nil, err
-	}
-
-	return &ver, nil
-}
-
-// Tries to determine which repo version meets the specified criteria:
-//  * Maps to the specified commit string (or an equivalent commit).
-//  * Is equal to the specified version read from `version.yml` (if not-null).
-func (r *Repo) inferVersion(commit string, vyVer *newtutil.RepoVersion) (
-	*newtutil.RepoVersion, error) {
-
+// Tries to determine which repo version maps to the specified commit string
+// (or an equivalent commit).
+func (r *Repo) inferVersion(commit string) (*newtutil.RepoVersion, error) {
 	// Search `repository.yml` for versions that the specified commit maps to.
 	ryVers, err := r.VersFromEquivCommit(commit)
 	if err != nil {
 		return nil, err
 	}
 
-	// If valid versions were derived from both `version.yml` and the specified
-	// commit+`repository.yml`, look for a common version.
-	if vyVer != nil {
-		if len(ryVers) > 0 {
-			for _, cv := range ryVers {
-				if newtutil.CompareRepoVersions(*vyVer, cv) == 0 {
-					return vyVer, nil
-				}
-			}
-
-			util.OneTimeWarning(
-				"Version mismatch in %s:%s; repository.yml:%s, version.yml:%s",
-				r.Name(), commit, versString(ryVers), vyVer.String())
-		} else {
-			// If the set of commits doesn't contain a version from
-			// `repository.yml`, record the commit hash in the version
-			// specifier.  This will distinguish the returned version from its
-			// corresponding official release.
-			hash, err := r.downloader.HashFor(r.Path(), commit)
-			if err != nil {
-				return nil, err
-			}
-			vyVer.Commit = hash
-		}
-
-		// Always prefer the version in `version.yml`.
-		log.Debugf("Inferred version %s from %s:%s from version.yml",
-			vyVer.String(), r.Name(), commit)
-		return vyVer, nil
-	}
-
 	if len(ryVers) > 0 {
 		log.Debugf("Inferred version %s for %s:%s from repository.yml",
 			ryVers[0].String(), r.Name(), commit)
@@ -421,11 +329,6 @@ func (r *Repo) inferVersion(commit string, vyVer *newtutil.RepoVersion) (
 // Retrieves the installed version of the repo.  Returns nil if the version
 // cannot be detected.
 func (r *Repo) InstalledVersion() (*newtutil.RepoVersion, error) {
-	vyVer, err := r.installedVersionYml()
-	if err != nil && err != versionYmlMissing && err != versionYmlBad {
-		return nil, err
-	}
-
 	hash, err := r.CurrentHash()
 	if err != nil {
 		return nil, err
@@ -434,7 +337,7 @@ func (r *Repo) InstalledVersion() (*newtutil.RepoVersion, error) {
 		return nil, nil
 	}
 
-	ver, err := r.inferVersion(hash, vyVer)
+	ver, err := r.inferVersion(hash)
 	if err != nil {
 		return nil, err
 	}
@@ -447,30 +350,10 @@ func (r *Repo) InstalledVersion() (*newtutil.RepoVersion, error) {
 func (r *Repo) NonInstalledVersion(
 	commit string) (*newtutil.RepoVersion, error) {
 
-	ver, versionYmlErr := r.nonInstalledVersionYml(commit)
-	if versionYmlErr != nil &&
-		versionYmlErr != versionYmlMissing &&
-		versionYmlErr != versionYmlBad {
-
-		return nil, versionYmlErr
-	}
-
-	ver, err := r.inferVersion(commit, ver)
+	ver, err := r.inferVersion(commit)
 	if err != nil {
 		return nil, err
 	}
 
-	if ver == nil {
-		if versionYmlErr == versionYmlMissing {
-			util.OneTimeWarning(
-				"%s:%s does not contain a `version.yml` file.",
-				r.Name(), commit)
-		} else if versionYmlErr == versionYmlBad {
-			util.OneTimeWarning(
-				"%s:%s contains a malformed `version.yml` file.",
-				r.Name(), commit)
-		}
-	}
-
 	return ver, nil
 }


[mynewt-newt] 01/02: Only allow one version per repo dependency

Posted by cc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git

commit 617cbec45edf773919efeb77a43ba09aa85fa3ea
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Thu Jan 16 12:23:53 2020 -0800

    Only allow one version per repo dependency
    
    Newt used to allow a repo dependency to specify multiple version using
    a comparison operator, e.g.,
    
        repository.apache-mynewt-core:
            type: git
            vers: '>=1.0.0'     # <------
            url: git@github.com:apache/mynewt-core.git
    
    This commit removes support for this functionality.  Now a repo
    dependency can only specify a single version (or commit string).
---
 newt/deprepo/deprepo.go       |  34 ++++++------
 newt/deprepo/graph.go         |  29 +++++------
 newt/deprepo/matrix.go        |   4 +-
 newt/install/install.go       |  77 +++++++++++++--------------
 newt/newtutil/repo_version.go | 117 ++----------------------------------------
 newt/project/project.go       |   9 ++--
 newt/repo/repo.go             |   6 +--
 newt/repo/version.go          |  15 +++---
 8 files changed, 87 insertions(+), 204 deletions(-)

diff --git a/newt/deprepo/deprepo.go b/newt/deprepo/deprepo.go
index e518e26..56c65ab 100644
--- a/newt/deprepo/deprepo.go
+++ b/newt/deprepo/deprepo.go
@@ -37,7 +37,7 @@ type RepoMap map[string]*repo.Repo
 type VersionMap map[string]newtutil.RepoVersion
 
 // [repo-name] => requirements-for-key-repo
-type RequirementMap map[string][]newtutil.RepoVersionReq
+type RequirementMap map[string]newtutil.RepoVersion
 
 // Indicates an inability to find an acceptable version of a particular repo.
 type Conflict struct {
@@ -113,14 +113,14 @@ func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
 	dg := DepGraph{}
 
 	// First, add the hard dependencies expressed in `project.yml`.
-	for repoName, verReqs := range rootReqs {
+	for repoName, verReq := range rootReqs {
 		repo := repos[repoName]
-		normalizedReqs, err := repo.NormalizeVerReqs(verReqs)
+		normalizedReq, err := repo.NormalizeVerReq(verReq)
 		if err != nil {
 			return nil, err
 		}
 
-		if err := dg.AddRootDep(repoName, normalizedReqs); err != nil {
+		if err := dg.AddRootDep(repoName, []newtutil.RepoVersion{normalizedReq}); err != nil {
 			return nil, err
 		}
 	}
@@ -136,7 +136,7 @@ func BuildDepGraph(repos RepoMap, rootReqs RequirementMap) (DepGraph, error) {
 			reqMap := RequirementMap{}
 			for _, d := range deps {
 				depRepo := repos[d.Name]
-				verReqs, err := depRepo.NormalizeVerReqs(d.VerReqs)
+				verReqs, err := depRepo.NormalizeVerReq(d.VerReqs)
 				if err != nil {
 					return nil, err
 				}
@@ -170,13 +170,13 @@ func PruneMatrix(m *Matrix, repos RepoMap, rootReqs RequirementMap) error {
 		// Remove versions of this depended-on package that don't satisfy the
 		// dependency's version requirements.
 		r := repos[dep.Name]
-		normalizedReqs, err := r.NormalizeVerReqs(dep.VerReqs)
+		normalizedReq, err := r.NormalizeVerReq(dep.VerReqs)
 		if err != nil {
 			return err
 		}
 		filter := Filter{
 			Name: dependentName,
-			Reqs: normalizedReqs,
+			Req:  normalizedReq,
 		}
 		m.ApplyFilter(dep.Name, filter)
 
@@ -217,16 +217,14 @@ func PruneMatrix(m *Matrix, repos RepoMap, rootReqs RequirementMap) error {
 	// which doesn't satisfy a requirement in `project.yml` is a
 	// known-bad-version and can be removed.  These repos' dependencies can
 	// then be pruned in turn.
-	for repoName, reqs := range rootReqs {
-		if len(reqs) > 0 {
-			dep := &repo.RepoDependency{
-				Name:    repoName,
-				VerReqs: reqs,
-			}
+	for repoName, req := range rootReqs {
+		dep := &repo.RepoDependency{
+			Name:    repoName,
+			VerReqs: req,
+		}
 
-			if err := recurse("project.yml", dep); err != nil {
-				return err
-			}
+		if err := recurse("project.yml", dep); err != nil {
+			return err
 		}
 	}
 
@@ -271,7 +269,7 @@ func ConflictError(conflicts []Conflict) error {
 		lines := []string{}
 		for _, f := range c.Filters {
 			lines = append(lines, fmt.Sprintf("\n    %30s requires %s %s",
-				f.Name, c.RepoName, newtutil.RepoVerReqsString(f.Reqs)))
+				f.Name, c.RepoName, f.Req.String()))
 		}
 		sort.Strings(lines)
 		s += strings.Join(lines, "")
@@ -368,7 +366,7 @@ func FindAcceptableVersions(m Matrix, dg DepGraph) (VersionMap, []Conflict) {
 			if filterName != "" {
 				conflict.Filters = append(conflict.Filters, Filter{
 					Name: filterName,
-					Reqs: node.VerReqs,
+					Req:  node.VerReq,
 				})
 			}
 		}
diff --git a/newt/deprepo/graph.go b/newt/deprepo/graph.go
index 557b2ce..676b9dd 100644
--- a/newt/deprepo/graph.go
+++ b/newt/deprepo/graph.go
@@ -52,7 +52,7 @@ type DepGraphNode struct {
 	// Name of depended-on repo.
 	Name string
 	// Expresses the versions of the repo that satisfy this dependency.
-	VerReqs []newtutil.RepoVersionReq
+	VerReq newtutil.RepoVersion
 }
 
 // A repo dependency graph.
@@ -67,7 +67,7 @@ type RevdepGraphNode struct {
 	// The version of the dependent repo.
 	Ver newtutil.RepoVersion
 	// The dependent's version requirements that apply to the graph key.
-	VerReqs []newtutil.RepoVersionReq
+	VerReq newtutil.RepoVersion
 }
 
 // A repo reverse dependency graph.
@@ -88,8 +88,7 @@ func (dep *Dependent) String() string {
 }
 
 func (dgn *DepGraphNode) String() string {
-	return fmt.Sprintf("%s,%s", dgn.Name,
-		newtutil.RepoVerReqsString(dgn.VerReqs))
+	return fmt.Sprintf("%s,%s", dgn.Name, dgn.VerReq.String())
 }
 
 func (dg DepGraph) String() string {
@@ -110,7 +109,7 @@ func (dg DepGraph) String() string {
 
 func (rgn *RevdepGraphNode) String() string {
 	return fmt.Sprintf("%s,%s", repoNameVerString(rgn.Name, rgn.Ver),
-		newtutil.RepoVerReqsString(rgn.VerReqs))
+		rgn.VerReq.String())
 }
 
 func (rg RevdepGraph) String() string {
@@ -149,10 +148,10 @@ func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
 			repoName, repoVer.String())
 	}
 
-	for depName, depReqs := range reqMap {
+	for depName, depReq := range reqMap {
 		dg[dep] = append(dg[dep], DepGraphNode{
-			Name:    depName,
-			VerReqs: depReqs,
+			Name:   depName,
+			VerReq: depReq,
 		})
 	}
 
@@ -161,7 +160,7 @@ func (dg DepGraph) AddRepoVer(repoName string, repoVer newtutil.RepoVersion,
 
 // Adds a root dependency (i.e., required repo specified in `project.yml`).
 func (dg DepGraph) AddRootDep(repoName string,
-	verReqs []newtutil.RepoVersionReq) error {
+	verReqs []newtutil.RepoVersion) error {
 
 	rootDeps := dg[rootDependent]
 	for _, d := range rootDeps {
@@ -173,8 +172,8 @@ func (dg DepGraph) AddRootDep(repoName string,
 	}
 
 	dg[rootDependent] = append(dg[rootDependent], DepGraphNode{
-		Name:    repoName,
-		VerReqs: verReqs,
+		Name:   repoName,
+		VerReq: verReqs[0],
 	})
 
 	return nil
@@ -195,9 +194,9 @@ func (dg DepGraph) Reverse() RevdepGraph {
 			// Nothing depends on project.yml (""), so exclude it from the result.
 			if node.Name != "" {
 				rg[node.Name] = append(rg[node.Name], RevdepGraphNode{
-					Name:    dependent.Name,
-					Ver:     dependent.Ver,
-					VerReqs: node.VerReqs,
+					Name:   dependent.Name,
+					Ver:    dependent.Ver,
+					VerReq: node.VerReq,
 				})
 			}
 		}
@@ -256,7 +255,7 @@ func (dg DepGraph) conflictingRepos(vm VersionMap) []string {
 				}
 			}
 			if nodeApplies {
-				if !dependeeVer.SatisfiesAll(node.VerReqs) {
+				if !dependeeVer.Satisfies(node.VerReq) {
 					badRepoMap[dependeeName] = struct{}{}
 					break
 				}
diff --git a/newt/deprepo/matrix.go b/newt/deprepo/matrix.go
index 97dc982..ca7d99a 100644
--- a/newt/deprepo/matrix.go
+++ b/newt/deprepo/matrix.go
@@ -31,7 +31,7 @@ import (
 // Eliminates non-matching version numbers when applied to a matrix row.
 type Filter struct {
 	Name string
-	Reqs []newtutil.RepoVersionReq
+	Req  newtutil.RepoVersion
 }
 
 // Contains all versions of a single repo.  These version numbers are read from
@@ -150,7 +150,7 @@ func (m *Matrix) ApplyFilter(repoName string, filter Filter) {
 
 	goodVers := []newtutil.RepoVersion{}
 	for _, v := range row.Vers {
-		if v.SatisfiesAll(filter.Reqs) {
+		if v.Satisfies(filter.Req) {
 			goodVers = append(goodVers, v)
 		}
 	}
diff --git a/newt/install/install.go b/newt/install/install.go
index c25cfd1..4105f84 100644
--- a/newt/install/install.go
+++ b/newt/install/install.go
@@ -271,29 +271,29 @@ func (inst *Installer) ensureDepsInList(repos []*repo.Repo,
 // 3. Else, assume 0.0.0.
 func (inst *Installer) inferReqVers(repos []*repo.Repo) error {
 	for _, r := range repos {
-		reqs, ok := inst.reqs[r.Name()]
+		req, ok := inst.reqs[r.Name()]
 		if ok {
-			for i, req := range reqs {
-				if req.Ver.Commit != "" {
-					ver, err := r.NonInstalledVersion(req.Ver.Commit)
-					if err != nil {
-						return err
-					}
+			if req.Commit != "" {
+				ver, err := r.NonInstalledVersion(req.Commit)
+				if err != nil {
+					return err
+				}
 
-					if ver == nil {
-						util.OneTimeWarning(
-							"Could not detect version of requested repo "+
-								"%s:%s; assuming 0.0.0",
-							r.Name(), req.Ver.Commit)
+				if ver == nil {
+					util.OneTimeWarning(
+						"Could not detect version of requested repo "+
+							"%s:%s; assuming 0.0.0",
+						r.Name(), req.Commit)
 
-						ver = &req.Ver
-					}
-					reqs[i].Ver = *ver
-					reqs[i].Ver.Commit, err = r.HashFromVer(reqs[i].Ver)
-					if err != nil {
-						return err
-					}
+					ver = &req
 				}
+				req = *ver
+				req.Commit, err = r.HashFromVer(req)
+				if err != nil {
+					return err
+				}
+
+				inst.reqs[r.Name()] = req
 			}
 		}
 	}
@@ -314,7 +314,7 @@ func (inst *Installer) detectIllegalRepoReqs(
 
 	var lines []string
 	for _, r := range repos {
-		reqs, ok := inst.reqs[r.Name()]
+		req, ok := inst.reqs[r.Name()]
 		if ok {
 			row := m.FindRow(r.Name())
 			if row == nil {
@@ -323,21 +323,20 @@ func (inst *Installer) detectIllegalRepoReqs(
 			}
 
 			r := inst.repos[r.Name()]
-			nreqs, err := r.NormalizeVerReqs(reqs)
+			nreq, err := r.NormalizeVerReq(req)
 			if err != nil {
 				return err
 			}
 
 			anySatisfied := false
 			for _, ver := range row.Vers {
-				if ver.SatisfiesAll(nreqs) {
+				if ver.Satisfies(nreq) {
 					anySatisfied = true
 					break
 				}
 			}
 			if !anySatisfied {
-				line := fmt.Sprintf("    %s,%s", r.Name(),
-					newtutil.RepoVerReqsString(nreqs))
+				line := fmt.Sprintf("    %s,%s", r.Name(), nreq.String())
 				lines = append(lines, line)
 			}
 		}
@@ -570,25 +569,23 @@ func (inst *Installer) versionMapRepos(
 // from `project.yml` and `repository.yml` files.  If override is false,
 // attempting to set a commit for the same repo twice results in an error.
 func (inst *Installer) assignCommits(vm deprepo.VersionMap, repoName string,
-	reqs []newtutil.RepoVersionReq, override bool) error {
+	req newtutil.RepoVersion, override bool) error {
 
-	for _, req := range reqs {
-		curVer := vm[repoName]
-		if curVer.Satisfies(req) {
-			if req.Ver.Commit != "" {
-				prevCommit := vm[repoName].Commit
-				newCommit := req.Ver.Commit
+	curVer := vm[repoName]
+	if curVer.Satisfies(req) {
+		if req.Commit != "" {
+			prevCommit := vm[repoName].Commit
+			newCommit := req.Commit
 
-				if !override && prevCommit != "" && prevCommit != newCommit {
-					return util.FmtNewtError(
-						"repo %s: multiple commits: %s, %s",
-						repoName, vm[repoName].Commit, req.Ver.Commit)
+			if !override && prevCommit != "" && prevCommit != newCommit {
+				return util.FmtNewtError(
+					"repo %s: multiple commits: %s, %s",
+					repoName, vm[repoName].Commit, req.Commit)
 
-				}
-				ver := vm[repoName]
-				ver.Commit = req.Ver.Commit
-				vm[repoName] = ver
 			}
+			ver := vm[repoName]
+			ver.Commit = req.Commit
+			vm[repoName] = ver
 		}
 	}
 
@@ -674,7 +671,7 @@ func (inst *Installer) calcVersionMap(candidates []*repo.Repo) (
 				// assigned last so that they can override inter-repo
 				// dependencies.
 				if node.Name != "" {
-					if err := inst.assignCommits(vm, name, node.VerReqs, false); err != nil {
+					if err := inst.assignCommits(vm, name, node.VerReq, false); err != nil {
 						return nil, err
 					}
 				}
diff --git a/newt/newtutil/repo_version.go b/newt/newtutil/repo_version.go
index cc1280f..5e64b42 100644
--- a/newt/newtutil/repo_version.go
+++ b/newt/newtutil/repo_version.go
@@ -22,14 +22,11 @@ package newtutil
 import (
 	"fmt"
 	"math"
-	"regexp"
 	"sort"
 	"strconv"
 	"strings"
 
 	"mynewt.apache.org/newt/util"
-
-	log "github.com/sirupsen/logrus"
 )
 
 const (
@@ -48,11 +45,6 @@ const (
 // the minor and revision parts are floating.
 const VERSION_FLOATING = -1
 
-type RepoVersionReq struct {
-	CompareType string
-	Ver         RepoVersion
-}
-
 type RepoVersion struct {
 	Major     int64
 	Minor     int64
@@ -65,10 +57,6 @@ func (v *RepoVersion) IsNormalized() bool {
 	return v.Stability == VERSION_STABILITY_NONE
 }
 
-func (vm *RepoVersionReq) String() string {
-	return vm.CompareType + vm.Ver.String()
-}
-
 func (v *RepoVersion) toComparable() RepoVersion {
 	clone := *v
 
@@ -102,50 +90,9 @@ func CompareRepoVersions(v1 RepoVersion, v2 RepoVersion) int64 {
 	return 0
 }
 
-func (v *RepoVersion) Satisfies(verReq RepoVersionReq) bool {
-	if verReq.Ver.Commit != "" && verReq.CompareType != "==" {
-		log.Warningf("RepoVersion comparison with a tag %s %s %s",
-			verReq.Ver, verReq.CompareType, v)
-	}
-	r := CompareRepoVersions(verReq.Ver, *v)
-	switch verReq.CompareType {
-	case "<":
-		if r <= 0 {
-			return false
-		}
-	case "<=":
-		if r < 0 {
-			return false
-		}
-	case ">":
-		if r >= 0 {
-			return false
-		}
-	case ">=":
-		if r > 0 {
-			return false
-		}
-	case "==":
-		if r != 0 {
-			return false
-		}
-	}
-
-	if verReq.Ver.Stability != v.Stability {
-		return false
-	}
-
-	return true
-}
-
-func (v *RepoVersion) SatisfiesAll(verReqs []RepoVersionReq) bool {
-	for _, r := range verReqs {
-		if !v.Satisfies(r) {
-			return false
-		}
-	}
-
-	return true
+// XXX: Remove this
+func (v *RepoVersion) Satisfies(verReq RepoVersion) bool {
+	return CompareRepoVersions(verReq, *v) == 0
 }
 
 func (ver *RepoVersion) String() string {
@@ -240,64 +187,6 @@ func ParseRepoVersion(verStr string) (RepoVersion, error) {
 	return ver, nil
 }
 
-// Parse a set of version string constraints on a dependency.
-// This function
-// The version string contains a list of version constraints in the following format:
-//    - <comparison><version>
-// Where <comparison> can be any one of the following comparison
-//   operators: <=, <, >, >=, ==
-// And <version> is specified in the form: X.Y.Z where X, Y and Z are all
-// int64 types in decimal form
-func ParseRepoVersionReqs(versStr string) ([]RepoVersionReq, error) {
-	var err error
-
-	verReqs := []RepoVersionReq{}
-
-	re, err := regexp.Compile(`(<=|>=|==|>|<)(.+)`)
-	if err != nil {
-		return nil, err
-	}
-
-	matches := re.FindAllStringSubmatch(versStr, -1)
-	if matches != nil {
-		for _, match := range matches {
-			vm := RepoVersionReq{}
-			vm.CompareType = match[1]
-			if vm.Ver, err = ParseRepoVersion(match[2]); err != nil {
-				return nil, err
-			}
-
-			verReqs = append(verReqs, vm)
-		}
-	} else {
-		vm := RepoVersionReq{}
-		vm.CompareType = "=="
-		if vm.Ver, err = ParseRepoVersion(versStr); err != nil {
-			return nil, err
-		}
-
-		verReqs = append(verReqs, vm)
-	}
-
-	if len(verReqs) == 0 {
-		verReqs = nil
-	}
-
-	return verReqs, nil
-}
-
-func RepoVerReqsString(verReqs []RepoVersionReq) string {
-	s := ""
-	for i, r := range verReqs {
-		if i != 0 {
-			s += " "
-		}
-		s += r.String()
-	}
-
-	return s
-}
-
 type verSorter struct {
 	vers []RepoVersion
 }
diff --git a/newt/project/project.go b/newt/project/project.go
index bd5925c..42e4669 100644
--- a/newt/project/project.go
+++ b/newt/project/project.go
@@ -228,7 +228,8 @@ func (proj *Project) RepoIsInstalled(rname string) bool {
 }
 
 func (proj *Project) RepoIsRoot(rname string) bool {
-	return proj.rootRepoReqs[rname] != nil
+	_, ok := proj.rootRepoReqs[rname]
+	return ok == true
 }
 
 func (proj *Project) LocalRepo() *repo.Repo {
@@ -549,7 +550,7 @@ func (proj *Project) loadConfig() error {
 					return err
 				}
 			}
-			verReqs, err := newtutil.ParseRepoVersionReqs(fields["vers"])
+			verReq, err := newtutil.ParseRepoVersion(fields["vers"])
 			if err != nil {
 				return util.FmtNewtError(
 					"Repo \"%s\" contains invalid version requirement: "+
@@ -560,7 +561,7 @@ func (proj *Project) loadConfig() error {
 			if err := proj.addRepo(r); err != nil {
 				return err
 			}
-			proj.rootRepoReqs[repoName] = verReqs
+			proj.rootRepoReqs[repoName] = verReq
 		}
 	}
 
@@ -609,7 +610,7 @@ func (proj *Project) Init(dir string) error {
 	interfaces.SetProject(proj)
 
 	proj.repos = map[string]*repo.Repo{}
-	proj.rootRepoReqs = map[string][]newtutil.RepoVersionReq{}
+	proj.rootRepoReqs = map[string]newtutil.RepoVersion{}
 
 	// Load Project configuration
 	if err := proj.loadConfig(); err != nil {
diff --git a/newt/repo/repo.go b/newt/repo/repo.go
index 6aedd56..075e0bf 100644
--- a/newt/repo/repo.go
+++ b/newt/repo/repo.go
@@ -67,7 +67,7 @@ type Repo struct {
 
 type RepoDependency struct {
 	Name    string
-	VerReqs []newtutil.RepoVersionReq
+	VerReqs newtutil.RepoVersion
 	Fields  map[string]string
 }
 
@@ -433,7 +433,7 @@ func parseRepoDepMap(depName string,
 	}
 
 	for commit, verReqsStr := range versMap {
-		verReqs, err := newtutil.ParseRepoVersionReqs(verReqsStr)
+		verReq, err := newtutil.ParseRepoVersion(verReqsStr)
 		if err != nil {
 			return nil, util.FmtNewtError("invalid version string: %s: %s",
 				verReqsStr, err.Error())
@@ -441,7 +441,7 @@ func parseRepoDepMap(depName string,
 
 		result[commit] = &RepoDependency{
 			Name:    depName,
-			VerReqs: verReqs,
+			VerReqs: verReq,
 			Fields:  fields,
 		}
 	}
diff --git a/newt/repo/version.go b/newt/repo/version.go
index e5a935c..561477e 100644
--- a/newt/repo/version.go
+++ b/newt/repo/version.go
@@ -257,23 +257,22 @@ func (r *Repo) NormalizeVersion(
 }
 
 // Normalizes the version component of a version requirement.
-func (r *Repo) NormalizeVerReq(verReq newtutil.RepoVersionReq) (
-	newtutil.RepoVersionReq, error) {
+func (r *Repo) NormalizeVerReq(verReq newtutil.RepoVersion) (
+	newtutil.RepoVersion, error) {
 
-	ver, err := r.NormalizeVersion(verReq.Ver)
+	ver, err := r.NormalizeVersion(verReq)
 	if err != nil {
 		return verReq, err
 	}
 
-	verReq.Ver = ver
-	return verReq, nil
+	return ver, nil
 }
 
 // Normalizes the version component of each specified version requirement.
-func (r *Repo) NormalizeVerReqs(verReqs []newtutil.RepoVersionReq) (
-	[]newtutil.RepoVersionReq, error) {
+func (r *Repo) NormalizeVerReqs(verReqs []newtutil.RepoVersion) (
+	[]newtutil.RepoVersion, error) {
 
-	result := make([]newtutil.RepoVersionReq, len(verReqs))
+	result := make([]newtutil.RepoVersion, len(verReqs))
 	for i, verReq := range verReqs {
 		n, err := r.NormalizeVerReq(verReq)
 		if err != nil {