github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modget/get.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package modget implements the module-aware ``go get'' command. 6 package modget 7 8 import ( 9 "cmd/go/internal/base" 10 "cmd/go/internal/cfg" 11 "cmd/go/internal/get" 12 "cmd/go/internal/load" 13 "cmd/go/internal/modfetch" 14 "cmd/go/internal/modload" 15 "cmd/go/internal/module" 16 "cmd/go/internal/mvs" 17 "cmd/go/internal/par" 18 "cmd/go/internal/search" 19 "cmd/go/internal/semver" 20 "cmd/go/internal/str" 21 "cmd/go/internal/work" 22 "fmt" 23 "os" 24 pathpkg "path" 25 "path/filepath" 26 "strings" 27 ) 28 29 var CmdGet = &base.Command{ 30 // Note: -d -m -u are listed explicitly because they are the most common get flags. 31 // Do not send CLs removing them because they're covered by [get flags]. 32 UsageLine: "go get [-d] [-m] [-u] [-v] [-insecure] [build flags] [packages]", 33 Short: "add dependencies to current module and install them", 34 Long: ` 35 Get resolves and adds dependencies to the current development module 36 and then builds and installs them. 37 38 The first step is to resolve which dependencies to add. 39 40 For each named package or package pattern, get must decide which version of 41 the corresponding module to use. By default, get chooses the latest tagged 42 release version, such as v0.4.5 or v1.2.3. If there are no tagged release 43 versions, get chooses the latest tagged prerelease version, such as 44 v0.0.1-pre1. If there are no tagged versions at all, get chooses the latest 45 known commit. 46 47 This default version selection can be overridden by adding an @version 48 suffix to the package argument, as in 'go get golang.org/x/text@v0.3.0'. 49 For modules stored in source control repositories, the version suffix can 50 also be a commit hash, branch identifier, or other syntax known to the 51 source control system, as in 'go get golang.org/x/text@master'. 52 The version suffix @latest explicitly requests the default behavior 53 described above. 54 55 If a module under consideration is already a dependency of the current 56 development module, then get will update the required version. 57 Specifying a version earlier than the current required version is valid and 58 downgrades the dependency. The version suffix @none indicates that the 59 dependency should be removed entirely. 60 61 Although get defaults to using the latest version of the module containing 62 a named package, it does not use the latest version of that module's 63 dependencies. Instead it prefers to use the specific dependency versions 64 requested by that module. For example, if the latest A requires module 65 B v1.2.3, while B v1.2.4 and v1.3.1 are also available, then 'go get A' 66 will use the latest A but then use B v1.2.3, as requested by A. (If there 67 are competing requirements for a particular module, then 'go get' resolves 68 those requirements by taking the maximum requested version.) 69 70 The -u flag instructs get to update dependencies to use newer minor or 71 patch releases when available. Continuing the previous example, 72 'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3). 73 74 The -u=patch flag (not -u patch) instructs get to update dependencies 75 to use newer patch releases when available. Continuing the previous example, 76 'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3). 77 78 In general, adding a new dependency may require upgrading 79 existing dependencies to keep a working build, and 'go get' does 80 this automatically. Similarly, downgrading one dependency may 81 require downgrading other dependenceis, and 'go get' does 82 this automatically as well. 83 84 The -m flag instructs get to stop here, after resolving, upgrading, 85 and downgrading modules and updating go.mod. When using -m, 86 each specified package path must be a module path as well, 87 not the import path of a package below the module root. 88 89 The -insecure flag permits fetching from repositories and resolving 90 custom domains using insecure schemes such as HTTP. Use with caution. 91 92 The second step is to download (if needed), build, and install 93 the named packages. 94 95 If an argument names a module but not a package (because there is no 96 Go source code in the module's root directory), then the install step 97 is skipped for that argument, instead of causing a build failure. 98 For example 'go get golang.org/x/perf' succeeds even though there 99 is no code corresponding to that import path. 100 101 Note that package patterns are allowed and are expanded after resolving 102 the module versions. For example, 'go get golang.org/x/perf/cmd/...' 103 adds the latest golang.org/x/perf and then installs the commands in that 104 latest version. 105 106 The -d flag instructs get to download the source code needed to build 107 the named packages, including downloading necessary dependencies, 108 but not to build and install them. 109 110 With no package arguments, 'go get' applies to the main module, 111 and to the Go package in the current directory, if any. In particular, 112 'go get -u' and 'go get -u=patch' update all the dependencies of the 113 main module. With no package arguments and also without -u, 114 'go get' is not much more than 'go install', and 'go get -d' not much 115 more than 'go list'. 116 117 For more about modules, see 'go help modules'. 118 119 For more about specifying packages, see 'go help packages'. 120 121 This text describes the behavior of get using modules to manage source 122 code and dependencies. If instead the go command is running in GOPATH 123 mode, the details of get's flags and effects change, as does 'go help get'. 124 See 'go help modules' and 'go help gopath-get'. 125 126 See also: go build, go install, go clean, go mod. 127 `, 128 } 129 130 // Note that this help text is a stopgap to make the module-aware get help text 131 // available even in non-module settings. It should be deleted when the old get 132 // is deleted. It should NOT be considered to set a precedent of having hierarchical 133 // help names with dashes. 134 var HelpModuleGet = &base.Command{ 135 UsageLine: "module-get", 136 Short: "module-aware go get", 137 Long: ` 138 The 'go get' command changes behavior depending on whether the 139 go command is running in module-aware mode or legacy GOPATH mode. 140 This help text, accessible as 'go help module-get' even in legacy GOPATH mode, 141 describes 'go get' as it operates in module-aware mode. 142 143 Usage: ` + CmdGet.UsageLine + ` 144 ` + CmdGet.Long, 145 } 146 147 var ( 148 getD = CmdGet.Flag.Bool("d", false, "") 149 getF = CmdGet.Flag.Bool("f", false, "") 150 getFix = CmdGet.Flag.Bool("fix", false, "") 151 getM = CmdGet.Flag.Bool("m", false, "") 152 getT = CmdGet.Flag.Bool("t", false, "") 153 getU upgradeFlag 154 // -insecure is get.Insecure 155 // -v is cfg.BuildV 156 ) 157 158 // upgradeFlag is a custom flag.Value for -u. 159 type upgradeFlag string 160 161 func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u 162 163 func (v *upgradeFlag) Set(s string) error { 164 if s == "false" { 165 s = "" 166 } 167 *v = upgradeFlag(s) 168 return nil 169 } 170 171 func (v *upgradeFlag) String() string { return "" } 172 173 func init() { 174 work.AddBuildFlags(CmdGet) 175 CmdGet.Run = runGet // break init loop 176 CmdGet.Flag.BoolVar(&get.Insecure, "insecure", get.Insecure, "") 177 CmdGet.Flag.Var(&getU, "u", "") 178 } 179 180 // A task holds the state for processing a single get argument (path@vers). 181 type task struct { 182 arg string // original argument 183 index int 184 path string // package path part of arg 185 forceModulePath bool // path must be interpreted as a module path 186 vers string // version part of arg 187 m module.Version // module version indicated by argument 188 req []module.Version // m's requirement list (not upgraded) 189 } 190 191 func runGet(cmd *base.Command, args []string) { 192 // -mod=readonly has no effect on "go get". 193 if cfg.BuildMod == "readonly" { 194 cfg.BuildMod = "" 195 } 196 197 switch getU { 198 case "", "patch", "true": 199 // ok 200 default: 201 base.Fatalf("go get: unknown upgrade flag -u=%s", getU) 202 } 203 if *getF { 204 fmt.Fprintf(os.Stderr, "go get: -f flag is a no-op when using modules\n") 205 } 206 if *getFix { 207 fmt.Fprintf(os.Stderr, "go get: -fix flag is a no-op when using modules\n") 208 } 209 if *getT { 210 fmt.Fprintf(os.Stderr, "go get: -t flag is a no-op when using modules\n") 211 } 212 213 if cfg.BuildMod == "vendor" { 214 base.Fatalf("go get: disabled by -mod=%s", cfg.BuildMod) 215 } 216 217 modload.LoadBuildList() 218 219 // Do not allow any updating of go.mod until we've applied 220 // all the requested changes and checked that the result matches 221 // what was requested. 222 modload.DisallowWriteGoMod() 223 224 // Build task and install lists. 225 // The command-line arguments are of the form path@version 226 // or simply path, with implicit @latest. path@none is "downgrade away". 227 // At the end of the loop, we've resolved the list of arguments into 228 // a list of tasks (a path@vers that needs further processing) 229 // and a list of install targets (for the "go install" at the end). 230 var tasks []*task 231 var install []string 232 for _, arg := range search.CleanPatterns(args) { 233 // Argument is module query path@vers, or else path with implicit @latest. 234 path := arg 235 vers := "" 236 if i := strings.Index(arg, "@"); i >= 0 { 237 path, vers = arg[:i], arg[i+1:] 238 } 239 if strings.Contains(vers, "@") || arg != path && vers == "" { 240 base.Errorf("go get %s: invalid module version syntax", arg) 241 continue 242 } 243 if vers != "none" { 244 install = append(install, path) 245 } 246 247 // Deciding which module to upgrade/downgrade for a particular argument is difficult. 248 // Patterns only make it more difficult. 249 // We impose restrictions to avoid needing to interlace pattern expansion, 250 // like in modload.ImportPaths. 251 // Specifically, these patterns are supported: 252 // 253 // - Relative paths like ../../foo or ../../foo... are restricted to matching directories 254 // in the current module and therefore map to the current module. 255 // It's possible that the pattern matches no packages, but we will still treat it 256 // as mapping to the current module. 257 // TODO: In followup, could just expand the full list and remove the discrepancy. 258 // - The pattern "all" has its usual package meaning and maps to the list of modules 259 // from which the matched packages are drawn. This is potentially a subset of the 260 // module pattern "all". If module A requires B requires C but A does not import 261 // the parts of B that import C, the packages matched by "all" are only from A and B, 262 // so only A and B end up on the tasks list. 263 // TODO: Even in -m mode? 264 // - The patterns "std" and "cmd" expand to packages in the standard library, 265 // which aren't upgradable, so we skip over those. 266 // In -m mode they expand to non-module-paths, so they are disallowed. 267 // - Import path patterns like foo/bar... are matched against the module list, 268 // assuming any package match would imply a module pattern match. 269 // TODO: What about -m mode? 270 // - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import). 271 // 272 if search.IsRelativePath(path) { 273 // Check that this relative pattern only matches directories in the current module, 274 // and then record the current module as the target. 275 dir := path 276 if i := strings.Index(path, "..."); i >= 0 { 277 dir, _ = pathpkg.Split(path[:i]) 278 } 279 abs, err := filepath.Abs(dir) 280 if err != nil { 281 base.Errorf("go get %s: %v", arg, err) 282 continue 283 } 284 if !str.HasFilePathPrefix(abs, modload.ModRoot) { 285 base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot) 286 continue 287 } 288 // TODO: Check if abs is inside a nested module. 289 tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""}) 290 continue 291 } 292 if path == "all" { 293 // TODO: If *getM, should this be the module pattern "all"? 294 295 // This is the package pattern "all" not the module pattern "all": 296 // enumerate all the modules actually needed by builds of the packages 297 // in the main module, not incidental modules that happen to be 298 // in the package graph (and therefore build list). 299 // Note that LoadALL may add new modules to the build list to 300 // satisfy new imports, but vers == "latest" implicitly anyway, 301 // so we'll assume that's OK. 302 seen := make(map[module.Version]bool) 303 pkgs := modload.LoadALL() 304 for _, pkg := range pkgs { 305 m := modload.PackageModule(pkg) 306 if m.Path != "" && !seen[m] { 307 seen[m] = true 308 tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true}) 309 } 310 } 311 continue 312 } 313 if search.IsMetaPackage(path) { 314 // Already handled "all", so this must be "std" or "cmd", 315 // which are entirely in the standard library. 316 if path != arg { 317 base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg) 318 } 319 if *getM { 320 base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg) 321 continue 322 } 323 continue 324 } 325 if strings.Contains(path, "...") { 326 // Apply to modules in build list matched by pattern (golang.org/x/...), if any. 327 match := search.MatchPattern(path) 328 matched := false 329 for _, m := range modload.BuildList() { 330 if match(m.Path) || str.HasPathPrefix(path, m.Path) { 331 tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true}) 332 matched = true 333 } 334 } 335 // If matched, we're done. 336 // Otherwise assume pattern is inside a single module 337 // (golang.org/x/text/unicode/...) and leave for usual lookup. 338 // Unless we're using -m. 339 if matched { 340 continue 341 } 342 if *getM { 343 base.Errorf("go get %s: pattern matches no modules in build list", arg) 344 continue 345 } 346 } 347 tasks = append(tasks, &task{arg: arg, path: path, vers: vers}) 348 } 349 base.ExitIfErrors() 350 351 // Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks). 352 // Resolve each one in parallel. 353 reqs := modload.Reqs() 354 var lookup par.Work 355 for _, t := range tasks { 356 lookup.Add(t) 357 } 358 lookup.Do(10, func(item interface{}) { 359 t := item.(*task) 360 if t.vers == "none" { 361 // Wait for downgrade step. 362 t.m = module.Version{Path: t.path, Version: "none"} 363 return 364 } 365 m, err := getQuery(t.path, t.vers, t.forceModulePath) 366 if err != nil { 367 base.Errorf("go get %v: %v", t.arg, err) 368 return 369 } 370 t.m = m 371 }) 372 base.ExitIfErrors() 373 374 // Now we know the specific version of each path@vers. 375 // The final build list will be the union of three build lists: 376 // 1. the original build list 377 // 2. the modules named on the command line (other than @none) 378 // 3. the upgraded requirements of those modules (if upgrading) 379 // Start building those lists. 380 // This loop collects (2). 381 // Also, because the list of paths might have named multiple packages in a single module 382 // (or even the same package multiple times), now that we know the module for each 383 // package, this loop deduplicates multiple references to a given module. 384 // (If a module is mentioned multiple times, the listed target version must be the same each time.) 385 var named []module.Version 386 byPath := make(map[string]*task) 387 for _, t := range tasks { 388 prev, ok := byPath[t.m.Path] 389 if prev != nil && prev.m != t.m { 390 base.Errorf("go get: conflicting versions for module %s: %s and %s", t.m.Path, prev.m.Version, t.m.Version) 391 byPath[t.m.Path] = nil // sentinel to stop errors 392 continue 393 } 394 if ok { 395 continue // already added 396 } 397 byPath[t.m.Path] = t 398 if t.m.Version != "none" { 399 named = append(named, t.m) 400 } 401 } 402 base.ExitIfErrors() 403 404 // If the modules named on the command line have any dependencies 405 // and we're supposed to upgrade dependencies, 406 // chase down the full list of upgraded dependencies. 407 // This turns required from a not-yet-upgraded (3) to the final (3). 408 // (See list above.) 409 var required []module.Version 410 if getU != "" { 411 upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{ 412 Reqs: modload.Reqs(), 413 targets: named, 414 patch: getU == "patch", 415 tasks: byPath, 416 }) 417 if err != nil { 418 base.Fatalf("go get: %v", err) 419 } 420 required = upgraded[1:] // slice off upgradeTarget 421 base.ExitIfErrors() 422 } 423 424 // Put together the final build list as described above (1) (2) (3). 425 // If we're not using -u, then len(required) == 0 and ReloadBuildList 426 // chases down the dependencies of all the named module versions 427 // in one operation. 428 var list []module.Version 429 list = append(list, modload.BuildList()...) 430 list = append(list, named...) 431 list = append(list, required...) 432 modload.SetBuildList(list) 433 modload.ReloadBuildList() // note: does not update go.mod 434 base.ExitIfErrors() 435 436 // Scan for and apply any needed downgrades. 437 var down []module.Version 438 for _, m := range modload.BuildList() { 439 t := byPath[m.Path] 440 if t != nil && semver.Compare(m.Version, t.m.Version) > 0 { 441 down = append(down, module.Version{Path: m.Path, Version: t.m.Version}) 442 } 443 } 444 if len(down) > 0 { 445 list, err := mvs.Downgrade(modload.Target, modload.Reqs(), down...) 446 if err != nil { 447 base.Fatalf("go get: %v", err) 448 } 449 modload.SetBuildList(list) 450 modload.ReloadBuildList() // note: does not update go.mod 451 } 452 base.ExitIfErrors() 453 454 // Scan for any upgrades lost by the downgrades. 455 lost := make(map[string]string) 456 for _, m := range modload.BuildList() { 457 t := byPath[m.Path] 458 if t != nil && semver.Compare(m.Version, t.m.Version) != 0 { 459 lost[m.Path] = m.Version 460 } 461 } 462 if len(lost) > 0 { 463 desc := func(m module.Version) string { 464 s := m.Path + "@" + m.Version 465 t := byPath[m.Path] 466 if t != nil && t.arg != s { 467 s += " from " + t.arg 468 } 469 return s 470 } 471 downByPath := make(map[string]module.Version) 472 for _, d := range down { 473 downByPath[d.Path] = d 474 } 475 var buf strings.Builder 476 fmt.Fprintf(&buf, "go get: inconsistent versions:") 477 for _, t := range tasks { 478 if lost[t.m.Path] == "" { 479 continue 480 } 481 // We lost t because its build list requires a newer version of something in down. 482 // Figure out exactly what. 483 // Repeatedly constructing the build list is inefficient 484 // if there are MANY command-line arguments, 485 // but at least all the necessary requirement lists are cached at this point. 486 list, err := mvs.BuildList(t.m, reqs) 487 if err != nil { 488 base.Fatalf("go get: %v", err) 489 } 490 491 fmt.Fprintf(&buf, "\n\t%s", desc(t.m)) 492 sep := " requires" 493 for _, m := range list { 494 if down, ok := downByPath[m.Path]; ok && semver.Compare(down.Version, m.Version) < 0 { 495 fmt.Fprintf(&buf, "%s %s@%s (not %s)", sep, m.Path, m.Version, desc(down)) 496 sep = "," 497 } 498 } 499 if sep != "," { 500 // We have no idea why this happened. 501 // At least report the problem. 502 fmt.Fprintf(&buf, " ended up at %v unexpectedly (please report at golang.org/issue/new)", lost[t.m.Path]) 503 } 504 } 505 base.Fatalf("%v", buf.String()) 506 } 507 508 // Everything succeeded. Update go.mod. 509 modload.AllowWriteGoMod() 510 modload.WriteGoMod() 511 512 // If -m was specified, we're done after the module work. No download, no build. 513 if *getM { 514 return 515 } 516 517 if len(install) > 0 { 518 // All requested versions were explicitly @none. 519 // Note that 'go get -u' without any arguments results in len(install) == 1: 520 // search.CleanImportPaths returns "." for empty args. 521 work.BuildInit() 522 pkgs := load.PackagesAndErrors(install) 523 var todo []*load.Package 524 for _, p := range pkgs { 525 // Ignore "no Go source files" errors for 'go get' operations on modules. 526 if p.Error != nil { 527 if len(args) == 0 && getU != "" && strings.HasPrefix(p.Error.Err, "no Go files") { 528 // Upgrading modules: skip the implicitly-requested package at the 529 // current directory, even if it is not tho module root. 530 continue 531 } 532 if strings.Contains(p.Error.Err, "cannot find module providing") && modload.ModuleInfo(p.ImportPath) != nil { 533 // Explicitly-requested module, but it doesn't contain a package at the 534 // module root. 535 continue 536 } 537 } 538 todo = append(todo, p) 539 } 540 541 // If -d was specified, we're done after the download: no build. 542 // (The load.PackagesAndErrors is what did the download 543 // of the named packages and their dependencies.) 544 if len(todo) > 0 && !*getD { 545 work.InstallPackages(install, todo) 546 } 547 } 548 } 549 550 // getQuery evaluates the given package path, version pair 551 // to determine the underlying module version being requested. 552 // If forceModulePath is set, getQuery must interpret path 553 // as a module path. 554 func getQuery(path, vers string, forceModulePath bool) (module.Version, error) { 555 if vers == "" { 556 vers = "latest" 557 } 558 559 // First choice is always to assume path is a module path. 560 // If that works out, we're done. 561 info, err := modload.Query(path, vers, modload.Allowed) 562 if err == nil { 563 return module.Version{Path: path, Version: info.Version}, nil 564 } 565 566 // Even if the query fails, if the path must be a real module, then report the query error. 567 if forceModulePath || *getM { 568 return module.Version{}, err 569 } 570 571 // Otherwise, try a package path. 572 m, _, err := modload.QueryPackage(path, vers, modload.Allowed) 573 return m, err 574 } 575 576 // An upgrader adapts an underlying mvs.Reqs to apply an 577 // upgrade policy to a list of targets and their dependencies. 578 // If patch=false, the upgrader implements "get -u". 579 // If patch=true, the upgrader implements "get -u=patch". 580 type upgrader struct { 581 mvs.Reqs 582 targets []module.Version 583 patch bool 584 tasks map[string]*task 585 } 586 587 // upgradeTarget is a fake "target" requiring all the modules to be upgraded. 588 var upgradeTarget = module.Version{Path: "upgrade target", Version: ""} 589 590 // Required returns the requirement list for m. 591 // Other than the upgradeTarget, we defer to u.Reqs. 592 func (u *upgrader) Required(m module.Version) ([]module.Version, error) { 593 if m == upgradeTarget { 594 return u.targets, nil 595 } 596 return u.Reqs.Required(m) 597 } 598 599 // Upgrade returns the desired upgrade for m. 600 // If m is a tagged version, then Upgrade returns the latest tagged version. 601 // If m is a pseudo-version, then Upgrade returns the latest tagged version 602 // when that version has a time-stamp newer than m. 603 // Otherwise Upgrade returns m (preserving the pseudo-version). 604 // This special case prevents accidental downgrades 605 // when already using a pseudo-version newer than the latest tagged version. 606 func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { 607 // Allow pkg@vers on the command line to override the upgrade choice v. 608 // If t's version is < v, then we're going to downgrade anyway, 609 // and it's cleaner to avoid moving back and forth and picking up 610 // extraneous other newer dependencies. 611 // If t's version is > v, then we're going to upgrade past v anyway, 612 // and again it's cleaner to avoid moving back and forth picking up 613 // extraneous other newer dependencies. 614 if t := u.tasks[m.Path]; t != nil { 615 return t.m, nil 616 } 617 618 // Note that query "latest" is not the same as 619 // using repo.Latest. 620 // The query only falls back to untagged versions 621 // if nothing is tagged. The Latest method 622 // only ever returns untagged versions, 623 // which is not what we want. 624 query := "latest" 625 if u.patch { 626 // For patch upgrade, query "v1.2". 627 query = semver.MajorMinor(m.Version) 628 } 629 info, err := modload.Query(m.Path, query, modload.Allowed) 630 if err != nil { 631 // Report error but return m, to let version selection continue. 632 // (Reporting the error will fail the command at the next base.ExitIfErrors.) 633 // Special case: if the error is "no matching versions" then don't 634 // even report the error. Because Query does not consider pseudo-versions, 635 // it may happen that we have a pseudo-version but during -u=patch 636 // the query v0.0 matches no versions (not even the one we're using). 637 if !strings.Contains(err.Error(), "no matching versions") { 638 base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err) 639 } 640 return m, nil 641 } 642 643 // If we're on a later prerelease, keep using it, 644 // even though normally an Upgrade will ignore prereleases. 645 if semver.Compare(info.Version, m.Version) < 0 { 646 return m, nil 647 } 648 649 // If we're on a pseudo-version chronologically after the latest tagged version, keep using it. 650 // This avoids some accidental downgrades. 651 if mTime, err := modfetch.PseudoVersionTime(m.Version); err == nil && info.Time.Before(mTime) { 652 return m, nil 653 } 654 655 return module.Version{Path: m.Path, Version: info.Version}, nil 656 }