]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add -retracted flag to 'go list'
authorJay Conrod <jayconrod@google.com>
Wed, 15 Apr 2020 18:42:15 +0000 (14:42 -0400)
committerJay Conrod <jayconrod@google.com>
Wed, 26 Aug 2020 21:17:01 +0000 (21:17 +0000)
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>
18 files changed:
src/cmd/go/alldocs.go
src/cmd/go/internal/list/list.go
src/cmd/go/internal/modcmd/download.go
src/cmd/go/internal/modcmd/why.go
src/cmd/go/internal/modload/build.go
src/cmd/go/internal/modload/list.go
src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.9.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_all_v1.9.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_prerelease_v1.9.1-pre.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v0.0.0-20200325131415-0123456789ab [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.0.0-bad.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_self_pseudo_v1.9.0.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_list_pseudo.txt
src/cmd/go/testdata/script/mod_list_retract.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_retract_replace.txt [new file with mode: 0644]

index 609ede49cd06638a109724a4217e02e4ad51dd57..98861c8a0d204ef7ea2f50966deb81ec765c486d 100644 (file)
 //         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
 //     }
 //
 // 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.)
 //
 // 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.
index e68c39f39220a95195aafa4c650c706a4ced2584..6d81c1cad1dc2e8f61ce5f6b7147797da7cab87a 100644 (file)
@@ -10,6 +10,7 @@ import (
        "bytes"
        "context"
        "encoding/json"
+       "fmt"
        "io"
        "os"
        "sort"
@@ -215,6 +216,7 @@ applied to a Go struct, but now a Module struct:
         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 replaced source code.)
 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 @@ to semantic versioning, earliest to latest. The flag also changes
 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 @@ func init() {
 }
 
 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 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
                }
        }
 
+       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 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
 
                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 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
                }
        }
 
+       // 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 {
index d4c161fca1c675f8d46cbb1c00f423a9f223be21..41f294d47552ce70e8387fd1349668d4cd2042f2 100644 (file)
@@ -12,8 +12,8 @@ import (
 
        "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 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
        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
                }
index da33fff89e1e06d37133bf4cae697ae3bf34e0c8..b16887d31866b5af2bfdc599207fb90167eaa362 100644 (file)
@@ -69,12 +69,13 @@ func runWhy(ctx context.Context, cmd *base.Command, args []string) {
        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)
index a29e085875232d79e3f71bba7ee26723964f7a75..e9f9a82fabb2efdc972c38e046373201f6f7b2b4 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        "context"
        "encoding/hex"
+       "errors"
        "fmt"
        "internal/goroot"
        "os"
@@ -58,7 +59,9 @@ func PackageModuleInfo(pkgpath string) *modinfo.ModulePublic {
        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 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
                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 @@ func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
 }
 
 // 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)
+}
+
+// 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 bool) *modinfo.ModulePublic {
+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 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList bool) *modi
                        if err == nil {
                                m.Dir = dir
                        }
+
+                       if listRetracted {
+                               addRetraction(ctx, m)
+                       }
                }
 
                if m.GoVersion == "" {
@@ -205,6 +242,7 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList bool) *modi
                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
index a3461eea26f3f89868f7ea532a138805719afd82..8c7b9a3950b59c62372af1d4da09c66a53d9642a 100644 (file)
@@ -20,12 +20,12 @@ import (
        "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 @@ func ListModules(ctx context.Context, args []string, listU, listVersions bool) [
                                                addUpdate(ctx, m)
                                        }
                                        if listVersions {
-                                               addVersions(ctx, m)
+                                               addVersions(ctx, m, listRetracted)
+                                       }
+                                       if listRetracted || listU {
+                                               addRetraction(ctx, m)
                                        }
                                        <-sem
                                }()
@@ -54,10 +57,10 @@ func ListModules(ctx context.Context, args []string, listU, listVersions bool) [
        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 @@ func listModules(ctx context.Context, args []string, listVersions bool) []*modin
                        }
 
                        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 @@ func listModules(ctx context.Context, args []string, listVersions bool) []*modin
                                })
                                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 @@ func listModules(ctx context.Context, args []string, listVersions bool) []*modin
                                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 @@ func listModules(ctx context.Context, args []string, listVersions bool) []*modin
                                        // 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 (file)
index 0000000..2023c7b
--- /dev/null
@@ -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 (file)
index 0000000..bba919e
--- /dev/null
@@ -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 (file)
index 0000000..4dc486b
--- /dev/null
@@ -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 (file)
index 0000000..04c2845
--- /dev/null
@@ -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 (file)
index 0000000..7c1c047
--- /dev/null
@@ -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 (file)
index 0000000..abf44fd
--- /dev/null
@@ -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 (file)
index 0000000..f9ab41e
--- /dev/null
@@ -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 (file)
index 0000000..d47eda0
--- /dev/null
@@ -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 (file)
index 0000000..db09cc6
--- /dev/null
@@ -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"}
+
index 3a10b3a0407ec60d76243adf84f19071483da660..056c0931285685eccf00e874bff370084e81ea34 100644 (file)
@@ -10,30 +10,25 @@ go mod download github.com/dmitshur-test/modtest5@v0.5.0-alpha
 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 @@ v0.0.0-20181023043359-a85b471d5412
        "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 (file)
index 0000000..4e177b3
--- /dev/null
@@ -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 (file)
index 0000000..b710485
--- /dev/null
@@ -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