]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add subdirectory support to go-import meta tag
authorSam Thanawalla <samthanawalla@google.com>
Tue, 29 Oct 2024 14:22:11 +0000 (14:22 +0000)
committerGopher Robot <gobot@golang.org>
Thu, 10 Apr 2025 18:14:09 +0000 (11:14 -0700)
This CL adds ability to specify a subdirectory in the go-import meta tag.
A go-import meta tag now will support:
<meta name="go-import" content="root-path vcs repo-url subdir">

Fixes: #34055
Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest
Change-Id: Iedac520f97e0646254cc1bd2f97d5a9a5236829b
Reviewed-on: https://go-review.googlesource.com/c/go/+/625577
Reviewed-by: Michael Matloob <matloob@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Sam Thanawalla <samthanawalla@google.com>

14 files changed:
src/cmd/go/alldocs.go
src/cmd/go/internal/help/helpdoc.go
src/cmd/go/internal/modfetch/coderepo.go
src/cmd/go/internal/modfetch/coderepo_test.go
src/cmd/go/internal/modfetch/repo.go
src/cmd/go/internal/vcs/discovery.go
src/cmd/go/internal/vcs/discovery_test.go
src/cmd/go/internal/vcs/vcs.go
src/cmd/go/internal/vcs/vcs_test.go
src/cmd/go/testdata/script/mod_get_subdir.txt [new file with mode: 0644]
src/cmd/go/testdata/vcstest/git/gitreposubdir.txt [new file with mode: 0644]
src/cmd/go/testdata/vcstest/git/gitreposubdirv2.txt [new file with mode: 0644]
src/cmd/go/testdata/vcstest/go/gitreposubdir.txt [new file with mode: 0644]
src/cmd/go/testdata/vcstest/go/gitreposubdirv2.txt [new file with mode: 0644]

index 18ba20127d3836d4a5bb5ce08703a2cdf92b4b04..e40d981bd44dc02dd0a52f28a524ca69af9f51aa 100644 (file)
 //
 //     <meta name="go-import" content="import-prefix vcs repo-root">
 //
+// Starting in Go 1.25, an optional subdirectory will be recognized by the
+// go command:
+//
+//     <meta name="go-import" content="import-prefix vcs repo-root subdir">
+//
 // 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
 // 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"
 //     <meta name="go-import" content="example.org git https://code.org/r/p/exproj">
 //
 // 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
+//
+//     <meta name="go-import" content="example.org git https://code.org/r/p/exproj foo/subdir">
+//
+// 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.
index 47e5d73dd2a769d362fa0202d0d3796860bb28fe..7f8565a3cbab82948b7f088bac53aa90af0f600f 100644 (file)
@@ -241,6 +241,11 @@ The meta tag has the form:
 
        <meta name="go-import" content="import-prefix vcs repo-root">
 
+Starting in Go 1.25, an optional subdirectory will be recognized by the
+go command:
+
+       <meta name="go-import" content="import-prefix vcs repo-root subdir">
+
 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
        <meta name="go-import" content="example.org git https://code.org/r/p/exproj">
 
 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
+
+       <meta name="go-import" content="example.org git https://code.org/r/p/exproj foo/subdir">
+
+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.
index 1d0c98f3658eddef4047627b311889515f62773b..afed35c970975e99896a84cda5f2bb519919287d 100644 (file)
@@ -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,
index aad78722c09c6a61068a8dc55da5ba251f61e1f3..68594746601a220996aff22f0dfb4f9481fdbb82 100644 (file)
@@ -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)
        }
index c4dbf8342cb68357060dc5263bae28bab044118d..dd707ec264d165c33673f36b063c8a1de82dba4d 100644 (file)
@@ -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) {
index bc2c5a35ac1a0604032c336b5d83041a51ead3b3..8129fd408203c4b9cf6436b7aba0c5d7ab741cc3 100644 (file)
@@ -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)
                }
        }
 
index eb99fdf64c141a91b702996cc730db5942714e98..e03eeaaa4c182c0b55b9e62c6d18263907113894 100644 (file)
@@ -18,15 +18,15 @@ var parseMetaGoImportsTests = []struct {
        {
                `<meta name="go-import" content="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", ""}},
        },
        {
                `<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
                <meta name="go-import" content="baz/quux git http://github.com/rsc/baz/quux">`,
                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 {
                <meta name="go-import" content="foo/bar mod http://github.com/rsc/baz/quux">`,
                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 {
                <meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
                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 {
                <meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">`,
                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 {
                <meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
                </head>`,
                IgnoreMod,
-               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}},
        },
        {
                `<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar">
                <body>`,
                IgnoreMod,
-               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", ""}},
        },
        {
                `<!doctype html><meta name="go-import" content="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 <div style=position:relative>.
                `<!doctype html><title>Page Not Found</title><meta name=go-import content="chitin.io/chitin git https://github.com/chitin-io/chitin"><div style=position:relative>DRAFT</div>`,
                IgnoreMod,
-               []metaImport{{"chitin.io/chitin", "git", "https://github.com/chitin-io/chitin"}},
+               []metaImport{{"chitin.io/chitin", "git", "https://github.com/chitin-io/chitin", ""}},
        },
        {
                `<meta name="go-import" content="myitcv.io git https://github.com/myitcv/x">
                <meta name="go-import" content="myitcv.io/blah2 mod https://raw.githubusercontent.com/myitcv/pubx/master">
                `,
                IgnoreMod,
-               []metaImport{{"myitcv.io", "git", "https://github.com/myitcv/x"}},
+               []metaImport{{"myitcv.io", "git", "https://github.com/myitcv/x", ""}},
        },
        {
                `<meta name="go-import" content="myitcv.io git https://github.com/myitcv/x">
@@ -90,10 +90,20 @@ var parseMetaGoImportsTests = []struct {
                `,
                PreferMod,
                []metaImport{
-                       {"myitcv.io/blah2", "mod", "https://raw.githubusercontent.com/myitcv/pubx/master"},
-                       {"myitcv.io", "git", "https://github.com/myitcv/x"},
+                       {"myitcv.io/blah2", "mod", "https://raw.githubusercontent.com/myitcv/pubx/master", ""},
+                       {"myitcv.io", "git", "https://github.com/myitcv/x", ""},
                },
        },
+       {
+               `<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar subdir">`,
+               IgnoreMod,
+               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", "subdir"}},
+       },
+       {
+               `<meta name="go-import" content="foo/bar git https://github.com/rsc/foo/bar subdir/path">`,
+               IgnoreMod,
+               []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar", "subdir/path"}},
+       },
 }
 
 func TestParseMetaGoImports(t *testing.T) {
index 1d10c7f6e95e4d1c78993031ffb1934abfb597db..acf9404aee1633323b27c3dd824dbbedfbd4482d 100644 (file)
@@ -1056,7 +1056,8 @@ func checkGOVCS(vcs *Cmd, root string) error {
 // RepoRoot describes the repository root for a tree of source code.
 type RepoRoot struct {
        Repo     string // repository URL, including scheme
-       Root     string // import path corresponding to root of repo
+       Root     string // import path corresponding to the SubDir
+       SubDir   string // subdirectory within the repo (empty for root)
        IsCustom bool   // defined by served <meta> tags (as opposed to hard-coded pattern)
        VCS      *Cmd
 }
@@ -1368,6 +1369,7 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
        rr := &RepoRoot{
                Repo:     repoURL,
                Root:     mmi.Prefix,
+               SubDir:   mmi.SubDir,
                IsCustom: true,
                VCS:      vcs,
        }
@@ -1457,9 +1459,9 @@ type fetchResult struct {
 }
 
 // metaImport represents the parsed <meta name="go-import"
-// content="prefix vcs reporoot" /> tags from HTML files.
+// content="prefix vcs reporoot subdir" /> tags from HTML files.
 type metaImport struct {
-       Prefix, VCS, RepoRoot string
+       Prefix, VCS, RepoRoot, SubDir string
 }
 
 // An ImportMismatchError is returned where metaImport/s are present
index 2ce85ea210967fd4966fe497817d04e5eb2f5e26..c1431549481f47c967db02470c100b569e639729 100644 (file)
@@ -425,6 +425,28 @@ func TestMatchGoImport(t *testing.T) {
                        path: "myitcv.io/other",
                        mi:   metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
                },
+               {
+                       imports: []metaImport{
+                               {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
+                       },
+                       path: "example.com/user/foo",
+                       mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
+               },
+               {
+                       imports: []metaImport{
+                               {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "foo/subdir"},
+                       },
+                       path: "example.com/user/foo",
+                       mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "foo/subdir"},
+               },
+               {
+                       imports: []metaImport{
+                               {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
+                               {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: ""},
+                       },
+                       path: "example.com/user/foo",
+                       err:  errors.New("multiple meta tags match import path"),
+               },
        }
 
        for _, test := range tests {
diff --git a/src/cmd/go/testdata/script/mod_get_subdir.txt b/src/cmd/go/testdata/script/mod_get_subdir.txt
new file mode 100644 (file)
index 0000000..d31ecda
--- /dev/null
@@ -0,0 +1,77 @@
+# golang.org/issue/34055
+# Starting in Go 1.25, go-import meta tag support an optional subdirectory paramater.
+# The corresponding go-import meta tag is specified as
+# <meta name="go-import" content="vcs-test.golang.org/go/gitreposubdir git https://vcs-test.golang.org/git/gitreposubdir foo/subdir">
+# and contains the module in vcs-test.golang.org/git/gitreposubdir/foo/subdir.
+# See testdata/vcstest/go/gitreposubdir.txt and testdata/vcstest/git/gitreposubdir.txt
+
+[short] skip 'builds a go program'
+[!git] skip
+
+env GO111MODULE=on
+env GOPROXY=direct
+env GOSUMDB=off
+
+# Get the module without having to specify the subdir.
+cd a
+cp go.mod go.mod.orig
+go get vcs-test.golang.org/go/gitreposubdir@v1.2.3
+exists $GOPATH/pkg/mod/vcs-test.golang.org/go/gitreposubdir@v1.2.3
+go get vcs-test.golang.org/go/gitreposubdirv2/v2@v2.0.0
+exists $GOPATH/pkg/mod/vcs-test.golang.org/go/gitreposubdirv2/v2@v2.0.0
+
+# Import the module without having to specify the subdir.
+cp go.mod.orig go.mod
+go mod tidy
+
+# Run main.go which has the import.
+go run main.go
+stdout 'hello, world'
+stdout 'hello, world v2'
+
+# Fail if subdir is specified in get.
+! go get vcs-test.golang.org/go/gitreposubdir/foo/subdir
+stderr 'module vcs-test.golang.org/go/gitreposubdir@upgrade found \(v1.2.3\), but does not contain package vcs-test.golang.org/go/gitreposubdir/foo/subdir'
+! go get vcs-test.golang.org/go/gitreposubdirv2/v2/foo/subdir
+stderr 'module vcs-test.golang.org/go/gitreposubdirv2/v2@upgrade found \(v2.0.0\), but does not contain package vcs-test.golang.org/go/gitreposubdirv2/v2/foo/subdir'
+
+# Fail if subdir is specified in the import.
+cd ../b
+! go mod tidy
+stderr 'module vcs-test.golang.org/go/gitreposubdir@latest found \(v1.2.3\), but does not contain package vcs-test.golang.org/go/gitreposubdir/foo/subdir'
+stderr 'module vcs-test.golang.org/go/gitreposubdirv2/v2@latest found \(v2.0.0\), but does not contain package vcs-test.golang.org/go/gitreposubdirv2/v2/foo/subdir'
+
+-- a/main.go --
+package main
+
+import (
+    "fmt"
+    "vcs-test.golang.org/go/gitreposubdir"
+    "vcs-test.golang.org/go/gitreposubdirv2/v2"
+)
+
+func main() {
+    fmt.Println(greeter.Hello())
+    fmt.Println(greeterv2.Hello())
+}
+-- a/go.mod --
+module example
+
+go 1.24
+-- b/main.go --
+package main
+
+import (
+    "fmt"
+    "vcs-test.golang.org/go/gitreposubdir/foo/subdir"
+    "vcs-test.golang.org/go/gitreposubdirv2/v2/foo/subdir"
+)
+
+func main() {
+    fmt.Println(greeter.Hello())
+    fmt.Println(greeterv2.Hello())
+}
+-- b/go.mod --
+module example
+
+go 1.24
diff --git a/src/cmd/go/testdata/vcstest/git/gitreposubdir.txt b/src/cmd/go/testdata/vcstest/git/gitreposubdir.txt
new file mode 100644 (file)
index 0000000..f9ba762
--- /dev/null
@@ -0,0 +1,26 @@
+handle git
+
+env GIT_AUTHOR_NAME='Sam Thanawalla'
+env GIT_AUTHOR_EMAIL='samthanawalla@google.com'
+env GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME
+env GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL
+
+at 2019-10-07T14:15:32-04:00
+
+git init
+
+git add foo/subdir
+git commit -m 'initial commit'
+git branch -m master
+git tag foo/subdir/v1.2.3
+
+-- foo/subdir/go.mod --
+module vcs-test.golang.org/go/gitreposubdir
+
+go 1.23
+-- foo/subdir/hello.go --
+package greeter
+
+func Hello() string {
+       return "hello, world"
+}
diff --git a/src/cmd/go/testdata/vcstest/git/gitreposubdirv2.txt b/src/cmd/go/testdata/vcstest/git/gitreposubdirv2.txt
new file mode 100644 (file)
index 0000000..35b2f9e
--- /dev/null
@@ -0,0 +1,31 @@
+handle git
+
+env GIT_AUTHOR_NAME='Sam Thanawalla'
+env GIT_AUTHOR_EMAIL='samthanawalla@google.com'
+env GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME
+env GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL
+
+at 2019-10-07T14:15:32-04:00
+
+git init
+
+git add subdir
+git commit -m 'initial commit'
+git branch -m master
+git tag subdir/v2.0.0
+git show-ref --tags --heads
+cmp stdout .git-refs
+
+-- .git-refs --
+5212d800bfd1f6377da46aee6cbceca2f60d4ea6 refs/heads/master
+5212d800bfd1f6377da46aee6cbceca2f60d4ea6 refs/tags/subdir/v2.0.0
+-- subdir/go.mod --
+module vcs-test.golang.org/go/gitreposubdirv2/v2
+
+go 1.23
+-- subdir/hello.go --
+package greeterv2
+
+func Hello() string {
+       return "hello, world v2"
+}
diff --git a/src/cmd/go/testdata/vcstest/go/gitreposubdir.txt b/src/cmd/go/testdata/vcstest/go/gitreposubdir.txt
new file mode 100644 (file)
index 0000000..53a1607
--- /dev/null
@@ -0,0 +1,6 @@
+handle dir
+
+-- index.html --
+<!DOCTYPE html>
+<html>
+<meta name="go-import" content="vcs-test.golang.org/go/gitreposubdir git https://vcs-test.golang.org/git/gitreposubdir foo/subdir">
diff --git a/src/cmd/go/testdata/vcstest/go/gitreposubdirv2.txt b/src/cmd/go/testdata/vcstest/go/gitreposubdirv2.txt
new file mode 100644 (file)
index 0000000..da77226
--- /dev/null
@@ -0,0 +1,6 @@
+handle dir
+
+-- v2/index.html --
+<!DOCTYPE html>
+<html>
+<meta name="go-import" content="vcs-test.golang.org/go/gitreposubdirv2/v2 git https://vcs-test.golang.org/git/gitreposubdirv2 subdir">