summaryrefslogtreecommitdiffstats
path: root/meta
diff options
context:
space:
mode:
authorDeepak Rathore <deeratho@cisco.com>2026-02-11 21:01:50 -0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2026-02-27 17:45:06 +0000
commitc13443407a5d7d6fe566677aecae19a749c291c3 (patch)
tree7695746c7e44c6bd7f7961a3bf82fd0b2aca5ee2 /meta
parenta231c49abc399af64185f7bc8ca1cded0191dd8b (diff)
downloadpoky-c13443407a5d7d6fe566677aecae19a749c291c3.tar.gz
go 1.22.12: Fix CVE-2025-68119
Upstream Repository: https://github.com/golang/go.git Bug details: https://nvd.nist.gov/vuln/detail/CVE-2025-68119 Type: Security Fix CVE: CVE-2025-68119 Score: 7.0 Patch: [1] https://github.com/golang/go/commit/62452bed4801 [2] https://github.com/golang/go/commit/73fe85f0ea1b Note: - First commit [1] is a dependent patch which is required additionally in original fix [2] to define ENV variable changes in src/cmd/go/internal/vcs/vcs.go file. (From OE-Core rev: ef995146623cf65c2e30f37b09847883ca7481bb) Signed-off-by: Deepak Rathore <deeratho@cisco.com> Signed-off-by: Yoann Congal <yoann.congal@smile.fr> Signed-off-by: Paul Barker <paul@pbarker.dev> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta')
-rw-r--r--meta/recipes-devtools/go/go-1.22.12.inc2
-rw-r--r--meta/recipes-devtools/go/go/CVE-2025-68119-dependent.patch175
-rw-r--r--meta/recipes-devtools/go/go/CVE-2025-68119.patch828
3 files changed, 1005 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.22.12.inc b/meta/recipes-devtools/go/go-1.22.12.inc
index 82019f25dd..ca0f05f7c8 100644
--- a/meta/recipes-devtools/go/go-1.22.12.inc
+++ b/meta/recipes-devtools/go/go-1.22.12.inc
@@ -35,6 +35,8 @@ SRC_URI += "\
35 file://CVE-2025-61726.patch \ 35 file://CVE-2025-61726.patch \
36 file://CVE-2025-61728.patch \ 36 file://CVE-2025-61728.patch \
37 file://CVE-2025-61731.patch \ 37 file://CVE-2025-61731.patch \
38 file://CVE-2025-68119-dependent.patch \
39 file://CVE-2025-68119.patch \
38" 40"
39SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71" 41SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"
40 42
diff --git a/meta/recipes-devtools/go/go/CVE-2025-68119-dependent.patch b/meta/recipes-devtools/go/go/CVE-2025-68119-dependent.patch
new file mode 100644
index 0000000000..5875d129cc
--- /dev/null
+++ b/meta/recipes-devtools/go/go/CVE-2025-68119-dependent.patch
@@ -0,0 +1,175 @@
1From 121b6cb231b5d904c03739495fcda69152d83f88 Mon Sep 17 00:00:00 2001
2From: Matt Harbison <mharbison72@gmail.com>
3Date: Sat, 3 Aug 2024 00:06:30 +0000
4Subject: [PATCH] cmd/go: fix the accuracy of Mercurial vcs.* stamped data
5
6There were a few Mercurial command line uses that could cause the wrong
7data to be used:
8
9* The log command needs '-r.' to specify the currently checked out commit
10* HGPLAIN is needed to disable optional output on commands
11* '-S' is needed to for the 'status' command to recurse into any subrepos
12
13The most likely issue to be seen here was the use of '-l1' instead of
14'-r.', which prints the most recent commit instead of the current checkout.
15Since tagging in Mercurial creates a new commit, this basically means the
16data was wrong for every tagged build.
17
18This also adds an hgrc config file to the test, with config options to
19keep the time and author values fixed. It's what's used in the Mercurial
20test harness to keep the commit hashes stable, and allows the tests here to
21also match the time and the revision ID, to prevent regressing.
22
23Fixes #63532
24
25CVE: CVE-2025-68119
26Upstream-Status: Backport [https://github.com/golang/go/commit/62452bed4801]
27
28Change-Id: I5b9971ce87c83431ec77e4a002bdc33fcf393856
29GitHub-Last-Rev: 62c9db0a28fee5881d0fe49f7bbb6e1653c7ff60
30GitHub-Pull-Request: golang/go#63557
31Reviewed-on: https://go-review.googlesource.com/c/go/+/535377
32Reviewed-by: Bryan Mills <bcmills@google.com>
33LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
34Reviewed-by: Sam Thanawalla <samthanawalla@google.com>
35Auto-Submit: Sam Thanawalla <samthanawalla@google.com>
36Reviewed-by: Michael Matloob <matloob@golang.org>
37(cherry picked from commit 62452bed480108623910feace4a5cea5448e6822)
38Signed-off-by: Deepak Rathore <deeratho@cisco.com>
39---
40 src/cmd/go/internal/vcs/vcs.go | 13 +++++--
41 .../testdata/script/version_buildvcs_hg.txt | 39 ++++++++++++++++---
42 2 files changed, 43 insertions(+), 9 deletions(-)
43
44diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
45index 89d9f0e94e..60f76d77cf 100644
46--- a/src/cmd/go/internal/vcs/vcs.go
47+++ b/src/cmd/go/internal/vcs/vcs.go
48@@ -37,6 +37,7 @@ import (
49 type Cmd struct {
50 Name string
51 Cmd string // name of binary to invoke command
52+ Env []string // any environment values to set/override
53 RootNames []rootName // filename and mode indicating the root of a checkout directory
54
55 CreateCmd []string // commands to download a fresh copy of a repository
56@@ -154,6 +155,10 @@ func vcsByCmd(cmd string) *Cmd {
57 var vcsHg = &Cmd{
58 Name: "Mercurial",
59 Cmd: "hg",
60+
61+ // HGPLAIN=1 turns off additional output that a user may have enabled via
62+ // config options or certain extensions.
63+ Env: []string{"HGPLAIN=1"},
64 RootNames: []rootName{
65 {filename: ".hg", isDir: true},
66 },
67@@ -189,12 +194,11 @@ func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
68
69 func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
70 // Output changeset ID and seconds since epoch.
71- out, err := vcsHg.runOutputVerboseOnly(rootDir, `log -l1 -T {node}:{date|hgdate}`)
72+ out, err := vcsHg.runOutputVerboseOnly(rootDir, `log -r. -T {node}:{date|hgdate}`)
73 if err != nil {
74 return Status{}, err
75 }
76
77- // Successful execution without output indicates an empty repo (no commits).
78 var rev string
79 var commitTime time.Time
80 if len(out) > 0 {
81@@ -209,7 +213,7 @@ func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
82 }
83
84 // Also look for untracked files.
85- out, err = vcsHg.runOutputVerboseOnly(rootDir, "status")
86+ out, err = vcsHg.runOutputVerboseOnly(rootDir, "status -S")
87 if err != nil {
88 return Status{}, err
89 }
90@@ -689,6 +693,9 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
91
92 cmd := exec.Command(v.Cmd, args...)
93 cmd.Dir = dir
94+ if v.Env != nil {
95+ cmd.Env = append(cmd.Environ(), v.Env...)
96+ }
97 if cfg.BuildX {
98 fmt.Fprintf(os.Stderr, "cd %s\n", dir)
99 fmt.Fprintf(os.Stderr, "%s %s\n", v.Cmd, strings.Join(args, " "))
100diff --git a/src/cmd/go/testdata/script/version_buildvcs_hg.txt b/src/cmd/go/testdata/script/version_buildvcs_hg.txt
101index fbbd886102..13904fae12 100644
102--- a/src/cmd/go/testdata/script/version_buildvcs_hg.txt
103+++ b/src/cmd/go/testdata/script/version_buildvcs_hg.txt
104@@ -6,6 +6,8 @@
105 [short] skip
106 env GOBIN=$WORK/gopath/bin
107 env oldpath=$PATH
108+env TZ=GMT
109+env HGRCPATH=$WORK/hgrc
110 cd repo/a
111
112 # If there's no local repository, there's no VCS info.
113@@ -29,24 +31,43 @@ cd ..
114 env PATH=$oldpath
115 rm .hg
116
117-# If there is an empty repository in a parent directory, only "uncommitted" is tagged.
118+# An empty repository or one explicitly updated to null uses the null cset ID,
119+# and the time is hard set to 1/1/70, regardless of the current time.
120 exec hg init
121 cd a
122 go install
123 go version -m $GOBIN/a$GOEXE
124-! stdout vcs.revision
125-! stdout vcs.time
126+stdout '^\tbuild\tvcs.revision=0000000000000000000000000000000000000000$'
127+stdout '^\tbuild\tvcs.time=1970-01-01T00:00:00Z$'
128 stdout '^\tbuild\tvcs.modified=true$'
129 cd ..
130
131 # Revision and commit time are tagged for repositories with commits.
132 exec hg add a README
133-exec hg commit -m 'initial commit'
134+exec hg commit -m 'initial commit' --user test-user --date '2024-07-31T01:21:27+00:00'
135 cd a
136 go install
137 go version -m $GOBIN/a$GOEXE
138-stdout '^\tbuild\tvcs.revision='
139-stdout '^\tbuild\tvcs.time='
140+stdout '^\tbuild\tvcs.revision=71eaed52daeaafea83cb604f75b0a0336ef2c345$'
141+stdout '^\tbuild\tvcs.time=2024-07-31T01:21:27Z$'
142+stdout '^\tbuild\tvcs.modified=false$'
143+rm $GOBIN/a$GOEXE
144+
145+# Add an extra commit and then back off of it to show that the hash is
146+# from the checked out revision, not the tip revision.
147+cp ../../outside/empty.txt .
148+exec hg ci -Am 'another commit' --user test-user --date '2024-08-01T19:24:38+00:00'
149+exec hg update --clean -r '.^'
150+
151+# Modified state is not thrown off by extra status output
152+exec hg bisect -v -g .
153+exec hg bisect -v -b '.^^'
154+exec hg status
155+stdout '^.+'
156+go install
157+go version -m $GOBIN/a$GOEXE
158+stdout '^\tbuild\tvcs.revision=71eaed52daeaafea83cb604f75b0a0336ef2c345$'
159+stdout '^\tbuild\tvcs.time=2024-07-31T01:21:27Z$'
160 stdout '^\tbuild\tvcs.modified=false$'
161 rm $GOBIN/a$GOEXE
162
163@@ -88,4 +109,10 @@ go 1.18
164 package main
165
166 func main() {}
167+-- $WORK/hgrc --
168+[ui]
169+# tweakdefaults is an opt-in that may print extra output in commands like
170+# status. That can be disabled by setting HGPLAIN=1.
171+tweakdefaults = 1
172+
173 -- outside/empty.txt --
174--
1752.35.6
diff --git a/meta/recipes-devtools/go/go/CVE-2025-68119.patch b/meta/recipes-devtools/go/go/CVE-2025-68119.patch
new file mode 100644
index 0000000000..d3f1724453
--- /dev/null
+++ b/meta/recipes-devtools/go/go/CVE-2025-68119.patch
@@ -0,0 +1,828 @@
1From 204e2fdacfbdb72a0b85fb526c8599128e430e94 Mon Sep 17 00:00:00 2001
2From: Roland Shoemaker <bracewell@google.com>
3Date: Wed, 10 Dec 2025 08:13:07 -0500
4Subject: [PATCH] [release-branch.go1.24] cmd/go: update VCS commands to use
5 safer flag/argument syntax
6
7In various situations, the toolchain invokes VCS commands. Some of these
8commands take arbitrary input, either provided by users or fetched from
9external sources. To prevent potential command injection vulnerabilities
10or misinterpretation of arguments as flags, this change updates the VCS
11commands to use various techniques to separate flags from positional
12arguments, and to directly associate flags with their values.
13
14Additionally, we update the environment variable for Mercurial to use
15`HGPLAIN=+strictflags`, which is the more explicit way to disable user
16configurations (intended or otherwise) that might interfere with command
17execution.
18
19We also now disallow version strings from being prefixed with '-' or
20'/', as doing so opens us up to making the same mistake again in the
21future. As far as we know there are currently ~0 public modules affected
22by this.
23
24While I was working on cmd/go/internal/vcs, I also noticed that a
25significant portion of the commands being implemented were dead code.
26In order to reduce the maintenance burden and surface area for potential
27issues, I removed the dead code for unused commands.
28
29We should probably follow up with a more structured change to make it
30harder to accidentally re-introduce these issues in the future, but for
31now this addresses the issue at hand.
32
33Thanks to splitline (@splitline) from DEVCORE Research Team for
34reporting this issue.
35
36Fixes CVE-2025-68119
37Updates #77099
38Fixes #77103
39
40CVE: CVE-2025-68119
41Upstream-Status: Backport [https://github.com/golang/go/commit/73fe85f0ea1b]
42
43Backport Changes:
44- In file src/cmd/go/internal/modfetch/codehost/git.go, Replaced the
45 function call runGIT with RUN. This changes is not present in current
46 version v1.22.12 and this changes were introduced in version v1.24 by
47 this commit https://github.com/golang/go/commit/8aa2eed8fb90
48
49Change-Id: I9d9f4ee05b95be49fe14edf71a1b8e6c0784378e
50Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3260
51Reviewed-by: Damien Neil <dneil@google.com>
52Reviewed-by: Nicholas Husin <husin@google.com>
53Reviewed-on: https://go-review.googlesource.com/c/go/+/736710
54Auto-Submit: Michael Pratt <mpratt@google.com>
55Reviewed-by: Junyang Shao <shaojunyang@google.com>
56LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
57(cherry picked from commit 94a1296a457387d1fd6eca1a9bcd44e89bdd9d55)
58Reviewed-on: https://go-review.googlesource.com/c/go/+/739421
59Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
60(cherry picked from commit 73fe85f0ea1bf2cec8e9a89bf5645de06ecaa0a6)
61Signed-off-by: Deepak Rathore <deeratho@cisco.com>
62---
63 src/cmd/go/internal/modcmd/edit.go | 10 +-
64 src/cmd/go/internal/modfetch/codehost/git.go | 20 +-
65 src/cmd/go/internal/modfetch/codehost/vcs.go | 20 +-
66 src/cmd/go/internal/modget/query.go | 5 +-
67 src/cmd/go/internal/modload/build.go | 12 +-
68 src/cmd/go/internal/modload/list.go | 30 +-
69 src/cmd/go/internal/toolchain/select.go | 7 +-
70 src/cmd/go/internal/vcs/vcs.go | 331 +------------------
71 src/cmd/go/internal/workcmd/edit.go | 5 +-
72 9 files changed, 96 insertions(+), 344 deletions(-)
73
74diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go
75index db131b0881..330603fe32 100644
76--- a/src/cmd/go/internal/modcmd/edit.go
77+++ b/src/cmd/go/internal/modcmd/edit.go
78@@ -284,7 +284,10 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) {
79
80 // parsePathVersion parses -flag=arg expecting arg to be path@version.
81 func parsePathVersion(flag, arg string) (path, version string) {
82- before, after, found := strings.Cut(arg, "@")
83+ before, after, found, err := modload.ParsePathVersion(arg)
84+ if err != nil {
85+ base.Fatalf("go: -%s=%s: %v", flag, arg, err)
86+ }
87 if !found {
88 base.Fatalf("go: -%s=%s: need path@version", flag, arg)
89 }
90@@ -318,7 +321,10 @@ func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version
91 if allowDirPath && modfile.IsDirectoryPath(arg) {
92 return arg, "", nil
93 }
94- before, after, found := strings.Cut(arg, "@")
95+ before, after, found, err := modload.ParsePathVersion(arg)
96+ if err != nil {
97+ return "", "", err
98+ }
99 if !found {
100 path = arg
101 } else {
102diff --git a/src/cmd/go/internal/modfetch/codehost/git.go b/src/cmd/go/internal/modfetch/codehost/git.go
103index 9996be7af7..45727ae3fb 100644
104--- a/src/cmd/go/internal/modfetch/codehost/git.go
105+++ b/src/cmd/go/internal/modfetch/codehost/git.go
106@@ -246,7 +246,7 @@ func (r *gitRepo) loadRefs(ctx context.Context) (map[string]string, error) {
107 r.refsErr = err
108 return
109 }
110- out, gitErr := Run(ctx, r.dir, "git", "ls-remote", "-q", r.remote)
111+ out, gitErr := Run(ctx, r.dir, "git", "ls-remote", "-q","--end-of-options", r.remote)
112 release()
113
114 if gitErr != nil {
115@@ -509,7 +509,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
116 if fromTag && !slices.Contains(info.Tags, tag) {
117 // The local repo includes the commit hash we want, but it is missing
118 // the corresponding tag. Add that tag and try again.
119- _, err := Run(ctx, r.dir, "git", "tag", tag, hash)
120+ _, err := Run(ctx, r.dir, "git", "tag","--end-of-options", tag, hash)
121 if err != nil {
122 return nil, err
123 }
124@@ -554,7 +554,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
125 // an apparent Git bug introduced in Git 2.21 (commit 61c771),
126 // which causes the handler for protocol version 1 to sometimes miss
127 // tags that point to the requested commit (see https://go.dev/issue/56881).
128- _, err = Run(ctx, r.dir, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", r.remote, refspec)
129+ _, err = Run(ctx, r.dir, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1","--end-of-options", r.remote, refspec)
130 release()
131
132 if err == nil {
133@@ -597,12 +597,12 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
134 }
135 defer release()
136
137- if _, err := Run(ctx, r.dir, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
138+ if _, err := Run(ctx, r.dir, "git", "fetch", "-f","--end-of-options", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
139 return err
140 }
141
142 if _, err := os.Stat(filepath.Join(r.dir, "shallow")); err == nil {
143- if _, err := Run(ctx, r.dir, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
144+ if _, err := Run(ctx, r.dir, "git", "fetch", "--unshallow", "-f", "--end-of-options",r.remote); err != nil {
145 return err
146 }
147 }
148@@ -615,7 +615,7 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
149 // statLocal returns a new RevInfo describing rev in the local git repository.
150 // It uses version as info.Version.
151 func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo, error) {
152- out, err := Run(ctx, r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
153+ out, err := Run(ctx, r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D","--end-of-options", rev, "--")
154 if err != nil {
155 // Return info with Origin.RepoSum if possible to allow caching of negative lookup.
156 var info *RevInfo
157@@ -705,7 +705,7 @@ func (r *gitRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64)
158 if err != nil {
159 return nil, err
160 }
161- out, err := Run(ctx, r.dir, "git", "cat-file", "blob", info.Name+":"+file)
162+ out, err := Run(ctx, r.dir, "git", "cat-file","--end-of-options", "blob", info.Name+":"+file)
163 if err != nil {
164 return nil, fs.ErrNotExist
165 }
166@@ -723,7 +723,7 @@ func (r *gitRepo) RecentTag(ctx context.Context, rev, prefix string, allowed fun
167 // result is definitive.
168 describe := func() (definitive bool) {
169 var out []byte
170- out, err = Run(ctx, r.dir, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
171+ out, err = Run(ctx, r.dir, "git", "for-each-ref", "--format=%(refname)", "--merged="+rev)
172 if err != nil {
173 return true
174 }
175@@ -865,7 +865,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
176 // TODO: Use maxSize or drop it.
177 args := []string{}
178 if subdir != "" {
179- args = append(args, "--", subdir)
180+ args = append(args, subdir)
181 }
182 info, err := r.Stat(ctx, rev) // download rev into local git repo
183 if err != nil {
184@@ -887,7 +887,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
185 // text file line endings. Setting -c core.autocrlf=input means only
186 // translate files on the way into the repo, not on the way out (archive).
187 // The -c core.eol=lf should be unnecessary but set it anyway.
188- archive, err := Run(ctx, r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
189+ archive, err := Run(ctx, r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", "--end-of-options", info.Name, args)
190 if err != nil {
191 if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
192 return nil, fs.ErrNotExist
193diff --git a/src/cmd/go/internal/modfetch/codehost/vcs.go b/src/cmd/go/internal/modfetch/codehost/vcs.go
194index 5bd100556b..425f61269f 100644
195--- a/src/cmd/go/internal/modfetch/codehost/vcs.go
196+++ b/src/cmd/go/internal/modfetch/codehost/vcs.go
197@@ -162,20 +162,20 @@ var vcsCmds = map[string]*vcsCmd{
198 branchRE: re(`(?m)^[^\n]+$`),
199 badLocalRevRE: re(`(?m)^(tip)$`),
200 statLocal: func(rev, remote string) []string {
201- return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
202+ return []string{"hg", "log", "-l1", fmt.Sprintf("--rev=%s", rev), "--template", "{node} {date|hgdate} {tags}"}
203 },
204 parseStat: hgParseStat,
205 fetch: []string{"hg", "pull", "-f"},
206 latest: "tip",
207 readFile: func(rev, file, remote string) []string {
208- return []string{"hg", "cat", "-r", rev, file}
209+ return []string{"hg", "cat", fmt.Sprintf("--rev=%s", rev), "--", file}
210 },
211 readZip: func(rev, subdir, remote, target string) []string {
212 pattern := []string{}
213 if subdir != "" {
214- pattern = []string{"-I", subdir + "/**"}
215+ pattern = []string{fmt.Sprintf("--include=%s", subdir+"/**")}
216 }
217- return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, "--", target)
218+ return str.StringList("hg", "archive", "-t", "zip", "--no-decode", fmt.Sprintf("--rev=%s", rev), "--prefix=prefix/", pattern, "--", target)
219 },
220 },
221
222@@ -215,19 +215,19 @@ var vcsCmds = map[string]*vcsCmd{
223 tagRE: re(`(?m)^\S+`),
224 badLocalRevRE: re(`^revno:-`),
225 statLocal: func(rev, remote string) []string {
226- return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
227+ return []string{"bzr", "log", "-l1", "--long", "--show-ids", fmt.Sprintf("--revision=%s", rev)}
228 },
229 parseStat: bzrParseStat,
230 latest: "revno:-1",
231 readFile: func(rev, file, remote string) []string {
232- return []string{"bzr", "cat", "-r", rev, file}
233+ return []string{"bzr", "cat", fmt.Sprintf("--revision=%s", rev), "--", file}
234 },
235 readZip: func(rev, subdir, remote, target string) []string {
236 extra := []string{}
237 if subdir != "" {
238 extra = []string{"./" + subdir}
239 }
240- return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", "--", target, extra)
241+ return str.StringList("bzr", "export", "--format=zip", fmt.Sprintf("--revision=%s", rev), "--root=prefix/", "--", target, extra)
242 },
243 },
244
245@@ -242,17 +242,17 @@ var vcsCmds = map[string]*vcsCmd{
246 },
247 tagRE: re(`XXXTODO`),
248 statLocal: func(rev, remote string) []string {
249- return []string{"fossil", "info", "-R", ".fossil", rev}
250+ return []string{"fossil", "info", "-R", ".fossil", "--", rev}
251 },
252 parseStat: fossilParseStat,
253 latest: "trunk",
254 readFile: func(rev, file, remote string) []string {
255- return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
256+ return []string{"fossil", "cat", "-R", ".fossil", fmt.Sprintf("-r=%s", rev), "--", file}
257 },
258 readZip: func(rev, subdir, remote, target string) []string {
259 extra := []string{}
260 if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
261- extra = []string{"--include", subdir}
262+ extra = []string{fmt.Sprintf("--include=%s", subdir)}
263 }
264 // Note that vcsRepo.ReadZip below rewrites this command
265 // to run in a different directory, to work around a fossil bug.
266diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
267index 498ba6c2ff..0d33a52677 100644
268--- a/src/cmd/go/internal/modget/query.go
269+++ b/src/cmd/go/internal/modget/query.go
270@@ -139,7 +139,10 @@ func errSet(err error) pathSet { return pathSet{err: err} }
271 // newQuery returns a new query parsed from the raw argument,
272 // which must be either path or path@version.
273 func newQuery(raw string) (*query, error) {
274- pattern, rawVers, found := strings.Cut(raw, "@")
275+ pattern, rawVers, found, err := modload.ParsePathVersion(raw)
276+ if err != nil {
277+ return nil, err
278+ }
279 if found && (strings.Contains(rawVers, "@") || rawVers == "") {
280 return nil, fmt.Errorf("invalid module version syntax %q", raw)
281 }
282diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
283index 5cf1487c3e..08acf3aa2b 100644
284--- a/src/cmd/go/internal/modload/build.go
285+++ b/src/cmd/go/internal/modload/build.go
286@@ -12,7 +12,6 @@ import (
287 "io/fs"
288 "os"
289 "path/filepath"
290- "strings"
291
292 "cmd/go/internal/base"
293 "cmd/go/internal/cfg"
294@@ -88,7 +87,16 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
295 return nil
296 }
297
298- if path, vers, found := strings.Cut(path, "@"); found {
299+ path, vers, found, err := ParsePathVersion(path)
300+ if err != nil {
301+ return &modinfo.ModulePublic{
302+ Path: path,
303+ Error: &modinfo.ModuleError{
304+ Err: err.Error(),
305+ },
306+ }
307+ }
308+ if found {
309 m := module.Version{Path: path, Version: vers}
310 return moduleInfo(ctx, nil, m, 0, nil)
311 }
312diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
313index ef93c25121..e9efb1918e 100644
314--- a/src/cmd/go/internal/modload/list.go
315+++ b/src/cmd/go/internal/modload/list.go
316@@ -149,7 +149,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
317 }
318 continue
319 }
320- if path, vers, found := strings.Cut(arg, "@"); found {
321+ path, vers, found, err := ParsePathVersion(arg)
322+ if err != nil {
323+ base.Fatalf("go: %v", err)
324+ }
325+ if found {
326 if vers == "upgrade" || vers == "patch" {
327 if _, ok := rs.rootSelected(path); !ok || rs.pruning == unpruned {
328 needFullGraph = true
329@@ -175,7 +179,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
330
331 matchedModule := map[module.Version]bool{}
332 for _, arg := range args {
333- if path, vers, found := strings.Cut(arg, "@"); found {
334+ path, vers, found, err := ParsePathVersion(arg)
335+ if err != nil {
336+ base.Fatalf("go: %v", err)
337+ }
338+ if found {
339 var current string
340 if mg == nil {
341 current, _ = rs.rootSelected(path)
342@@ -308,3 +316,21 @@ func modinfoError(path, vers string, err error) *modinfo.ModuleError {
343
344 return &modinfo.ModuleError{Err: err.Error()}
345 }
346+
347+// ParsePathVersion parses arg expecting arg to be path@version. If there is no
348+// '@' in arg, found is false, vers is "", and path is arg. This mirrors the
349+// typical usage of strings.Cut. ParsePathVersion is meant to be a general
350+// replacement for strings.Cut in module version parsing. If the version is
351+// invalid, an error is returned. The version is considered invalid if it is
352+// prefixed with '-' or '/', which can cause security problems when constructing
353+// commands to execute that use the version.
354+func ParsePathVersion(arg string) (path, vers string, found bool, err error) {
355+ path, vers, found = strings.Cut(arg, "@")
356+ if !found {
357+ return arg, "", false, nil
358+ }
359+ if len(vers) > 0 && (vers[0] == '-' || vers[0] == '/') {
360+ return "", "", false, fmt.Errorf("invalid module version %q", vers)
361+ }
362+ return path, vers, true, nil
363+}
364diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go
365index 14a8d3c21d..838ebae6a7 100644
366--- a/src/cmd/go/internal/toolchain/select.go
367+++ b/src/cmd/go/internal/toolchain/select.go
368@@ -614,7 +614,10 @@ func goInstallVersion() bool {
369 if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
370 return false
371 }
372- path, version, _ := strings.Cut(pkgArg, "@")
373+ path, version, _, err := modload.ParsePathVersion(pkgArg)
374+ if err != nil {
375+ base.Fatalf("go: %v", err)
376+ }
377 if path == "" || version == "" || gover.IsToolchain(path) {
378 return false
379 }
380@@ -650,7 +653,7 @@ func goInstallVersion() bool {
381 allowed = nil
382 }
383 noneSelected := func(path string) (version string) { return "none" }
384- _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
385+ _, err = modload.QueryPackages(ctx, path, version, noneSelected, allowed)
386 if errors.Is(err, gover.ErrTooNew) {
387 // Run early switch, same one go install or go run would eventually do,
388 // if it understood all the command-line flags.
389diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
390index 60f76d77cf..55bf25ff62 100644
391--- a/src/cmd/go/internal/vcs/vcs.go
392+++ b/src/cmd/go/internal/vcs/vcs.go
393@@ -17,7 +17,6 @@ import (
394 "os"
395 "os/exec"
396 "path/filepath"
397- "regexp"
398 "strconv"
399 "strings"
400 "sync"
401@@ -40,20 +39,10 @@ type Cmd struct {
402 Env []string // any environment values to set/override
403 RootNames []rootName // filename and mode indicating the root of a checkout directory
404
405- CreateCmd []string // commands to download a fresh copy of a repository
406- DownloadCmd []string // commands to download updates into an existing repository
407-
408- TagCmd []tagCmd // commands to list tags
409- TagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
410- TagSyncCmd []string // commands to sync to specific tag
411- TagSyncDefault []string // commands to sync to default tag
412-
413 Scheme []string
414 PingCmd string
415
416- RemoteRepo func(v *Cmd, rootDir string) (remoteRepo string, err error)
417- ResolveRepo func(v *Cmd, rootDir, remoteRepo string) (realRepo string, err error)
418- Status func(v *Cmd, rootDir string) (Status, error)
419+ Status func(v *Cmd, rootDir string) (Status, error)
420 }
421
422 // Status is the current state of a local repository.
423@@ -156,40 +145,16 @@ var vcsHg = &Cmd{
424 Name: "Mercurial",
425 Cmd: "hg",
426
427- // HGPLAIN=1 turns off additional output that a user may have enabled via
428- // config options or certain extensions.
429- Env: []string{"HGPLAIN=1"},
430+ // HGPLAIN=+strictflags turns off additional output that a user may have
431+ // enabled via config options or certain extensions.
432+ Env: []string{"HGPLAIN=+strictflags"},
433 RootNames: []rootName{
434 {filename: ".hg", isDir: true},
435 },
436
437- CreateCmd: []string{"clone -U -- {repo} {dir}"},
438- DownloadCmd: []string{"pull"},
439-
440- // We allow both tag and branch names as 'tags'
441- // for selecting a version. This lets people have
442- // a go.release.r60 branch and a go1 branch
443- // and make changes in both, without constantly
444- // editing .hgtags.
445- TagCmd: []tagCmd{
446- {"tags", `^(\S+)`},
447- {"branches", `^(\S+)`},
448- },
449- TagSyncCmd: []string{"update -r {tag}"},
450- TagSyncDefault: []string{"update default"},
451-
452- Scheme: []string{"https", "http", "ssh"},
453- PingCmd: "identify -- {scheme}://{repo}",
454- RemoteRepo: hgRemoteRepo,
455- Status: hgStatus,
456-}
457-
458-func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
459- out, err := vcsHg.runOutput(rootDir, "paths default")
460- if err != nil {
461- return "", err
462- }
463- return strings.TrimSpace(string(out)), nil
464+ Scheme: []string{"https", "http", "ssh"},
465+ PingCmd: "identify -- {scheme}://{repo}",
466+ Status: hgStatus,
467 }
468
469 func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
470@@ -252,25 +217,6 @@ var vcsGit = &Cmd{
471 {filename: ".git", isDir: true},
472 },
473
474- CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
475- DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
476-
477- TagCmd: []tagCmd{
478- // tags/xxx matches a git tag named xxx
479- // origin/xxx matches a git branch named xxx on the default remote repository
480- {"show-ref", `(?:tags|origin)/(\S+)$`},
481- },
482- TagLookupCmd: []tagCmd{
483- {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
484- },
485- TagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
486- // both createCmd and downloadCmd update the working dir.
487- // No need to do more here. We used to 'checkout master'
488- // but that doesn't work if the default branch is not named master.
489- // DO NOT add 'checkout master' here.
490- // See golang.org/issue/9032.
491- TagSyncDefault: []string{"submodule update --init --recursive"},
492-
493 Scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
494
495 // Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
496@@ -279,54 +225,7 @@ var vcsGit = &Cmd{
497 // See golang.org/issue/33836.
498 PingCmd: "ls-remote {scheme}://{repo}",
499
500- RemoteRepo: gitRemoteRepo,
501- Status: gitStatus,
502-}
503-
504-// scpSyntaxRe matches the SCP-like addresses used by Git to access
505-// repositories by SSH.
506-var scpSyntaxRe = lazyregexp.New(`^(\w+)@([\w.-]+):(.*)$`)
507-
508-func gitRemoteRepo(vcsGit *Cmd, rootDir string) (remoteRepo string, err error) {
509- const cmd = "config remote.origin.url"
510- outb, err := vcsGit.run1(rootDir, cmd, nil, false)
511- if err != nil {
512- // if it doesn't output any message, it means the config argument is correct,
513- // but the config value itself doesn't exist
514- if outb != nil && len(outb) == 0 {
515- return "", errors.New("remote origin not found")
516- }
517- return "", err
518- }
519- out := strings.TrimSpace(string(outb))
520-
521- var repoURL *urlpkg.URL
522- if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
523- // Match SCP-like syntax and convert it to a URL.
524- // Eg, "git@github.com:user/repo" becomes
525- // "ssh://git@github.com/user/repo".
526- repoURL = &urlpkg.URL{
527- Scheme: "ssh",
528- User: urlpkg.User(m[1]),
529- Host: m[2],
530- Path: m[3],
531- }
532- } else {
533- repoURL, err = urlpkg.Parse(out)
534- if err != nil {
535- return "", err
536- }
537- }
538-
539- // Iterate over insecure schemes too, because this function simply
540- // reports the state of the repo. If we can't see insecure schemes then
541- // we can't report the actual repo URL.
542- for _, s := range vcsGit.Scheme {
543- if repoURL.Scheme == s {
544- return repoURL.String(), nil
545- }
546- }
547- return "", errors.New("unable to parse output of git " + cmd)
548+ Status: gitStatus,
549 }
550
551 func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
552@@ -366,62 +265,9 @@ var vcsBzr = &Cmd{
553 {filename: ".bzr", isDir: true},
554 },
555
556- CreateCmd: []string{"branch -- {repo} {dir}"},
557-
558- // Without --overwrite bzr will not pull tags that changed.
559- // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
560- DownloadCmd: []string{"pull --overwrite"},
561-
562- TagCmd: []tagCmd{{"tags", `^(\S+)`}},
563- TagSyncCmd: []string{"update -r {tag}"},
564- TagSyncDefault: []string{"update -r revno:-1"},
565-
566- Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
567- PingCmd: "info -- {scheme}://{repo}",
568- RemoteRepo: bzrRemoteRepo,
569- ResolveRepo: bzrResolveRepo,
570- Status: bzrStatus,
571-}
572-
573-func bzrRemoteRepo(vcsBzr *Cmd, rootDir string) (remoteRepo string, err error) {
574- outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
575- if err != nil {
576- return "", err
577- }
578- return strings.TrimSpace(string(outb)), nil
579-}
580-
581-func bzrResolveRepo(vcsBzr *Cmd, rootDir, remoteRepo string) (realRepo string, err error) {
582- outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
583- if err != nil {
584- return "", err
585- }
586- out := string(outb)
587-
588- // Expect:
589- // ...
590- // (branch root|repository branch): <URL>
591- // ...
592-
593- found := false
594- for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
595- i := strings.Index(out, prefix)
596- if i >= 0 {
597- out = out[i+len(prefix):]
598- found = true
599- break
600- }
601- }
602- if !found {
603- return "", fmt.Errorf("unable to parse output of bzr info")
604- }
605-
606- i := strings.Index(out, "\n")
607- if i < 0 {
608- return "", fmt.Errorf("unable to parse output of bzr info")
609- }
610- out = out[:i]
611- return strings.TrimSpace(out), nil
612+ Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
613+ PingCmd: "info -- {scheme}://{repo}",
614+ Status: bzrStatus,
615 }
616
617 func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
618@@ -489,45 +335,11 @@ var vcsSvn = &Cmd{
619 {filename: ".svn", isDir: true},
620 },
621
622- CreateCmd: []string{"checkout -- {repo} {dir}"},
623- DownloadCmd: []string{"update"},
624-
625 // There is no tag command in subversion.
626 // The branch information is all in the path names.
627
628- Scheme: []string{"https", "http", "svn", "svn+ssh"},
629- PingCmd: "info -- {scheme}://{repo}",
630- RemoteRepo: svnRemoteRepo,
631-}
632-
633-func svnRemoteRepo(vcsSvn *Cmd, rootDir string) (remoteRepo string, err error) {
634- outb, err := vcsSvn.runOutput(rootDir, "info")
635- if err != nil {
636- return "", err
637- }
638- out := string(outb)
639-
640- // Expect:
641- //
642- // ...
643- // URL: <URL>
644- // ...
645- //
646- // Note that we're not using the Repository Root line,
647- // because svn allows checking out subtrees.
648- // The URL will be the URL of the subtree (what we used with 'svn co')
649- // while the Repository Root may be a much higher parent.
650- i := strings.Index(out, "\nURL: ")
651- if i < 0 {
652- return "", fmt.Errorf("unable to parse output of svn info")
653- }
654- out = out[i+len("\nURL: "):]
655- i = strings.Index(out, "\n")
656- if i < 0 {
657- return "", fmt.Errorf("unable to parse output of svn info")
658- }
659- out = out[:i]
660- return strings.TrimSpace(out), nil
661+ Scheme: []string{"https", "http", "svn", "svn+ssh"},
662+ PingCmd: "info -- {scheme}://{repo}",
663 }
664
665 // fossilRepoName is the name go get associates with a fossil repository. In the
666@@ -543,24 +355,8 @@ var vcsFossil = &Cmd{
667 {filename: "_FOSSIL_", isDir: false},
668 },
669
670- CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
671- DownloadCmd: []string{"up"},
672-
673- TagCmd: []tagCmd{{"tag ls", `(.*)`}},
674- TagSyncCmd: []string{"up tag:{tag}"},
675- TagSyncDefault: []string{"up trunk"},
676-
677- Scheme: []string{"https", "http"},
678- RemoteRepo: fossilRemoteRepo,
679- Status: fossilStatus,
680-}
681-
682-func fossilRemoteRepo(vcsFossil *Cmd, rootDir string) (remoteRepo string, err error) {
683- out, err := vcsFossil.runOutput(rootDir, "remote-url")
684- if err != nil {
685- return "", err
686- }
687- return strings.TrimSpace(string(out)), nil
688+ Scheme: []string{"https", "http"},
689+ Status: fossilStatus,
690 }
691
692 var errFossilInfo = errors.New("unable to parse output of fossil info")
693@@ -661,7 +457,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
694 args[i] = expand(m, arg)
695 }
696
697- if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
698+ if len(args) >= 2 && args[0] == "--go-internal-mkdir" {
699 var err error
700 if filepath.IsAbs(args[1]) {
701 err = os.Mkdir(args[1], fs.ModePerm)
702@@ -674,7 +470,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
703 args = args[2:]
704 }
705
706- if len(args) >= 2 && args[0] == "-go-internal-cd" {
707+ if len(args) >= 2 && args[0] == "--go-internal-cd" {
708 if filepath.IsAbs(args[1]) {
709 dir = args[1]
710 } else {
711@@ -735,99 +531,6 @@ func (v *Cmd) Ping(scheme, repo string) error {
712 return v.runVerboseOnly(dir, v.PingCmd, "scheme", scheme, "repo", repo)
713 }
714
715-// Create creates a new copy of repo in dir.
716-// The parent of dir must exist; dir must not.
717-func (v *Cmd) Create(dir, repo string) error {
718- release, err := base.AcquireNet()
719- if err != nil {
720- return err
721- }
722- defer release()
723-
724- for _, cmd := range v.CreateCmd {
725- if err := v.run(filepath.Dir(dir), cmd, "dir", dir, "repo", repo); err != nil {
726- return err
727- }
728- }
729- return nil
730-}
731-
732-// Download downloads any new changes for the repo in dir.
733-func (v *Cmd) Download(dir string) error {
734- release, err := base.AcquireNet()
735- if err != nil {
736- return err
737- }
738- defer release()
739-
740- for _, cmd := range v.DownloadCmd {
741- if err := v.run(dir, cmd); err != nil {
742- return err
743- }
744- }
745- return nil
746-}
747-
748-// Tags returns the list of available tags for the repo in dir.
749-func (v *Cmd) Tags(dir string) ([]string, error) {
750- var tags []string
751- for _, tc := range v.TagCmd {
752- out, err := v.runOutput(dir, tc.cmd)
753- if err != nil {
754- return nil, err
755- }
756- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
757- for _, m := range re.FindAllStringSubmatch(string(out), -1) {
758- tags = append(tags, m[1])
759- }
760- }
761- return tags, nil
762-}
763-
764-// TagSync syncs the repo in dir to the named tag,
765-// which either is a tag returned by tags or is v.tagDefault.
766-func (v *Cmd) TagSync(dir, tag string) error {
767- if v.TagSyncCmd == nil {
768- return nil
769- }
770- if tag != "" {
771- for _, tc := range v.TagLookupCmd {
772- out, err := v.runOutput(dir, tc.cmd, "tag", tag)
773- if err != nil {
774- return err
775- }
776- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
777- m := re.FindStringSubmatch(string(out))
778- if len(m) > 1 {
779- tag = m[1]
780- break
781- }
782- }
783- }
784-
785- release, err := base.AcquireNet()
786- if err != nil {
787- return err
788- }
789- defer release()
790-
791- if tag == "" && v.TagSyncDefault != nil {
792- for _, cmd := range v.TagSyncDefault {
793- if err := v.run(dir, cmd); err != nil {
794- return err
795- }
796- }
797- return nil
798- }
799-
800- for _, cmd := range v.TagSyncCmd {
801- if err := v.run(dir, cmd, "tag", tag); err != nil {
802- return err
803- }
804- }
805- return nil
806-}
807-
808 // A vcsPath describes how to convert an import path into a
809 // version control system and repository name.
810 type vcsPath struct {
811diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go
812index 8d975b0b3d..c1252cc95e 100644
813--- a/src/cmd/go/internal/workcmd/edit.go
814+++ b/src/cmd/go/internal/workcmd/edit.go
815@@ -242,7 +242,10 @@ func allowedVersionArg(arg string) bool {
816 // parsePathVersionOptional parses path[@version], using adj to
817 // describe any errors.
818 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
819- before, after, found := strings.Cut(arg, "@")
820+ before, after, found, err := modload.ParsePathVersion(arg)
821+ if err != nil {
822+ return "", "", err
823+ }
824 if !found {
825 path = arg
826 } else {
827--
8282.35.6