internal/fetch: ModuleGetter.FS returns the content dir

Move the responsibility for getting the module zip's content directory
from FetchModule to the ModuleGetters.

This will enable a ModuleGetter that reads directly from the on-disk
filesystem.

For golang/go#47834

Change-Id: I035a88680569272845ad12deb09ed94e6a37bf66
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/343961
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/fetch/fetch.go b/internal/fetch/fetch.go
index 563ba5b..5eeae8f 100644
--- a/internal/fetch/fetch.go
+++ b/internal/fetch/fetch.go
@@ -185,7 +185,7 @@
 	}
 	startFetchInfo(fi)
 
-	var fsys fs.FS
+	var contentDir fs.FS
 	if fr.ModulePath == stdlib.ModulePath {
 		var (
 			resolvedVersion string
@@ -195,31 +195,25 @@
 		if err != nil {
 			return fi, err
 		}
-		fsys = zr
+		v := resolvedVersion
+		if stdlib.SupportedBranches[fr.RequestedVersion] {
+			v = fr.RequestedVersion
+		}
+		contentDir, err = fs.Sub(zr, fr.ModulePath+"@"+v)
+		if err != nil {
+			return fi, err
+		}
 		// If the requested version is a branch name like "master" or "main", we cannot
 		// determine the right resolved version until we start working with the repo.
 		fr.ResolvedVersion = resolvedVersion
 		fi.Version = resolvedVersion
 	} else {
-		fsys, err = mg.FS(ctx, fr.ModulePath, fr.ResolvedVersion)
+		contentDir, err = mg.FS(ctx, fr.ModulePath, fr.ResolvedVersion)
 		if err != nil {
 			return fi, err
 		}
 	}
 
-	// Use the content directory of the module filesystem throughout. That is
-	// the "<module>@<resolvedVersion>" directory that all module zips are
-	// expected to have according to the zip archive layout specification at
-	// https://golang.org/ref/mod#zip-files.
-	v := fr.ResolvedVersion
-	if fr.ModulePath == stdlib.ModulePath && stdlib.SupportedBranches[fr.RequestedVersion] {
-		v = fr.RequestedVersion
-	}
-	contentDir, err := fs.Sub(fsys, fr.ModulePath+"@"+v)
-	if err != nil {
-		return fi, err
-	}
-
 	// Set fr.HasGoMod as early as possible, because the go command uses it to
 	// decide the latest version in some cases (see fetchRawLatestVersion in
 	// this package) and all it requires is a valid zip.
diff --git a/internal/fetch/fs_test.go b/internal/fetch/fs_test.go
index 060103f..3290015 100644
--- a/internal/fetch/fs_test.go
+++ b/internal/fetch/fs_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"context"
-	"fmt"
 	"io/ioutil"
 	"testing"
 	"time"
@@ -78,7 +77,7 @@
 			t.Fatal(err)
 		}
 		// Just check that the go.mod file is there and has the right contents.
-		f, err := fsys.Open(fmt.Sprintf("%s@%s/go.mod", modulePath, version))
+		f, err := fsys.Open("go.mod")
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/internal/fetch/getters.go b/internal/fetch/getters.go
index 89591cd..f2b9a07 100644
--- a/internal/fetch/getters.go
+++ b/internal/fetch/getters.go
@@ -31,11 +31,17 @@
 type ModuleGetter interface {
 	// Info returns basic information about the module.
 	Info(ctx context.Context, path, version string) (*proxy.VersionInfo, error)
+
 	// Mod returns the contents of the module's go.mod file.
 	Mod(ctx context.Context, path, version string) ([]byte, error)
-	// FS returns an FS for the module's contents. The FS should match the format
-	// of a module zip file.
+
+	// FS returns an FS for the module's contents. The FS should match the
+	// format of a module zip file's content directory. That is the
+	// "<module>@<resolvedVersion>" directory that all module zips are expected
+	// to have according to the zip archive layout specification at
+	// https://golang.org/ref/mod#zip-files.
 	FS(ctx context.Context, path, version string) (fs.FS, error)
+
 	// ZipSize returns the approximate size of the zip file in bytes.
 	// It is used only for load-shedding.
 	ZipSize(ctx context.Context, path, version string) (int64, error)
@@ -62,7 +68,11 @@
 // FS returns an FS for the module's contents. The FS should match the format
 // of a module zip file.
 func (g *proxyModuleGetter) FS(ctx context.Context, path, version string) (fs.FS, error) {
-	return g.prox.Zip(ctx, path, version)
+	zr, err := g.prox.Zip(ctx, path, version)
+	if err != nil {
+		return nil, err
+	}
+	return fs.Sub(zr, path+"@"+version)
 }
 
 // ZipSize returns the approximate size of the zip file in bytes.
@@ -140,7 +150,11 @@
 	if err := g.checkPath(path); err != nil {
 		return nil, err
 	}
-	return createZipReader(g.dir, path, LocalVersion)
+	zr, err := createZipReader(g.dir, path, LocalVersion)
+	if err != nil {
+		return nil, err
+	}
+	return fs.Sub(zr, path+"@"+LocalVersion)
 }
 
 // ZipSize returns the approximate size of the zip file in bytes.
@@ -231,7 +245,11 @@
 	if err != nil {
 		return nil, err
 	}
-	return zip.NewReader(bytes.NewReader(data), int64(len(data)))
+	zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
+	if err != nil {
+		return nil, err
+	}
+	return fs.Sub(zr, path+"@"+version)
 }
 
 // ZipSize returns the approximate size of the zip file in bytes.