cmd/go/internal/modfetch: clean up Query

Clean up module query notation to make it easier to explain.

Apply a suggestion from Bryan in the previous CL to apply
the preference for releases (not pre-releases) to all queries,
not just "latest".

Change "v1" to mean "the latest v1.x.x" not "v1.0.0".
Change "v1.2" to mean "the latest v1.2.x" not "v1.2.0".
There was a TODO about checking for a tag/branch named v1,
but using the latest tagged v1.x.x release seems better than
taking the last commit (possibly untagged) from a branch
anyway.

Add <= and >= operators instead of misintepreting them
as < and > with malformed semantic versions.

Given that "v1" and "v1.2" now essentially address a wide range
of values, disallow ambiguous <=v1, <=v1.2, >v1, and >v1.2.

Add test.

Change-Id: I97a39c5a468ee710a19b587c46d1821f03d76acd
Reviewed-on: https://go-review.googlesource.com/122257
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/list/list.go b/vendor/cmd/go/internal/list/list.go
index 855d0d6..788542a 100644
--- a/vendor/cmd/go/internal/list/list.go
+++ b/vendor/cmd/go/internal/list/list.go
@@ -358,10 +358,6 @@
 			// TODO(rsc): Could make this mean something with -m.
 			base.Fatalf("go list -deps cannot be used with -m")
 		}
-		if *listE {
-			// TODO(rsc): Could make this mean something with -m.
-			base.Fatalf("go list -e cannot be used with -m")
-		}
 		if *listExport {
 			base.Fatalf("go list -export cannot be used with -m")
 		}
@@ -375,6 +371,13 @@
 		vgo.LoadBuildList()
 
 		mods := vgo.ListModules(args, *listU, *listVersions)
+		if !*listE {
+			for _, m := range mods {
+				if m.Error != nil {
+					base.Fatalf("go list -m %s: %v", m.Path, m.Error.Err)
+				}
+			}
+		}
 		for _, m := range mods {
 			do(m)
 		}
diff --git a/vendor/cmd/go/internal/modfetch/query.go b/vendor/cmd/go/internal/modfetch/query.go
index 5902d5e..9d3a6f0 100644
--- a/vendor/cmd/go/internal/modfetch/query.go
+++ b/vendor/cmd/go/internal/modfetch/query.go
@@ -16,86 +16,171 @@
 // The version must take one of the following forms:
 //
 //	- the literal string "latest", denoting the latest available, allowed tagged version,
-//	  with non-prereleases preferred over prereleases
-//	- v1.2.3, a semantic version string
-//	- v1 or v1.2, an abbreviated semantic version string completed by adding zeroes (v1.0.0 or v1.2.0)
-//	- >v1.2.3, denoting the earliest available version after v1.2.3 (including prereleases)
-//	- <v1.2.3, denoting the latest available version before v1.2.3 (including prereleases)
-//	- a repository commit identifier, denoting that version
+//	  with non-prereleases preferred over prereleases.
+//	  If there are no tagged versions in the repo, latest returns the most recent commit.
+//	- v1, denoting the latest available tagged version v1.x.x.
+//	- v1.2, denoting the latest available tagged version v1.2.x.
+//	- v1.2.3, a semantic version string denoting that tagged version.
+//	- <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,
+//	   denoting the version closest to the target and satisfying the given operator,
+//	   with non-prereleases preferred over prereleases.
+//	- a repository commit identifier, denoting that commit.
 //
 // If the allowed function is non-nil, Query excludes any versions for which allowed returns false.
 //
-func Query(path, vers string, allowed func(module.Version) bool) (*RevInfo, error) {
+func Query(path, query string, allowed func(module.Version) bool) (*RevInfo, error) {
 	if allowed == nil {
 		allowed = func(module.Version) bool { return true }
 	}
-	if semver.IsValid(vers) {
-		// TODO: This turns query for "v2" into Stat "v2.0.0",
-		// but probably it should allow checking for a branch named "v2".
-		vers = semver.Canonical(vers)
+
+	// Parse query to detect parse errors (and possibly handle query)
+	// before any network I/O.
+	badVersion := func(v string) (*RevInfo, error) {
+		return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)
+	}
+	var ok func(module.Version) bool
+	var preferOlder bool
+	switch {
+	case query == "latest":
+		ok = allowed
+
+	case strings.HasPrefix(query, "<="):
+		v := query[len("<="):]
+		if !semver.IsValid(v) {
+			return badVersion(v)
+		}
+		if isSemverPrefix(v) {
+			// Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
+			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
+		}
+		ok = func(m module.Version) bool {
+			return semver.Compare(m.Version, v) <= 0 && allowed(m)
+		}
+
+	case strings.HasPrefix(query, "<"):
+		v := query[len("<"):]
+		if !semver.IsValid(v) {
+			return badVersion(v)
+		}
+		ok = func(m module.Version) bool {
+			return semver.Compare(m.Version, v) < 0 && allowed(m)
+		}
+
+	case strings.HasPrefix(query, ">="):
+		v := query[len(">="):]
+		if !semver.IsValid(v) {
+			return badVersion(v)
+		}
+		ok = func(m module.Version) bool {
+			return semver.Compare(m.Version, v) >= 0 && allowed(m)
+		}
+		preferOlder = true
+
+	case strings.HasPrefix(query, ">"):
+		v := query[len(">"):]
+		if !semver.IsValid(v) {
+			return badVersion(v)
+		}
+		if isSemverPrefix(v) {
+			// Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
+			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
+		}
+		ok = func(m module.Version) bool {
+			return semver.Compare(m.Version, v) > 0 && allowed(m)
+		}
+		preferOlder = true
+
+	case semver.IsValid(query) && isSemverPrefix(query):
+		ok = func(m module.Version) bool {
+			return matchSemverPrefix(query, m.Version) && allowed(m)
+		}
+
+	case semver.IsValid(query):
+		vers := semver.Canonical(query)
 		if !allowed(module.Version{Path: path, Version: vers}) {
 			return nil, fmt.Errorf("%s@%s excluded", path, vers)
 		}
+		return Stat(path, vers)
 
-		// Fast path that avoids network overhead of Lookup (resolving path to repo host),
-		// if we already have this stat information cached on disk.
-		info, err := Stat(path, vers)
-		if err == nil {
-			return info, nil
+	default:
+		// Direct lookup of semantic version or commit identifier.
+		info, err := Stat(path, query)
+		if err != nil {
+			return nil, err
 		}
+		if !allowed(module.Version{Path: path, Version: info.Version}) {
+			return nil, fmt.Errorf("%s@%s excluded", path, info.Version)
+		}
+		return info, nil
 	}
 
+	// Load versions and execute query.
 	repo, err := Lookup(path)
 	if err != nil {
 		return nil, err
 	}
-
-	if semver.IsValid(vers) {
-		return repo.Stat(vers)
-	}
-	if strings.HasPrefix(vers, ">") || strings.HasPrefix(vers, "<") || vers == "latest" {
-		var op string
-		if vers != "latest" {
-			if !semver.IsValid(vers[1:]) {
-				return nil, fmt.Errorf("invalid semantic version in range %s", vers)
-			}
-			op, vers = vers[:1], vers[1:]
-		}
-		versions, err := repo.Versions("")
-		if err != nil {
-			return nil, err
-		}
-		if len(versions) == 0 && vers == "latest" {
-			return repo.Latest()
-		}
-		if vers == "latest" {
-			// Prefer a proper (non-prerelease) release.
-			for i := len(versions) - 1; i >= 0; i-- {
-				if semver.Prerelease(versions[i]) == "" && allowed(module.Version{Path: path, Version: versions[i]}) {
-					return repo.Stat(versions[i])
-				}
-			}
-			// Fall back to pre-releases if that's all we have.
-			for i := len(versions) - 1; i >= 0; i-- {
-				if semver.Prerelease(versions[i]) != "" && allowed(module.Version{Path: path, Version: versions[i]}) {
-					return repo.Stat(versions[i])
-				}
-			}
-		} else if op == "<" {
-			for i := len(versions) - 1; i >= 0; i-- {
-				if semver.Compare(versions[i], vers) < 0 && allowed(module.Version{Path: path, Version: versions[i]}) {
-					return repo.Stat(versions[i])
-				}
-			}
-		} else {
-			for i := 0; i < len(versions); i++ {
-				if semver.Compare(versions[i], vers) > 0 && allowed(module.Version{Path: path, Version: versions[i]}) {
-					return repo.Stat(versions[i])
-				}
-			}
-		}
-		return nil, fmt.Errorf("no matching versions for %s%s", op, vers)
+	versions, err := repo.Versions("")
+	if err != nil {
+		return nil, err
 	}
 
-	return repo.Stat(vers)
+	if preferOlder {
+		for _, v := range versions {
+			if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
+				return repo.Stat(v)
+			}
+		}
+		for _, v := range versions {
+			if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
+				return repo.Stat(v)
+			}
+		}
+	} else {
+		for i := len(versions) - 1; i >= 0; i-- {
+			v := versions[i]
+			if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
+				return repo.Stat(v)
+			}
+		}
+		for i := len(versions) - 1; i >= 0; i-- {
+			v := versions[i]
+			if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
+				return repo.Stat(v)
+			}
+		}
+	}
+
+	if query == "latest" {
+		// Special case for "latest": if no tags match, use latest commit in repo,
+		// provided it is not excluded.
+		if info, err := repo.Latest(); err == nil && allowed(module.Version{Path: path, Version: info.Version}) {
+			return info, nil
+		}
+	}
+
+	return nil, fmt.Errorf("no matching versions for query %q", query)
+}
+
+// isSemverPrefix reports whether v is a semantic version prefix: v1 or  v1.2 (not v1.2.3).
+// The caller is assumed to have checked that semver.IsValid(v) is true.
+func isSemverPrefix(v string) bool {
+	dots := 0
+	for i := 0; i < len(v); i++ {
+		switch v[i] {
+		case '-', '+':
+			return false
+		case '.':
+			dots++
+			if dots >= 2 {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// matchSemverPrefix reports whether the shortened semantic version p
+// matches the full-width (non-shortened) semantic version v.
+func matchSemverPrefix(p, v string) bool {
+	return len(v) > len(p) && v[len(p)] == '.' && v[:len(p)] == p
 }
diff --git a/vendor/cmd/go/internal/modfetch/query_test.go b/vendor/cmd/go/internal/modfetch/query_test.go
new file mode 100644
index 0000000..eabdf75
--- /dev/null
+++ b/vendor/cmd/go/internal/modfetch/query_test.go
@@ -0,0 +1,129 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modfetch
+
+import (
+	"cmd/go/internal/module"
+	"internal/testenv"
+	"path"
+	"strings"
+	"testing"
+)
+
+var (
+	queryRepo   = "vcs-test.golang.org/git/querytest.git"
+	queryRepoV2 = queryRepo + "/v2"
+	queryRepoV3 = queryRepo + "/v3"
+
+	// Empty version list (no semver tags), not actually empty.
+	emptyRepo = "vcs-test.golang.org/git/emptytest.git"
+)
+
+var queryTests = []struct {
+	path  string
+	query string
+	allow string
+	vers  string
+	err   string
+}{
+	/*
+		git init
+		echo module vcs-test.golang.org/git/querytest.git >go.mod
+		git add go.mod
+		git commit -m v1 go.mod
+		git tag start
+		for i in v0.0.0-pre1 v0.0.0 v0.0.1 v0.0.2 v0.0.3 v0.1.0 v0.1.1 v0.1.2 v0.3.0 v1.0.0 v1.1.0 v1.9.0 v1.9.9 v1.9.10-pre1; do
+			echo before $i >status
+			git add status
+			git commit -m "before $i" status
+			echo at $i >status
+			git commit -m "at $i" status
+			git tag $i
+		done
+		git tag favorite v0.0.3
+
+		git branch v2 start
+		git checkout v2
+		echo module vcs-test.golang.org/git/querytest.git/v2 >go.mod
+		git commit -m v2 go.mod
+		for i in v2.0.0 v2.1.0 v2.2.0 v2.5.5; do
+			echo before $i >status
+			git add status
+			git commit -m "before $i" status
+			echo at $i >status
+			git commit -m "at $i" status
+			git tag $i
+		done
+		echo after v2.5.5 >status
+		git commit -m 'after v2.5.5' status
+		git checkout master
+		zip -r ../querytest.zip
+		gsutil cp ../querytest.zip gs://vcs-test/git/querytest.zip
+		curl 'https://vcs-test.golang.org/git/querytest?go-get=1'
+	*/
+	{path: queryRepo, query: "<v0.0.0", vers: "v0.0.0-pre1"},
+	{path: queryRepo, query: "<v0.0.0-pre1", err: `no matching versions for query "<v0.0.0-pre1"`},
+	{path: queryRepo, query: "<=v0.0.0", vers: "v0.0.0"},
+	{path: queryRepo, query: ">v0.0.0", vers: "v0.0.1"},
+	{path: queryRepo, query: ">=v0.0.0", vers: "v0.0.0"},
+	{path: queryRepo, query: "v0.0.1", vers: "v0.0.1"},
+	{path: queryRepo, query: "v0.0.1+foo", vers: "v0.0.1"},
+	{path: queryRepo, query: "v0.0.99", err: `unknown revision v0.0.99`},
+	{path: queryRepo, query: "v0", vers: "v0.3.0"},
+	{path: queryRepo, query: "v0.1", vers: "v0.1.2"},
+	{path: queryRepo, query: "v0.2", err: `no matching versions for query "v0.2"`},
+	{path: queryRepo, query: "v0.0", vers: "v0.0.3"},
+	{path: queryRepo, query: "latest", vers: "v1.9.9"},
+	{path: queryRepo, query: "latest", allow: "NOMATCH", err: `no matching versions for query "latest"`},
+	{path: queryRepo, query: ">v1.9.9", vers: "v1.9.10-pre1"},
+	{path: queryRepo, query: ">v1.10.0", err: `no matching versions for query ">v1.10.0"`},
+	{path: queryRepo, query: ">=v1.10.0", err: `no matching versions for query ">=v1.10.0"`},
+	{path: queryRepo, query: "6cf84eb", vers: "v0.0.0-20180704023347-6cf84ebaea54"},
+	{path: queryRepo, query: "start", vers: "v0.0.0-20180704023101-5e9e31667ddf"},
+	{path: queryRepo, query: "7a1b6bf", vers: "v0.1.0"},
+
+	{path: queryRepoV2, query: "<v0.0.0", err: `no matching versions for query "<v0.0.0"`},
+	{path: queryRepoV2, query: "<=v0.0.0", err: `no matching versions for query "<=v0.0.0"`},
+	{path: queryRepoV2, query: ">v0.0.0", vers: "v2.0.0"},
+	{path: queryRepoV2, query: ">=v0.0.0", vers: "v2.0.0"},
+	{path: queryRepoV2, query: "v0.0.1+foo", vers: "v2.0.0-20180704023347-179bc86b1be3"},
+	{path: queryRepoV2, query: "latest", vers: "v2.5.5"},
+
+	{path: queryRepoV3, query: "latest", vers: "v3.0.0-20180704024501-e0cf3de987e6"},
+
+	{path: emptyRepo, query: "latest", vers: "v0.0.0-20180704023549-7bb914627242"},
+	{path: emptyRepo, query: ">v0.0.0", err: `no matching versions for query ">v0.0.0"`},
+	{path: emptyRepo, query: "<v10.0.0", err: `no matching versions for query "<v10.0.0"`},
+}
+
+func TestQuery(t *testing.T) {
+	testenv.MustHaveExternalNetwork(t)
+
+	for _, tt := range queryTests {
+		allow := tt.allow
+		if allow == "" {
+			allow = "*"
+		}
+		allowed := func(m module.Version) bool {
+			ok, _ := path.Match(allow, m.Version)
+			return ok
+		}
+		t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.query+"/"+allow, func(t *testing.T) {
+			info, err := Query(tt.path, tt.query, allowed)
+			if tt.err != "" {
+				if err != nil && err.Error() == tt.err {
+					return
+				}
+				t.Fatalf("Query(%q, %q, %v): %v, want error %q", tt.path, tt.query, allow, err, tt.err)
+			}
+			if err != nil {
+				t.Fatalf("Query(%q, %q, %v): %v", tt.path, tt.query, allow, err)
+			}
+			if info.Version != tt.vers {
+				t.Errorf("Query(%q, %q, %v) = %v, want %v", tt.path, tt.query, allow, info.Version, tt.vers)
+			}
+		})
+	}
+}
diff --git a/vendor/cmd/go/internal/vgo/list.go b/vendor/cmd/go/internal/vgo/list.go
index 7470625..761eae1 100644
--- a/vendor/cmd/go/internal/vgo/list.go
+++ b/vendor/cmd/go/internal/vgo/list.go
@@ -10,7 +10,9 @@
 	"strings"
 
 	"cmd/go/internal/base"
+	"cmd/go/internal/modfetch"
 	"cmd/go/internal/modinfo"
+	"cmd/go/internal/module"
 	"cmd/go/internal/par"
 	"cmd/go/internal/search"
 )
@@ -53,9 +55,20 @@
 		if search.IsRelativePath(arg) {
 			base.Fatalf("vgo: cannot use relative path %s to specify module", arg)
 		}
-		if strings.Contains(arg, "@") {
-			// TODO(rsc): Add support for 'go list -m golang.org/x/text@v0.3.0'
-			base.Fatalf("vgo: list path@version not implemented")
+		if i := strings.Index(arg, "@"); i >= 0 {
+			info, err := modfetch.Query(arg[:i], arg[i+1:], nil)
+			if err != nil {
+				mods = append(mods, &modinfo.ModulePublic{
+					Path:    arg[:i],
+					Version: arg[i+1:],
+					Error: &modinfo.ModuleError{
+						Err: err.Error(),
+					},
+				})
+				continue
+			}
+			mods = append(mods, moduleInfo(module.Version{Path: arg[:i], Version: info.Version}, false))
+			continue
 		}
 
 		// Module path or pattern.
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go
index 30ef894..fec216f 100644
--- a/vendor/cmd/go/vgo_test.go
+++ b/vendor/cmd/go/vgo_test.go
@@ -501,6 +501,17 @@
 	if tg.getStdout() != want {
 		t.Errorf("vgo list versions:\nhave:\n%s\nwant:\n%s", tg.getStdout(), want)
 	}
+
+	tg.run("-vgo", "list", "-m", "rsc.io/quote@>v1.5.2")
+	tg.grepStdout(`v1.5.3-pre1`, "expected to find v1.5.3-pre1")
+	tg.run("-vgo", "list", "-m", "rsc.io/quote@<v1.5.4")
+	tg.grepStdout(`v1.5.2$`, "expected to find v1.5.2 (NOT v1.5.3-pre1)")
+
+	tg.runFail("-vgo", "list", "-m", "rsc.io/quote@>v1.5.3")
+	tg.grepStderr(`go list -m rsc.io/quote: no matching versions for query ">v1.5.3"`, "expected no matching version")
+
+	tg.run("-vgo", "list", "-m", "-e", "-f={{.Error.Err}}", "rsc.io/quote@>v1.5.3")
+	tg.grepStdout(`no matching versions for query ">v1.5.3"`, "expected no matching version")
 }
 
 func TestVgoBadDomain(t *testing.T) {
@@ -773,8 +784,9 @@
 	tg.grepStderr("finding github.com/gorilla/mux v1.6.1", "find version 1.6.1")
 
 	tg.must(ioutil.WriteFile(tg.path("x/go.mod"), gomod, 0666))
-	tg.runFail("-vgo", "get", "github.com/gorilla/mux@v1.6")
-	tg.grepStderr("github.com/gorilla/mux@v1.6.0 excluded", "print version excluded")
+	tg.run("-vgo", "get", "github.com/gorilla/mux@>=v1.6")
+	tg.run("-vgo", "list", "-m", "...mux")
+	tg.grepStdout("github.com/gorilla/mux v1.6.[1-9]", "expected version 1.6.1 or later")
 }
 
 func TestRequireExcluded(t *testing.T) {