aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cmd/go/internal/modcmd/edit.go10
-rw-r--r--src/cmd/go/internal/modfetch/codehost/git.go20
-rw-r--r--src/cmd/go/internal/modfetch/codehost/vcs.go26
-rw-r--r--src/cmd/go/internal/modget/query.go5
-rw-r--r--src/cmd/go/internal/modload/build.go12
-rw-r--r--src/cmd/go/internal/modload/list.go30
-rw-r--r--src/cmd/go/internal/toolchain/select.go7
-rw-r--r--src/cmd/go/internal/vcs/vcs.go353
-rw-r--r--src/cmd/go/internal/vcs/vcs_test.go36
-rw-r--r--src/cmd/go/internal/workcmd/edit.go5
10 files changed, 157 insertions, 347 deletions
diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go
index 69ebb14813..cd15b822ad 100644
--- a/src/cmd/go/internal/modcmd/edit.go
+++ b/src/cmd/go/internal/modcmd/edit.go
@@ -328,7 +328,10 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) {
// parsePathVersion parses -flag=arg expecting arg to be path@version.
func parsePathVersion(flag, arg string) (path, version string) {
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: -%s=%s: %v", flag, arg, err)
+ }
if !found {
base.Fatalf("go: -%s=%s: need path@version", flag, arg)
}
@@ -362,7 +365,10 @@ func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version
if allowDirPath && modfile.IsDirectoryPath(arg) {
return arg, "", nil
}
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ return "", "", err
+ }
if !found {
path = arg
} else {
diff --git a/src/cmd/go/internal/modfetch/codehost/git.go b/src/cmd/go/internal/modfetch/codehost/git.go
index 3d50dca3c4..6eb61772db 100644
--- a/src/cmd/go/internal/modfetch/codehost/git.go
+++ b/src/cmd/go/internal/modfetch/codehost/git.go
@@ -261,7 +261,7 @@ func (r *gitRepo) loadRefs(ctx context.Context) (map[string]string, error) {
r.refsErr = err
return
}
- out, gitErr := r.runGit(ctx, "git", "ls-remote", "-q", r.remote)
+ out, gitErr := r.runGit(ctx, "git", "ls-remote", "-q", "--end-of-options", r.remote)
release()
if gitErr != nil {
@@ -530,7 +530,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
if fromTag && !slices.Contains(info.Tags, tag) {
// The local repo includes the commit hash we want, but it is missing
// the corresponding tag. Add that tag and try again.
- _, err := r.runGit(ctx, "git", "tag", tag, hash)
+ _, err := r.runGit(ctx, "git", "tag", "--end-of-options", tag, hash)
if err != nil {
return nil, err
}
@@ -579,7 +579,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
// an apparent Git bug introduced in Git 2.21 (commit 61c771),
// which causes the handler for protocol version 1 to sometimes miss
// tags that point to the requested commit (see https://go.dev/issue/56881).
- _, err = r.runGit(ctx, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", r.remote, refspec)
+ _, err = r.runGit(ctx, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", "--end-of-options", r.remote, refspec)
release()
if err == nil {
@@ -625,12 +625,12 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
}
defer release()
- if _, err := r.runGit(ctx, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
+ if _, err := r.runGit(ctx, "git", "fetch", "-f", "--end-of-options", 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 := r.runGit(ctx, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
+ if _, err := r.runGit(ctx, "git", "fetch", "--unshallow", "-f", "--end-of-options", r.remote); err != nil {
return err
}
}
@@ -643,7 +643,7 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
// statLocal returns a new RevInfo describing rev in the local git repository.
// It uses version as info.Version.
func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo, error) {
- out, err := r.runGit(ctx, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
+ out, err := r.runGit(ctx, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", "--end-of-options", rev, "--")
if err != nil {
// Return info with Origin.RepoSum if possible to allow caching of negative lookup.
var info *RevInfo
@@ -733,7 +733,7 @@ func (r *gitRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64)
if err != nil {
return nil, err
}
- out, err := r.runGit(ctx, "git", "cat-file", "blob", info.Name+":"+file)
+ out, err := r.runGit(ctx, "git", "cat-file", "--end-of-options", "blob", info.Name+":"+file)
if err != nil {
return nil, fs.ErrNotExist
}
@@ -751,7 +751,7 @@ func (r *gitRepo) RecentTag(ctx context.Context, rev, prefix string, allowed fun
// result is definitive.
describe := func() (definitive bool) {
var out []byte
- out, err = r.runGit(ctx, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
+ out, err = r.runGit(ctx, "git", "for-each-ref", "--format=%(refname)", "--merged="+rev)
if err != nil {
return true
}
@@ -903,7 +903,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// TODO: Use maxSize or drop it.
args := []string{}
if subdir != "" {
- args = append(args, "--", subdir)
+ args = append(args, subdir)
}
info, err := r.Stat(ctx, rev) // download rev into local git repo
if err != nil {
@@ -925,7 +925,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// 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 := r.runGit(ctx, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
+ archive, err := r.runGit(ctx, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", "--end-of-options", info.Name, args)
if err != nil {
if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
return nil, fs.ErrNotExist
diff --git a/src/cmd/go/internal/modfetch/codehost/vcs.go b/src/cmd/go/internal/modfetch/codehost/vcs.go
index aae1a60bfa..59264a3b90 100644
--- a/src/cmd/go/internal/modfetch/codehost/vcs.go
+++ b/src/cmd/go/internal/modfetch/codehost/vcs.go
@@ -188,6 +188,7 @@ var vcsCmds = map[string]*vcsCmd{
"hg",
"--config=extensions.goreposum=" + filepath.Join(cfg.GOROOT, "lib/hg/goreposum.py"),
"goreposum",
+ "--",
remote,
}
},
@@ -196,6 +197,7 @@ var vcsCmds = map[string]*vcsCmd{
"hg",
"--config=extensions.goreposum=" + filepath.Join(cfg.GOROOT, "lib/hg/goreposum.py"),
"golookup",
+ "--",
remote,
ref,
}
@@ -216,26 +218,26 @@ var vcsCmds = map[string]*vcsCmd{
branchRE: re(`(?m)^[^\n]+$`),
badLocalRevRE: re(`(?m)^(tip)$`),
statLocal: func(rev, remote string) []string {
- return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
+ return []string{"hg", "log", "-l1", fmt.Sprintf("--rev=%s", rev), "--template", "{node} {date|hgdate} {tags}"}
},
parseStat: hgParseStat,
fetch: []string{"hg", "pull", "-f"},
latest: "tip",
descendsFrom: func(rev, tag string) []string {
- return []string{"hg", "log", "-r", "ancestors(" + rev + ") and " + tag}
+ return []string{"hg", "log", "--rev=ancestors(" + rev + ") and " + tag}
},
recentTags: func(rev string) []string {
- return []string{"hg", "log", "-r", "ancestors(" + rev + ") and tag()", "--template", "{tags}\n"}
+ return []string{"hg", "log", "--rev=ancestors(" + rev + ") and tag()", "--template", "{tags}\n"}
},
readFile: func(rev, file, remote string) []string {
- return []string{"hg", "cat", "-r", rev, file}
+ return []string{"hg", "cat", fmt.Sprintf("--rev=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
pattern := []string{}
if subdir != "" {
- pattern = []string{"-I", subdir + "/**"}
+ pattern = []string{fmt.Sprintf("--include=%s", subdir+"/**")}
}
- return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, "--", target)
+ return str.StringList("hg", "archive", "-t", "zip", "--no-decode", fmt.Sprintf("--rev=%s", rev), "--prefix=prefix/", pattern, "--", target)
},
},
@@ -275,19 +277,19 @@ var vcsCmds = map[string]*vcsCmd{
tagRE: re(`(?m)^\S+`),
badLocalRevRE: re(`^revno:-`),
statLocal: func(rev, remote string) []string {
- return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
+ return []string{"bzr", "log", "-l1", "--long", "--show-ids", fmt.Sprintf("--revision=%s", rev)}
},
parseStat: bzrParseStat,
latest: "revno:-1",
readFile: func(rev, file, remote string) []string {
- return []string{"bzr", "cat", "-r", rev, file}
+ return []string{"bzr", "cat", fmt.Sprintf("--revision=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" {
extra = []string{"./" + subdir}
}
- return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", "--", target, extra)
+ return str.StringList("bzr", "export", "--format=zip", fmt.Sprintf("--revision=%s", rev), "--root=prefix/", "--", target, extra)
},
},
@@ -302,17 +304,17 @@ var vcsCmds = map[string]*vcsCmd{
},
tagRE: re(`XXXTODO`),
statLocal: func(rev, remote string) []string {
- return []string{"fossil", "info", "-R", ".fossil", rev}
+ return []string{"fossil", "info", "-R", ".fossil", "--", rev}
},
parseStat: fossilParseStat,
latest: "trunk",
readFile: func(rev, file, remote string) []string {
- return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
+ return []string{"fossil", "cat", "-R", ".fossil", fmt.Sprintf("-r=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
- extra = []string{"--include", subdir}
+ extra = []string{fmt.Sprintf("--include=%s", subdir)}
}
// Note that vcsRepo.ReadZip below rewrites this command
// to run in a different directory, to work around a fossil bug.
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
index 3086dbc1ad..e9807edda5 100644
--- a/src/cmd/go/internal/modget/query.go
+++ b/src/cmd/go/internal/modget/query.go
@@ -140,7 +140,10 @@ func errSet(err error) pathSet { return pathSet{err: err} }
// newQuery returns a new query parsed from the raw argument,
// which must be either path or path@version.
func newQuery(loaderstate *modload.State, raw string) (*query, error) {
- pattern, rawVers, found := strings.Cut(raw, "@")
+ pattern, rawVers, found, err := modload.ParsePathVersion(raw)
+ if err != nil {
+ return nil, err
+ }
if found && (strings.Contains(rawVers, "@") || rawVers == "") {
return nil, fmt.Errorf("invalid module version syntax %q", raw)
}
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index b560dd6a61..cb4371357c 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -12,7 +12,6 @@ import (
"io/fs"
"os"
"path/filepath"
- "strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -88,7 +87,16 @@ func ModuleInfo(loaderstate *State, ctx context.Context, path string) *modinfo.M
return nil
}
- if path, vers, found := strings.Cut(path, "@"); found {
+ path, vers, found, err := ParsePathVersion(path)
+ if err != nil {
+ return &modinfo.ModulePublic{
+ Path: path,
+ Error: &modinfo.ModuleError{
+ Err: err.Error(),
+ },
+ }
+ }
+ if found {
m := module.Version{Path: path, Version: vers}
return moduleInfo(loaderstate, ctx, nil, m, 0, nil)
}
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index 316fda4003..32f8c5fe3d 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -150,7 +150,11 @@ func listModules(loaderstate *State, ctx context.Context, rs *Requirements, args
}
continue
}
- if path, vers, found := strings.Cut(arg, "@"); found {
+ path, vers, found, err := ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ if found {
if vers == "upgrade" || vers == "patch" {
if _, ok := rs.rootSelected(loaderstate, path); !ok || rs.pruning == unpruned {
needFullGraph = true
@@ -176,7 +180,11 @@ func listModules(loaderstate *State, ctx context.Context, rs *Requirements, args
matchedModule := map[module.Version]bool{}
for _, arg := range args {
- if path, vers, found := strings.Cut(arg, "@"); found {
+ path, vers, found, err := ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ if found {
var current string
if mg == nil {
current, _ = rs.rootSelected(loaderstate, path)
@@ -317,3 +325,21 @@ func modinfoError(path, vers string, err error) *modinfo.ModuleError {
return &modinfo.ModuleError{Err: err.Error()}
}
+
+// ParsePathVersion parses arg expecting arg to be path@version. If there is no
+// '@' in arg, found is false, vers is "", and path is arg. This mirrors the
+// typical usage of strings.Cut. ParsePathVersion is meant to be a general
+// replacement for strings.Cut in module version parsing. If the version is
+// invalid, an error is returned. The version is considered invalid if it is
+// prefixed with '-' or '/', which can cause security problems when constructing
+// commands to execute that use the version.
+func ParsePathVersion(arg string) (path, vers string, found bool, err error) {
+ path, vers, found = strings.Cut(arg, "@")
+ if !found {
+ return arg, "", false, nil
+ }
+ if len(vers) > 0 && (vers[0] == '-' || vers[0] == '/') {
+ return "", "", false, fmt.Errorf("invalid module version %q", vers)
+ }
+ return path, vers, true, nil
+}
diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go
index 192fb62fc2..0cb93f67e1 100644
--- a/src/cmd/go/internal/toolchain/select.go
+++ b/src/cmd/go/internal/toolchain/select.go
@@ -667,7 +667,10 @@ func maybeSwitchForGoInstallVersion(loaderstate *modload.State, minVers string)
if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
return
}
- path, version, _ := strings.Cut(pkgArg, "@")
+ path, version, _, err := modload.ParsePathVersion(pkgArg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
if path == "" || version == "" || gover.IsToolchain(path) {
return
}
@@ -702,7 +705,7 @@ func maybeSwitchForGoInstallVersion(loaderstate *modload.State, minVers string)
allowed = nil
}
noneSelected := func(path string) (version string) { return "none" }
- _, err := modload.QueryPackages(loaderstate, ctx, path, version, noneSelected, allowed)
+ _, err = modload.QueryPackages(loaderstate, ctx, path, version, noneSelected, allowed)
if errors.Is(err, gover.ErrTooNew) {
// Run early switch, same one go install or go run would eventually do,
// if it understood all the command-line flags.
diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
index 7c198c5f2b..9e8efaf206 100644
--- a/src/cmd/go/internal/vcs/vcs.go
+++ b/src/cmd/go/internal/vcs/vcs.go
@@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"path/filepath"
- "regexp"
"strconv"
"strings"
"sync"
@@ -41,20 +40,10 @@ type Cmd struct {
Env []string // any environment values to set/override
RootNames []rootName // filename and mode indicating the root of a checkout directory
- CreateCmd []string // commands to download a fresh copy of a repository
- DownloadCmd []string // commands to download updates into an existing repository
-
- TagCmd []tagCmd // commands to list tags
- TagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
- TagSyncCmd []string // commands to sync to specific tag
- TagSyncDefault []string // commands to sync to default tag
-
Scheme []string
PingCmd string
- RemoteRepo func(v *Cmd, rootDir string) (remoteRepo string, err error)
- ResolveRepo func(v *Cmd, rootDir, remoteRepo string) (realRepo string, err error)
- Status func(v *Cmd, rootDir string) (Status, error)
+ Status func(v *Cmd, rootDir string) (Status, error)
}
// Status is the current state of a local repository.
@@ -157,40 +146,16 @@ var vcsHg = &Cmd{
Name: "Mercurial",
Cmd: "hg",
- // HGPLAIN=1 turns off additional output that a user may have enabled via
- // config options or certain extensions.
- Env: []string{"HGPLAIN=1"},
+ // HGPLAIN=+strictflags turns off additional output that a user may have
+ // enabled via config options or certain extensions.
+ Env: []string{"HGPLAIN=+strictflags"},
RootNames: []rootName{
{filename: ".hg", isDir: true},
},
- CreateCmd: []string{"clone -U -- {repo} {dir}"},
- DownloadCmd: []string{"pull"},
-
- // We allow both tag and branch names as 'tags'
- // for selecting a version. This lets people have
- // a go.release.r60 branch and a go1 branch
- // and make changes in both, without constantly
- // editing .hgtags.
- TagCmd: []tagCmd{
- {"tags", `^(\S+)`},
- {"branches", `^(\S+)`},
- },
- TagSyncCmd: []string{"update -r {tag}"},
- TagSyncDefault: []string{"update default"},
-
- Scheme: []string{"https", "http", "ssh"},
- PingCmd: "identify -- {scheme}://{repo}",
- RemoteRepo: hgRemoteRepo,
- Status: hgStatus,
-}
-
-func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsHg.runOutput(rootDir, "paths default")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
+ Scheme: []string{"https", "http", "ssh"},
+ PingCmd: "identify -- {scheme}://{repo}",
+ Status: hgStatus,
}
func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
@@ -253,25 +218,6 @@ var vcsGit = &Cmd{
{filename: ".git", isDir: true},
},
- CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
- DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
-
- TagCmd: []tagCmd{
- // tags/xxx matches a git tag named xxx
- // origin/xxx matches a git branch named xxx on the default remote repository
- {"show-ref", `(?:tags|origin)/(\S+)$`},
- },
- TagLookupCmd: []tagCmd{
- {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
- },
- TagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
- // both createCmd and downloadCmd update the working dir.
- // No need to do more here. We used to 'checkout master'
- // but that doesn't work if the default branch is not named master.
- // DO NOT add 'checkout master' here.
- // See golang.org/issue/9032.
- TagSyncDefault: []string{"submodule update --init --recursive"},
-
Scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
// Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
@@ -280,54 +226,7 @@ var vcsGit = &Cmd{
// See golang.org/issue/33836.
PingCmd: "ls-remote {scheme}://{repo}",
- RemoteRepo: gitRemoteRepo,
- Status: gitStatus,
-}
-
-// scpSyntaxRe matches the SCP-like addresses used by Git to access
-// repositories by SSH.
-var scpSyntaxRe = lazyregexp.New(`^(\w+)@([\w.-]+):(.*)$`)
-
-func gitRemoteRepo(vcsGit *Cmd, rootDir string) (remoteRepo string, err error) {
- const cmd = "config remote.origin.url"
- outb, err := vcsGit.run1(rootDir, cmd, nil, false)
- if err != nil {
- // if it doesn't output any message, it means the config argument is correct,
- // but the config value itself doesn't exist
- if outb != nil && len(outb) == 0 {
- return "", errors.New("remote origin not found")
- }
- return "", err
- }
- out := strings.TrimSpace(string(outb))
-
- var repoURL *urlpkg.URL
- if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
- // Match SCP-like syntax and convert it to a URL.
- // Eg, "git@github.com:user/repo" becomes
- // "ssh://git@github.com/user/repo".
- repoURL = &urlpkg.URL{
- Scheme: "ssh",
- User: urlpkg.User(m[1]),
- Host: m[2],
- Path: m[3],
- }
- } else {
- repoURL, err = urlpkg.Parse(out)
- if err != nil {
- return "", err
- }
- }
-
- // Iterate over insecure schemes too, because this function simply
- // reports the state of the repo. If we can't see insecure schemes then
- // we can't report the actual repo URL.
- for _, s := range vcsGit.Scheme {
- if repoURL.Scheme == s {
- return repoURL.String(), nil
- }
- }
- return "", errors.New("unable to parse output of git " + cmd)
+ Status: gitStatus,
}
func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
@@ -367,62 +266,9 @@ var vcsBzr = &Cmd{
{filename: ".bzr", isDir: true},
},
- CreateCmd: []string{"branch -- {repo} {dir}"},
-
- // Without --overwrite bzr will not pull tags that changed.
- // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
- DownloadCmd: []string{"pull --overwrite"},
-
- TagCmd: []tagCmd{{"tags", `^(\S+)`}},
- TagSyncCmd: []string{"update -r {tag}"},
- TagSyncDefault: []string{"update -r revno:-1"},
-
- Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
- PingCmd: "info -- {scheme}://{repo}",
- RemoteRepo: bzrRemoteRepo,
- ResolveRepo: bzrResolveRepo,
- Status: bzrStatus,
-}
-
-func bzrRemoteRepo(vcsBzr *Cmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(outb)), nil
-}
-
-func bzrResolveRepo(vcsBzr *Cmd, rootDir, remoteRepo string) (realRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- // ...
- // (branch root|repository branch): <URL>
- // ...
-
- found := false
- for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
- i := strings.Index(out, prefix)
- if i >= 0 {
- out = out[i+len(prefix):]
- found = true
- break
- }
- }
- if !found {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
-
- i := strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
+ Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
+ Status: bzrStatus,
}
func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
@@ -490,46 +336,12 @@ var vcsSvn = &Cmd{
{filename: ".svn", isDir: true},
},
- CreateCmd: []string{"checkout -- {repo} {dir}"},
- DownloadCmd: []string{"update"},
-
// There is no tag command in subversion.
// The branch information is all in the path names.
- Scheme: []string{"https", "http", "svn", "svn+ssh"},
- PingCmd: "info -- {scheme}://{repo}",
- RemoteRepo: svnRemoteRepo,
- Status: svnStatus,
-}
-
-func svnRemoteRepo(vcsSvn *Cmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsSvn.runOutput(rootDir, "info")
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- //
- // ...
- // URL: <URL>
- // ...
- //
- // Note that we're not using the Repository Root line,
- // because svn allows checking out subtrees.
- // The URL will be the URL of the subtree (what we used with 'svn co')
- // while the Repository Root may be a much higher parent.
- i := strings.Index(out, "\nURL: ")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[i+len("\nURL: "):]
- i = strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
+ Scheme: []string{"https", "http", "svn", "svn+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
+ Status: svnStatus,
}
func svnStatus(vcsSvn *Cmd, rootDir string) (Status, error) {
@@ -574,24 +386,8 @@ var vcsFossil = &Cmd{
{filename: "_FOSSIL_", isDir: false},
},
- CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
- DownloadCmd: []string{"up"},
-
- TagCmd: []tagCmd{{"tag ls", `(.*)`}},
- TagSyncCmd: []string{"up tag:{tag}"},
- TagSyncDefault: []string{"up trunk"},
-
- Scheme: []string{"https", "http"},
- RemoteRepo: fossilRemoteRepo,
- Status: fossilStatus,
-}
-
-func fossilRemoteRepo(vcsFossil *Cmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsFossil.runOutput(rootDir, "remote-url")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
+ Scheme: []string{"https", "http"},
+ Status: fossilStatus,
}
var errFossilInfo = errors.New("unable to parse output of fossil info")
@@ -692,7 +488,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args[i] = expand(m, arg)
}
- if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
+ if len(args) >= 2 && args[0] == "--go-internal-mkdir" {
var err error
if filepath.IsAbs(args[1]) {
err = os.Mkdir(args[1], fs.ModePerm)
@@ -705,7 +501,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args = args[2:]
}
- if len(args) >= 2 && args[0] == "-go-internal-cd" {
+ if len(args) >= 2 && args[0] == "--go-internal-cd" {
if filepath.IsAbs(args[1]) {
dir = args[1]
} else {
@@ -766,99 +562,6 @@ func (v *Cmd) Ping(scheme, repo string) error {
return v.runVerboseOnly(dir, v.PingCmd, "scheme", scheme, "repo", repo)
}
-// Create creates a new copy of repo in dir.
-// The parent of dir must exist; dir must not.
-func (v *Cmd) Create(dir, repo string) error {
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- for _, cmd := range v.CreateCmd {
- if err := v.run(filepath.Dir(dir), cmd, "dir", dir, "repo", repo); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Download downloads any new changes for the repo in dir.
-func (v *Cmd) Download(dir string) error {
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- for _, cmd := range v.DownloadCmd {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Tags returns the list of available tags for the repo in dir.
-func (v *Cmd) Tags(dir string) ([]string, error) {
- var tags []string
- for _, tc := range v.TagCmd {
- out, err := v.runOutput(dir, tc.cmd)
- if err != nil {
- return nil, err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- for _, m := range re.FindAllStringSubmatch(string(out), -1) {
- tags = append(tags, m[1])
- }
- }
- return tags, nil
-}
-
-// TagSync syncs the repo in dir to the named tag,
-// which either is a tag returned by tags or is v.tagDefault.
-func (v *Cmd) TagSync(dir, tag string) error {
- if v.TagSyncCmd == nil {
- return nil
- }
- if tag != "" {
- for _, tc := range v.TagLookupCmd {
- out, err := v.runOutput(dir, tc.cmd, "tag", tag)
- if err != nil {
- return err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- m := re.FindStringSubmatch(string(out))
- if len(m) > 1 {
- tag = m[1]
- break
- }
- }
- }
-
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- if tag == "" && v.TagSyncDefault != nil {
- for _, cmd := range v.TagSyncDefault {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
- }
-
- for _, cmd := range v.TagSyncCmd {
- if err := v.run(dir, cmd, "tag", tag); err != nil {
- return err
- }
- }
- return nil
-}
-
// A vcsPath describes how to convert an import path into a
// version control system and repository name.
type vcsPath struct {
@@ -1385,6 +1088,10 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
}
}
+ if err := validateRepoSubDir(mmi.SubDir); err != nil {
+ return nil, fmt.Errorf("%s: invalid subdirectory %q: %v", resp.URL, mmi.SubDir, err)
+ }
+
if err := validateRepoRoot(mmi.RepoRoot); err != nil {
return nil, fmt.Errorf("%s: invalid repo root %q: %v", resp.URL, mmi.RepoRoot, err)
}
@@ -1416,6 +1123,22 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
return rr, nil
}
+// validateRepoSubDir returns an error if subdir is not a valid subdirectory path.
+// We consider a subdirectory path to be valid as long as it doesn't have a leading
+// slash (/) or hyphen (-).
+func validateRepoSubDir(subdir string) error {
+ if subdir == "" {
+ return nil
+ }
+ if subdir[0] == '/' {
+ return errors.New("leading slash")
+ }
+ if subdir[0] == '-' {
+ return errors.New("leading hyphen")
+ }
+ return nil
+}
+
// validateRepoRoot returns an error if repoRoot does not seem to be
// a valid URL with scheme.
func validateRepoRoot(repoRoot string) error {
diff --git a/src/cmd/go/internal/vcs/vcs_test.go b/src/cmd/go/internal/vcs/vcs_test.go
index 361d85bcfb..ab70e517e2 100644
--- a/src/cmd/go/internal/vcs/vcs_test.go
+++ b/src/cmd/go/internal/vcs/vcs_test.go
@@ -507,6 +507,42 @@ func TestValidateRepoRoot(t *testing.T) {
}
}
+func TestValidateRepoSubDir(t *testing.T) {
+ tests := []struct {
+ subdir string
+ ok bool
+ }{
+ {
+ subdir: "",
+ ok: true,
+ },
+ {
+ subdir: "sub/dir",
+ ok: true,
+ },
+ {
+ subdir: "/leading/slash",
+ ok: false,
+ },
+ {
+ subdir: "-leading/hyphen",
+ ok: false,
+ },
+ }
+
+ for _, test := range tests {
+ err := validateRepoSubDir(test.subdir)
+ ok := err == nil
+ if ok != test.ok {
+ want := "error"
+ if test.ok {
+ want = "nil"
+ }
+ t.Errorf("validateRepoSubDir(%q) = %q, want %s", test.subdir, err, want)
+ }
+ }
+}
+
var govcsTests = []struct {
govcs string
path string
diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go
index b18098ba5d..9b62dbb15f 100644
--- a/src/cmd/go/internal/workcmd/edit.go
+++ b/src/cmd/go/internal/workcmd/edit.go
@@ -279,7 +279,10 @@ func allowedVersionArg(arg string) bool {
// parsePathVersionOptional parses path[@version], using adj to
// describe any errors.
func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ return "", "", err
+ }
if !found {
path = arg
} else {