cmd/gorelease: don't suggest a version that already exists
Fixes golang/go#37562
Change-Id: Ie02cfaa9efc8c8375481540e551ae38f19c3a2e8
Reviewed-on: https://go-review.googlesource.com/c/exp/+/288032
Trust: Jean de Klerk <deklerk@google.com>
Run-TryBot: Jean de Klerk <deklerk@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go
index 3e09351..124ad0d 100644
--- a/cmd/gorelease/gorelease.go
+++ b/cmd/gorelease/gorelease.go
@@ -81,6 +81,7 @@
import (
"bytes"
+ "context"
"encoding/json"
"errors"
"flag"
@@ -141,7 +142,8 @@
if err != nil {
log.Fatal(err)
}
- success, err := runRelease(os.Stdout, wd, os.Args[1:])
+ ctx := context.WithValue(context.Background(), "env", append(os.Environ(), "GO111MODULE=on"))
+ success, err := runRelease(ctx, os.Stdout, wd, os.Args[1:])
if err != nil {
if _, ok := err.(*usageError); ok {
fmt.Fprintln(os.Stderr, err)
@@ -158,7 +160,7 @@
// runRelease is the main function of gorelease. It's called by tests, so
// it writes to w instead of os.Stdout and returns an error instead of
// exiting.
-func runRelease(w io.Writer, dir string, args []string) (success bool, err error) {
+func runRelease(ctx context.Context, w io.Writer, dir string, args []string) (success bool, err error) {
// Validate arguments and flags. We'll print our own errors, since we want to
// test without printing to stderr.
fs := flag.NewFlagSet("gorelease", flag.ContinueOnError)
@@ -213,7 +215,7 @@
repoRoot := findRepoRoot(modRoot)
// Load packages for the version to be released from the local directory.
- release, err := loadLocalModule(modRoot, repoRoot, releaseVersion)
+ release, err := loadLocalModule(ctx, modRoot, repoRoot, releaseVersion)
if err != nil {
return false, err
}
@@ -225,13 +227,13 @@
baseModPath = release.modPath
max = releaseVersion
}
- base, err := loadDownloadedModule(baseModPath, baseVersion, max)
+ base, err := loadDownloadedModule(ctx, baseModPath, baseVersion, max)
if err != nil {
return false, err
}
// Compare packages and check for other issues.
- report, err := makeReleaseReport(base, release)
+ report, err := makeReleaseReport(ctx, base, release)
if err != nil {
return false, err
}
@@ -259,6 +261,10 @@
diagnostics []string // problems not related to loading specific packages
pkgs []*packages.Package // loaded packages with type information
+
+ // Versions of this module which already exist. Only loaded for release
+ // (not base).
+ existingVersions []string
}
// loadLocalModule loads information about a module and its packages from a
@@ -269,7 +275,7 @@
// repoRoot is the root directory of the repository containing the module or "".
//
// version is a proposed version for the module or "".
-func loadLocalModule(modRoot, repoRoot, version string) (m moduleInfo, err error) {
+func loadLocalModule(ctx context.Context, modRoot, repoRoot, version string) (m moduleInfo, err error) {
if repoRoot != "" && !hasFilePathPrefix(modRoot, repoRoot) {
return moduleInfo{}, fmt.Errorf("module root %q is not in repository root %q", modRoot, repoRoot)
}
@@ -373,7 +379,7 @@
err = fmt.Errorf("removing temporary module directory: %v", rerr)
}
}()
- tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, prepareDiagnostics, err := prepareLoadDir(m.goModFile, m.modPath, tmpModRoot, version, false)
+ tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, prepareDiagnostics, err := prepareLoadDir(ctx, m.goModFile, m.modPath, tmpModRoot, version, false)
if err != nil {
return moduleInfo{}, err
}
@@ -384,7 +390,7 @@
}()
var loadDiagnostics []string
- m.pkgs, loadDiagnostics, err = loadPackages(m.modPath, tmpModRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths)
+ m.pkgs, loadDiagnostics, err = loadPackages(ctx, m.modPath, tmpModRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths)
if err != nil {
return moduleInfo{}, err
}
@@ -392,7 +398,7 @@
m.diagnostics = append(m.diagnostics, prepareDiagnostics...)
m.diagnostics = append(m.diagnostics, loadDiagnostics...)
- highestVersion, err := findSelectedVersion(tmpLoadDir, m.modPath)
+ highestVersion, err := findSelectedVersion(ctx, tmpLoadDir, m.modPath)
if err != nil {
return moduleInfo{}, err
}
@@ -405,6 +411,13 @@
m.highestTransitiveVersion = highestVersion
}
+ // Calculate the existing versions.
+ ev, err := existingVersions(ctx, m.modPath, tmpLoadDir)
+ if err != nil {
+ return moduleInfo{}, err
+ }
+ m.existingVersions = ev
+
return m, nil
}
@@ -422,7 +435,7 @@
// If version is "" and max is not "", available versions greater than or equal
// to max will not be considered. Typically, loadDownloadedModule is used to
// load the base version, and max is the release version.
-func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error) {
+func loadDownloadedModule(ctx context.Context, modPath, version, max string) (m moduleInfo, err error) {
// Check the module path and version.
// If the version is a query, resolve it to a canonical version.
m = moduleInfo{modPath: modPath}
@@ -445,7 +458,7 @@
if version == "" {
// Unspecified version: use the highest version below max.
m.versionInferred = true
- if m.version, err = inferBaseVersion(modPath, max); err != nil {
+ if m.version, err = inferBaseVersion(ctx, modPath, max); err != nil {
return moduleInfo{}, err
}
if m.version == "none" {
@@ -454,7 +467,7 @@
} else if version != module.CanonicalVersion(version) {
// Version query: find the real version.
m.versionQuery = version
- if m.version, err = queryVersion(modPath, version); err != nil {
+ if m.version, err = queryVersion(ctx, modPath, version); err != nil {
return moduleInfo{}, err
}
if m.version != "none" && max != "" && semver.Compare(m.version, max) >= 0 {
@@ -479,7 +492,7 @@
// which is not inside modRoot. This is what the go command uses. Even if
// the module didn't have a go.mod file, one will be synthesized there.
v := module.Version{Path: modPath, Version: m.version}
- if m.modRoot, m.goModPath, err = downloadModule(v); err != nil {
+ if m.modRoot, m.goModPath, err = downloadModule(ctx, v); err != nil {
return moduleInfo{}, err
}
if m.goModData, err = ioutil.ReadFile(m.goModPath); err != nil {
@@ -494,7 +507,7 @@
m.modPath = m.goModFile.Module.Mod.Path
// Load packages.
- tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, _, err := prepareLoadDir(nil, m.modPath, m.modRoot, m.version, true)
+ tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths, _, err := prepareLoadDir(ctx, nil, m.modPath, m.modRoot, m.version, true)
if err != nil {
return moduleInfo{}, err
}
@@ -504,7 +517,7 @@
}
}()
- if m.pkgs, _, err = loadPackages(m.modPath, m.modRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths); err != nil {
+ if m.pkgs, _, err = loadPackages(ctx, m.modPath, m.modRoot, tmpLoadDir, tmpGoModData, tmpGoSumData, pkgPaths); err != nil {
return moduleInfo{}, err
}
@@ -518,7 +531,7 @@
// The report recommends or validates a release version and indicates a
// version control tag to use (with an appropriate prefix, for modules not
// in the repository root directory).
-func makeReleaseReport(base, release moduleInfo) (report, error) {
+func makeReleaseReport(ctx context.Context, base, release moduleInfo) (report, error) {
// Compare each pair of packages.
// Ignore internal packages.
// If we don't have a base version to compare against just check the new
@@ -609,6 +622,38 @@
return r, nil
}
+// existingVersions returns the versions that already exist for the given
+// modPath.
+func existingVersions(ctx context.Context, modPath, modRoot string) (versions []string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("listing versions of %s: %w", modPath, err)
+ }
+ }()
+
+ type listVersions struct {
+ Versions []string `json: "Versions"`
+ }
+ cmd := exec.CommandContext(ctx, "go", "list", "-json", "-m", "-versions", modPath)
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
+ cmd.Dir = modRoot
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, cleanCmdError(err)
+ }
+ if len(out) == 0 {
+ return nil, nil
+ }
+
+ var lv listVersions
+ if err := json.Unmarshal(out, &lv); err != nil {
+ return nil, err
+ }
+ return lv.Versions, nil
+}
+
// findRepoRoot finds the root directory of the repository that contains dir.
// findRepoRoot returns "" if it can't find the repository root.
func findRepoRoot(dir string) string {
@@ -671,14 +716,14 @@
// highest available release version. Pre-release versions are not considered.
// If there is no available version, and max appears to be the first release
// version (for example, "v0.1.0", "v2.0.0"), "none" is returned.
-func inferBaseVersion(modPath, max string) (baseVersion string, err error) {
+func inferBaseVersion(ctx context.Context, modPath, max string) (baseVersion string, err error) {
defer func() {
if err != nil {
err = &baseVersionError{err: err}
}
}()
- versions, err := loadVersions(modPath)
+ versions, err := loadVersions(ctx, modPath)
if err != nil {
return "", err
}
@@ -698,7 +743,7 @@
}
// queryVersion returns the canonical version for a given module version query.
-func queryVersion(modPath, query string) (resolved string, err error) {
+func queryVersion(ctx context.Context, modPath, query string) (resolved string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("could not resolve version %s@%s: %w", modPath, query, err)
@@ -718,9 +763,12 @@
}
}()
arg := modPath + "@" + query
- cmd := exec.Command("go", "list", "-m", "-f", "{{.Version}}", "--", arg)
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-f", "{{.Version}}", "--", arg)
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
cmd.Dir = tmpDir
- cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ cmd.Env = append(cmd.Env, "GO111MODULE=on")
out, err := cmd.Output()
if err != nil {
return "", cleanCmdError(err)
@@ -731,7 +779,13 @@
// loadVersions loads the list of versions for the given module using
// 'go list -m -versions'. The returned versions are sorted in ascending
// semver order.
-func loadVersions(modPath string) ([]string, error) {
+func loadVersions(ctx context.Context, modPath string) (versions []string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not load versions for %s: %v", modPath, err)
+ }
+ }()
+
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
return nil, err
@@ -741,14 +795,17 @@
err = rerr
}
}()
- cmd := exec.Command("go", "list", "-m", "-versions", "--", modPath)
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-versions", "--", modPath)
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
cmd.Dir = tmpDir
- cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ cmd.Env = append(cmd.Env, "GO111MODULE=on")
out, err := cmd.Output()
if err != nil {
return nil, cleanCmdError(err)
}
- versions := strings.Fields(string(out))
+ versions = strings.Fields(string(out))
if len(versions) > 0 {
versions = versions[1:] // skip module path
}
@@ -847,7 +904,7 @@
// downloadModule downloads a specific version of a module to the
// module cache using 'go mod download'.
-func downloadModule(m module.Version) (modRoot, goModPath string, err error) {
+func downloadModule(ctx context.Context, m module.Version) (modRoot, goModPath string, err error) {
defer func() {
if err != nil {
err = &downloadError{m: m, err: cleanCmdError(err)}
@@ -865,7 +922,10 @@
return "", "", err
}
defer os.Remove(tmpDir)
- cmd := exec.Command("go", "mod", "download", "-json", "--", m.Path+"@"+m.Version)
+ cmd := exec.CommandContext(ctx, "go", "mod", "download", "-json", "--", m.Path+"@"+m.Version)
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
cmd.Dir = tmpDir
out, err := cmd.Output()
var xerr *exec.ExitError
@@ -931,7 +991,7 @@
//
// pkgPaths are the import paths of the module being loaded, including the path
// to any main packages (as if they were importable).
-func prepareLoadDir(modFile *modfile.File, modPath, modRoot, version string, cached bool) (dir string, goModData, goSumData []byte, pkgPaths []string, diagnostics []string, err error) {
+func prepareLoadDir(ctx context.Context, modFile *modfile.File, modPath, modRoot, version string, cached bool) (dir string, goModData, goSumData []byte, pkgPaths []string, diagnostics []string, err error) {
defer func() {
if err != nil {
if cached {
@@ -1003,7 +1063,10 @@
}
// Add missing requirements.
- cmd := exec.Command("go", "get", "-d", ".")
+ cmd := exec.CommandContext(ctx, "go", "get", "-d", ".")
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
cmd.Dir = dir
if _, err := cmd.Output(); err != nil {
return "", nil, nil, nil, nil, fmt.Errorf("looking for missing dependencies: %w", cleanCmdError(err))
@@ -1011,17 +1074,15 @@
// Report new requirements in go.mod.
goModPath := filepath.Join(dir, "go.mod")
- loadReqs := func(data []byte) ([]string, error) {
+ loadReqs := func(data []byte) (reqs []module.Version, err error) {
modFile, err := modfile.ParseLax(goModPath, data, nil)
if err != nil {
return nil, err
}
- lines := make([]string, len(modFile.Require))
- for i, req := range modFile.Require {
- lines[i] = req.Mod.String()
+ for _, r := range modFile.Require {
+ reqs = append(reqs, r.Mod)
}
- sort.Strings(lines)
- return lines, nil
+ return reqs, nil
}
oldReqs, err := loadReqs(goModData)
@@ -1037,19 +1098,27 @@
return "", nil, nil, nil, nil, err
}
- oldMap := make(map[string]bool)
+ oldMap := make(map[module.Version]bool)
for _, req := range oldReqs {
oldMap[req] = true
}
- var missing []string
+ var missing []module.Version
for _, req := range newReqs {
+ // Ignore cyclic imports, since a module never needs to require itself.
+ if req.Path == modPath {
+ continue
+ }
if !oldMap[req] {
missing = append(missing, req)
}
}
if len(missing) > 0 {
- diagnostics = append(diagnostics, fmt.Sprintf("go.mod: the following requirements are needed\n\t%s\nRun 'go mod tidy' to add missing requirements.", strings.Join(missing, "\n\t")))
+ var missingReqs []string
+ for _, m := range missing {
+ missingReqs = append(missingReqs, m.String())
+ }
+ diagnostics = append(diagnostics, fmt.Sprintf("go.mod: the following requirements are needed\n\t%s\nRun 'go mod tidy' to add missing requirements.", strings.Join(missingReqs, "\n\t")))
return dir, goModData, goSumData, imps, diagnostics, nil
}
@@ -1137,14 +1206,18 @@
// returned through diagnostics.
// err will be non-nil in case of a fatal error that prevented packages
// from being loaded.
-func loadPackages(modPath, modRoot, loadDir string, goModData, goSumData []byte, pkgPaths []string) (pkgs []*packages.Package, diagnostics []string, err error) {
+func loadPackages(ctx context.Context, modPath, modRoot, loadDir string, goModData, goSumData []byte, pkgPaths []string) (pkgs []*packages.Package, diagnostics []string, err error) {
// Load packages.
// TODO(jayconrod): if there are errors loading packages in the release
// version, try loading in the release directory. Errors there would imply
// that packages don't load without replace / exclude directives.
cfg := &packages.Config{
- Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
- Dir: loadDir,
+ Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
+ Dir: loadDir,
+ Context: ctx,
+ }
+ if env, ok := ctx.Value("env").([]string); ok {
+ cfg.Env = env
}
if len(pkgPaths) > 0 {
pkgs, err = packages.Load(cfg, pkgPaths...)
@@ -1219,8 +1292,17 @@
// containing the go.mod for modPath.
//
// If no module cycle exists, it returns empty string.
-func findSelectedVersion(modDir, modPath string) (latestVersion string, err error) {
- cmd := exec.Command("go", "list", "-m", "-f", "{{.Version}}", "--", modPath)
+func findSelectedVersion(ctx context.Context, modDir, modPath string) (latestVersion string, err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("could not find selected version for %s: %v", modPath, err)
+ }
+ }()
+
+ cmd := exec.CommandContext(ctx, "go", "list", "-m", "-f", "{{.Version}}", "--", modPath)
+ if env, ok := ctx.Value("env").([]string); ok {
+ cmd.Env = env
+ }
cmd.Dir = modDir
out, err := cmd.Output()
if err != nil {
diff --git a/cmd/gorelease/gorelease_test.go b/cmd/gorelease/gorelease_test.go
index a29f438..fca52c1 100644
--- a/cmd/gorelease/gorelease_test.go
+++ b/cmd/gorelease/gorelease_test.go
@@ -6,6 +6,7 @@
import (
"bytes"
+ "context"
"flag"
"fmt"
"io/ioutil"
@@ -16,62 +17,58 @@
"strings"
"testing"
+ "golang.org/x/mod/module"
"golang.org/x/tools/txtar"
)
-var workDir string
-
var (
testwork = flag.Bool("testwork", false, "preserve work directory")
updateGolden = flag.Bool("u", false, "update expected text in test files instead of failing")
)
-func TestMain(m *testing.M) {
- status := 1
- defer func() {
- if !*testwork && workDir != "" {
- os.RemoveAll(workDir)
- }
- os.Exit(status)
- }()
+// prepareProxy creates a proxy dir and returns an associated ctx.
+//
+// proxyVersions must be a map of module version to true. If proxyVersions is
+// empty, all modules in mod/ will be included in the proxy list. If proxy
+// versions is non-empty, only those modules in mod/ that match an entry in
+// proxyVersions will be included.
+//
+// ctx must be used in runRelease.
+// cleanup must be called when the relevant tests are finished.
+func prepareProxy(proxyVersions map[module.Version]bool, tests []*test) (ctx context.Context, cleanup func(), _ error) {
+ env := append(os.Environ(), "GO111MODULE=on", "GOSUMDB=off")
- flag.Parse()
-
- proxyDir, proxyURL, err := buildProxyDir()
+ proxyDir, proxyURL, err := buildProxyDir(proxyVersions, tests)
if err != nil {
- fmt.Fprintln(os.Stderr, err)
- return
+ return nil, nil, fmt.Errorf("error building proxy dir: %v", err)
}
- os.Setenv("GOPROXY", proxyURL)
- if *testwork {
- fmt.Fprintf(os.Stderr, "test proxy dir: %s\ntest proxy URL: %s\n", proxyDir, proxyURL)
- } else {
- defer os.RemoveAll(proxyDir)
- }
+ env = append(env, fmt.Sprintf("GOPROXY=%s", proxyURL))
cacheDir, err := ioutil.TempDir("", "gorelease_test-gocache")
if err != nil {
- fmt.Fprintln(os.Stderr, err)
- return
+ return nil, nil, err
}
- os.Setenv("GOPATH", cacheDir)
- if *testwork {
- fmt.Fprintf(os.Stderr, "test cache dir: %s\n", cacheDir)
- } else {
- defer func() {
- if err := exec.Command("go", "clean", "-modcache").Run(); err != nil {
- fmt.Fprintln(os.Stderr, err)
+ env = append(env, fmt.Sprintf("GOPATH=%s", cacheDir))
+
+ return context.WithValue(context.Background(), "env", env), func() {
+ if *testwork {
+ fmt.Fprintf(os.Stderr, "test cache dir: %s\n", cacheDir)
+ fmt.Fprintf(os.Stderr, "test proxy dir: %s\ntest proxy URL: %s\n", proxyDir, proxyURL)
+ } else {
+ cmd := exec.Command("go", "clean", "-modcache")
+ cmd.Env = env
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error running go clean: %v", err))
}
+
if err := os.RemoveAll(cacheDir); err != nil {
- fmt.Fprintln(os.Stderr, err)
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error removing cache dir %s: %v", cacheDir, err))
}
- }()
- }
-
- os.Setenv("GO111MODULE", "on")
- os.Setenv("GOSUMDB", "off")
-
- status = m.Run()
+ if err := os.RemoveAll(proxyDir); err != nil {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("error removing proxy dir %s: %v", proxyDir, err))
+ }
+ }
+ }, nil
}
// test describes an individual test case, written as a .test file in the
@@ -126,6 +123,13 @@
// want is set to the contents of the file named "want" in the txtar archive.
want []byte
+
+ // proxyVersions is used to set the exact contents of the GOPROXY.
+ //
+ // If empty, all of testadata/mod/ will be included in the proxy.
+ // If it is not empty, each entry must be of the form <modpath>@v<version>
+ // and exist in testdata/mod/.
+ proxyVersions map[module.Version]bool
}
// readTest reads and parses a .test file with the given name.
@@ -180,6 +184,22 @@
if err != nil {
return nil, fmt.Errorf("%s:%d: %v", testPath, lineNum, err)
}
+ case "proxyVersions":
+ parts := strings.Split(value, ",")
+ proxyVersions := make(map[module.Version]bool)
+ for _, modpathWithVersion := range parts {
+ vParts := strings.Split(modpathWithVersion, "@")
+ if len(vParts) != 2 {
+ return nil, fmt.Errorf("proxyVersions entry %s is invalid: it should be of the format <modpath>@v<semver> (ex: github.com/foo/bar@v1.2.3)", modpathWithVersion)
+ }
+ modPath, version := vParts[0], vParts[1]
+ mv := module.Version{
+ Path: modPath,
+ Version: version,
+ }
+ proxyVersions[mv] = true
+ }
+ t.proxyVersions = proxyVersions
default:
return nil, fmt.Errorf("%s:%d: unknown key: %q", testPath, lineNum, key)
}
@@ -233,21 +253,41 @@
t.Fatal("no .test files found in testdata directory")
}
+ var tests []*test
for _, testPath := range testPaths {
- testPath := testPath
- testName := strings.TrimSuffix(strings.TrimPrefix(filepath.ToSlash(testPath), "testdata/"), ".test")
- t.Run(testName, func(t *testing.T) {
- t.Parallel()
+ test, err := readTest(testPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tests = append(tests, test)
+ }
- test, err := readTest(testPath)
- if err != nil {
- t.Fatal(err)
- }
+ defaultContext, cleanup, err := prepareProxy(nil, tests)
+ if err != nil {
+ t.Fatalf("preparing test proxy: %v", err)
+ }
+ t.Cleanup(cleanup)
+
+ for _, test := range tests {
+ testName := strings.TrimSuffix(strings.TrimPrefix(filepath.ToSlash(test.testPath), "testdata/"), ".test")
+ t.Run(testName, func(t *testing.T) {
+ ctx := defaultContext
if test.skip != "" {
t.Skip(test.skip)
}
+ t.Parallel()
+
+ if len(test.proxyVersions) > 0 {
+ var cleanup func()
+ ctx, cleanup, err = prepareProxy(test.proxyVersions, tests)
+ if err != nil {
+ t.Fatalf("preparing test proxy: %v", err)
+ }
+ t.Cleanup(cleanup)
+ }
+
// Extract the files in the release version. They may be part of the
// test archive or in testdata/mod.
testDir, err := ioutil.TempDir("", "")
@@ -257,7 +297,9 @@
if *testwork {
fmt.Fprintf(os.Stderr, "test dir: %s\n", testDir)
} else {
- defer os.RemoveAll(testDir)
+ t.Cleanup(func() {
+ os.RemoveAll(testDir)
+ })
}
var arc *txtar.Archive
@@ -286,7 +328,7 @@
}
buf := &bytes.Buffer{}
releaseDir := filepath.Join(testDir, test.dir)
- success, err := runRelease(buf, releaseDir, args)
+ success, err := runRelease(ctx, buf, releaseDir, args)
if err != nil {
if !test.wantError {
t.Fatalf("unexpected error: %v", err)
diff --git a/cmd/gorelease/proxy_test.go b/cmd/gorelease/proxy_test.go
index 8aaebea..d91d983 100644
--- a/cmd/gorelease/proxy_test.go
+++ b/cmd/gorelease/proxy_test.go
@@ -24,22 +24,33 @@
// buildProxyDir constructs a temporary directory suitable for use as a
// module proxy with a file:// URL. The caller is responsible for deleting
// the directory when it's no longer needed.
-func buildProxyDir() (proxyDir, proxyURL string, err error) {
+//
+// proxyVersions must be a map of module version true. If proxyVersions is
+// empty, all modules in mod/ will be included in the proxy list. If proxy
+// versions is non-empty, only those modules in mod/ that match an entry in
+// proxyVersions will be included.
+func buildProxyDir(proxyVersions map[module.Version]bool, tests []*test) (proxyDir, proxyURL string, err error) {
proxyDir, err = ioutil.TempDir("", "gorelease-proxy")
if err != nil {
return "", "", err
}
- defer func(proxyDir string) {
- if err != nil {
- os.RemoveAll(proxyDir)
- }
- }(proxyDir)
txtarPaths, err := filepath.Glob(filepath.FromSlash("testdata/mod/*.txt"))
if err != nil {
return "", "", err
}
+
+ // Map of modPath to versions for that modPath.
versionLists := make(map[string][]string)
+
+ for _, t := range tests {
+ versionLists[t.modPath] = []string{}
+ modDir := filepath.Join(proxyDir, t.modPath, "@v")
+ if err := os.MkdirAll(modDir, 0777); err != nil {
+ return "", "", err
+ }
+ }
+
for _, txtarPath := range txtarPaths {
base := filepath.Base(txtarPath)
stem := base[:len(base)-len(".txt")]
@@ -49,6 +60,20 @@
}
modPath := strings.ReplaceAll(stem[:i], "_", "/")
version := stem[i+1:]
+ mv := module.Version{
+ Path: modPath,
+ Version: version,
+ }
+
+ // User has supplied proxyVersions. Honor proxy versions by only
+ // accepting those versions supplied in proxyVersions.
+ if len(proxyVersions) > 0 {
+ if !proxyVersions[mv] {
+ // modPath@version is not in proxyVersions: skip.
+ continue
+ }
+ }
+
versionLists[modPath] = append(versionLists[modPath], version)
modDir := filepath.Join(proxyDir, modPath, "@v")
diff --git a/cmd/gorelease/report.go b/cmd/gorelease/report.go
index aede072..0dbeca1 100644
--- a/cmd/gorelease/report.go
+++ b/cmd/gorelease/report.go
@@ -178,6 +178,12 @@
return
}
+ for _, v := range r.release.existingVersions {
+ if semver.Compare(v, r.release.version) == 0 {
+ setNotValid("version %s already exists", v)
+ }
+ }
+
// Check that compatible / incompatible changes are consistent.
if semver.Major(r.base.version) == "v0" || r.base.modPath != r.release.modPath {
return
@@ -244,6 +250,24 @@
// and link to documentation.
}
+ // Check whether we're comparing to the latest version of base.
+ //
+ // This could happen further up, but we want the more pressing errors above
+ // to take precedence.
+ var latestForBaseMajor string
+ for _, v := range r.release.existingVersions {
+ if semver.Major(v) != semver.Major(r.base.version) {
+ continue
+ }
+ if latestForBaseMajor == "" || semver.Compare(latestForBaseMajor, v) < 0 {
+ latestForBaseMajor = v
+ }
+ }
+ if latestForBaseMajor != "" && latestForBaseMajor != r.base.version {
+ setNotValid(fmt.Sprintf("Can only suggest a release version when compared against the most recent version of this major: %s.", latestForBaseMajor))
+ return
+ }
+
if r.base.version == "none" {
if _, pathMajor, ok := module.SplitPathVersion(r.release.modPath); !ok {
panic(fmt.Sprintf("could not parse module path %q", r.release.modPath))
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test
new file mode 100644
index 0000000..63cb373
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_major.test
@@ -0,0 +1,24 @@
+mod=example.com/basic
+base=v1.0.1
+success=false
+# A() was removed, which is a breaking change: it shouldn't try to suggest a
+# higher version.
+-- want --
+example.com/basic/a
+-------------------
+Incompatible changes:
+- A: removed
+Compatible changes:
+- B: added
+
+Cannot suggest a release version.
+Incompatible changes were detected.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func B() int { return 0 }
+
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test
new file mode 100644
index 0000000..cb43f45
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_minor.test
@@ -0,0 +1,23 @@
+mod=example.com/basic
+base=v0.0.1
+success=false
+# B() was added, so now it should suggest a new minor version. # But, there's a
+# later version that already exists: so it should not try to suggest anything at
+# all.
+-- want --
+example.com/basic/a
+-------------------
+Compatible changes:
+- B: added
+
+Cannot suggest a release version.
+Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
+func B() int { return 0 }
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test
new file mode 100644
index 0000000..81ed36c
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_suggest_patch.test
@@ -0,0 +1,22 @@
+mod=example.com/basic
+base=v0.1.0
+success=false
+# A() was changed in a small way, so now it should suggest a new patch version.
+# But, there's a later version that already exists: so it should not try to
+# suggest anything at all.
+-- want --
+Cannot suggest a release version.
+Can only suggest a release version when compared against the most recent version of this major: v0.1.2.
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 1 }
+func A2() int { return 2 }
+-- b/b.go --
+package b
+
+func B() int { return 3 }
diff --git a/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test b/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test
new file mode 100644
index 0000000..969606a
--- /dev/null
+++ b/cmd/gorelease/testdata/alreadyexists/alreadyexists_verify.test
@@ -0,0 +1,17 @@
+mod=example.com/basic
+base=v0.0.1
+release=v0.1.0
+success=false
+# The contents below are a copy of the v0.0.1 contents - nothing has changed.
+# But v0.1.0 already exists, so it should present a diagnostic.
+-- want --
+v0.1.0 is not a valid semantic version for this release.
+version v0.1.0 already exists
+-- go.mod --
+module example.com/basic
+
+go 1.12
+-- a/a.go --
+package a
+
+func A() int { return 0 }
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_suggest.test b/cmd/gorelease/testdata/basic/v0_compatible_suggest.test
index 32029f1..014fe08 100644
--- a/cmd/gorelease/testdata/basic/v0_compatible_suggest.test
+++ b/cmd/gorelease/testdata/basic/v0_compatible_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v0.1.0
base=v0.0.1
+proxyVersions=example.com/basic@v0.0.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v0_compatible_verify.test b/cmd/gorelease/testdata/basic/v0_compatible_verify.test
index 8324045..5461eea 100644
--- a/cmd/gorelease/testdata/basic/v0_compatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v0_compatible_verify.test
@@ -2,6 +2,7 @@
version=v0.1.0
base=v0.0.1
release=v0.1.0
+proxyVersions=example.com/basic@v0.0.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test b/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test
index 312b6b3..4aa8364 100644
--- a/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test
+++ b/cmd/gorelease/testdata/basic/v0_incompatible_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v0.1.2
base=v0.1.1
+proxyVersions=example.com/basic@v0.1.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v0_incompatible_verify.test b/cmd/gorelease/testdata/basic/v0_incompatible_verify.test
index 4784e3d..c6742d3 100644
--- a/cmd/gorelease/testdata/basic/v0_incompatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v0_incompatible_verify.test
@@ -2,6 +2,7 @@
version=v0.1.2
base=v0.1.1
release=v0.1.2
+proxyVersions=example.com/basic@v0.1.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v0_patch_suggest.test b/cmd/gorelease/testdata/basic/v0_patch_suggest.test
index cee5349..a98e6b6 100644
--- a/cmd/gorelease/testdata/basic/v0_patch_suggest.test
+++ b/cmd/gorelease/testdata/basic/v0_patch_suggest.test
@@ -1,5 +1,6 @@
mod=example.com/basic
version=v0.1.1
base=v0.1.0
+proxyVersions=example.com/basic@v0.1.0
-- want --
Suggested version: v0.1.1
diff --git a/cmd/gorelease/testdata/basic/v0_patch_verify.test b/cmd/gorelease/testdata/basic/v0_patch_verify.test
index a641f8c..b01e475 100644
--- a/cmd/gorelease/testdata/basic/v0_patch_verify.test
+++ b/cmd/gorelease/testdata/basic/v0_patch_verify.test
@@ -2,5 +2,6 @@
version=v0.1.1
base=v0.1.0
release=v0.1.1
+proxyVersions=example.com/basic@v0.1.0
-- want --
v0.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v0_pre_suggest.test b/cmd/gorelease/testdata/basic/v0_pre_suggest.test
index 2c09e5f..169a325 100644
--- a/cmd/gorelease/testdata/basic/v0_pre_suggest.test
+++ b/cmd/gorelease/testdata/basic/v0_pre_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v0.1.2
base=v0.1.1-pre
+proxyVersions=example.com/basic@v0.1.1-pre
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_compatible_suggest.test b/cmd/gorelease/testdata/basic/v1_compatible_suggest.test
index fb27db1..42b4ddd 100644
--- a/cmd/gorelease/testdata/basic/v1_compatible_suggest.test
+++ b/cmd/gorelease/testdata/basic/v1_compatible_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v1.1.0
base=v1.0.1
+proxyVersions=example.com/basic@v1.0.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_compatible_verify.test b/cmd/gorelease/testdata/basic/v1_compatible_verify.test
index 8c98ee1..45822ee 100644
--- a/cmd/gorelease/testdata/basic/v1_compatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_compatible_verify.test
@@ -2,6 +2,7 @@
version=v1.1.0
base=v1.0.1
release=v1.1.0
+proxyVersions=example.com/basic@v1.0.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test b/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test
index eaa57be..47e17ca 100644
--- a/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_fork_base_modpath_version_verify.test
@@ -2,6 +2,7 @@
base=example.com/basic@v1.1.1
version=v1.1.2
release=v1.1.2
+proxyVersions=example.com/basic@v1.1.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_fork_base_verify.test b/cmd/gorelease/testdata/basic/v1_fork_base_verify.test
index 8840112..569b734 100644
--- a/cmd/gorelease/testdata/basic/v1_fork_base_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_fork_base_verify.test
@@ -4,7 +4,7 @@
mod=example.com/basic
version=v1.1.2
base=example.com/basicfork@v1.1.2
-release=v1.1.2
+release=v1.1.3
-- want --
example.com/basicfork/a
-----------------------
diff --git a/cmd/gorelease/testdata/basic/v1_incompatible_verify.test b/cmd/gorelease/testdata/basic/v1_incompatible_verify.test
index e5d6c4d..149b849 100644
--- a/cmd/gorelease/testdata/basic/v1_incompatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_incompatible_verify.test
@@ -3,6 +3,7 @@
base=v1.1.1
success=false
release=v1.1.2
+proxyVersions=example.com/basic@v1.1.1
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_patch_suggest.test b/cmd/gorelease/testdata/basic/v1_patch_suggest.test
index a0effa1..a72569d 100644
--- a/cmd/gorelease/testdata/basic/v1_patch_suggest.test
+++ b/cmd/gorelease/testdata/basic/v1_patch_suggest.test
@@ -1,5 +1,6 @@
mod=example.com/basic
version=v1.1.1
base=v1.1.0
+proxyVersions=example.com/basic@v1.1.0
-- want --
Suggested version: v1.1.1
diff --git a/cmd/gorelease/testdata/basic/v1_patch_verify.test b/cmd/gorelease/testdata/basic/v1_patch_verify.test
index 64c734b..a01d5be 100644
--- a/cmd/gorelease/testdata/basic/v1_patch_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_patch_verify.test
@@ -2,5 +2,6 @@
version=v1.1.1
base=v1.1.0
release=v1.1.1
+proxyVersions=example.com/basic@v1.1.0
-- want --
v1.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v1_pre_suggest.test b/cmd/gorelease/testdata/basic/v1_pre_suggest.test
index de0f2c0..efca8c4 100644
--- a/cmd/gorelease/testdata/basic/v1_pre_suggest.test
+++ b/cmd/gorelease/testdata/basic/v1_pre_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v1.1.2
base=v1.1.1-pre
+proxyVersions=example.com/basic@v1.1.1-pre
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_higher.test b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
index 7bf77bc..ba6838f 100644
--- a/cmd/gorelease/testdata/basic/v1_querybase_higher.test
+++ b/cmd/gorelease/testdata/basic/v1_querybase_higher.test
@@ -3,6 +3,5 @@
release=v1.0.1
base=>v1.0.1
error=true
-
-- want --
base version v1.1.0 (>v1.0.1) must be lower than release version v1.0.1
diff --git a/cmd/gorelease/testdata/basic/v1_querybase_suggest.test b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
index 6bfc95c..ec73b41 100644
--- a/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
+++ b/cmd/gorelease/testdata/basic/v1_querybase_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic
version=v1.0.1
base=version-1.0.1
+proxyVersions=example.com/basic@version-1.0.1,example.com/basic@v1.0.1
-- want --
Base version: v1.0.1 (version-1.0.1)
Suggested version: v1.0.2
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test
index 1d56841..d27a389 100644
--- a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_query_verify.test
@@ -2,6 +2,7 @@
base=example.com/basic@>=v1.1.0
version=v2.0.1
release=v2.0.1
+proxyVersions=example.com/basic@v1.1.0
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test
index 6533f3a..54fe0db 100644
--- a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_verify.test
@@ -2,6 +2,7 @@
base=example.com/basic
version=v2.1.0
release=v2.1.0
+proxyVersions=example.com/basic@v1.1.2
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test
index 22ed2f4..8edac73 100644
--- a/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test
+++ b/cmd/gorelease/testdata/basic/v1_v2_base_modpath_version_verify.test
@@ -2,6 +2,7 @@
base=example.com/basic@v1.1.0
version=v2.0.1
release=v2.0.1
+proxyVersions=example.com/basic@v1.1.0
-- want --
example.com/basic/a
-------------------
diff --git a/cmd/gorelease/testdata/basic/v2_compatible_suggest.test b/cmd/gorelease/testdata/basic/v2_compatible_suggest.test
index a421c56..78740e2 100644
--- a/cmd/gorelease/testdata/basic/v2_compatible_suggest.test
+++ b/cmd/gorelease/testdata/basic/v2_compatible_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic/v2
version=v2.1.0
base=v2.0.1
+proxyVersions=example.com/basic/v2@v2.0.1
-- want --
example.com/basic/v2/a
----------------------
diff --git a/cmd/gorelease/testdata/basic/v2_compatible_verify.test b/cmd/gorelease/testdata/basic/v2_compatible_verify.test
index ac138a7..63307ee 100644
--- a/cmd/gorelease/testdata/basic/v2_compatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v2_compatible_verify.test
@@ -2,6 +2,7 @@
version=v2.1.0
base=v2.0.1
release=v2.1.0
+proxyVersions=example.com/basic/v2@v2.0.1
-- want --
example.com/basic/v2/a
----------------------
diff --git a/cmd/gorelease/testdata/basic/v2_incompatible_verify.test b/cmd/gorelease/testdata/basic/v2_incompatible_verify.test
index 8ea2095..3948f41 100644
--- a/cmd/gorelease/testdata/basic/v2_incompatible_verify.test
+++ b/cmd/gorelease/testdata/basic/v2_incompatible_verify.test
@@ -3,6 +3,7 @@
base=v2.1.1
success=false
release=v2.1.2
+proxyVersions=example.com/basic/v2@v2.1.1
-- want --
example.com/basic/v2/a
----------------------
diff --git a/cmd/gorelease/testdata/basic/v2_patch_suggest.test b/cmd/gorelease/testdata/basic/v2_patch_suggest.test
index 1416690..991885c 100644
--- a/cmd/gorelease/testdata/basic/v2_patch_suggest.test
+++ b/cmd/gorelease/testdata/basic/v2_patch_suggest.test
@@ -1,5 +1,6 @@
mod=example.com/basic/v2
version=v2.1.1
base=v2.1.0
+proxyVersions=example.com/basic/v2@v2.1.0
-- want --
Suggested version: v2.1.1
diff --git a/cmd/gorelease/testdata/basic/v2_patch_verify.test b/cmd/gorelease/testdata/basic/v2_patch_verify.test
index 4aead44..f5c7f04 100644
--- a/cmd/gorelease/testdata/basic/v2_patch_verify.test
+++ b/cmd/gorelease/testdata/basic/v2_patch_verify.test
@@ -2,5 +2,6 @@
version=v2.1.1
base=v2.1.0
release=v2.1.1
+proxyVersions=example.com/basic/v2@v2.1.0
-- want --
v2.1.1 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/basic/v2_pre_suggest.test b/cmd/gorelease/testdata/basic/v2_pre_suggest.test
index c786dcb..94716f4 100644
--- a/cmd/gorelease/testdata/basic/v2_pre_suggest.test
+++ b/cmd/gorelease/testdata/basic/v2_pre_suggest.test
@@ -1,6 +1,7 @@
mod=example.com/basic/v2
version=v2.1.2
base=v2.1.1-pre
+proxyVersions=example.com/basic/v2@v2.1.1-pre
-- want --
example.com/basic/v2/a
----------------------
diff --git a/cmd/gorelease/testdata/empty/empty.test b/cmd/gorelease/testdata/empty/empty.test
index c87fd6b..cd95565 100644
--- a/cmd/gorelease/testdata/empty/empty.test
+++ b/cmd/gorelease/testdata/empty/empty.test
@@ -2,5 +2,6 @@
base=v0.0.1
version=v0.0.2
release=v0.0.2
+proxyVersions=example.com/empty@v0.0.1
-- want --
v0.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/errors/bad_release.test b/cmd/gorelease/testdata/errors/bad_release.test
index 6015532..74dc009 100644
--- a/cmd/gorelease/testdata/errors/bad_release.test
+++ b/cmd/gorelease/testdata/errors/bad_release.test
@@ -2,7 +2,6 @@
base=v0.1.0
release=master
error=true
-
-- want --
usage: gorelease [-base=version] [-version=version]
release version "master" is not a canonical semantic version
diff --git a/cmd/gorelease/testdata/errors/base_higher.test b/cmd/gorelease/testdata/errors/base_higher.test
index 12d7bba..a2325f9 100644
--- a/cmd/gorelease/testdata/errors/base_higher.test
+++ b/cmd/gorelease/testdata/errors/base_higher.test
@@ -2,7 +2,6 @@
base=v0.2.0
release=v0.1.0
error=true
-
-- want --
usage: gorelease [-base=version] [-version=version]
base version ("v0.2.0") must be lower than release version ("v0.1.0")
diff --git a/cmd/gorelease/testdata/errors/base_modpath_none.test b/cmd/gorelease/testdata/errors/base_modpath_none.test
index bb438d9..74a6946 100644
--- a/cmd/gorelease/testdata/errors/base_modpath_none.test
+++ b/cmd/gorelease/testdata/errors/base_modpath_none.test
@@ -1,7 +1,6 @@
mod=example.com/basic/v2
base=example.com/basic@none
error=true
-
-- want --
usage: gorelease [-base=version] [-version=version]
base version ("example.com/basic@none") cannot have version "none" with explicit module path
diff --git a/cmd/gorelease/testdata/errors/errors.test b/cmd/gorelease/testdata/errors/errors.test
index 002a8fc..a7b396b 100644
--- a/cmd/gorelease/testdata/errors/errors.test
+++ b/cmd/gorelease/testdata/errors/errors.test
@@ -3,7 +3,7 @@
base=v0.1.0
release=v0.2.0
success=false
-
+proxyVersions=example.com/errors@v0.1.0
-- want --
example.com/errors/added
------------------------
diff --git a/cmd/gorelease/testdata/errors/same_base_release.test b/cmd/gorelease/testdata/errors/same_base_release.test
index 0d0a09d..4da457b 100644
--- a/cmd/gorelease/testdata/errors/same_base_release.test
+++ b/cmd/gorelease/testdata/errors/same_base_release.test
@@ -2,7 +2,6 @@
base=v0.1.0
release=v0.1.0
error=true
-
-- want --
usage: gorelease [-base=version] [-version=version]
-base and -version must be different
diff --git a/cmd/gorelease/testdata/errors/upgrade_base.test b/cmd/gorelease/testdata/errors/upgrade_base.test
index d09b68a..994b3b7 100644
--- a/cmd/gorelease/testdata/errors/upgrade_base.test
+++ b/cmd/gorelease/testdata/errors/upgrade_base.test
@@ -2,6 +2,5 @@
version=v0.1.0
base=upgrade
error=true
-
-- want --
could not resolve version example.com/errors@upgrade: query is based on requirements in main go.mod file
diff --git a/cmd/gorelease/testdata/first/v0_0_0.test b/cmd/gorelease/testdata/first/v0_0_0.test
index d3004df..8214501 100644
--- a/cmd/gorelease/testdata/first/v0_0_0.test
+++ b/cmd/gorelease/testdata/first/v0_0_0.test
@@ -1,7 +1,6 @@
mod=example.com/first
base=none
release=v0.0.0
-
-- want --
v0.0.0 is a valid semantic version for this release.
-- go.mod --
diff --git a/cmd/gorelease/testdata/first/v0_0_1.test b/cmd/gorelease/testdata/first/v0_0_1.test
index acbc4c9..79630a1 100644
--- a/cmd/gorelease/testdata/first/v0_0_1.test
+++ b/cmd/gorelease/testdata/first/v0_0_1.test
@@ -1,7 +1,6 @@
mod=example.com/first
base=none
release=v0.0.1
-
-- want --
v0.0.1 is a valid semantic version for this release.
-- go.mod --
diff --git a/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test b/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test
index 6c2df3f..8b17521 100644
--- a/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test
+++ b/cmd/gorelease/testdata/first/v0_1_0-alpha.1.test
@@ -1,7 +1,6 @@
mod=example.com/first
base=none
release=v0.1.0-alpha.1
-
-- want --
v0.1.0-alpha.1 is a valid semantic version for this release.
-- go.mod --
diff --git a/cmd/gorelease/testdata/first/v0_1_0.test b/cmd/gorelease/testdata/first/v0_1_0.test
index 8c56a93..656ba5d 100644
--- a/cmd/gorelease/testdata/first/v0_1_0.test
+++ b/cmd/gorelease/testdata/first/v0_1_0.test
@@ -1,7 +1,6 @@
mod=example.com/first
base=none
release=v0.1.0
-
-- want --
v0.1.0 is a valid semantic version for this release.
-- go.mod --
diff --git a/cmd/gorelease/testdata/first/v0_err.test b/cmd/gorelease/testdata/first/v0_err.test
index 623a4ee..c655554 100644
--- a/cmd/gorelease/testdata/first/v0_err.test
+++ b/cmd/gorelease/testdata/first/v0_err.test
@@ -6,7 +6,6 @@
# TODO(golang.org/issue/36087): go list doesn't report positions in correct
# place for scanner errors.
skip=packages.Load gives error with extra "-: " prefix
-
-- want --
example.com/first
-----------------
diff --git a/cmd/gorelease/testdata/first/v1_0_0.test b/cmd/gorelease/testdata/first/v1_0_0.test
index 9bbd271..e1b4f5c 100644
--- a/cmd/gorelease/testdata/first/v1_0_0.test
+++ b/cmd/gorelease/testdata/first/v1_0_0.test
@@ -1,7 +1,6 @@
mod=example.com/first
base=none
release=v1.0.0
-
-- want --
v1.0.0 is a valid semantic version for this release.
-- go.mod --
diff --git a/cmd/gorelease/testdata/first/v2_err.test b/cmd/gorelease/testdata/first/v2_err.test
index 09938c8..dfc71d5 100644
--- a/cmd/gorelease/testdata/first/v2_err.test
+++ b/cmd/gorelease/testdata/first/v2_err.test
@@ -6,7 +6,6 @@
# TODO(golang.org/issue/36087): go list doesn't report positions in correct
# place for scanner errors.
skip=packages.Load gives error with extra "-: " prefix
-
-- want --
example.com/first
-----------------
diff --git a/cmd/gorelease/testdata/first/v2_moderr.test b/cmd/gorelease/testdata/first/v2_moderr.test
index 2b6879c..63bb806 100644
--- a/cmd/gorelease/testdata/first/v2_moderr.test
+++ b/cmd/gorelease/testdata/first/v2_moderr.test
@@ -2,7 +2,6 @@
base=none
release=v2.0.0
success=false
-
-- want --
v2.0.0 is not a valid semantic version for this release.
The module path does not end with the major version suffix /v2,
diff --git a/cmd/gorelease/testdata/internalcompat/internalcompat.test b/cmd/gorelease/testdata/internalcompat/internalcompat.test
index 8bc32fa..9bf59a3 100644
--- a/cmd/gorelease/testdata/internalcompat/internalcompat.test
+++ b/cmd/gorelease/testdata/internalcompat/internalcompat.test
@@ -1,6 +1,5 @@
mod=example.com/internalcompat/b
version=v1.0.0
-release=v1.0.0
+release=v1.0.1
base=example.com/internalcompat/a@v1.0.0
-
-- want --
diff --git a/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
index 7200d20..55e152d 100644
--- a/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
+++ b/cmd/gorelease/testdata/mod/example.com_cycle_v1.0.0.txt
@@ -8,7 +8,6 @@
go 1.12
require example.com/cycledep v1.0.0
-require example.com/cycle v1.5.0
-- cycle/main.go --
package main
diff --git a/cmd/gorelease/testdata/nomod/nomod.test b/cmd/gorelease/testdata/nomod/nomod.test
index 9f332fe..973d393 100644
--- a/cmd/gorelease/testdata/nomod/nomod.test
+++ b/cmd/gorelease/testdata/nomod/nomod.test
@@ -2,5 +2,6 @@
version=v0.0.2
base=v0.0.1
release=v0.0.2
+proxyVersions=example.com/nomod@v0.0.1
-- want --
v0.0.2 is a valid semantic version for this release.
diff --git a/cmd/gorelease/testdata/private/unreported.test b/cmd/gorelease/testdata/private/unreported.test
index 3aabad3..8ed58fe 100644
--- a/cmd/gorelease/testdata/private/unreported.test
+++ b/cmd/gorelease/testdata/private/unreported.test
@@ -1,5 +1,6 @@
mod=example.com/private
version=v1.0.1
base=v1.0.0
+proxyVersions=example.com/private@v1.0.0
-- want --
Suggested version: v1.0.1
diff --git a/cmd/gorelease/testdata/regress/issue37756.test b/cmd/gorelease/testdata/regress/issue37756.test
index 0a9093c..f95c55d 100644
--- a/cmd/gorelease/testdata/regress/issue37756.test
+++ b/cmd/gorelease/testdata/regress/issue37756.test
@@ -3,6 +3,7 @@
mod=example.com/issue37756
version=v1.1.0
base=v1.0.0
+proxyVersions=example.com/issue37756@v1.0.0
-- want --
example.com/issue37756/a
------------------------
diff --git a/cmd/gorelease/testdata/require/add_requirement.test b/cmd/gorelease/testdata/require/add_requirement.test
index 3cbc78b..7833dd3 100644
--- a/cmd/gorelease/testdata/require/add_requirement.test
+++ b/cmd/gorelease/testdata/require/add_requirement.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.0.1
+proxyVersions=example.com/require@v0.0.1,example.com/basic@v1.0.1
-- want --
Suggested version: v0.1.0
-- go.mod --
diff --git a/cmd/gorelease/testdata/require/decrement_go_version.test b/cmd/gorelease/testdata/require/decrement_go_version.test
index abcd56f..7360f13 100644
--- a/cmd/gorelease/testdata/require/decrement_go_version.test
+++ b/cmd/gorelease/testdata/require/decrement_go_version.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
-- want --
Suggested version: v0.0.2
-- go.mod --
diff --git a/cmd/gorelease/testdata/require/increment_go_version.test b/cmd/gorelease/testdata/require/increment_go_version.test
index 2f7c993..a698bee 100644
--- a/cmd/gorelease/testdata/require/increment_go_version.test
+++ b/cmd/gorelease/testdata/require/increment_go_version.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
-- want --
Suggested version: v0.1.0
-- go.mod --
diff --git a/cmd/gorelease/testdata/require/increment_requirement_minor.test b/cmd/gorelease/testdata/require/increment_requirement_minor.test
index a453b3e..0d83508 100644
--- a/cmd/gorelease/testdata/require/increment_requirement_minor.test
+++ b/cmd/gorelease/testdata/require/increment_requirement_minor.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.1.0
+proxyVersions=example.com/require@v0.1.0,example.com/basic@v1.1.0,example.com/basic@v1.0.1
-- want --
Suggested version: v0.2.0
-- go.mod --
diff --git a/cmd/gorelease/testdata/require/increment_requirement_patch.test b/cmd/gorelease/testdata/require/increment_requirement_patch.test
index 0469ae4..2665fd3 100644
--- a/cmd/gorelease/testdata/require/increment_requirement_patch.test
+++ b/cmd/gorelease/testdata/require/increment_requirement_patch.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.1.1
+proxyVersions=example.com/require@v0.1.1,example.com/basic@v1.1.1,example.com/basic@v1.1.0
-- want --
Suggested version: v0.1.2
-- go.mod --
diff --git a/cmd/gorelease/testdata/require/remove_requirements.test b/cmd/gorelease/testdata/require/remove_requirements.test
index f57d12a..b3f82ed 100644
--- a/cmd/gorelease/testdata/require/remove_requirements.test
+++ b/cmd/gorelease/testdata/require/remove_requirements.test
@@ -1,5 +1,6 @@
mod=example.com/require
base=v0.0.1
+proxyVersions=example.com/require@v0.0.1
-- want --
Suggested version: v0.0.2
-- go.mod --
diff --git a/go.mod b/go.mod
index 00c5b45..4e6430c 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f
- golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449
+ golang.org/x/mod v0.4.2
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
diff --git a/go.sum b/go.sum
index 545190e..971c6c5 100644
--- a/go.sum
+++ b/go.sum
@@ -17,8 +17,8 @@
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ=
-golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=