// CheckRetractions returns an error if module m has been retracted by
// its author.
-func CheckRetractions(ctx context.Context, m module.Version) error {
+func CheckRetractions(ctx context.Context, m module.Version) (err error) {
+ defer func() {
+ if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) {
+ return
+ }
+ // Attribute the error to the version being checked, not the version from
+ // which the retractions were to be loaded.
+ if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) {
+ err = mErr.Err
+ }
+ err = &retractionLoadingError{m: m, err: err}
+ }()
+
if m.Version == "" {
// Main module, standard library, or file replacement module.
// Cannot be retracted.
return nil
}
-
- // Look up retraction information from the latest available version of
- // the module. Cache retraction information so we don't parse the go.mod
- // file repeatedly.
- type entry struct {
- retract []retraction
- err error
+ if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ // All versions of the module were replaced.
+ // Don't load retractions, since we'd just load the replacement.
+ return nil
}
- path := m.Path
- e := retractCache.Do(path, func() (v interface{}) {
- ctx, span := trace.StartSpan(ctx, "checkRetractions "+path)
- defer span.Done()
-
- if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
- // All versions of the module were replaced with a local directory.
- // Don't load retractions.
- return &entry{nil, nil}
- }
- // Find the latest version of the module.
- // Ignore exclusions from the main module's go.mod.
- const ignoreSelected = ""
- var allowAll AllowedFunc
- rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
- if err != nil {
- return &entry{nil, err}
- }
-
- // Load go.mod for that version.
- // If the version is replaced, we'll load retractions from the replacement.
- //
- // If there's an error loading the go.mod, we'll return it here.
- // These errors should generally be ignored by callers of checkRetractions,
- // since they happen frequently when we're offline. These errors are not
- // equivalent to ErrDisallowed, so they may be distinguished from
- // retraction errors.
- //
- // We load the raw file here: the go.mod file may have a different module
- // path that we expect if the module or its repository was renamed.
- // We still want to apply retractions to other aliases of the module.
- rm := resolveReplacement(module.Version{Path: path, Version: rev.Version})
- summary, err := rawGoModSummary(rm)
- if err != nil {
- return &entry{nil, err}
- }
- return &entry{summary.retract, nil}
- }).(*entry)
-
- if err := e.err; err != nil {
- // Attribute the error to the version being checked, not the version from
- // which the retractions were to be loaded.
- var mErr *module.ModuleError
- if errors.As(err, &mErr) {
- err = mErr.Err
- }
- return &retractionLoadingError{m: m, err: err}
+ // Find the latest available version of the module, and load its go.mod. If
+ // the latest version is replaced, we'll load the replacement.
+ //
+ // If there's an error loading the go.mod, we'll return it here. These errors
+ // should generally be ignored by callers since they happen frequently when
+ // we're offline. These errors are not equivalent to ErrDisallowed, so they
+ // may be distinguished from retraction errors.
+ //
+ // We load the raw file here: the go.mod file may have a different module
+ // path that we expect if the module or its repository was renamed.
+ // We still want to apply retractions to other aliases of the module.
+ rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ if err != nil {
+ return err
+ }
+ summary, err := rawGoModSummary(rm)
+ if err != nil {
+ return err
}
var rationale []string
isRetracted := false
- for _, r := range e.retract {
+ for _, r := range summary.retract {
if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 {
isRetracted = true
if r.Rationale != "" {
return nil
}
-var retractCache par.Cache
-
type ModuleRetractedError struct {
Rationale []string
}
if len(e.Rationale) > 0 {
// This is meant to be a short error printed on a terminal, so just
// print the first rationale.
- msg += ": " + ShortRetractionRationale(e.Rationale[0])
+ msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
}
return msg
}
return e.err
}
-// ShortRetractionRationale returns a retraction rationale string that is safe
-// to print in a terminal. It returns hard-coded strings if the rationale
-// is empty, too long, or contains non-printable characters.
-func ShortRetractionRationale(rationale string) string {
- const maxRationaleBytes = 500
- if i := strings.Index(rationale, "\n"); i >= 0 {
- rationale = rationale[:i]
- }
- rationale = strings.TrimSpace(rationale)
- if rationale == "" {
- return "retracted by module author"
- }
- if len(rationale) > maxRationaleBytes {
- return "(rationale omitted: too long)"
- }
- for _, r := range rationale {
+// ShortMessage returns a string from go.mod (for example, a retraction
+// rationale or deprecation message) that is safe to print in a terminal.
+//
+// If the given string is empty, ShortMessage returns the given default. If the
+// given string is too long or contains non-printable characters, ShortMessage
+// returns a hard-coded string.
+func ShortMessage(message, emptyDefault string) string {
+ const maxLen = 500
+ if i := strings.Index(message, "\n"); i >= 0 {
+ message = message[:i]
+ }
+ message = strings.TrimSpace(message)
+ if message == "" {
+ return emptyDefault
+ }
+ if len(message) > maxLen {
+ return "(message omitted: too long)"
+ }
+ for _, r := range message {
if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
- return "(rationale omitted: contains non-printable characters)"
+ return "(message omitted: contains non-printable characters)"
}
}
// NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here.
- return rationale
+ return message
}
// Replacement returns the replacement for mod, if any, from go.mod.
}
var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
+
+// queryLatestVersionIgnoringRetractions looks up the latest version of the
+// module with the given path without considering retracted or excluded
+// versions.
+//
+// If all versions of the module are replaced,
+// queryLatestVersionIgnoringRetractions returns the replacement without making
+// a query.
+//
+// If the queried latest version is replaced,
+// queryLatestVersionIgnoringRetractions returns the replacement.
+func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
+ type entry struct {
+ latest module.Version
+ err error
+ }
+ e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
+ ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
+ defer span.Done()
+
+ if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
+ // All versions of the module were replaced.
+ // No need to query.
+ return &entry{latest: repl}
+ }
+
+ // Find the latest version of the module.
+ // Ignore exclusions from the main module's go.mod.
+ const ignoreSelected = ""
+ var allowAll AllowedFunc
+ rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
+ if err != nil {
+ return &entry{err: err}
+ }
+ latest := module.Version{Path: path, Version: rev.Version}
+ if repl := resolveReplacement(latest); repl.Path != "" {
+ latest = repl
+ }
+ return &entry{latest: latest}
+ }).(*entry)
+ return e.latest, e.err
+}
+
+var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result