diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 18ba20127d..e40d981bd4 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -2952,6 +2952,11 @@ // // // +// Starting in Go 1.25, an optional subdirectory will be recognized by the +// go command: +// +// +// // The import-prefix is the import path corresponding to the repository // root. It must be a prefix or an exact match of the package being // fetched with "go get". If it's not an exact match, another http @@ -2966,6 +2971,12 @@ // The repo-root is the root of the version control system // containing a scheme and not containing a .vcs qualifier. // +// The subdir specifies the directory within the repo-root where the +// Go module's root (including its go.mod file) is located. It allows +// you to organize your repository with the Go module code in a subdirectory +// rather than directly at the repository's root. +// If set, all vcs tags must be prefixed with "subdir". i.e. "subdir/v1.2.3" +// // For example, // // import "example.org/pkg/foo" @@ -2980,8 +2991,15 @@ // // // the go tool will verify that https://example.org/?go-get=1 contains the -// same meta tag and then git clone https://code.org/r/p/exproj into -// GOPATH/src/example.org. +// same meta tag and then download the code from the Git repository at https://code.org/r/p/exproj +// +// If that page contains the meta tag +// +// +// +// the go tool will verify that https://example.org/?go-get=1 contains the same meta +// tag and then download the code from the "foo/subdir" subdirectory within the Git repository +// at https://code.org/r/p/exproj // // Downloaded packages are stored in the module cache. // See https://golang.org/ref/mod#module-cache. diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go index 47e5d73dd2..7f8565a3cb 100644 --- a/src/cmd/go/internal/help/helpdoc.go +++ b/src/cmd/go/internal/help/helpdoc.go @@ -241,6 +241,11 @@ The meta tag has the form: +Starting in Go 1.25, an optional subdirectory will be recognized by the +go command: + + + The import-prefix is the import path corresponding to the repository root. It must be a prefix or an exact match of the package being fetched with "go get". If it's not an exact match, another http @@ -255,6 +260,12 @@ The vcs is one of "bzr", "fossil", "git", "hg", "svn". The repo-root is the root of the version control system containing a scheme and not containing a .vcs qualifier. +The subdir specifies the directory within the repo-root where the +Go module's root (including its go.mod file) is located. It allows +you to organize your repository with the Go module code in a subdirectory +rather than directly at the repository's root. +If set, all vcs tags must be prefixed with "subdir". i.e. "subdir/v1.2.3" + For example, import "example.org/pkg/foo" @@ -269,8 +280,15 @@ If that page contains the meta tag the go tool will verify that https://example.org/?go-get=1 contains the -same meta tag and then git clone https://code.org/r/p/exproj into -GOPATH/src/example.org. +same meta tag and then download the code from the Git repository at https://code.org/r/p/exproj + +If that page contains the meta tag + + + +the go tool will verify that https://example.org/?go-get=1 contains the same meta +tag and then download the code from the "foo/subdir" subdirectory within the Git repository +at https://code.org/r/p/exproj Downloaded packages are stored in the module cache. See https://golang.org/ref/mod#module-cache. diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go index 1d0c98f365..afed35c970 100644 --- a/src/cmd/go/internal/modfetch/coderepo.go +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -59,9 +59,11 @@ type codeRepo struct { } // newCodeRepo returns a Repo that reads the source code for the module with the -// given path, from the repo stored in code, with the root of the repo -// containing the path given by codeRoot. -func newCodeRepo(code codehost.Repo, codeRoot, path string) (Repo, error) { +// given path, from the repo stored in code. +// codeRoot gives the import path corresponding to the root of the repository, +// and subdir gives the subdirectory within the repo containing the module. +// If subdir is empty, the module is at the root of the repo. +func newCodeRepo(code codehost.Repo, codeRoot, subdir, path string) (Repo, error) { if !hasPathPrefix(path, codeRoot) { return nil, fmt.Errorf("mismatched repo: found %s for %s", codeRoot, path) } @@ -108,6 +110,16 @@ func newCodeRepo(code codehost.Repo, codeRoot, path string) (Repo, error) { // pathMajor = .v2 // pseudoMajor = v2 // + // Starting in 1.25, subdir may be passed in by the go-import meta tag. + // So it may be the case that: + // path = github.com/rsc/foo/v2 + // codeRoot = github.com/rsc/foo + // subdir = bar/subdir + // pathPrefix = github.com/rsc/foo + // pathMajor = /v2 + // pseudoMajor = v2 + // which means that codeDir = bar/subdir + codeDir := "" if codeRoot != path { if !hasPathPrefix(pathPrefix, codeRoot) { @@ -115,6 +127,9 @@ func newCodeRepo(code codehost.Repo, codeRoot, path string) (Repo, error) { } codeDir = strings.Trim(pathPrefix[len(codeRoot):], "/") } + if subdir != "" { + codeDir = filepath.ToSlash(filepath.Join(codeDir, subdir)) + } r := &codeRepo{ modPath: path, diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go index aad78722c0..6859474660 100644 --- a/src/cmd/go/internal/modfetch/coderepo_test.go +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -884,6 +884,16 @@ var latestTests = []struct { path: "swtch.com/testmod", version: "v1.1.1", }, + { + vcs: "git", + path: "vcs-test.golang.org/go/gitreposubdir", + version: "v1.2.3", + }, + { + vcs: "git", + path: "vcs-test.golang.org/go/gitreposubdirv2/v2", + version: "v2.0.0", + }, } func TestLatest(t *testing.T) { @@ -950,7 +960,7 @@ func TestNonCanonicalSemver(t *testing.T) { }, } - cr, err := newCodeRepo(ch, root, root) + cr, err := newCodeRepo(ch, root, "", root) if err != nil { t.Fatal(err) } diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index c4dbf8342c..dd707ec264 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -240,7 +240,7 @@ func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) if err != nil { return nil, err } - r, err := newCodeRepo(code, codeRoot, path) + r, err := newCodeRepo(code, codeRoot, "", path) if err == nil && traceRepo { r = newLoggingRepo(r) } @@ -319,7 +319,7 @@ func lookupDirect(ctx context.Context, path string) (Repo, error) { if err != nil { return nil, err } - return newCodeRepo(code, rr.Root, path) + return newCodeRepo(code, rr.Root, rr.SubDir, path) } func lookupCodeRepo(ctx context.Context, rr *vcs.RepoRoot, local bool) (codehost.Repo, error) { diff --git a/src/cmd/go/internal/vcs/discovery.go b/src/cmd/go/internal/vcs/discovery.go index bc2c5a35ac..8129fd4082 100644 --- a/src/cmd/go/internal/vcs/discovery.go +++ b/src/cmd/go/internal/vcs/discovery.go @@ -54,12 +54,17 @@ func parseMetaGoImports(r io.Reader, mod ModuleMode) ([]metaImport, error) { if attrValue(e.Attr, "name") != "go-import" { continue } - if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 { - imports = append(imports, metaImport{ + if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 || len(f) == 4 { + mi := metaImport{ Prefix: f[0], VCS: f[1], RepoRoot: f[2], - }) + } + // An optional subdirectory may be provided. + if len(f) == 4 { + mi.SubDir = f[3] + } + imports = append(imports, mi) } } diff --git a/src/cmd/go/internal/vcs/discovery_test.go b/src/cmd/go/internal/vcs/discovery_test.go index eb99fdf64c..e03eeaaa4c 100644 --- a/src/cmd/go/internal/vcs/discovery_test.go +++ b/src/cmd/go/internal/vcs/discovery_test.go @@ -18,15 +18,15 @@ var parseMetaGoImportsTests = []struct { { ``, IgnoreMod, - []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}}, + []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}}, }, { ` `, IgnoreMod, []metaImport{ - {"foo/bar", "git", "https://github.com/rsc/foo/bar"}, - {"baz/quux", "git", "http://github.com/rsc/baz/quux"}, + {"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}, + {"baz/quux", "git", "http://github.com/rsc/baz/quux", ""}, }, }, { @@ -34,7 +34,7 @@ var parseMetaGoImportsTests = []struct { `, IgnoreMod, []metaImport{ - {"foo/bar", "git", "https://github.com/rsc/foo/bar"}, + {"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}, }, }, { @@ -42,7 +42,7 @@ var parseMetaGoImportsTests = []struct { `, IgnoreMod, []metaImport{ - {"foo/bar", "git", "https://github.com/rsc/foo/bar"}, + {"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}, }, }, { @@ -50,7 +50,7 @@ var parseMetaGoImportsTests = []struct { `, PreferMod, []metaImport{ - {"foo/bar", "mod", "http://github.com/rsc/baz/quux"}, + {"foo/bar", "mod", "http://github.com/rsc/baz/quux", ""}, }, }, { @@ -58,31 +58,31 @@ var parseMetaGoImportsTests = []struct { `, IgnoreMod, - []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}}, + []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}}, }, { `
`, IgnoreMod, - []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}}, + []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}}, }, { ``, IgnoreMod, - []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}}, + []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}}, }, { // XML doesn't like