For #56886.
For #38714.
Change-Id: I15c4a8673407d3423d7e203d645c6d0fb780d192
Reviewed-on: https://go-review.googlesource.com/c/go/+/452456
Reviewed-by: Michael Matloob <matloob@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
import (
"bytes"
+ "context"
"fmt"
"go/build"
"internal/buildcfg"
GoPathError = fmt.Sprintf("%s is not set", env)
return ""
}
+
+// WithBuildXWriter returns a Context in which BuildX output is written
+// to given io.Writer.
+func WithBuildXWriter(ctx context.Context, xLog io.Writer) context.Context {
+ return context.WithValue(ctx, buildXContextKey{}, xLog)
+}
+
+type buildXContextKey struct{}
+
+// BuildXWriter returns nil if BuildX is false, or
+// the writer to which BuildX output should be written otherwise.
+func BuildXWriter(ctx context.Context) (io.Writer, bool) {
+ if !BuildX {
+ return nil, false
+ }
+ if v := ctx.Value(buildXContextKey{}); v != nil {
+ return v.(io.Writer), true
+ }
+ return os.Stderr, true
+}
// Consider starting this as a background goroutine and retrieving the result
// asynchronously when we're actually ready to build the package, or when we
// actually need to evaluate whether the package's metadata is stale.
- p.setBuildInfo(opts.AutoVCS)
+ p.setBuildInfo(ctx, opts.AutoVCS)
}
// If cgo is not enabled, ignore cgo supporting sources
//
// Note that the GoVersion field is not set here to avoid encoding it twice.
// It is stored separately in the binary, mostly for historical reasons.
-func (p *Package) setBuildInfo(autoVCS bool) {
+func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) {
setPkgErrorf := func(format string, args ...any) {
if p.Error == nil {
p.Error = &PackageError{Err: fmt.Errorf(format, args...)}
if mi.Replace != nil {
dm.Replace = debugModFromModinfo(mi.Replace)
} else if mi.Version != "" {
- dm.Sum = modfetch.Sum(module.Version{Path: mi.Path, Version: mi.Version})
+ dm.Sum = modfetch.Sum(ctx, module.Version{Path: mi.Path, Version: mi.Version})
}
return dm
}
return nil, fmt.Errorf("%s: %w", args[0], err)
}
rootMod := qrs[0].Mod
- data, err := modfetch.GoMod(rootMod.Path, rootMod.Version)
+ data, err := modfetch.GoMod(ctx, rootMod.Path, rootMod.Version)
if err != nil {
return nil, fmt.Errorf("%s: %w", args[0], err)
}
// leaving the results (including any error) in m itself.
func DownloadModule(ctx context.Context, m *ModuleJSON) {
var err error
- _, file, err := modfetch.InfoFile(m.Path, m.Version)
+ _, file, err := modfetch.InfoFile(ctx, m.Path, m.Version)
if err != nil {
m.Error = err.Error()
return
}
m.Info = file
- m.GoMod, err = modfetch.GoModFile(m.Path, m.Version)
+ m.GoMod, err = modfetch.GoModFile(ctx, m.Path, m.Version)
if err != nil {
m.Error = err.Error()
return
}
- m.GoModSum, err = modfetch.GoModSum(m.Path, m.Version)
+ m.GoModSum, err = modfetch.GoModSum(ctx, m.Path, m.Version)
if err != nil {
m.Error = err.Error()
return
m.Error = err.Error()
return
}
- m.Sum = modfetch.Sum(mod)
+ m.Sum = modfetch.Sum(ctx, mod)
m.Dir, err = modfetch.Download(ctx, mod)
if err != nil {
m.Error = err.Error()
// Make a best-effort attempt to acquire the side lock, only to exclude
// previous versions of the 'go' command from making simultaneous edits.
- if unlock, err := modfetch.SideLock(); err == nil {
+ if unlock, err := modfetch.SideLock(ctx); err == nil {
defer unlock()
}
errsChans[i] = errsc
mod := mod // use a copy to avoid data races
go func() {
- errsc <- verifyMod(mod)
+ errsc <- verifyMod(ctx, mod)
<-sem
}()
}
}
}
-func verifyMod(mod module.Version) []error {
+func verifyMod(ctx context.Context, mod module.Version) []error {
var errs []error
- zip, zipErr := modfetch.CachePath(mod, "zip")
+ zip, zipErr := modfetch.CachePath(ctx, mod, "zip")
if zipErr == nil {
_, zipErr = os.Stat(zip)
}
- dir, dirErr := modfetch.DownloadDir(mod)
+ dir, dirErr := modfetch.DownloadDir(ctx, mod)
data, err := os.ReadFile(zip + "hash")
if err != nil {
if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) &&
import (
"bytes"
+ "context"
"encoding/json"
"errors"
"fmt"
"golang.org/x/mod/semver"
)
-func cacheDir(path string) (string, error) {
- if err := checkCacheDir(); err != nil {
+func cacheDir(ctx context.Context, path string) (string, error) {
+ if err := checkCacheDir(ctx); err != nil {
return "", err
}
enc, err := module.EscapePath(path)
return filepath.Join(cfg.GOMODCACHE, "cache/download", enc, "/@v"), nil
}
-func CachePath(m module.Version, suffix string) (string, error) {
- dir, err := cacheDir(m.Path)
+func CachePath(ctx context.Context, m module.Version, suffix string) (string, error) {
+ dir, err := cacheDir(ctx, m.Path)
if err != nil {
return "", err
}
// An error satisfying errors.Is(err, fs.ErrNotExist) will be returned
// along with the directory if the directory does not exist or if the directory
// is not completely populated.
-func DownloadDir(m module.Version) (string, error) {
- if err := checkCacheDir(); err != nil {
+func DownloadDir(ctx context.Context, m module.Version) (string, error) {
+ if err := checkCacheDir(ctx); err != nil {
return "", err
}
enc, err := module.EscapePath(m.Path)
// Check if a .partial file exists. This is created at the beginning of
// a download and removed after the zip is extracted.
- partialPath, err := CachePath(m, "partial")
+ partialPath, err := CachePath(ctx, m, "partial")
if err != nil {
return dir, err
}
// to re-calculate it. Note that checkMod will repopulate the ziphash
// file if it doesn't exist, but if the module is excluded by checks
// through GONOSUMDB or GOPRIVATE, that check and repopulation won't happen.
- ziphashPath, err := CachePath(m, "ziphash")
+ ziphashPath, err := CachePath(ctx, m, "ziphash")
if err != nil {
return dir, err
}
// lockVersion locks a file within the module cache that guards the downloading
// and extraction of the zipfile for the given module version.
-func lockVersion(mod module.Version) (unlock func(), err error) {
- path, err := CachePath(mod, "lock")
+func lockVersion(ctx context.Context, mod module.Version) (unlock func(), err error) {
+ path, err := CachePath(ctx, mod, "lock")
if err != nil {
return nil, err
}
// edits to files outside the cache, such as go.sum and go.mod files in the
// user's working directory.
// If err is nil, the caller MUST eventually call the unlock function.
-func SideLock() (unlock func(), err error) {
- if err := checkCacheDir(); err != nil {
+func SideLock(ctx context.Context) (unlock func(), err error) {
+ if err := checkCacheDir(ctx); err != nil {
return nil, err
}
gomodCache par.ErrCache[string, []byte]
once sync.Once
- initRepo func() (Repo, error)
+ initRepo func(context.Context) (Repo, error)
r Repo
}
-func newCachingRepo(path string, initRepo func() (Repo, error)) *cachingRepo {
+func newCachingRepo(ctx context.Context, path string, initRepo func(context.Context) (Repo, error)) *cachingRepo {
return &cachingRepo{
path: path,
initRepo: initRepo,
}
}
-func (r *cachingRepo) repo() Repo {
+func (r *cachingRepo) repo(ctx context.Context) Repo {
r.once.Do(func() {
var err error
- r.r, err = r.initRepo()
+ r.r, err = r.initRepo(ctx)
if err != nil {
r.r = errRepo{r.path, err}
}
return r.r
}
-func (r *cachingRepo) CheckReuse(old *codehost.Origin) error {
- return r.repo().CheckReuse(old)
+func (r *cachingRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
+ return r.repo(ctx).CheckReuse(ctx, old)
}
func (r *cachingRepo) ModulePath() string {
return r.path
}
-func (r *cachingRepo) Versions(prefix string) (*Versions, error) {
+func (r *cachingRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
v, err := r.versionsCache.Do(prefix, func() (*Versions, error) {
- return r.repo().Versions(prefix)
+ return r.repo(ctx).Versions(ctx, prefix)
})
if err != nil {
err error
}
-func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
+func (r *cachingRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
info, err := r.statCache.Do(rev, func() (*RevInfo, error) {
- file, info, err := readDiskStat(r.path, rev)
+ file, info, err := readDiskStat(ctx, r.path, rev)
if err == nil {
return info, err
}
- info, err = r.repo().Stat(rev)
+ info, err = r.repo(ctx).Stat(ctx, rev)
if err == nil {
// If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
// then save the information under the proper version, for future use.
if info.Version != rev {
- file, _ = CachePath(module.Version{Path: r.path, Version: info.Version}, "info")
+ file, _ = CachePath(ctx, module.Version{Path: r.path, Version: info.Version}, "info")
r.statCache.Do(info.Version, func() (*RevInfo, error) {
return info, nil
})
}
- if err := writeDiskStat(file, info); err != nil {
+ if err := writeDiskStat(ctx, file, info); err != nil {
fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
}
}
return info, err
}
-func (r *cachingRepo) Latest() (*RevInfo, error) {
+func (r *cachingRepo) Latest(ctx context.Context) (*RevInfo, error) {
info, err := r.latestCache.Do(struct{}{}, func() (*RevInfo, error) {
- info, err := r.repo().Latest()
+ info, err := r.repo(ctx).Latest(ctx)
// Save info for likely future Stat call.
if err == nil {
r.statCache.Do(info.Version, func() (*RevInfo, error) {
return info, nil
})
- if file, _, err := readDiskStat(r.path, info.Version); err != nil {
- writeDiskStat(file, info)
+ if file, _, err := readDiskStat(ctx, r.path, info.Version); err != nil {
+ writeDiskStat(ctx, file, info)
}
}
return info, err
}
-func (r *cachingRepo) GoMod(version string) ([]byte, error) {
+func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) {
text, err := r.gomodCache.Do(version, func() ([]byte, error) {
- file, text, err := readDiskGoMod(r.path, version)
+ file, text, err := readDiskGoMod(ctx, r.path, version)
if err == nil {
// Note: readDiskGoMod already called checkGoMod.
return text, nil
}
- text, err = r.repo().GoMod(version)
+ text, err = r.repo(ctx).GoMod(ctx, version)
if err == nil {
if err := checkGoMod(r.path, version, text); err != nil {
return text, err
}
- if err := writeDiskGoMod(file, text); err != nil {
+ if err := writeDiskGoMod(ctx, file, text); err != nil {
fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
}
}
return append([]byte(nil), text...), nil
}
-func (r *cachingRepo) Zip(dst io.Writer, version string) error {
- return r.repo().Zip(dst, version)
+func (r *cachingRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
+ return r.repo(ctx).Zip(ctx, dst, version)
}
-// InfoFile is like Lookup(path).Stat(version) but also returns the name of the file
+// InfoFile is like Lookup(ctx, path).Stat(version) but also returns the name of the file
// containing the cached information.
-func InfoFile(path, version string) (*RevInfo, string, error) {
+func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) {
if !semver.IsValid(version) {
return nil, "", fmt.Errorf("invalid version %q", version)
}
- if file, info, err := readDiskStat(path, version); err == nil {
+ if file, info, err := readDiskStat(ctx, path, version); err == nil {
return info, file, nil
}
var info *RevInfo
var err2info map[error]*RevInfo
err := TryProxies(func(proxy string) error {
- i, err := Lookup(proxy, path).Stat(version)
+ i, err := Lookup(ctx, proxy, path).Stat(ctx, version)
if err == nil {
info = i
} else {
}
// Stat should have populated the disk cache for us.
- file, err := CachePath(module.Version{Path: path, Version: version}, "info")
+ file, err := CachePath(ctx, module.Version{Path: path, Version: version}, "info")
if err != nil {
return nil, "", err
}
return info, file, nil
}
-// GoMod is like Lookup(path).GoMod(rev) but avoids the
+// GoMod is like Lookup(ctx, path).GoMod(rev) but avoids the
// repository path resolution in Lookup if the result is
// already cached on local disk.
-func GoMod(path, rev string) ([]byte, error) {
+func GoMod(ctx context.Context, path, rev string) ([]byte, error) {
// Convert commit hash to pseudo-version
// to increase cache hit rate.
if !semver.IsValid(rev) {
- if _, info, err := readDiskStat(path, rev); err == nil {
+ if _, info, err := readDiskStat(ctx, path, rev); err == nil {
rev = info.Version
} else {
if errors.Is(err, statCacheErr) {
return nil, err
}
err := TryProxies(func(proxy string) error {
- info, err := Lookup(proxy, path).Stat(rev)
+ info, err := Lookup(ctx, proxy, path).Stat(ctx, rev)
if err == nil {
rev = info.Version
}
}
}
- _, data, err := readDiskGoMod(path, rev)
+ _, data, err := readDiskGoMod(ctx, path, rev)
if err == nil {
return data, nil
}
err = TryProxies(func(proxy string) (err error) {
- data, err = Lookup(proxy, path).GoMod(rev)
+ data, err = Lookup(ctx, proxy, path).GoMod(ctx, rev)
return err
})
return data, err
// GoModFile is like GoMod but returns the name of the file containing
// the cached information.
-func GoModFile(path, version string) (string, error) {
+func GoModFile(ctx context.Context, path, version string) (string, error) {
if !semver.IsValid(version) {
return "", fmt.Errorf("invalid version %q", version)
}
- if _, err := GoMod(path, version); err != nil {
+ if _, err := GoMod(ctx, path, version); err != nil {
return "", err
}
// GoMod should have populated the disk cache for us.
- file, err := CachePath(module.Version{Path: path, Version: version}, "mod")
+ file, err := CachePath(ctx, module.Version{Path: path, Version: version}, "mod")
if err != nil {
return "", err
}
// GoModSum returns the go.sum entry for the module version's go.mod file.
// (That is, it returns the entry listed in go.sum as "path version/go.mod".)
-func GoModSum(path, version string) (string, error) {
+func GoModSum(ctx context.Context, path, version string) (string, error) {
if !semver.IsValid(version) {
return "", fmt.Errorf("invalid version %q", version)
}
- data, err := GoMod(path, version)
+ data, err := GoMod(ctx, path, version)
if err != nil {
return "", err
}
// returning the name of the cache file and the result.
// If the read fails, the caller can use
// writeDiskStat(file, info) to write a new cache entry.
-func readDiskStat(path, rev string) (file string, info *RevInfo, err error) {
- file, data, err := readDiskCache(path, rev, "info")
+func readDiskStat(ctx context.Context, path, rev string) (file string, info *RevInfo, err error) {
+ file, data, err := readDiskCache(ctx, path, rev, "info")
if err != nil {
// If the cache already contains a pseudo-version with the given hash, we
// would previously return that pseudo-version without checking upstream.
// Fall back to this resolution scheme only if the GOPROXY setting prohibits
// us from resolving upstream tags.
if cfg.GOPROXY == "off" {
- if file, info, err := readDiskStatByHash(path, rev); err == nil {
+ if file, info, err := readDiskStatByHash(ctx, path, rev); err == nil {
return file, info, nil
}
}
// Remarshal and update the cache file if needed.
data2, err := json.Marshal(info)
if err == nil && !bytes.Equal(data2, data) {
- writeDiskCache(file, data)
+ writeDiskCache(ctx, file, data)
}
return file, info, nil
}
// Without this check we'd be doing network I/O to the remote repo
// just to find out about a commit we already know about
// (and have cached under its pseudo-version).
-func readDiskStatByHash(path, rev string) (file string, info *RevInfo, err error) {
+func readDiskStatByHash(ctx context.Context, path, rev string) (file string, info *RevInfo, err error) {
if cfg.GOMODCACHE == "" {
// Do not download to current directory.
return "", nil, errNotCached
return "", nil, errNotCached
}
rev = rev[:12]
- cdir, err := cacheDir(path)
+ cdir, err := cacheDir(ctx, path)
if err != nil {
return "", nil, errNotCached
}
v := strings.TrimSuffix(name, ".info")
if module.IsPseudoVersion(v) && semver.Compare(v, maxVersion) > 0 {
maxVersion = v
- file, info, err = readDiskStat(path, strings.TrimSuffix(name, ".info"))
+ file, info, err = readDiskStat(ctx, path, strings.TrimSuffix(name, ".info"))
}
}
}
// returning the name of the cache file and the result.
// If the read fails, the caller can use
// writeDiskGoMod(file, data) to write a new cache entry.
-func readDiskGoMod(path, rev string) (file string, data []byte, err error) {
- file, data, err = readDiskCache(path, rev, "mod")
+func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) {
+ file, data, err = readDiskCache(ctx, path, rev, "mod")
// If the file has an old auto-conversion prefix, pretend it's not there.
if bytes.HasPrefix(data, oldVgoPrefix) {
// It returns the name of the cache file and the content of the file.
// If the read fails, the caller can use
// writeDiskCache(file, data) to write a new cache entry.
-func readDiskCache(path, rev, suffix string) (file string, data []byte, err error) {
- file, err = CachePath(module.Version{Path: path, Version: rev}, suffix)
+func readDiskCache(ctx context.Context, path, rev, suffix string) (file string, data []byte, err error) {
+ file, err = CachePath(ctx, module.Version{Path: path, Version: rev}, suffix)
if err != nil {
return "", nil, errNotCached
}
// writeDiskStat writes a stat result cache entry.
// The file name must have been returned by a previous call to readDiskStat.
-func writeDiskStat(file string, info *RevInfo) error {
+func writeDiskStat(ctx context.Context, file string, info *RevInfo) error {
if file == "" {
return nil
}
if err != nil {
return err
}
- return writeDiskCache(file, js)
+ return writeDiskCache(ctx, file, js)
}
// writeDiskGoMod writes a go.mod cache entry.
// The file name must have been returned by a previous call to readDiskGoMod.
-func writeDiskGoMod(file string, text []byte) error {
- return writeDiskCache(file, text)
+func writeDiskGoMod(ctx context.Context, file string, text []byte) error {
+ return writeDiskCache(ctx, file, text)
}
// writeDiskCache is the generic "write to a cache file" implementation.
// The file must have been returned by a previous call to readDiskCache.
-func writeDiskCache(file string, data []byte) error {
+func writeDiskCache(ctx context.Context, file string, data []byte) error {
if file == "" {
return nil
}
// Write the file to a temporary location, and then rename it to its final
// path to reduce the likelihood of a corrupt file existing at that final path.
- f, err := tempFile(filepath.Dir(file), filepath.Base(file), 0666)
+ f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0666)
if err != nil {
return err
}
}
if strings.HasSuffix(file, ".mod") {
- rewriteVersionList(filepath.Dir(file))
+ rewriteVersionList(ctx, filepath.Dir(file))
}
return nil
}
// tempFile creates a new temporary file with given permission bits.
-func tempFile(dir, prefix string, perm fs.FileMode) (f *os.File, err error) {
+func tempFile(ctx context.Context, dir, prefix string, perm fs.FileMode) (f *os.File, err error) {
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+".tmp")
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
if os.IsExist(err) {
+ if ctx.Err() != nil {
+ return nil, ctx.Err()
+ }
continue
}
break
// rewriteVersionList rewrites the version list in dir
// after a new *.mod file has been written.
-func rewriteVersionList(dir string) (err error) {
+func rewriteVersionList(ctx context.Context, dir string) (err error) {
if filepath.Base(dir) != "@v" {
base.Fatalf("go: internal error: misuse of rewriteVersionList")
}
// checkCacheDir checks if the directory specified by GOMODCACHE exists. An
// error is returned if it does not.
-func checkCacheDir() error {
+func checkCacheDir(ctx context.Context) error {
if cfg.GOMODCACHE == "" {
// modload.Init exits if GOPATH[0] is empty, and cfg.GOMODCACHE
// is set to GOPATH[0]/pkg/mod if GOMODCACHE is empty, so this should never happen.
package modfetch
import (
+ "context"
"os"
"path/filepath"
"testing"
)
func TestWriteDiskCache(t *testing.T) {
+ ctx := context.Background()
+
tmpdir, err := os.MkdirTemp("", "go-writeCache-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
- err = writeDiskCache(filepath.Join(tmpdir, "file"), []byte("data"))
+ err = writeDiskCache(ctx, filepath.Join(tmpdir, "file"), []byte("data"))
if err != nil {
t.Fatal(err)
}
import (
"bytes"
+ "context"
"crypto/sha256"
"fmt"
"io"
// taken from can be reused.
// The subdir gives subdirectory name where the module root is expected to be found,
// "" for the root or "sub/dir" for a subdirectory (no trailing slash).
- CheckReuse(old *Origin, subdir string) error
+ CheckReuse(ctx context.Context, old *Origin, subdir string) error
// List lists all tags with the given prefix.
- Tags(prefix string) (*Tags, error)
+ Tags(ctx context.Context, prefix string) (*Tags, error)
// Stat returns information about the revision rev.
// A revision can be any identifier known to the underlying service:
// commit hash, branch, tag, and so on.
- Stat(rev string) (*RevInfo, error)
+ Stat(ctx context.Context, rev string) (*RevInfo, error)
// Latest returns the latest revision on the default branch,
// whatever that means in the underlying implementation.
- Latest() (*RevInfo, error)
+ Latest(ctx context.Context) (*RevInfo, error)
// ReadFile reads the given file in the file tree corresponding to revision rev.
// It should refuse to read more than maxSize bytes.
//
// If the requested file does not exist it should return an error for which
// os.IsNotExist(err) returns true.
- ReadFile(rev, file string, maxSize int64) (data []byte, err error)
+ ReadFile(ctx context.Context, rev, file string, maxSize int64) (data []byte, err error)
// ReadZip downloads a zip file for the subdir subdirectory
// of the given revision to a new file in a given temporary directory.
// It returns a ReadCloser for a streamed copy of the zip file.
// All files in the zip file are expected to be
// nested in a single top-level directory, whose name is not specified.
- ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)
+ ReadZip(ctx context.Context, rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)
// RecentTag returns the most recent tag on rev or one of its predecessors
// with the given prefix. allowed may be used to filter out unwanted versions.
- RecentTag(rev, prefix string, allowed func(tag string) bool) (tag string, err error)
+ RecentTag(ctx context.Context, rev, prefix string, allowed func(tag string) bool) (tag string, err error)
// DescendsFrom reports whether rev or any of its ancestors has the given tag.
//
// DescendsFrom must return true for any tag returned by RecentTag for the
// same revision.
- DescendsFrom(rev, tag string) (bool, error)
+ DescendsFrom(ctx context.Context, rev, tag string) (bool, error)
}
// An Origin describes the provenance of a given repo method result.
// WorkDir returns the name of the cached work directory to use for the
// given repository type and name.
-func WorkDir(typ, name string) (dir, lockfile string, err error) {
+func WorkDir(ctx context.Context, typ, name string) (dir, lockfile string, err error) {
if cfg.GOMODCACHE == "" {
return "", "", fmt.Errorf("neither GOPATH nor GOMODCACHE are set")
}
key := typ + ":" + name
dir = filepath.Join(cfg.GOMODCACHE, "cache/vcs", fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
- if cfg.BuildX {
- fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", filepath.Dir(dir), typ, name)
+ xLog, buildX := cfg.BuildXWriter(ctx)
+ if buildX {
+ fmt.Fprintf(xLog, "mkdir -p %s # %s %s\n", filepath.Dir(dir), typ, name)
}
if err := os.MkdirAll(filepath.Dir(dir), 0777); err != nil {
return "", "", err
}
lockfile = dir + ".lock"
- if cfg.BuildX {
- fmt.Fprintf(os.Stderr, "# lock %s\n", lockfile)
+ if buildX {
+ fmt.Fprintf(xLog, "# lock %s\n", lockfile)
}
unlock, err := lockedfile.MutexAt(lockfile).Lock()
if have != key {
return "", "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
}
- if cfg.BuildX {
- fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
+ if buildX {
+ fmt.Fprintf(xLog, "# %s for %s %s\n", dir, typ, name)
}
return dir, lockfile, nil
}
// Info file or directory missing. Start from scratch.
- if cfg.BuildX {
- fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
+ if xLog != nil {
+ fmt.Fprintf(xLog, "mkdir -p %s # %s %s\n", dir, typ, name)
}
os.RemoveAll(dir)
if err := os.MkdirAll(dir, 0777); err != nil {
// It returns the standard output and, for a non-zero exit,
// a *RunError indicating the command, exit status, and standard error.
// Standard error is unavailable for commands that exit successfully.
-func Run(dir string, cmdline ...any) ([]byte, error) {
- return RunWithStdin(dir, nil, cmdline...)
+func Run(ctx context.Context, dir string, cmdline ...any) ([]byte, error) {
+ return RunWithStdin(ctx, dir, nil, cmdline...)
}
// bashQuoter escapes characters that have special meaning in double-quoted strings in the bash shell.
// See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html.
var bashQuoter = strings.NewReplacer(`"`, `\"`, `$`, `\$`, "`", "\\`", `\`, `\\`)
-func RunWithStdin(dir string, stdin io.Reader, cmdline ...any) ([]byte, error) {
+func RunWithStdin(ctx context.Context, dir string, stdin io.Reader, cmdline ...any) ([]byte, error) {
if dir != "" {
muIface, ok := dirLock.Load(dir)
if !ok {
if os.Getenv("TESTGOVCS") == "panic" {
panic(fmt.Sprintf("use of vcs: %v", cmd))
}
- if cfg.BuildX {
+ if xLog, ok := cfg.BuildXWriter(ctx); ok {
text := new(strings.Builder)
if dir != "" {
text.WriteString("cd ")
text.WriteString(arg)
}
}
- fmt.Fprintf(os.Stderr, "%s\n", text)
+ fmt.Fprintf(xLog, "%s\n", text)
start := time.Now()
defer func() {
- fmt.Fprintf(os.Stderr, "%.3fs # %s\n", time.Since(start).Seconds(), text)
+ fmt.Fprintf(xLog, "%.3fs # %s\n", time.Since(start).Seconds(), text)
}()
}
// TODO: Impose limits on command output size.
// TODO: Set environment to get English error messages.
var stderr bytes.Buffer
var stdout bytes.Buffer
- c := exec.Command(cmd[0], cmd[1:]...)
+ c := exec.CommandContext(ctx, cmd[0], cmd[1:]...)
+ c.Cancel = func() error { return c.Process.Signal(os.Interrupt) }
c.Dir = dir
c.Stdin = stdin
c.Stderr = &stderr
import (
"bytes"
+ "context"
"crypto/sha256"
"encoding/base64"
"errors"
// LocalGitRepo is like Repo but accepts both Git remote references
// and paths to repositories on the local file system.
-func LocalGitRepo(remote string) (Repo, error) {
- return newGitRepoCached(remote, true)
+func LocalGitRepo(ctx context.Context, remote string) (Repo, error) {
+ return newGitRepoCached(ctx, remote, true)
}
// A notExistError wraps another error to retain its original text
localOK bool
}
-func newGitRepoCached(remote string, localOK bool) (Repo, error) {
+func newGitRepoCached(ctx context.Context, remote string, localOK bool) (Repo, error) {
return gitRepoCache.Do(gitCacheKey{remote, localOK}, func() (Repo, error) {
- return newGitRepo(remote, localOK)
+ return newGitRepo(ctx, remote, localOK)
})
}
-func newGitRepo(remote string, localOK bool) (Repo, error) {
+func newGitRepo(ctx context.Context, remote string, localOK bool) (Repo, error) {
r := &gitRepo{remote: remote}
if strings.Contains(remote, "://") {
// This is a remote path.
var err error
- r.dir, r.mu.Path, err = WorkDir(gitWorkDirType, r.remote)
+ r.dir, r.mu.Path, err = WorkDir(ctx, gitWorkDirType, r.remote)
if err != nil {
return nil, err
}
defer unlock()
if _, err := os.Stat(filepath.Join(r.dir, "objects")); err != nil {
- if _, err := Run(r.dir, "git", "init", "--bare"); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "init", "--bare"); err != nil {
os.RemoveAll(r.dir)
return nil, err
}
// but this lets us say git fetch origin instead, which
// is a little nicer. More importantly, using a named remote
// avoids a problem with Git LFS. See golang.org/issue/25605.
- if _, err := Run(r.dir, "git", "remote", "add", "origin", "--", r.remote); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "remote", "add", "origin", "--", r.remote); err != nil {
os.RemoveAll(r.dir)
return nil, err
}
// long branch names.
//
// See https://github.com/git-for-windows/git/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path.
- if _, err := Run(r.dir, "git", "config", "core.longpaths", "true"); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "config", "core.longpaths", "true"); err != nil {
os.RemoveAll(r.dir)
return nil, err
}
}
type gitRepo struct {
+ ctx context.Context
+
remote, remoteURL string
local bool
dir string
// loadLocalTags loads tag references from the local git cache
// into the map r.localTags.
// Should only be called as r.localTagsOnce.Do(r.loadLocalTags).
-func (r *gitRepo) loadLocalTags() {
+func (r *gitRepo) loadLocalTags(ctx context.Context) {
// The git protocol sends all known refs and ls-remote filters them on the client side,
// so we might as well record both heads and tags in one shot.
// Most of the time we only care about tags but sometimes we care about heads too.
- out, err := Run(r.dir, "git", "tag", "-l")
+ out, err := Run(ctx, r.dir, "git", "tag", "-l")
if err != nil {
return
}
}
}
-func (r *gitRepo) CheckReuse(old *Origin, subdir string) error {
+func (r *gitRepo) CheckReuse(ctx context.Context, old *Origin, subdir string) error {
if old == nil {
return fmt.Errorf("missing origin")
}
return fmt.Errorf("non-specific origin")
}
- r.loadRefs()
+ r.loadRefs(ctx)
if r.refsErr != nil {
return r.refsErr
}
}
}
if old.TagSum != "" {
- tags, err := r.Tags(old.TagPrefix)
+ tags, err := r.Tags(ctx, old.TagPrefix)
if err != nil {
return err
}
// loadRefs loads heads and tags references from the remote into the map r.refs.
// The result is cached in memory.
-func (r *gitRepo) loadRefs() (map[string]string, error) {
+func (r *gitRepo) loadRefs(ctx context.Context) (map[string]string, error) {
r.refsOnce.Do(func() {
// The git protocol sends all known refs and ls-remote filters them on the client side,
// so we might as well record both heads and tags in one shot.
// Most of the time we only care about tags but sometimes we care about heads too.
- out, gitErr := Run(r.dir, "git", "ls-remote", "-q", r.remote)
+ out, gitErr := Run(ctx, r.dir, "git", "ls-remote", "-q", r.remote)
if gitErr != nil {
if rerr, ok := gitErr.(*RunError); ok {
if bytes.Contains(rerr.Stderr, []byte("fatal: could not read Username")) {
return r.refs, r.refsErr
}
-func (r *gitRepo) Tags(prefix string) (*Tags, error) {
- refs, err := r.loadRefs()
+func (r *gitRepo) Tags(ctx context.Context, prefix string) (*Tags, error) {
+ refs, err := r.loadRefs(ctx)
if err != nil {
return nil, err
}
}
}
-func (r *gitRepo) Latest() (*RevInfo, error) {
- refs, err := r.loadRefs()
+func (r *gitRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ refs, err := r.loadRefs(ctx)
if err != nil {
return nil, err
}
if refs["HEAD"] == "" {
return nil, ErrNoCommits
}
- statInfo, err := r.Stat(refs["HEAD"])
+ statInfo, err := r.Stat(ctx, refs["HEAD"])
if err != nil {
return nil, err
}
// for use when the server requires giving a ref instead of a hash.
// There may be multiple ref names for a given hash,
// in which case this returns some name - it doesn't matter which.
-func (r *gitRepo) findRef(hash string) (ref string, ok bool) {
- refs, err := r.loadRefs()
+func (r *gitRepo) findRef(ctx context.Context, hash string) (ref string, ok bool) {
+ refs, err := r.loadRefs(ctx)
if err != nil {
return "", false
}
// stat stats the given rev in the local repository,
// or else it fetches more info from the remote repository and tries again.
-func (r *gitRepo) stat(rev string) (info *RevInfo, err error) {
+func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err error) {
if r.local {
- return r.statLocal(rev, rev)
+ return r.statLocal(ctx, rev, rev)
}
// Fast path: maybe rev is a hash we already have locally.
didStatLocal := false
if len(rev) >= minHashDigits && len(rev) <= 40 && AllHex(rev) {
- if info, err := r.statLocal(rev, rev); err == nil {
+ if info, err := r.statLocal(ctx, rev, rev); err == nil {
return info, nil
}
didStatLocal = true
// Maybe rev is a tag we already have locally.
// (Note that we're excluding branches, which can be stale.)
- r.localTagsOnce.Do(r.loadLocalTags)
+ r.localTagsOnce.Do(func() { r.loadLocalTags(ctx) })
if r.localTags[rev] {
- return r.statLocal(rev, "refs/tags/"+rev)
+ return r.statLocal(ctx, rev, "refs/tags/"+rev)
}
// Maybe rev is the name of a tag or branch on the remote server.
// Or maybe it's the prefix of a hash of a named ref.
// Try to resolve to both a ref (git name) and full (40-hex-digit) commit hash.
- refs, err := r.loadRefs()
+ refs, err := r.loadRefs(ctx)
if err != nil {
return nil, err
}
// (or already have the hash we need, just without its tag).
// Either way, try a local stat before falling back to network I/O.
if !didStatLocal {
- if info, err := r.statLocal(rev, hash); err == nil {
+ if info, err := r.statLocal(ctx, rev, hash); err == nil {
if after, found := strings.CutPrefix(ref, "refs/tags/"); found {
// Make sure tag exists, so it will be in localTags next time the go command is run.
- Run(r.dir, "git", "tag", after, hash)
+ Run(ctx, r.dir, "git", "tag", after, hash)
}
return info, nil
}
ref = hash
refspec = hash + ":refs/dummy"
}
- _, err := Run(r.dir, "git", "fetch", "-f", "--depth=1", r.remote, refspec)
+ _, err := Run(ctx, r.dir, "git", "fetch", "-f", "--depth=1", r.remote, refspec)
if err == nil {
- return r.statLocal(rev, ref)
+ return r.statLocal(ctx, rev, ref)
}
// Don't try to be smart about parsing the error.
// It's too complex and varies too much by git version.
// Last resort.
// Fetch all heads and tags and hope the hash we want is in the history.
- if err := r.fetchRefsLocked(); err != nil {
+ if err := r.fetchRefsLocked(ctx); err != nil {
return nil, err
}
- return r.statLocal(rev, rev)
+ return r.statLocal(ctx, rev, rev)
}
// fetchRefsLocked fetches all heads and tags from the origin, along with the
// for more detail.)
//
// fetchRefsLocked requires that r.mu remain locked for the duration of the call.
-func (r *gitRepo) fetchRefsLocked() error {
+func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
if r.fetchLevel < fetchAll {
// NOTE: To work around a bug affecting Git clients up to at least 2.23.0
// (2019-08-16), we must first expand the set of local refs, and only then
// golang.org/issue/34266 and
// https://github.com/git/git/blob/4c86140027f4a0d2caaa3ab4bd8bfc5ce3c11c8a/transport.c#L1303-L1309.)
- if _, err := Run(r.dir, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
return err
}
if _, err := os.Stat(filepath.Join(r.dir, "shallow")); err == nil {
- if _, err := Run(r.dir, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
return err
}
}
// statLocal returns a new RevInfo describing rev in the local git repository.
// It uses version as info.Version.
-func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) {
- out, err := Run(r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
+func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo, error) {
+ out, err := Run(ctx, r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
if err != nil {
// Return info with Origin.RepoSum if possible to allow caching of negative lookup.
var info *RevInfo
- if refs, err := r.loadRefs(); err == nil {
+ if refs, err := r.loadRefs(ctx); err == nil {
info = r.unknownRevisionInfo(refs)
}
return info, &UnknownRevisionError{Rev: rev}
return info, nil
}
-func (r *gitRepo) Stat(rev string) (*RevInfo, error) {
+func (r *gitRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
if rev == "latest" {
- return r.Latest()
+ return r.Latest(ctx)
}
return r.statCache.Do(rev, func() (*RevInfo, error) {
- return r.stat(rev)
+ return r.stat(ctx, rev)
})
}
-func (r *gitRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
+func (r *gitRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64) ([]byte, error) {
// TODO: Could use git cat-file --batch.
- info, err := r.Stat(rev) // download rev into local git repo
+ info, err := r.Stat(ctx, rev) // download rev into local git repo
if err != nil {
return nil, err
}
- out, err := Run(r.dir, "git", "cat-file", "blob", info.Name+":"+file)
+ out, err := Run(ctx, r.dir, "git", "cat-file", "blob", info.Name+":"+file)
if err != nil {
return nil, fs.ErrNotExist
}
return out, nil
}
-func (r *gitRepo) RecentTag(rev, prefix string, allowed func(tag string) bool) (tag string, err error) {
- info, err := r.Stat(rev)
+func (r *gitRepo) RecentTag(ctx context.Context, rev, prefix string, allowed func(tag string) bool) (tag string, err error) {
+ info, err := r.Stat(ctx, rev)
if err != nil {
return "", err
}
// result is definitive.
describe := func() (definitive bool) {
var out []byte
- out, err = Run(r.dir, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
+ out, err = Run(ctx, r.dir, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
if err != nil {
return true
}
// Git didn't find a version tag preceding the requested rev.
// See whether any plausible tag exists.
- tags, err := r.Tags(prefix + "v")
+ tags, err := r.Tags(ctx, prefix+"v")
if err != nil {
return "", err
}
}
defer unlock()
- if err := r.fetchRefsLocked(); err != nil {
+ if err := r.fetchRefsLocked(ctx); err != nil {
return "", err
}
return tag, err
}
-func (r *gitRepo) DescendsFrom(rev, tag string) (bool, error) {
+func (r *gitRepo) DescendsFrom(ctx context.Context, rev, tag string) (bool, error) {
// The "--is-ancestor" flag was added to "git merge-base" in version 1.8.0, so
// this won't work with Git 1.7.1. According to golang.org/issue/28550, cmd/go
// already doesn't work with Git 1.7.1, so at least it's not a regression.
//
// git merge-base --is-ancestor exits with status 0 if rev is an ancestor, or
// 1 if not.
- _, err := Run(r.dir, "git", "merge-base", "--is-ancestor", "--", tag, rev)
+ _, err := Run(ctx, r.dir, "git", "merge-base", "--is-ancestor", "--", tag, rev)
// Git reports "is an ancestor" with exit code 0 and "not an ancestor" with
// exit code 1.
}
// See whether the tag and rev even exist.
- tags, err := r.Tags(tag)
+ tags, err := r.Tags(ctx, tag)
if err != nil {
return false, err
}
// NOTE: r.stat is very careful not to fetch commits that we shouldn't know
// about, like rejected GitHub pull requests, so don't try to short-circuit
// that here.
- if _, err = r.stat(rev); err != nil {
+ if _, err = r.stat(ctx, rev); err != nil {
return false, err
}
// efficient to only fetch the history from rev to tag, but that's much more
// complicated, and any kind of shallow fetch is fairly likely to trigger
// bugs in JGit servers and/or the go command anyway.
- if err := r.fetchRefsLocked(); err != nil {
+ if err := r.fetchRefsLocked(ctx); err != nil {
return false, err
}
}
- _, err = Run(r.dir, "git", "merge-base", "--is-ancestor", "--", tag, rev)
+ _, err = Run(ctx, r.dir, "git", "merge-base", "--is-ancestor", "--", tag, rev)
if err == nil {
return true, nil
}
return false, err
}
-func (r *gitRepo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error) {
+func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64) (zip io.ReadCloser, err error) {
// TODO: Use maxSize or drop it.
args := []string{}
if subdir != "" {
args = append(args, "--", subdir)
}
- info, err := r.Stat(rev) // download rev into local git repo
+ info, err := r.Stat(ctx, rev) // download rev into local git repo
if err != nil {
return nil, err
}
// text file line endings. Setting -c core.autocrlf=input means only
// translate files on the way into the repo, not on the way out (archive).
// The -c core.eol=lf should be unnecessary but set it anyway.
- archive, err := Run(r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
+ archive, err := Run(ctx, r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
if err != nil {
if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
return nil, fs.ErrNotExist
"bytes"
"cmd/go/internal/cfg"
"cmd/go/internal/vcweb/vcstest"
+ "context"
"flag"
"internal/testenv"
"io"
// If we use a file:// URL to access the local directory,
// then git starts up all the usual protocol machinery,
// which will let us test remote git archive invocations.
- _, localGitURLErr = Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo)
+ _, localGitURLErr = Run(context.Background(), "", "git", "clone", "--mirror", gitrepo1, localGitRepo)
if localGitURLErr != nil {
return
}
- _, localGitURLErr = Run(localGitRepo, "git", "config", "daemon.uploadarch", "true")
+ _, localGitURLErr = Run(context.Background(), localGitRepo, "git", "config", "daemon.uploadarch", "true")
})
if localGitURLErr != nil {
)
func testMain(m *testing.M) (err error) {
- cfg.BuildX = true
+ cfg.BuildX = testing.Verbose()
srv, err := vcstest.NewServer()
if err != nil {
return nil
}
-func testRepo(t *testing.T, remote string) (Repo, error) {
+func testContext(t testing.TB) context.Context {
+ w := newTestWriter(t)
+ return cfg.WithBuildXWriter(context.Background(), w)
+}
+
+// A testWriter is an io.Writer that writes to a test's log.
+//
+// The writer batches written data until the last byte of a write is a newline
+// character, then flushes the batched data as a single call to Logf.
+// Any remaining unflushed data is logged during Cleanup.
+type testWriter struct {
+ t testing.TB
+
+ mu sync.Mutex
+ buf bytes.Buffer
+}
+
+func newTestWriter(t testing.TB) *testWriter {
+ w := &testWriter{t: t}
+
+ t.Cleanup(func() {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ if b := w.buf.Bytes(); len(b) > 0 {
+ w.t.Logf("%s", b)
+ w.buf.Reset()
+ }
+ })
+
+ return w
+}
+
+func (w *testWriter) Write(p []byte) (int, error) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ n, err := w.buf.Write(p)
+ if b := w.buf.Bytes(); len(b) > 0 && b[len(b)-1] == '\n' {
+ w.t.Logf("%s", b)
+ w.buf.Reset()
+ }
+ return n, err
+}
+
+func testRepo(ctx context.Context, t *testing.T, remote string) (Repo, error) {
if remote == "localGitRepo" {
- return LocalGitRepo(localGitURL(t))
+ return LocalGitRepo(ctx, localGitURL(t))
}
vcsName := "git"
for _, k := range []string{"hg"} {
if runtime.GOOS == "android" && strings.HasSuffix(testenv.Builder(), "-corellium") {
testenv.SkipFlaky(t, 59940)
}
- return NewRepo(vcsName, remote)
+ return NewRepo(ctx, vcsName, remote)
}
func TestTags(t *testing.T) {
runTest := func(tt tagsTest) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
+ ctx := testContext(t)
- r, err := testRepo(t, tt.repo)
+ r, err := testRepo(ctx, t, tt.repo)
if err != nil {
t.Fatal(err)
}
- tags, err := r.Tags(tt.prefix)
+ tags, err := r.Tags(ctx, tt.prefix)
if err != nil {
t.Fatal(err)
}
runTest := func(tt latestTest) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
+ ctx := testContext(t)
- r, err := testRepo(t, tt.repo)
+ r, err := testRepo(ctx, t, tt.repo)
if err != nil {
t.Fatal(err)
}
- info, err := r.Latest()
+ info, err := r.Latest(ctx)
if err != nil {
t.Fatal(err)
}
runTest := func(tt readFileTest) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
+ ctx := testContext(t)
- r, err := testRepo(t, tt.repo)
+ r, err := testRepo(ctx, t, tt.repo)
if err != nil {
t.Fatal(err)
}
- data, err := r.ReadFile(tt.rev, tt.file, 100)
+ data, err := r.ReadFile(ctx, tt.rev, tt.file, 100)
if err != nil {
if tt.err == "" {
t.Fatalf("ReadFile: unexpected error %v", err)
runTest := func(tt readZipTest) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
+ ctx := testContext(t)
- r, err := testRepo(t, tt.repo)
+ r, err := testRepo(ctx, t, tt.repo)
if err != nil {
t.Fatal(err)
}
- rc, err := r.ReadZip(tt.rev, tt.subdir, 100000)
+ rc, err := r.ReadZip(ctx, tt.rev, tt.subdir, 100000)
if err != nil {
if tt.err == "" {
t.Fatalf("ReadZip: unexpected error %v", err)
runTest := func(tt statTest) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
+ ctx := testContext(t)
- r, err := testRepo(t, tt.repo)
+ r, err := testRepo(ctx, t, tt.repo)
if err != nil {
t.Fatal(err)
}
- info, err := r.Stat(tt.rev)
+ info, err := r.Stat(ctx, tt.rev)
if err != nil {
if tt.err == "" {
t.Fatalf("Stat: unexpected error %v", err)
import (
"archive/zip"
+ "context"
"encoding/xml"
"fmt"
"io"
return info, nil
}
-func svnReadZip(dst io.Writer, workDir, rev, subdir, remote string) (err error) {
+func svnReadZip(ctx context.Context, dst io.Writer, workDir, rev, subdir, remote string) (err error) {
// The subversion CLI doesn't provide a command to write the repository
// directly to an archive, so we need to export it to the local filesystem
// instead. Unfortunately, the local filesystem might apply arbitrary
remotePath += "/" + subdir
}
- out, err := Run(workDir, []string{
+ out, err := Run(ctx, workDir, []string{
"svn", "list",
"--non-interactive",
"--xml",
}
defer os.RemoveAll(exportDir) // best-effort
- _, err = Run(workDir, []string{
+ _, err = Run(ctx, workDir, []string{
"svn", "export",
"--non-interactive",
"--quiet",
package codehost
import (
+ "context"
"errors"
"fmt"
"internal/lazyregexp"
remote string
}
-func NewRepo(vcs, remote string) (Repo, error) {
+func NewRepo(ctx context.Context, vcs, remote string) (Repo, error) {
return vcsRepoCache.Do(vcsCacheKey{vcs, remote}, func() (Repo, error) {
- repo, err := newVCSRepo(vcs, remote)
+ repo, err := newVCSRepo(ctx, vcs, remote)
if err != nil {
return nil, &VCSError{err}
}
fetchErr error
}
-func newVCSRepo(vcs, remote string) (Repo, error) {
+func newVCSRepo(ctx context.Context, vcs, remote string) (Repo, error) {
if vcs == "git" {
- return newGitRepo(remote, false)
+ return newGitRepo(ctx, remote, false)
}
cmd := vcsCmds[vcs]
if cmd == nil {
r := &vcsRepo{remote: remote, cmd: cmd}
var err error
- r.dir, r.mu.Path, err = WorkDir(vcsWorkDirType+vcs, r.remote)
+ r.dir, r.mu.Path, err = WorkDir(ctx, vcsWorkDirType+vcs, r.remote)
if err != nil {
return nil, err
}
defer unlock()
if _, err := os.Stat(filepath.Join(r.dir, "."+vcs)); err != nil {
- if _, err := Run(r.dir, cmd.init(r.remote)); err != nil {
+ if _, err := Run(ctx, r.dir, cmd.init(r.remote)); err != nil {
os.RemoveAll(r.dir)
return nil, err
}
const vcsWorkDirType = "vcs1."
type vcsCmd struct {
- vcs string // vcs name "hg"
- init func(remote string) []string // cmd to init repo to track remote
- tags func(remote string) []string // cmd to list local tags
- tagRE *lazyregexp.Regexp // regexp to extract tag names from output of tags cmd
- branches func(remote string) []string // cmd to list local branches
- branchRE *lazyregexp.Regexp // regexp to extract branch names from output of tags cmd
- badLocalRevRE *lazyregexp.Regexp // regexp of names that must not be served out of local cache without doing fetch first
- statLocal func(rev, remote string) []string // cmd to stat local rev
- parseStat func(rev, out string) (*RevInfo, error) // cmd to parse output of statLocal
- fetch []string // cmd to fetch everything from remote
- latest string // name of latest commit on remote (tip, HEAD, etc)
- readFile func(rev, file, remote string) []string // cmd to read rev's file
- readZip func(rev, subdir, remote, target string) []string // cmd to read rev's subdir as zip file
- doReadZip func(dst io.Writer, workDir, rev, subdir, remote string) error // arbitrary function to read rev's subdir as zip file
+ vcs string // vcs name "hg"
+ init func(remote string) []string // cmd to init repo to track remote
+ tags func(remote string) []string // cmd to list local tags
+ tagRE *lazyregexp.Regexp // regexp to extract tag names from output of tags cmd
+ branches func(remote string) []string // cmd to list local branches
+ branchRE *lazyregexp.Regexp // regexp to extract branch names from output of tags cmd
+ badLocalRevRE *lazyregexp.Regexp // regexp of names that must not be served out of local cache without doing fetch first
+ statLocal func(rev, remote string) []string // cmd to stat local rev
+ parseStat func(rev, out string) (*RevInfo, error) // cmd to parse output of statLocal
+ fetch []string // cmd to fetch everything from remote
+ latest string // name of latest commit on remote (tip, HEAD, etc)
+ readFile func(rev, file, remote string) []string // cmd to read rev's file
+ readZip func(rev, subdir, remote, target string) []string // cmd to read rev's subdir as zip file
+ doReadZip func(ctx context.Context, dst io.Writer, workDir, rev, subdir, remote string) error // arbitrary function to read rev's subdir as zip file
}
var re = lazyregexp.New
},
}
-func (r *vcsRepo) loadTags() {
- out, err := Run(r.dir, r.cmd.tags(r.remote))
+func (r *vcsRepo) loadTags(ctx context.Context) {
+ out, err := Run(ctx, r.dir, r.cmd.tags(r.remote))
if err != nil {
return
}
}
}
-func (r *vcsRepo) loadBranches() {
+func (r *vcsRepo) loadBranches(ctx context.Context) {
if r.cmd.branches == nil {
return
}
- out, err := Run(r.dir, r.cmd.branches(r.remote))
+ out, err := Run(ctx, r.dir, r.cmd.branches(r.remote))
if err != nil {
return
}
}
}
-func (r *vcsRepo) CheckReuse(old *Origin, subdir string) error {
+func (r *vcsRepo) CheckReuse(ctx context.Context, old *Origin, subdir string) error {
return fmt.Errorf("vcs %s: CheckReuse: %w", r.cmd.vcs, errors.ErrUnsupported)
}
-func (r *vcsRepo) Tags(prefix string) (*Tags, error) {
+func (r *vcsRepo) Tags(ctx context.Context, prefix string) (*Tags, error) {
unlock, err := r.mu.Lock()
if err != nil {
return nil, err
}
defer unlock()
- r.tagsOnce.Do(r.loadTags)
+ r.tagsOnce.Do(func() { r.loadTags(ctx) })
tags := &Tags{
// None of the other VCS provide a reasonable way to compute TagSum
// without downloading the whole repo, so we only include VCS and URL
return tags, nil
}
-func (r *vcsRepo) Stat(rev string) (*RevInfo, error) {
+func (r *vcsRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
unlock, err := r.mu.Lock()
if err != nil {
return nil, err
if rev == "latest" {
rev = r.cmd.latest
}
- r.branchesOnce.Do(r.loadBranches)
+ r.branchesOnce.Do(func() { r.loadBranches(ctx) })
revOK := (r.cmd.badLocalRevRE == nil || !r.cmd.badLocalRevRE.MatchString(rev)) && !r.branches[rev]
if revOK {
- if info, err := r.statLocal(rev); err == nil {
+ if info, err := r.statLocal(ctx, rev); err == nil {
return info, nil
}
}
- r.fetchOnce.Do(r.fetch)
+ r.fetchOnce.Do(func() { r.fetch(ctx) })
if r.fetchErr != nil {
return nil, r.fetchErr
}
- info, err := r.statLocal(rev)
+ info, err := r.statLocal(ctx, rev)
if err != nil {
return nil, err
}
return info, nil
}
-func (r *vcsRepo) fetch() {
+func (r *vcsRepo) fetch(ctx context.Context) {
if len(r.cmd.fetch) > 0 {
- _, r.fetchErr = Run(r.dir, r.cmd.fetch)
+ _, r.fetchErr = Run(ctx, r.dir, r.cmd.fetch)
}
}
-func (r *vcsRepo) statLocal(rev string) (*RevInfo, error) {
- out, err := Run(r.dir, r.cmd.statLocal(rev, r.remote))
+func (r *vcsRepo) statLocal(ctx context.Context, rev string) (*RevInfo, error) {
+ out, err := Run(ctx, r.dir, r.cmd.statLocal(rev, r.remote))
if err != nil {
return nil, &UnknownRevisionError{Rev: rev}
}
return info, nil
}
-func (r *vcsRepo) Latest() (*RevInfo, error) {
- return r.Stat("latest")
+func (r *vcsRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ return r.Stat(ctx, "latest")
}
-func (r *vcsRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
+func (r *vcsRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64) ([]byte, error) {
if rev == "latest" {
rev = r.cmd.latest
}
- _, err := r.Stat(rev) // download rev into local repo
+ _, err := r.Stat(ctx, rev) // download rev into local repo
if err != nil {
return nil, err
}
}
defer unlock()
- out, err := Run(r.dir, r.cmd.readFile(rev, file, r.remote))
+ out, err := Run(ctx, r.dir, r.cmd.readFile(rev, file, r.remote))
if err != nil {
return nil, fs.ErrNotExist
}
return out, nil
}
-func (r *vcsRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
+func (r *vcsRepo) RecentTag(ctx context.Context, rev, prefix string, allowed func(string) bool) (tag string, err error) {
// We don't technically need to lock here since we're returning an error
// uncondititonally, but doing so anyway will help to avoid baking in
// lock-inversion bugs.
return "", vcsErrorf("vcs %s: RecentTag: %w", r.cmd.vcs, errors.ErrUnsupported)
}
-func (r *vcsRepo) DescendsFrom(rev, tag string) (bool, error) {
+func (r *vcsRepo) DescendsFrom(ctx context.Context, rev, tag string) (bool, error) {
unlock, err := r.mu.Lock()
if err != nil {
return false, err
return false, vcsErrorf("vcs %s: DescendsFrom: %w", r.cmd.vcs, errors.ErrUnsupported)
}
-func (r *vcsRepo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error) {
+func (r *vcsRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64) (zip io.ReadCloser, err error) {
if r.cmd.readZip == nil && r.cmd.doReadZip == nil {
return nil, vcsErrorf("vcs %s: ReadZip: %w", r.cmd.vcs, errors.ErrUnsupported)
}
N: maxSize,
ErrLimitReached: errors.New("ReadZip: encoded file exceeds allowed size"),
}
- err = r.cmd.doReadZip(lw, r.dir, rev, subdir, r.remote)
+ err = r.cmd.doReadZip(ctx, lw, r.dir, rev, subdir, r.remote)
if err == nil {
_, err = f.Seek(0, io.SeekStart)
}
args[i] = filepath.Join(r.dir, ".fossil")
}
}
- _, err = Run(filepath.Dir(f.Name()), args)
+ _, err = Run(ctx, filepath.Dir(f.Name()), args)
} else {
- _, err = Run(r.dir, r.cmd.readZip(rev, subdir, r.remote, f.Name()))
+ _, err = Run(ctx, r.dir, r.cmd.readZip(rev, subdir, r.remote, f.Name()))
}
if err != nil {
f.Close()
import (
"archive/zip"
"bytes"
+ "context"
"errors"
"fmt"
"io"
return r.modPath
}
-func (r *codeRepo) CheckReuse(old *codehost.Origin) error {
- return r.code.CheckReuse(old, r.codeDir)
+func (r *codeRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
+ return r.code.CheckReuse(ctx, old, r.codeDir)
}
-func (r *codeRepo) Versions(prefix string) (*Versions, error) {
+func (r *codeRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
// Special case: gopkg.in/macaroon-bakery.v2-unstable
// does not use the v2 tags (those are for macaroon-bakery.v2).
// It has no possible tags at all.
if r.codeDir != "" {
p = r.codeDir + "/" + p
}
- tags, err := r.code.Tags(p)
+ tags, err := r.code.Tags(ctx, p)
if err != nil {
return nil, &module.ModuleError{
Path: r.modPath,
semver.Sort(list)
semver.Sort(incompatible)
- return r.appendIncompatibleVersions(tags.Origin, list, incompatible)
+ return r.appendIncompatibleVersions(ctx, tags.Origin, list, incompatible)
}
// appendIncompatibleVersions appends "+incompatible" versions to list if
// prefix.
//
// Both list and incompatible must be sorted in semantic order.
-func (r *codeRepo) appendIncompatibleVersions(origin *codehost.Origin, list, incompatible []string) (*Versions, error) {
+func (r *codeRepo) appendIncompatibleVersions(ctx context.Context, origin *codehost.Origin, list, incompatible []string) (*Versions, error) {
versions := &Versions{
Origin: origin,
List: list,
}
versionHasGoMod := func(v string) (bool, error) {
- _, err := r.code.ReadFile(v, "go.mod", codehost.MaxGoMod)
+ _, err := r.code.ReadFile(ctx, v, "go.mod", codehost.MaxGoMod)
if err == nil {
return true, nil
}
return versions, nil
}
-func (r *codeRepo) Stat(rev string) (*RevInfo, error) {
+func (r *codeRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
if rev == "latest" {
- return r.Latest()
+ return r.Latest(ctx)
}
codeRev := r.revToRev(rev)
- info, err := r.code.Stat(codeRev)
+ info, err := r.code.Stat(ctx, codeRev)
if err != nil {
// Note: info may be non-nil to supply Origin for caching error.
var revInfo *RevInfo
},
}
}
- return r.convert(info, rev)
+ return r.convert(ctx, info, rev)
}
-func (r *codeRepo) Latest() (*RevInfo, error) {
- info, err := r.code.Latest()
+func (r *codeRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ info, err := r.code.Latest(ctx)
if err != nil {
return nil, err
}
- return r.convert(info, "")
+ return r.convert(ctx, info, "")
}
// convert converts a version as reported by the code host to a version as
//
// If statVers is a valid module version, it is used for the Version field.
// Otherwise, the Version is derived from the passed-in info and recent tags.
-func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) {
+func (r *codeRepo) convert(ctx context.Context, info *codehost.RevInfo, statVers string) (*RevInfo, error) {
// If this is a plain tag (no dir/ prefix)
// and the module path is unversioned,
// and if the underlying file tree has no go.mod,
ok, seen := incompatibleOk[""]
if !seen {
- _, errGoMod := r.code.ReadFile(info.Name, "go.mod", codehost.MaxGoMod)
+ _, errGoMod := r.code.ReadFile(ctx, info.Name, "go.mod", codehost.MaxGoMod)
ok = (errGoMod != nil)
incompatibleOk[""] = ok
}
major := semver.Major(v)
ok, seen = incompatibleOk[major]
if !seen {
- _, errGoModSub := r.code.ReadFile(info.Name, path.Join(major, "go.mod"), codehost.MaxGoMod)
+ _, errGoModSub := r.code.ReadFile(ctx, info.Name, path.Join(major, "go.mod"), codehost.MaxGoMod)
ok = (errGoModSub != nil)
incompatibleOk[major] = ok
}
// r.findDir verifies both of these conditions. Execute it now so that
// r.Stat will correctly return a notExistError if the go.mod location or
// declared module path doesn't match.
- _, _, _, err := r.findDir(v)
+ _, _, _, err := r.findDir(ctx, v)
if err != nil {
// TODO: It would be nice to return an error like "not a module".
// Right now we return "missing go.mod", which is a little confusing.
if r.pathMajor != "" { // "/v2" or "/.v2"
prefix += r.pathMajor[1:] + "." // += "v2."
}
- tags, err := r.code.Tags(prefix)
+ tags, err := r.code.Tags(ctx, prefix)
if err != nil {
return nil, err
}
// Determine version.
if module.IsPseudoVersion(statVers) {
- if err := r.validatePseudoVersion(info, statVers); err != nil {
+ if err := r.validatePseudoVersion(ctx, info, statVers); err != nil {
return nil, err
}
return checkCanonical(statVers)
tagPrefix = r.codeDir + "/"
}
- isRetracted, err := r.retractedVersions()
+ isRetracted, err := r.retractedVersions(ctx)
if err != nil {
isRetracted = func(string) bool { return false }
}
return !isRetracted(v)
}
if pseudoBase == "" {
- tag, err := r.code.RecentTag(info.Name, tagPrefix, tagAllowed)
+ tag, err := r.code.RecentTag(ctx, info.Name, tagPrefix, tagAllowed)
if err != nil && !errors.Is(err, errors.ErrUnsupported) {
return nil, err
}
// enough of the commit history to find a path between version and its base.
// Fortunately, many pseudo-versions — such as those for untagged repositories —
// have trivial bases!
-func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) (err error) {
+func (r *codeRepo) validatePseudoVersion(ctx context.Context, info *codehost.RevInfo, version string) (err error) {
defer func() {
if err != nil {
if _, ok := err.(*module.ModuleError); !ok {
}
}
- tags, err := r.code.Tags(tagPrefix + base)
+ tags, err := r.code.Tags(ctx, tagPrefix+base)
if err != nil {
return err
}
versionOnly := strings.TrimPrefix(tag.Name, tagPrefix)
if semver.Compare(versionOnly, base) == 0 {
lastTag = tag.Name
- ancestorFound, err = r.code.DescendsFrom(info.Name, tag.Name)
+ ancestorFound, err = r.code.DescendsFrom(ctx, info.Name, tag.Name)
if ancestorFound {
break
}
//
// If r.pathMajor is non-empty, this can be either r.codeDir or — if a go.mod
// file exists — r.codeDir/r.pathMajor[1:].
-func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err error) {
+func (r *codeRepo) findDir(ctx context.Context, version string) (rev, dir string, gomod []byte, err error) {
rev, err = r.versionToRev(version)
if err != nil {
return "", "", nil, err
// Load info about go.mod but delay consideration
// (except I/O error) until we rule out v2/go.mod.
file1 := path.Join(r.codeDir, "go.mod")
- gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
+ gomod1, err1 := r.code.ReadFile(ctx, rev, file1, codehost.MaxGoMod)
if err1 != nil && !os.IsNotExist(err1) {
return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.codeRoot, file1, rev, err1)
}
// a replace directive.
dir2 := path.Join(r.codeDir, r.pathMajor[1:])
file2 = path.Join(dir2, "go.mod")
- gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
+ gomod2, err2 := r.code.ReadFile(ctx, rev, file2, codehost.MaxGoMod)
if err2 != nil && !os.IsNotExist(err2) {
return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.codeRoot, file2, rev, err2)
}
return unversioned && replacingGopkgIn
}
-func (r *codeRepo) GoMod(version string) (data []byte, err error) {
+func (r *codeRepo) GoMod(ctx context.Context, version string) (data []byte, err error) {
if version != module.CanonicalVersion(version) {
return nil, fmt.Errorf("version %s is not canonical", version)
}
// only using the revision at the end.
// Invoke Stat to verify the metadata explicitly so we don't return
// a bogus file for an invalid version.
- _, err := r.Stat(version)
+ _, err := r.Stat(ctx, version)
if err != nil {
return nil, err
}
}
- rev, dir, gomod, err := r.findDir(version)
+ rev, dir, gomod, err := r.findDir(ctx, version)
if err != nil {
return nil, err
}
if gomod != nil {
return gomod, nil
}
- data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod)
+ data, err = r.code.ReadFile(ctx, rev, path.Join(dir, "go.mod"), codehost.MaxGoMod)
if err != nil {
if os.IsNotExist(err) {
return LegacyGoMod(r.modPath), nil
return r.modPath + "@" + rev
}
-func (r *codeRepo) retractedVersions() (func(string) bool, error) {
- vs, err := r.Versions("")
+func (r *codeRepo) retractedVersions(ctx context.Context) (func(string) bool, error) {
+ vs, err := r.Versions(ctx, "")
if err != nil {
return nil, err
}
highest = versions[len(versions)-1]
}
- data, err := r.GoMod(highest)
+ data, err := r.GoMod(ctx, highest)
if err != nil {
return nil, err
}
}, nil
}
-func (r *codeRepo) Zip(dst io.Writer, version string) error {
+func (r *codeRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
if version != module.CanonicalVersion(version) {
return fmt.Errorf("version %s is not canonical", version)
}
// only using the revision at the end.
// Invoke Stat to verify the metadata explicitly so we don't return
// a bogus file for an invalid version.
- _, err := r.Stat(version)
+ _, err := r.Stat(ctx, version)
if err != nil {
return err
}
}
- rev, subdir, _, err := r.findDir(version)
+ rev, subdir, _, err := r.findDir(ctx, version)
if err != nil {
return err
}
- dl, err := r.code.ReadZip(rev, subdir, codehost.MaxZipFile)
+ dl, err := r.code.ReadZip(ctx, rev, subdir, codehost.MaxZipFile)
if err != nil {
return err
}
}
if !haveLICENSE && subdir != "" {
- data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE)
+ data, err := r.code.ReadFile(ctx, rev, "LICENSE", codehost.MaxLICENSE)
if err == nil {
files = append(files, dataFile{name: "LICENSE", data: data})
}
import (
"archive/zip"
+ "context"
"crypto/sha256"
"encoding/hex"
"flag"
if tt.vcs != "mod" {
testenv.MustHaveExecPath(t, tt.vcs)
}
+ ctx := context.Background()
- repo := Lookup("direct", tt.path)
+ repo := Lookup(ctx, "direct", tt.path)
if tt.mpath == "" {
tt.mpath = tt.path
t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
}
- info, err := repo.Stat(tt.rev)
+ info, err := repo.Stat(ctx, tt.rev)
if err != nil {
if tt.err != "" {
if !strings.Contains(err.Error(), tt.err) {
}
if tt.gomod != "" || tt.gomodErr != "" {
- data, err := repo.GoMod(tt.version)
+ data, err := repo.GoMod(ctx, tt.version)
if err != nil && tt.gomodErr == "" {
t.Errorf("repo.GoMod(%q): %v", tt.version, err)
} else if err != nil && tt.gomodErr != "" {
} else {
w = f
}
- err = repo.Zip(w, tt.version)
+ err = repo.Zip(ctx, w, tt.version)
f.Close()
if err != nil {
if tt.zipErr != "" {
if tt.vcs != "mod" {
testenv.MustHaveExecPath(t, tt.vcs)
}
+ ctx := context.Background()
- repo := Lookup("direct", tt.path)
- list, err := repo.Versions(tt.prefix)
+ repo := Lookup(ctx, "direct", tt.path)
+ list, err := repo.Versions(ctx, tt.prefix)
if err != nil {
t.Fatalf("Versions(%q): %v", tt.prefix, err)
}
if tt.vcs != "mod" {
testenv.MustHaveExecPath(t, tt.vcs)
}
+ ctx := context.Background()
- repo := Lookup("direct", tt.path)
- info, err := repo.Latest()
+ repo := Lookup(ctx, "direct", tt.path)
+ info, err := repo.Latest(ctx)
if err != nil {
if tt.err != "" {
if err.Error() == tt.err {
codehost.Repo
}
-func (ch *fixedTagsRepo) Tags(string) (*codehost.Tags, error) {
+func (ch *fixedTagsRepo) Tags(ctx context.Context, prefix string) (*codehost.Tags, error) {
tags := &codehost.Tags{}
for _, t := range ch.tags {
tags.List = append(tags.List, codehost.Tag{Name: t})
}
func TestNonCanonicalSemver(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+
root := "golang.org/x/issue24476"
ch := &fixedTagsRepo{
tags: []string{
t.Fatal(err)
}
- v, err := cr.Versions("")
+ v, err := cr.Versions(ctx, "")
if err != nil {
t.Fatal(err)
}
// local download cache and returns the name of the directory
// corresponding to the root of the module's file tree.
func Download(ctx context.Context, mod module.Version) (dir string, err error) {
- if err := checkCacheDir(); err != nil {
+ if err := checkCacheDir(ctx); err != nil {
base.Fatalf("go: %v", err)
}
if err != nil {
return "", err
}
- checkMod(mod)
+ checkMod(ctx, mod)
return dir, nil
})
}
ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
defer span.Done()
- dir, err = DownloadDir(mod)
+ dir, err = DownloadDir(ctx, mod)
if err == nil {
// The directory has already been completely extracted (no .partial file exists).
return dir, nil
return "", err
}
- unlock, err := lockVersion(mod)
+ unlock, err := lockVersion(ctx, mod)
if err != nil {
return "", err
}
defer span.Done()
// Check whether the directory was populated while we were waiting on the lock.
- _, dirErr := DownloadDir(mod)
+ _, dirErr := DownloadDir(ctx, mod)
if dirErr == nil {
return dir, nil
}
}
}
- partialPath, err := CachePath(mod, "partial")
+ partialPath, err := CachePath(ctx, mod, "partial")
if err != nil {
return "", err
}
func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
// The par.Cache here avoids duplicate work.
return downloadZipCache.Do(mod, func() (string, error) {
- zipfile, err := CachePath(mod, "zip")
+ zipfile, err := CachePath(ctx, mod, "zip")
if err != nil {
return "", err
}
fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, vers)
}
}
- unlock, err := lockVersion(mod)
+ unlock, err := lockVersion(ctx, mod)
if err != nil {
return "", err
}
// contents of the file (by hashing it) before we commit it. Because the file
// is zip-compressed, we need an actual file — or at least an io.ReaderAt — to
// validate it: we can't just tee the stream as we write it.
- f, err := tempFile(filepath.Dir(zipfile), filepath.Base(zipfile), 0666)
+ f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0666)
if err != nil {
return err
}
if unrecoverableErr != nil {
return unrecoverableErr
}
- repo := Lookup(proxy, mod.Path)
- err := repo.Zip(f, mod.Version)
+ repo := Lookup(ctx, proxy, mod.Path)
+ err := repo.Zip(ctx, f, mod.Version)
if err != nil {
// Zip may have partially written to f before failing.
// (Perhaps the server crashed while sending the file?)
}
// checkMod checks the given module's checksum.
-func checkMod(mod module.Version) {
+func checkMod(ctx context.Context, mod module.Version) {
// Do the file I/O before acquiring the go.sum lock.
- ziphash, err := CachePath(mod, "ziphash")
+ ziphash, err := CachePath(ctx, mod, "ziphash")
if err != nil {
base.Fatalf("verifying %v", module.VersionError(mod, err))
}
data = bytes.TrimSpace(data)
if !isValidSum(data) {
// Recreate ziphash file from zip file and use that to check the mod sum.
- zip, err := CachePath(mod, "zip")
+ zip, err := CachePath(ctx, mod, "zip")
if err != nil {
base.Fatalf("verifying %v", module.VersionError(mod, err))
}
// Sum returns the checksum for the downloaded copy of the given module,
// if present in the download cache.
-func Sum(mod module.Version) string {
+func Sum(ctx context.Context, mod module.Version) string {
if cfg.GOMODCACHE == "" {
// Do not use current directory.
return ""
}
- ziphash, err := CachePath(mod, "ziphash")
+ ziphash, err := CachePath(ctx, mod, "ziphash")
if err != nil {
return ""
}
// It should have entries for both module content sums and go.mod sums
// (version ends with "/go.mod"). Existing sums will be preserved unless they
// have been marked for deletion with TrimGoSum.
-func WriteGoSum(keep map[module.Version]bool, readonly bool) error {
+func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error {
goSum.mu.Lock()
defer goSum.mu.Unlock()
// Make a best-effort attempt to acquire the side lock, only to exclude
// previous versions of the 'go' command from making simultaneous edits.
- if unlock, err := SideLock(); err == nil {
+ if unlock, err := SideLock(ctx); err == nil {
defer unlock()
}
package modfetch
import (
+ "context"
"encoding/json"
"errors"
"fmt"
var errProxyReuse = fmt.Errorf("proxy does not support CheckReuse")
-func (p *proxyRepo) CheckReuse(old *codehost.Origin) error {
+func (p *proxyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
return errProxyReuse
}
}
}
-func (p *proxyRepo) getBytes(path string) ([]byte, error) {
- body, err := p.getBody(path)
+func (p *proxyRepo) getBytes(ctx context.Context, path string) ([]byte, error) {
+ body, err := p.getBody(ctx, path)
if err != nil {
return nil, err
}
return b, nil
}
-func (p *proxyRepo) getBody(path string) (r io.ReadCloser, err error) {
+func (p *proxyRepo) getBody(ctx context.Context, path string) (r io.ReadCloser, err error) {
fullPath := pathpkg.Join(p.url.Path, path)
target := *p.url
return resp.Body, nil
}
-func (p *proxyRepo) Versions(prefix string) (*Versions, error) {
- data, err := p.getBytes("@v/list")
+func (p *proxyRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
+ data, err := p.getBytes(ctx, "@v/list")
if err != nil {
p.listLatestOnce.Do(func() {
p.listLatest, p.listLatestErr = nil, p.versionError("", err)
}
}
p.listLatestOnce.Do(func() {
- p.listLatest, p.listLatestErr = p.latestFromList(allLine)
+ p.listLatest, p.listLatestErr = p.latestFromList(ctx, allLine)
})
semver.Sort(list)
return &Versions{List: list}, nil
}
-func (p *proxyRepo) latest() (*RevInfo, error) {
+func (p *proxyRepo) latest(ctx context.Context) (*RevInfo, error) {
p.listLatestOnce.Do(func() {
- data, err := p.getBytes("@v/list")
+ data, err := p.getBytes(ctx, "@v/list")
if err != nil {
p.listLatestErr = p.versionError("", err)
return
}
list := strings.Split(string(data), "\n")
- p.listLatest, p.listLatestErr = p.latestFromList(list)
+ p.listLatest, p.listLatestErr = p.latestFromList(ctx, list)
})
return p.listLatest, p.listLatestErr
}
-func (p *proxyRepo) latestFromList(allLine []string) (*RevInfo, error) {
+func (p *proxyRepo) latestFromList(ctx context.Context, allLine []string) (*RevInfo, error) {
var (
bestTime time.Time
bestVersion string
}
// Call Stat to get all the other fields, including Origin information.
- return p.Stat(bestVersion)
+ return p.Stat(ctx, bestVersion)
}
-func (p *proxyRepo) Stat(rev string) (*RevInfo, error) {
+func (p *proxyRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
encRev, err := module.EscapeVersion(rev)
if err != nil {
return nil, p.versionError(rev, err)
}
- data, err := p.getBytes("@v/" + encRev + ".info")
+ data, err := p.getBytes(ctx, "@v/"+encRev+".info")
if err != nil {
return nil, p.versionError(rev, err)
}
return info, nil
}
-func (p *proxyRepo) Latest() (*RevInfo, error) {
- data, err := p.getBytes("@latest")
+func (p *proxyRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ data, err := p.getBytes(ctx, "@latest")
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, p.versionError("", err)
}
- return p.latest()
+ return p.latest(ctx)
}
info := new(RevInfo)
if err := json.Unmarshal(data, info); err != nil {
return info, nil
}
-func (p *proxyRepo) GoMod(version string) ([]byte, error) {
+func (p *proxyRepo) GoMod(ctx context.Context, version string) ([]byte, error) {
if version != module.CanonicalVersion(version) {
return nil, p.versionError(version, fmt.Errorf("internal error: version passed to GoMod is not canonical"))
}
if err != nil {
return nil, p.versionError(version, err)
}
- data, err := p.getBytes("@v/" + encVer + ".mod")
+ data, err := p.getBytes(ctx, "@v/"+encVer+".mod")
if err != nil {
return nil, p.versionError(version, err)
}
return data, nil
}
-func (p *proxyRepo) Zip(dst io.Writer, version string) error {
+func (p *proxyRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
if version != module.CanonicalVersion(version) {
return p.versionError(version, fmt.Errorf("internal error: version passed to Zip is not canonical"))
}
return p.versionError(version, err)
}
path := "@v/" + encVer + ".zip"
- body, err := p.getBody(path)
+ body, err := p.getBody(ctx, path)
if err != nil {
return p.versionError(version, err)
}
package modfetch
import (
+ "context"
"fmt"
"io"
"io/fs"
// are still satisfied on the server corresponding to this module.
// If so, the caller can reuse any cached Versions or RevInfo containing
// this origin rather than redownloading those from the server.
- CheckReuse(old *codehost.Origin) error
+ CheckReuse(ctx context.Context, old *codehost.Origin) error
// Versions lists all known versions with the given prefix.
// Pseudo-versions are not included.
//
// If the underlying repository does not exist,
// Versions returns an error matching errors.Is(_, os.NotExist).
- Versions(prefix string) (*Versions, error)
+ Versions(ctx context.Context, prefix string) (*Versions, error)
// Stat returns information about the revision rev.
// A revision can be any identifier known to the underlying service:
// commit hash, branch, tag, and so on.
- Stat(rev string) (*RevInfo, error)
+ Stat(ctx context.Context, rev string) (*RevInfo, error)
// Latest returns the latest revision on the default branch,
// whatever that means in the underlying source code repository.
// It is only used when there are no tagged versions.
- Latest() (*RevInfo, error)
+ Latest(ctx context.Context) (*RevInfo, error)
// GoMod returns the go.mod file for the given version.
- GoMod(version string) (data []byte, err error)
+ GoMod(ctx context.Context, version string) (data []byte, err error)
// Zip writes a zip file for the given version to dst.
- Zip(dst io.Writer, version string) error
+ Zip(ctx context.Context, dst io.Writer, version string) error
}
// A Versions describes the available versions in a module repository.
//
// A successful return does not guarantee that the module
// has any defined versions.
-func Lookup(proxy, path string) Repo {
+func Lookup(ctx context.Context, proxy, path string) Repo {
if traceRepo {
defer logCall("Lookup(%q, %q)", proxy, path)()
}
return lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo {
- return newCachingRepo(path, func() (Repo, error) {
- r, err := lookup(proxy, path)
+ return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) {
+ r, err := lookup(ctx, proxy, path)
if err == nil && traceRepo {
r = newLoggingRepo(r)
}
}
// lookup returns the module with the given module path.
-func lookup(proxy, path string) (r Repo, err error) {
+func lookup(ctx context.Context, proxy, path string) (r Repo, err error) {
if cfg.BuildMod == "vendor" {
return nil, errLookupDisabled
}
if module.MatchPrefixPatterns(cfg.GONOPROXY, path) {
switch proxy {
case "noproxy", "direct":
- return lookupDirect(path)
+ return lookupDirect(ctx, path)
default:
return nil, errNoproxy
}
case "off":
return errRepo{path, errProxyOff}, nil
case "direct":
- return lookupDirect(path)
+ return lookupDirect(ctx, path)
case "noproxy":
return nil, errUseProxy
default:
errUseProxy error = notExistErrorf("path does not match GOPRIVATE/GONOPROXY")
)
-func lookupDirect(path string) (Repo, error) {
+func lookupDirect(ctx context.Context, path string) (Repo, error) {
security := web.SecureOnly
if module.MatchPrefixPatterns(cfg.GOINSECURE, path) {
return newProxyRepo(rr.Repo, path)
}
- code, err := lookupCodeRepo(rr)
+ code, err := lookupCodeRepo(ctx, rr)
if err != nil {
return nil, err
}
return newCodeRepo(code, rr.Root, path)
}
-func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) {
- code, err := codehost.NewRepo(rr.VCS.Cmd, rr.Repo)
+func lookupCodeRepo(ctx context.Context, rr *vcs.RepoRoot) (codehost.Repo, error) {
+ code, err := codehost.NewRepo(ctx, rr.VCS.Cmd, rr.Repo)
if err != nil {
if _, ok := err.(*codehost.VCSError); ok {
return nil, err
return l.r.ModulePath()
}
-func (l *loggingRepo) CheckReuse(old *codehost.Origin) (err error) {
+func (l *loggingRepo) CheckReuse(ctx context.Context, old *codehost.Origin) (err error) {
defer func() {
logCall("CheckReuse[%s]: %v", l.r.ModulePath(), err)
}()
- return l.r.CheckReuse(old)
+ return l.r.CheckReuse(ctx, old)
}
-func (l *loggingRepo) Versions(prefix string) (*Versions, error) {
+func (l *loggingRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)()
- return l.r.Versions(prefix)
+ return l.r.Versions(ctx, prefix)
}
-func (l *loggingRepo) Stat(rev string) (*RevInfo, error) {
+func (l *loggingRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)()
- return l.r.Stat(rev)
+ return l.r.Stat(ctx, rev)
}
-func (l *loggingRepo) Latest() (*RevInfo, error) {
+func (l *loggingRepo) Latest(ctx context.Context) (*RevInfo, error) {
defer logCall("Repo[%s]: Latest()", l.r.ModulePath())()
- return l.r.Latest()
+ return l.r.Latest(ctx)
}
-func (l *loggingRepo) GoMod(version string) ([]byte, error) {
+func (l *loggingRepo) GoMod(ctx context.Context, version string) ([]byte, error) {
defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)()
- return l.r.GoMod(version)
+ return l.r.GoMod(ctx, version)
}
-func (l *loggingRepo) Zip(dst io.Writer, version string) error {
+func (l *loggingRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
dstName := "_"
if dst, ok := dst.(interface{ Name() string }); ok {
dstName = strconv.Quote(dst.Name())
}
defer logCall("Repo[%s]: Zip(%s, %q)", l.r.ModulePath(), dstName, version)()
- return l.r.Zip(dst, version)
+ return l.r.Zip(ctx, dst, version)
}
// errRepo is a Repo that returns the same error for all operations.
func (r errRepo) ModulePath() string { return r.modulePath }
-func (r errRepo) CheckReuse(old *codehost.Origin) error { return r.err }
-func (r errRepo) Versions(prefix string) (*Versions, error) { return nil, r.err }
-func (r errRepo) Stat(rev string) (*RevInfo, error) { return nil, r.err }
-func (r errRepo) Latest() (*RevInfo, error) { return nil, r.err }
-func (r errRepo) GoMod(version string) ([]byte, error) { return nil, r.err }
-func (r errRepo) Zip(dst io.Writer, version string) error { return r.err }
+func (r errRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error { return r.err }
+func (r errRepo) Versions(ctx context.Context, prefix string) (*Versions, error) { return nil, r.err }
+func (r errRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) { return nil, r.err }
+func (r errRepo) Latest(ctx context.Context) (*RevInfo, error) { return nil, r.err }
+func (r errRepo) GoMod(ctx context.Context, version string) ([]byte, error) { return nil, r.err }
+func (r errRepo) Zip(ctx context.Context, dst io.Writer, version string) error { return r.err }
// A notExistError is like fs.ErrNotExist, but with a custom message
type notExistError struct {
name := fmt.Sprintf("%s@%s", strings.ReplaceAll(test.m.Path, "/", "_"), test.m.Version)
t.Run(name, func(t *testing.T) {
t.Parallel()
- zipPath, err := modfetch.DownloadZip(context.Background(), test.m)
+ ctx := context.Background()
+
+ zipPath, err := modfetch.DownloadZip(ctx, test.m)
if err != nil {
if *updateTestData {
t.Logf("%s: could not download module: %s (will remove from testdata)", test.m, err)
return
}
- sum := modfetch.Sum(test.m)
+ sum := modfetch.Sum(ctx, test.m)
if sum != test.wantSum {
if *updateTestData {
t.Logf("%s: updating content sum to %s", test.m, sum)
if m.Version != "" {
if checksumOk("/go.mod") {
- gomod, err := modfetch.CachePath(mod, "mod")
+ gomod, err := modfetch.CachePath(ctx, mod, "mod")
if err == nil {
if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
m.GoMod = gomod
}
}
if checksumOk("") {
- dir, err := modfetch.DownloadDir(mod)
+ dir, err := modfetch.DownloadDir(ctx, mod)
if err == nil {
m.Dir = dir
}
if inWorkspaceMode() {
// go.mod files aren't updated in workspace mode, but we still want to
// update the go.work.sum file.
- return modfetch.WriteGoSum(keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
+ return modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
}
if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
// We aren't in a module, so we don't have anywhere to write a go.mod file.
// Don't write go.mod, but write go.sum in case we added or trimmed sums.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
- if err := modfetch.WriteGoSum(keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ if err := modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
return err
}
}
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
if err == nil {
- err = modfetch.WriteGoSum(keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
+ err = modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
}
}
}()
// Make a best-effort attempt to acquire the side lock, only to exclude
// previous versions of the 'go' command from making simultaneous edits.
- if unlock, err := modfetch.SideLock(); err == nil {
+ if unlock, err := modfetch.SideLock(ctx); err == nil {
defer unlock()
}
// loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion.
- if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
+ if err := modfetch.WriteGoSum(ctx, keep, mustHaveCompleteRequirements()); err != nil {
base.Fatalf("go: %v", err)
}
}
root = filepath.Join(replaceRelativeTo(), root)
}
} else if repl.Path != "" {
- root, err = modfetch.DownloadDir(repl)
+ root, err = modfetch.DownloadDir(ctx, repl)
} else {
- root, err = modfetch.DownloadDir(m)
+ root, err = modfetch.DownloadDir(ctx, m)
}
if err != nil {
return "", false
base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
}
name = "go.mod"
- data, err = modfetch.GoMod(m.Path, m.Version)
+ data, err = modfetch.GoMod(context.TODO(), m.Path, m.Version)
}
return name, data, err
}
// Note: modfetch.Lookup and repo.Versions are cached,
// so there's no need for us to add extra caching here.
err = modfetch.TryProxies(func(proxy string) error {
- repo, err := lookupRepo(proxy, path)
+ repo, err := lookupRepo(ctx, proxy, path)
if err != nil {
return err
}
- allVersions, err := repo.Versions("")
+ allVersions, err := repo.Versions(ctx, "")
if err != nil {
return err
}
// for a given module may be reused, according to the information in origin.
func checkReuse(ctx context.Context, path string, old *codehost.Origin) error {
return modfetch.TryProxies(func(proxy string) error {
- repo, err := lookupRepo(proxy, path)
+ repo, err := lookupRepo(ctx, proxy, path)
if err != nil {
return err
}
- return repo.CheckReuse(old)
+ return repo.CheckReuse(ctx, old)
})
}
return nil, fmt.Errorf("can't query specific version (%q) of standard-library module %q", query, path)
}
- repo, err := lookupRepo(proxy, path)
+ repo, err := lookupRepo(ctx, proxy, path)
if err != nil {
return nil, err
}
if old := reuse[module.Version{Path: path, Version: query}]; old != nil {
- if err := repo.CheckReuse(old.Origin); err == nil {
+ if err := repo.CheckReuse(ctx, old.Origin); err == nil {
info := &modfetch.RevInfo{
Version: old.Version,
Origin: old.Origin,
// If the identifier is not a canonical semver tag — including if it's a
// semver tag with a +metadata suffix — then modfetch.Stat will populate
// info.Version with a suitable pseudo-version.
- info, err := repo.Stat(query)
+ info, err := repo.Stat(ctx, query)
if err != nil {
queryErr := err
// The full query doesn't correspond to a tag. If it is a semantic version
// semantic versioning defines them to be equivalent.
canonicalQuery := module.CanonicalVersion(query)
if canonicalQuery != "" && query != canonicalQuery {
- info, err = repo.Stat(canonicalQuery)
+ info, err = repo.Stat(ctx, canonicalQuery)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return info, err
}
}
// Load versions and execute query.
- versions, err := repo.Versions(qm.prefix)
+ versions, err := repo.Versions(ctx, qm.prefix)
if err != nil {
return nil, err
}
}
lookup := func(v string) (*modfetch.RevInfo, error) {
- rev, err := repo.Stat(v)
+ rev, err := repo.Stat(ctx, v)
// Stat can return a non-nil rev and a non-nil err,
// in order to provide origin information to make the error cacheable.
if rev == nil && err != nil {
if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
return revErr, err
}
- rev, err = repo.Stat(current)
+ rev, err = repo.Stat(ctx, current)
if rev == nil && err != nil {
return revErr, err
}
}
if qm.mayUseLatest {
- latest, err := repo.Latest()
+ latest, err := repo.Latest(ctx)
if err == nil {
if qm.allowsVersion(ctx, latest.Version) {
return lookup(latest.Version)
// available versions, but cannot fetch specific source files.
type versionRepo interface {
ModulePath() string
- CheckReuse(*codehost.Origin) error
- Versions(prefix string) (*modfetch.Versions, error)
- Stat(rev string) (*modfetch.RevInfo, error)
- Latest() (*modfetch.RevInfo, error)
+ CheckReuse(context.Context, *codehost.Origin) error
+ Versions(ctx context.Context, prefix string) (*modfetch.Versions, error)
+ Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error)
+ Latest(context.Context) (*modfetch.RevInfo, error)
}
var _ versionRepo = modfetch.Repo(nil)
-func lookupRepo(proxy, path string) (repo versionRepo, err error) {
+func lookupRepo(ctx context.Context, proxy, path string) (repo versionRepo, err error) {
err = module.CheckPath(path)
if err == nil {
- repo = modfetch.Lookup(proxy, path)
+ repo = modfetch.Lookup(ctx, proxy, path)
} else {
repo = emptyRepo{path: path, err: err}
}
var _ versionRepo = emptyRepo{}
func (er emptyRepo) ModulePath() string { return er.path }
-func (er emptyRepo) CheckReuse(old *codehost.Origin) error {
+func (er emptyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
return fmt.Errorf("empty repo")
}
-func (er emptyRepo) Versions(prefix string) (*modfetch.Versions, error) {
+func (er emptyRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) {
return &modfetch.Versions{}, nil
}
-func (er emptyRepo) Stat(rev string) (*modfetch.RevInfo, error) { return nil, er.err }
-func (er emptyRepo) Latest() (*modfetch.RevInfo, error) { return nil, er.err }
+func (er emptyRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) {
+ return nil, er.err
+}
+func (er emptyRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) { return nil, er.err }
// A replacementRepo augments a versionRepo to include the replacement versions
// (if any) found in the main module's go.mod file.
func (rr *replacementRepo) ModulePath() string { return rr.repo.ModulePath() }
-func (rr *replacementRepo) CheckReuse(old *codehost.Origin) error {
+func (rr *replacementRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
return fmt.Errorf("replacement repo")
}
// Versions returns the versions from rr.repo augmented with any matching
// replacement versions.
-func (rr *replacementRepo) Versions(prefix string) (*modfetch.Versions, error) {
- repoVersions, err := rr.repo.Versions(prefix)
+func (rr *replacementRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) {
+ repoVersions, err := rr.repo.Versions(ctx, prefix)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
return &modfetch.Versions{List: versions}, nil
}
-func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
- info, err := rr.repo.Stat(rev)
+func (rr *replacementRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) {
+ info, err := rr.repo.Stat(ctx, rev)
if err == nil {
return info, err
}
return rr.replacementStat(v)
}
-func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
- info, err := rr.repo.Latest()
+func (rr *replacementRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) {
+ info, err := rr.repo.Latest(ctx)
path := rr.ModulePath()
if v, ok := MainModules.HighestReplaced()[path]; ok {