cmd/go: add -retracted flag to 'go list'
The -retracted flag causes 'go list' to load information about
retracted module module versions.
When -retracted is used with -f or -json, the Retracted field is set
to a string containing the reason for the retraction on retracted
module versions. The string is based on comments on the retract
directive. This field is also populated when the -u flag is used.
When -retracted is used with -versions, retracted versions are shown.
Normally, they are omitted.
For #24031
Change-Id: Ic13d516eddffb1b8404e21034f78cecc9896d1b8
Reviewed-on: https://go-review.googlesource.com/c/go/+/228382
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 609ede4..98861c8 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -916,6 +916,7 @@
// Dir string // directory holding files for this module, if any
// GoMod string // path to go.mod file used when loading this module, if any
// GoVersion string // go version used in module
+// Retracted string // retraction information, if any (with -retracted or -u)
// Error *ModuleError // error loading module
// }
//
@@ -947,14 +948,16 @@
// The -u flag adds information about available upgrades.
// When the latest version of a given module is newer than
// the current one, list -u sets the Module's Update field
-// to information about the newer module.
+// to information about the newer module. list -u will also set
+// the module's Retracted field if the current version is retracted.
// The Module's String method indicates an available upgrade by
// formatting the newer version in brackets after the current version.
+// If a version is retracted, the string "(retracted)" will follow it.
// For example, 'go list -m -u all' might print:
//
// my/main/module
// golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
-// rsc.io/pdf v0.1.1 [v0.1.2]
+// rsc.io/pdf v0.1.1 (retracted) [v0.1.2]
//
// (For tools, 'go list -m -u -json all' may be more convenient to parse.)
//
@@ -964,6 +967,14 @@
// the default output format to display the module path followed by the
// space-separated version list.
//
+// The -retracted flag causes list to report information about retracted
+// module versions. When -retracted is used with -f or -json, the Retracted
+// field will be set to a string explaining why the version was retracted.
+// The string is taken from comments on the retract directive in the
+// module's go.mod file. When -retracted is used with -versions, retracted
+// versions are listed together with unretracted versions. The -retracted
+// flag may be used with or without -m.
+//
// The arguments to list -m are interpreted as a list of modules, not packages.
// The main module is the module containing the current directory.
// The active modules are the main module and its dependencies.
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index e68c39f..6d81c1c 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -10,6 +10,7 @@
"bytes"
"context"
"encoding/json"
+ "fmt"
"io"
"os"
"sort"
@@ -215,6 +216,7 @@
Dir string // directory holding files for this module, if any
GoMod string // path to go.mod file used when loading this module, if any
GoVersion string // go version used in module
+ Retracted string // retraction information, if any (with -retracted or -u)
Error *ModuleError // error loading module
}
@@ -246,14 +248,16 @@
The -u flag adds information about available upgrades.
When the latest version of a given module is newer than
the current one, list -u sets the Module's Update field
-to information about the newer module.
+to information about the newer module. list -u will also set
+the module's Retracted field if the current version is retracted.
The Module's String method indicates an available upgrade by
formatting the newer version in brackets after the current version.
+If a version is retracted, the string "(retracted)" will follow it.
For example, 'go list -m -u all' might print:
my/main/module
golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text
- rsc.io/pdf v0.1.1 [v0.1.2]
+ rsc.io/pdf v0.1.1 (retracted) [v0.1.2]
(For tools, 'go list -m -u -json all' may be more convenient to parse.)
@@ -263,6 +267,14 @@
the default output format to display the module path followed by the
space-separated version list.
+The -retracted flag causes list to report information about retracted
+module versions. When -retracted is used with -f or -json, the Retracted
+field will be set to a string explaining why the version was retracted.
+The string is taken from comments on the retract directive in the
+module's go.mod file. When -retracted is used with -versions, retracted
+versions are listed together with unretracted versions. The -retracted
+flag may be used with or without -m.
+
The arguments to list -m are interpreted as a list of modules, not packages.
The main module is the module containing the current directory.
The active modules are the main module and its dependencies.
@@ -296,17 +308,18 @@
}
var (
- listCompiled = CmdList.Flag.Bool("compiled", false, "")
- listDeps = CmdList.Flag.Bool("deps", false, "")
- listE = CmdList.Flag.Bool("e", false, "")
- listExport = CmdList.Flag.Bool("export", false, "")
- listFmt = CmdList.Flag.String("f", "", "")
- listFind = CmdList.Flag.Bool("find", false, "")
- listJson = CmdList.Flag.Bool("json", false, "")
- listM = CmdList.Flag.Bool("m", false, "")
- listU = CmdList.Flag.Bool("u", false, "")
- listTest = CmdList.Flag.Bool("test", false, "")
- listVersions = CmdList.Flag.Bool("versions", false, "")
+ listCompiled = CmdList.Flag.Bool("compiled", false, "")
+ listDeps = CmdList.Flag.Bool("deps", false, "")
+ listE = CmdList.Flag.Bool("e", false, "")
+ listExport = CmdList.Flag.Bool("export", false, "")
+ listFmt = CmdList.Flag.String("f", "", "")
+ listFind = CmdList.Flag.Bool("find", false, "")
+ listJson = CmdList.Flag.Bool("json", false, "")
+ listM = CmdList.Flag.Bool("m", false, "")
+ listRetracted = CmdList.Flag.Bool("retracted", false, "")
+ listTest = CmdList.Flag.Bool("test", false, "")
+ listU = CmdList.Flag.Bool("u", false, "")
+ listVersions = CmdList.Flag.Bool("versions", false, "")
)
var nl = []byte{'\n'}
@@ -367,6 +380,16 @@
}
}
+ modload.Init()
+ if *listRetracted {
+ if cfg.BuildMod == "vendor" {
+ base.Fatalf("go list -retracted cannot be used when vendoring is enabled")
+ }
+ if !modload.Enabled() {
+ base.Fatalf("go list -retracted can only be used in module-aware mode")
+ }
+ }
+
if *listM {
// Module mode.
if *listCompiled {
@@ -416,7 +439,7 @@
modload.LoadBuildList(ctx)
- mods := modload.ListModules(ctx, args, *listU, *listVersions)
+ mods := modload.ListModules(ctx, args, *listU, *listVersions, *listRetracted)
if !*listE {
for _, m := range mods {
if m.Error != nil {
@@ -607,6 +630,55 @@
}
}
+ // TODO(golang.org/issue/40676): This mechanism could be extended to support
+ // -u without -m.
+ if *listRetracted {
+ // Load retractions for modules that provide packages that will be printed.
+ // TODO(golang.org/issue/40775): Packages from the same module refer to
+ // distinct ModulePublic instance. It would be nice if they could all point
+ // to the same instance. This would require additional global state in
+ // modload.loaded, so that should be refactored first. For now, we update
+ // all instances.
+ modToArg := make(map[*modinfo.ModulePublic]string)
+ argToMods := make(map[string][]*modinfo.ModulePublic)
+ var args []string
+ addModule := func(mod *modinfo.ModulePublic) {
+ if mod.Version == "" {
+ return
+ }
+ arg := fmt.Sprintf("%s@%s", mod.Path, mod.Version)
+ if argToMods[arg] == nil {
+ args = append(args, arg)
+ }
+ argToMods[arg] = append(argToMods[arg], mod)
+ modToArg[mod] = arg
+ }
+ for _, p := range pkgs {
+ if p.Module == nil {
+ continue
+ }
+ addModule(p.Module)
+ if p.Module.Replace != nil {
+ addModule(p.Module.Replace)
+ }
+ }
+
+ if len(args) > 0 {
+ listU := false
+ listVersions := false
+ rmods := modload.ListModules(ctx, args, listU, listVersions, *listRetracted)
+ for i, arg := range args {
+ rmod := rmods[i]
+ for _, mod := range argToMods[arg] {
+ mod.Retracted = rmod.Retracted
+ if rmod.Error != nil && mod.Error == nil {
+ mod.Error = rmod.Error
+ }
+ }
+ }
+ }
+ }
+
// Record non-identity import mappings in p.ImportMap.
for _, p := range pkgs {
for i, srcPath := range p.Internal.RawImports {
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index d4c161f..41f294d 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -12,8 +12,8 @@
"cmd/go/internal/base"
"cmd/go/internal/cfg"
- "cmd/go/internal/modload"
"cmd/go/internal/modfetch"
+ "cmd/go/internal/modload"
"cmd/go/internal/work"
"golang.org/x/mod/module"
@@ -136,9 +136,10 @@
var mods []*moduleJSON
listU := false
listVersions := false
+ listRetractions := false
type token struct{}
sem := make(chan token, runtime.GOMAXPROCS(0))
- for _, info := range modload.ListModules(ctx, args, listU, listVersions) {
+ for _, info := range modload.ListModules(ctx, args, listU, listVersions, listRetractions) {
if info.Replace != nil {
info = info.Replace
}
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index da33fff..b16887d 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -69,12 +69,13 @@
if *whyM {
listU := false
listVersions := false
+ listRetractions := false
for _, arg := range args {
if strings.Contains(arg, "@") {
base.Fatalf("go mod why: module query not allowed")
}
}
- mods := modload.ListModules(ctx, args, listU, listVersions)
+ mods := modload.ListModules(ctx, args, listU, listVersions, listRetractions)
byModule := make(map[module.Version][]string)
for _, path := range loadALL(ctx) {
m := modload.PackageModule(path)
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index a29e085..e9f9a82 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -8,6 +8,7 @@
"bytes"
"context"
"encoding/hex"
+ "errors"
"fmt"
"internal/goroot"
"os"
@@ -58,7 +59,9 @@
if !ok {
return nil
}
- return moduleInfo(context.TODO(), m, true)
+ fromBuildList := true
+ listRetracted := false
+ return moduleInfo(context.TODO(), m, fromBuildList, listRetracted)
}
func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
@@ -66,13 +69,17 @@
return nil
}
+ listRetracted := false
if i := strings.Index(path, "@"); i >= 0 {
- return moduleInfo(ctx, module.Version{Path: path[:i], Version: path[i+1:]}, false)
+ m := module.Version{Path: path[:i], Version: path[i+1:]}
+ fromBuildList := false
+ return moduleInfo(ctx, m, fromBuildList, listRetracted)
}
for _, m := range BuildList() {
if m.Path == path {
- return moduleInfo(ctx, m, true)
+ fromBuildList := true
+ return moduleInfo(ctx, m, fromBuildList, listRetracted)
}
}
@@ -100,11 +107,37 @@
}
// addVersions fills in m.Versions with the list of known versions.
-func addVersions(ctx context.Context, m *modinfo.ModulePublic) {
- m.Versions, _ = versions(ctx, m.Path, CheckAllowed)
+// Excluded versions will be omitted. If listRetracted is false, retracted
+// versions will also be omitted.
+func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
+ allowed := CheckAllowed
+ if listRetracted {
+ allowed = CheckExclusions
+ }
+ m.Versions, _ = versions(ctx, m.Path, allowed)
}
-func moduleInfo(ctx context.Context, m module.Version, fromBuildList bool) *modinfo.ModulePublic {
+// addRetraction fills in m.Retracted if the module was retracted by its author.
+// m.Error is set if there's an error loading retraction information.
+func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
+ if m.Version == "" {
+ return
+ }
+
+ err := checkRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
+ var rerr *retractedError
+ if errors.As(err, &rerr) {
+ if len(rerr.rationale) == 0 {
+ m.Retracted = []string{"retracted by module author"}
+ } else {
+ m.Retracted = rerr.rationale
+ }
+ } else if err != nil && m.Error == nil {
+ m.Error = &modinfo.ModuleError{Err: err.Error()}
+ }
+}
+
+func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetracted bool) *modinfo.ModulePublic {
if m == Target {
info := &modinfo.ModulePublic{
Path: m.Path,
@@ -152,6 +185,10 @@
if err == nil {
m.Dir = dir
}
+
+ if listRetracted {
+ addRetraction(ctx, m)
+ }
}
if m.GoVersion == "" {
@@ -205,6 +242,7 @@
completeFromModCache(info.Replace)
info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod
+ info.Retracted = info.Replace.Retracted
}
info.GoVersion = info.Replace.GoVersion
return info
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index a3461ee..8c7b9a3 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -20,12 +20,12 @@
"golang.org/x/mod/module"
)
-func ListModules(ctx context.Context, args []string, listU, listVersions bool) []*modinfo.ModulePublic {
- mods := listModules(ctx, args, listVersions)
+func ListModules(ctx context.Context, args []string, listU, listVersions, listRetracted bool) []*modinfo.ModulePublic {
+ mods := listModules(ctx, args, listVersions, listRetracted)
type token struct{}
sem := make(chan token, runtime.GOMAXPROCS(0))
- if listU || listVersions {
+ if listU || listVersions || listRetracted {
for _, m := range mods {
add := func(m *modinfo.ModulePublic) {
sem <- token{}
@@ -34,7 +34,10 @@
addUpdate(ctx, m)
}
if listVersions {
- addVersions(ctx, m)
+ addVersions(ctx, m, listRetracted)
+ }
+ if listRetracted || listU {
+ addRetraction(ctx, m)
}
<-sem
}()
@@ -54,10 +57,10 @@
return mods
}
-func listModules(ctx context.Context, args []string, listVersions bool) []*modinfo.ModulePublic {
+func listModules(ctx context.Context, args []string, listVersions, listRetracted bool) []*modinfo.ModulePublic {
LoadBuildList(ctx)
if len(args) == 0 {
- return []*modinfo.ModulePublic{moduleInfo(ctx, buildList[0], true)}
+ return []*modinfo.ModulePublic{moduleInfo(ctx, buildList[0], true, listRetracted)}
}
var mods []*modinfo.ModulePublic
@@ -84,9 +87,9 @@
}
allowed := CheckAllowed
- if IsRevisionQuery(vers) {
+ if IsRevisionQuery(vers) || listRetracted {
// Allow excluded and retracted versions if the user asked for a
- // specific revision.
+ // specific revision or used 'go list -retracted'.
allowed = nil
}
info, err := Query(ctx, path, vers, current, allowed)
@@ -98,7 +101,8 @@
})
continue
}
- mods = append(mods, moduleInfo(ctx, module.Version{Path: path, Version: info.Version}, false))
+ mod := moduleInfo(ctx, module.Version{Path: path, Version: info.Version}, false, listRetracted)
+ mods = append(mods, mod)
continue
}
@@ -123,7 +127,7 @@
matched = true
if !matchedBuildList[i] {
matchedBuildList[i] = true
- mods = append(mods, moduleInfo(ctx, m, true))
+ mods = append(mods, moduleInfo(ctx, m, true, listRetracted))
}
}
}
@@ -135,7 +139,8 @@
// Instead, resolve the module, even if it isn't an existing dependency.
info, err := Query(ctx, arg, "latest", "", nil)
if err == nil {
- mods = append(mods, moduleInfo(ctx, module.Version{Path: arg, Version: info.Version}, false))
+ mod := moduleInfo(ctx, module.Version{Path: arg, Version: info.Version}, false, listRetracted)
+ mods = append(mods, mod)
} else {
mods = append(mods, &modinfo.ModulePublic{
Path: arg,
diff --git a/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt
new file mode 100644
index 0000000..2023c7b
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt
@@ -0,0 +1,8 @@
+This version should be retracted, but the go.mod file for the version that would
+contain the retraction is not available.
+-- .mod --
+module example.com/retract/missingmod
+
+go 1.14
+-- .info --
+{"Version":"v1.0.0"}
diff --git a/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.9.0.txt
new file mode 100644
index 0000000..bba919e
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.9.0.txt
@@ -0,0 +1,4 @@
+The go.mod file at this version will be loaded to check for retractions
+of earlier versions. However, the .mod file is not available.
+-- .info --
+{"Version":"v1.9.0"}
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_all_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_retract_self_all_v1.9.0.txt
new file mode 100644
index 0000000..4dc486b
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_all_v1.9.0.txt
@@ -0,0 +1,14 @@
+Module example.com/retract/self/prev is a module that retracts its own
+latest version.
+
+No unretracted versions are available.
+
+-- .mod --
+module example.com/retract/self/all
+
+go 1.15
+
+retract v1.9.0 // bad
+
+-- .info --
+{"Version":"v1.9.0"}
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.0.0.txt
new file mode 100644
index 0000000..04c2845
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.0.0.txt
@@ -0,0 +1,16 @@
+Module example.com/retract/self/prerelease is a module that retracts its own
+latest version and all other release version.
+
+A pre-release version higher than the highest release version is still
+available, and that should be matched by @latest.
+
+-- .mod --
+module example.com/retract/self/prerelease
+
+go 1.15
+
+-- .info --
+{"Version":"v1.0.0"}
+
+-- p.go --
+package p
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.0.txt
new file mode 100644
index 0000000..7c1c047
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.0.txt
@@ -0,0 +1,19 @@
+Module example.com/retract/self/prerelease is a module that retracts its own
+latest version and all other release version.
+
+A pre-release version higher than the highest release version is still
+available, and that should be matched by @latest.
+
+-- .mod --
+module example.com/retract/self/prerelease
+
+go 1.15
+
+retract v1.0.0 // bad
+retract v1.9.0 // self
+
+-- .info --
+{"Version":"v1.9.0"}
+
+-- p.go --
+package p
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.1-pre.txt b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.1-pre.txt
new file mode 100644
index 0000000..abf44fd
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.1-pre.txt
@@ -0,0 +1,16 @@
+Module example.com/retract/self/prerelease is a module that retracts its own
+latest version and all other release version.
+
+A pre-release version higher than the highest release version is still
+available, and that should be matched by @latest.
+
+-- .mod --
+module example.com/retract/self/prerelease
+
+go 1.15
+
+-- .info --
+{"Version":"v1.9.1-pre"}
+
+-- p.go --
+package p
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v0.0.0-20200325131415-0123456789ab b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v0.0.0-20200325131415-0123456789ab
new file mode 100644
index 0000000..f9ab41e
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v0.0.0-20200325131415-0123456789ab
@@ -0,0 +1,20 @@
+See example.com_retract_self_pseudo_v1.9.0.txt.
+
+This version is not retracted. It should be returned by the proxy's
+@latest endpoint. It should match the @latest version query.
+
+TODO(golang.org/issue/24031): the proxy and proxy.golang.org both return
+the highest release version from the @latest endpoint, even if that
+version is retracted, so there is no way for the go command to
+discover an unretracted pseudo-version.
+
+-- .mod --
+module example.com/retract/self/pseudo
+
+go 1.15
+
+-- .info --
+{"Version":"v0.0.0-20200325131415-01234567890ab"}
+
+-- p.go --
+package p
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.0.0-bad.txt b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.0.0-bad.txt
new file mode 100644
index 0000000..d47eda0
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.0.0-bad.txt
@@ -0,0 +1,14 @@
+See example.com_retract_self_pseudo_v1.9.0.txt.
+
+This version is retracted.
+
+-- .mod --
+module example.com/retract/self/pseudo
+
+go 1.15
+
+-- .info --
+{"Version":"v1.0.0-bad"}
+
+-- p.go --
+package p
diff --git a/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.9.0.txt b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.9.0.txt
new file mode 100644
index 0000000..db09cc6
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.9.0.txt
@@ -0,0 +1,16 @@
+Module example.com/retract/self/pseudo is a module that retracts its own
+latest version, as well as an earlier version.
+
+An unretracted pseudo-version is available.
+
+-- .mod --
+module example.com/retract/self/pseudo
+
+go 1.15
+
+retract v1.0.0-bad // bad
+retract v1.9.0 // self
+
+-- .info --
+{"Version":"v1.9.0"}
+
diff --git a/src/cmd/go/testdata/script/mod_list_pseudo.txt b/src/cmd/go/testdata/script/mod_list_pseudo.txt
index 3a10b3a..056c093 100644
--- a/src/cmd/go/testdata/script/mod_list_pseudo.txt
+++ b/src/cmd/go/testdata/script/mod_list_pseudo.txt
@@ -10,30 +10,25 @@
go mod download github.com/dmitshur-test/modtest5@v0.5.0-alpha.0.20190619023908-3da23a9deb9e
cmp $GOPATH/pkg/mod/cache/download/github.com/dmitshur-test/modtest5/@v/list $WORK/modtest5.list
+env GOSUMDB=off # don't verify go.mod files when loading retractions
env GOPROXY=file:///$GOPATH/pkg/mod/cache/download
env GOPATH=$WORK/gopath2
mkdir $GOPATH
-go list -m -json github.com/dmitshur-test/modtest5@latest
-cmp stdout $WORK/modtest5.json
+go list -m -f '{{.Path}} {{.Version}} {{.Time.Format "2006-01-02"}}' github.com/dmitshur-test/modtest5@latest
+stdout '^github.com/dmitshur-test/modtest5 v0.5.0-alpha 2019-06-18$'
# If the module proxy contains only pseudo-versions, 'latest' should stat
# the version with the most recent timestamp — not the highest semantic
# version — and return its metadata.
env GOPROXY=file:///$WORK/tinyproxy
-go list -m -json dmitri.shuralyov.com/test/modtest3@latest
-cmp stdout $WORK/modtest3.json
+go list -m -f '{{.Path}} {{.Version}} {{.Time.Format "2006-01-02"}}' dmitri.shuralyov.com/test/modtest3@latest
+stdout '^dmitri.shuralyov.com/test/modtest3 v0.0.0-20181023043359-a85b471d5412 2018-10-22$'
-- $WORK/modtest5.list --
v0.0.0-20190619020302-197a620e0c9a
v0.5.0-alpha
v0.5.0-alpha.0.20190619023908-3da23a9deb9e
--- $WORK/modtest5.json --
-{
- "Path": "github.com/dmitshur-test/modtest5",
- "Version": "v0.5.0-alpha",
- "Time": "2019-06-18T19:04:46-07:00"
-}
-- $WORK/tinyproxy/dmitri.shuralyov.com/test/modtest3/@v/list --
v0.1.0-0.20161023043300-000000000000
v0.0.0-20181023043359-a85b471d5412
@@ -42,9 +37,3 @@
"Version": "v0.0.0-20181023043359-a85b471d5412",
"Time": "2018-10-22T21:33:59-07:00"
}
--- $WORK/modtest3.json --
-{
- "Path": "dmitri.shuralyov.com/test/modtest3",
- "Version": "v0.0.0-20181023043359-a85b471d5412",
- "Time": "2018-10-22T21:33:59-07:00"
-}
diff --git a/src/cmd/go/testdata/script/mod_list_retract.txt b/src/cmd/go/testdata/script/mod_list_retract.txt
new file mode 100644
index 0000000..4e177b3
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_list_retract.txt
@@ -0,0 +1,108 @@
+# 'go list -mod=vendor -retracted' reports an error.
+go mod vendor
+! go list -m -retracted -mod=vendor
+stderr '^go list -retracted cannot be used when vendoring is enabled$'
+rm vendor
+
+# 'go list -retracted' reports an error in GOPATH mode.
+env GO111MODULE=off
+! go list -retracted
+stderr '^go list -retracted can only be used in module-aware mode$'
+env GO111MODULE=
+
+# 'go list pkg' does not show retraction.
+go list -f '{{with .Module}}{{with .Retracted}}retracted{{end}}{{end}}' example.com/retract
+! stdout .
+
+# 'go list -retracted pkg' shows retraction.
+go list -retracted -f '{{with .Module}}{{with .Retracted}}retracted{{end}}{{end}}' example.com/retract
+stdout retracted
+
+# 'go list -m' does not show retraction.
+go list -m -f '{{with .Retracted}}retracted{{end}}' example.com/retract
+! stdout .
+
+# 'go list -m -retracted' shows retraction.
+go list -m -retracted -f '{{with .Retracted}}retracted{{end}}' example.com/retract
+
+# 'go list -m mod@version' does not show retraction.
+go list -m -f '{{with .Retracted}}retracted{{end}}' example.com/retract@v1.0.0-unused
+! stdout .
+
+# 'go list -m -retracted mod@version' shows an error if the go.mod that should
+# contain the retractions is not available.
+! go list -m -retracted example.com/retract/missingmod@v1.0.0
+stderr '^go list -m: loading module retractions: example.com/retract/missingmod@v1.9.0:.*404 Not Found$'
+go list -e -m -retracted -f '{{.Error.Err}}' example.com/retract/missingmod@v1.0.0
+stdout '^loading module retractions: example.com/retract/missingmod@v1.9.0:.*404 Not Found$'
+
+# 'go list -m -retracted mod@version' shows retractions.
+go list -m -retracted example.com/retract@v1.0.0-unused
+stdout '^example.com/retract v1.0.0-unused \(retracted\)$'
+go list -m -retracted -f '{{with .Retracted}}retracted{{end}}' example.com/retract@v1.0.0-unused
+stdout retracted
+
+# 'go list -m mod@latest' selects a previous release version, not self-retracted latest.
+go list -m -f '{{.Version}}{{with .Retracted}} retracted{{end}}' example.com/retract/self/prev@latest
+stdout '^v1.1.0$'
+
+# 'go list -m -retracted mod@latest' selects the self-retracted latest version.
+go list -m -retracted -f '{{.Version}}{{with .Retracted}} retracted{{end}}' example.com/retract/self/prev@latest
+stdout '^v1.9.0 retracted$'
+
+# 'go list -m mod@latest' selects a pre-release version if all release versions are retracted.
+go list -m -f '{{.Version}}{{with .Retracted}} retracted{{end}}' example.com/retract/self/prerelease@latest
+stdout '^v1.9.1-pre$'
+
+# 'go list -m -retracted mod@latest' selects the self-retracted latest version.
+go list -m -retracted -f '{{.Version}}{{with .Retracted}} retracted{{end}}' example.com/retract/self/prerelease@latest
+stdout '^v1.9.0 retracted$'
+
+# 'go list -m mod@latest' selects a pseudo-version if all versions are retracted.
+# TODO(golang.org/issue/24031): the proxy does not expose the pseudo-version,
+# even if all release versions are retracted.
+go list -m -e -f '{{.Error.Err}}' example.com/retract/self/pseudo@latest
+stdout '^module example.com/retract/self/pseudo: no matching versions for query "latest"$'
+
+# 'go list -m mod@latest' reports an error if all versions are retracted.
+go list -m -e -f '{{.Error.Err}}' example.com/retract/self/all@latest
+stdout '^module example.com/retract/self/all: no matching versions for query "latest"$'
+
+# 'go list -m mod@<v1.10' selects a previous release version, not self-retracted latest.
+# The @latest query is not special with respect to retractions.
+go list -m -f '{{.Version}}{{with .Retracted}} retracted{{end}}' example.com/retract/self/prev@<v1.10
+stdout '^v1.1.0$'
+
+# 'go list -m -versions' hides retracted versions.
+go list -m -versions example.com/retract
+stdout '^example.com/retract v1.0.0-good v1.1.0$'
+
+# 'go list -m -retracted -versions' shows retracted versions.
+go list -m -retracted -versions example.com/retract
+stdout '^example.com/retract v1.0.0-bad v1.0.0-good v1.0.0-unused v1.1.0$'
+
+# 'go list -m -u -versions' loads retractions and does not show retracted versions.
+go list -m -u -versions example.com/retract
+stdout '^example.com/retract v1.0.0-good v1.1.0$'
+go list -m -u -versions -f '{{with .Retracted}}retracted{{end}}' example.com/retract
+stdout retracted
+
+# 'go list -m -u' shows retraction.
+go list -m -u -f '{{with .Retracted}}retracted{{end}}' example.com/retract
+stdout retracted
+
+# 'go list -m -u' does not suggest an update to a self-retracted latest version.
+go list -m -u -f '{{with .Update}}{{.Version}}{{with .Retracted}} retracted{{end}}{{end}}' example.com/retract/self/prev@v1.0.0-bad
+stdout '^v1.1.0$'
+
+-- go.mod --
+module example.com/use
+
+go 1.15
+
+require example.com/retract v1.0.0-bad
+
+-- use.go --
+package use
+
+import _ "example.com/retract"
diff --git a/src/cmd/go/testdata/script/mod_retract_replace.txt b/src/cmd/go/testdata/script/mod_retract_replace.txt
new file mode 100644
index 0000000..b710485
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_retract_replace.txt
@@ -0,0 +1,51 @@
+# If the latest unretracted version of a module is replaced, 'go list' should
+# obtain retractions from the replacement.
+
+# The latest version, v1.9.0, is not available on the proxy.
+! go list -m -retracted example.com/retract/missingmod
+stderr '^go list -m: loading module retractions: example.com/retract/missingmod@v1.9.0:.*404 Not Found$'
+
+# If we replace that version, we should see retractions.
+go mod edit -replace=example.com/retract/missingmod@v1.9.0=./missingmod-v1.9.0
+go list -m -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract/missingmod
+stdout '^bad version$'
+
+# If we replace the retracted version, we should not see a retraction.
+go mod edit -replace=example.com/retract/missingmod=./missingmod-v1.9.0
+go list -m -retracted -f '{{if not .Retracted}}good version{{end}}' example.com/retract/missingmod
+stdout '^good version$'
+
+
+# If a replacement version is retracted, we should see a retraction.
+# It should appear in both the replaced module and the replacement, as other
+# fields like GoMod do.
+go list -m -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract
+! stdout .
+go list -m -retracted -f '{{if .Replace}}replaced{{end}}' example.com/retract
+! stdout .
+go mod edit -replace example.com/retract@v1.0.0-good=example.com/retract@v1.0.0-bad
+go list -m -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract
+stdout '^bad$'
+go list -m -retracted -f '{{with .Replace}}{{range .Retracted}}{{.}}{{end}}{{end}}' example.com/retract
+stdout '^bad$'
+
+-- go.mod --
+module m
+
+go 1.14
+
+require (
+ example.com/retract v1.0.0-good
+ example.com/retract/missingmod v1.0.0
+)
+-- missingmod-v1.0.0/go.mod --
+module example.com/retract/missingmod
+
+go 1.14
+-- missingmod-v1.9.0/go.mod --
+module example.com/retract/missingmod
+
+go 1.14
+
+// bad version
+retract v1.0.0