Skip to content

Commit b9a08f1

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
cmd/go: propagate origin information for inexact module queries
Module queries for "@latest" and inexact constraints (like "@v1.3") may consult information about tags and/or branches before finally returning either a result or an error. To correctly invalidate the origin information for the -reuse flag, the reported Origin needs to reflect all of those inputs. Fixes #61415. Change-Id: I054acbef7d218a92a3bbb44517326385e458d907 Reviewed-on: https://go-review.googlesource.com/c/go/+/542717 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Bryan Mills <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 3e80003 commit b9a08f1

File tree

6 files changed

+211
-59
lines changed

6 files changed

+211
-59
lines changed

src/cmd/go/internal/modfetch/coderepo.go

+41-25
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ func (r *codeRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
322322
func (r *codeRepo) Latest(ctx context.Context) (*RevInfo, error) {
323323
info, err := r.code.Latest(ctx)
324324
if err != nil {
325+
if info != nil {
326+
return &RevInfo{Origin: info.Origin}, err
327+
}
325328
return nil, err
326329
}
327330
return r.convert(ctx, info, "")
@@ -332,7 +335,44 @@ func (r *codeRepo) Latest(ctx context.Context) (*RevInfo, error) {
332335
//
333336
// If statVers is a valid module version, it is used for the Version field.
334337
// Otherwise, the Version is derived from the passed-in info and recent tags.
335-
func (r *codeRepo) convert(ctx context.Context, info *codehost.RevInfo, statVers string) (*RevInfo, error) {
338+
func (r *codeRepo) convert(ctx context.Context, info *codehost.RevInfo, statVers string) (revInfo *RevInfo, err error) {
339+
defer func() {
340+
if info.Origin == nil {
341+
return
342+
}
343+
if revInfo == nil {
344+
revInfo = new(RevInfo)
345+
} else if revInfo.Origin != nil {
346+
panic("internal error: RevInfo Origin unexpectedly already populated")
347+
}
348+
349+
origin := *info.Origin
350+
revInfo.Origin = &origin
351+
origin.Subdir = r.codeDir
352+
353+
v := revInfo.Version
354+
if module.IsPseudoVersion(v) && (v != statVers || !strings.HasPrefix(v, "v0.0.0-")) {
355+
// Add tags that are relevant to pseudo-version calculation to origin.
356+
prefix := r.codeDir
357+
if prefix != "" {
358+
prefix += "/"
359+
}
360+
if r.pathMajor != "" { // "/v2" or "/.v2"
361+
prefix += r.pathMajor[1:] + "." // += "v2."
362+
}
363+
tags, tagsErr := r.code.Tags(ctx, prefix)
364+
if tagsErr != nil {
365+
origin.ClearCheckable()
366+
if err == nil {
367+
err = tagsErr
368+
}
369+
} else {
370+
origin.TagPrefix = tags.Origin.TagPrefix
371+
origin.TagSum = tags.Origin.TagSum
372+
}
373+
}
374+
}()
375+
336376
// If this is a plain tag (no dir/ prefix)
337377
// and the module path is unversioned,
338378
// and if the underlying file tree has no go.mod,
@@ -463,31 +503,7 @@ func (r *codeRepo) convert(ctx context.Context, info *codehost.RevInfo, statVers
463503
return nil, errIncompatible
464504
}
465505

466-
origin := info.Origin
467-
if origin != nil {
468-
o := *origin
469-
origin = &o
470-
origin.Subdir = r.codeDir
471-
if module.IsPseudoVersion(v) && (v != statVers || !strings.HasPrefix(v, "v0.0.0-")) {
472-
// Add tags that are relevant to pseudo-version calculation to origin.
473-
prefix := r.codeDir
474-
if prefix != "" {
475-
prefix += "/"
476-
}
477-
if r.pathMajor != "" { // "/v2" or "/.v2"
478-
prefix += r.pathMajor[1:] + "." // += "v2."
479-
}
480-
tags, err := r.code.Tags(ctx, prefix)
481-
if err != nil {
482-
return nil, err
483-
}
484-
origin.TagPrefix = tags.Origin.TagPrefix
485-
origin.TagSum = tags.Origin.TagSum
486-
}
487-
}
488-
489506
return &RevInfo{
490-
Origin: origin,
491507
Name: info.Name,
492508
Short: info.Short,
493509
Time: info.Time,

src/cmd/go/internal/modload/build.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
162162
}
163163

164164
// mergeOrigin merges two origins,
165-
// returning and possibly modifying one of its arguments.
165+
// returning either a new origin or one of its unmodified arguments.
166166
// If the two origins conflict, mergeOrigin returns a non-specific one
167167
// that will not pass CheckReuse.
168168
// If m1 or m2 is nil, the other is returned unmodified.
@@ -194,11 +194,17 @@ func mergeOrigin(m1, m2 *codehost.Origin) *codehost.Origin {
194194
merged.TagPrefix = m2.TagPrefix
195195
}
196196
if m2.Hash != "" {
197-
if m1.Hash != "" && (m1.Hash != m2.Hash || m1.Ref != m2.Ref) {
197+
if m1.Hash != "" && m1.Hash != m2.Hash {
198198
merged.ClearCheckable()
199199
return merged
200200
}
201201
merged.Hash = m2.Hash
202+
}
203+
if m2.Ref != "" {
204+
if m1.Ref != "" && m1.Ref != m2.Ref {
205+
merged.ClearCheckable()
206+
return merged
207+
}
202208
merged.Ref = m2.Ref
203209
}
204210
return merged

src/cmd/go/internal/modload/query.go

+34-28
Original file line numberDiff line numberDiff line change
@@ -216,34 +216,35 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
216216
if err != nil {
217217
return nil, err
218218
}
219-
revErr := &modfetch.RevInfo{Origin: versions.Origin} // RevInfo to return with error
219+
origin := versions.Origin
220220

221-
releases, prereleases, err := qm.filterVersions(ctx, versions.List)
222-
if err != nil {
223-
return revErr, err
221+
revWithOrigin := func(rev *modfetch.RevInfo) *modfetch.RevInfo {
222+
if rev == nil {
223+
if origin == nil {
224+
return nil
225+
}
226+
return &modfetch.RevInfo{Origin: origin}
227+
}
228+
229+
clone := *rev
230+
clone.Origin = origin
231+
return &clone
224232
}
225233

226-
mergeRevOrigin := func(rev *modfetch.RevInfo, origin *codehost.Origin) *modfetch.RevInfo {
227-
merged := mergeOrigin(rev.Origin, origin)
228-
if merged == rev.Origin {
229-
return rev
230-
}
231-
clone := new(modfetch.RevInfo)
232-
*clone = *rev
233-
clone.Origin = merged
234-
return clone
234+
releases, prereleases, err := qm.filterVersions(ctx, versions.List)
235+
if err != nil {
236+
return revWithOrigin(nil), err
235237
}
236238

237239
lookup := func(v string) (*modfetch.RevInfo, error) {
238240
rev, err := repo.Stat(ctx, v)
239-
// Stat can return a non-nil rev and a non-nil err,
240-
// in order to provide origin information to make the error cacheable.
241-
if rev == nil && err != nil {
242-
return revErr, err
241+
if rev != nil {
242+
// Note that Stat can return a non-nil rev and a non-nil err,
243+
// in order to provide origin information to make the error cacheable.
244+
origin = mergeOrigin(origin, rev.Origin)
243245
}
244-
rev = mergeRevOrigin(rev, versions.Origin)
245246
if err != nil {
246-
return rev, err
247+
return revWithOrigin(nil), err
247248
}
248249

249250
if (query == "upgrade" || query == "patch") && module.IsPseudoVersion(current) && !rev.Time.IsZero() {
@@ -268,18 +269,20 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
268269
currentTime, err := module.PseudoVersionTime(current)
269270
if err == nil && rev.Time.Before(currentTime) {
270271
if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
271-
return revErr, err
272+
return revWithOrigin(nil), err
272273
}
273274
rev, err = repo.Stat(ctx, current)
274-
if rev == nil && err != nil {
275-
return revErr, err
275+
if rev != nil {
276+
origin = mergeOrigin(origin, rev.Origin)
277+
}
278+
if err != nil {
279+
return revWithOrigin(nil), err
276280
}
277-
rev = mergeRevOrigin(rev, versions.Origin)
278-
return rev, err
281+
return revWithOrigin(rev), nil
279282
}
280283
}
281284

282-
return rev, nil
285+
return revWithOrigin(rev), nil
283286
}
284287

285288
if qm.preferLower {
@@ -300,24 +303,27 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
300303

301304
if qm.mayUseLatest {
302305
latest, err := repo.Latest(ctx)
306+
if latest != nil {
307+
origin = mergeOrigin(origin, latest.Origin)
308+
}
303309
if err == nil {
304310
if qm.allowsVersion(ctx, latest.Version) {
305311
return lookup(latest.Version)
306312
}
307313
} else if !errors.Is(err, fs.ErrNotExist) {
308-
return revErr, err
314+
return revWithOrigin(nil), err
309315
}
310316
}
311317

312318
if (query == "upgrade" || query == "patch") && current != "" && current != "none" {
313319
// "upgrade" and "patch" may stay on the current version if allowed.
314320
if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
315-
return nil, err
321+
return revWithOrigin(nil), err
316322
}
317323
return lookup(current)
318324
}
319325

320-
return revErr, &NoMatchingVersionError{query: query, current: current}
326+
return revWithOrigin(nil), &NoMatchingVersionError{query: query, current: current}
321327
}
322328

323329
// IsRevisionQuery returns true if vers is a version query that may refer to
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
[short] skip 'generates a vcstest git repo'
2+
[!git] skip
3+
4+
env GOPROXY=direct
5+
6+
# Control case: fetching a nested module at a tag that exists should
7+
# emit Origin metadata for that tag and commit, and the origin should
8+
# be reusable for that tag.
9+
10+
go list -json -m --versions -e vcs-test.golang.org/git/issue61415.git/nested@has-nested
11+
cp stdout has-nested.json
12+
stdout '"Origin":'
13+
stdout '"VCS": "git"'
14+
stdout '"URL":' # randomly-chosen vcweb localhost URL
15+
stdout '"Subdir": "nested"'
16+
stdout '"TagPrefix": "nested/"'
17+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
18+
stdout '"Ref": "refs/tags/has-nested"'
19+
stdout '"Hash": "08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a"'
20+
21+
go list -reuse=has-nested.json -json -m --versions -e vcs-test.golang.org/git/issue61415.git/nested@has-nested
22+
stdout '"Origin":'
23+
stdout '"VCS": "git"'
24+
stdout '"URL":' # randomly-chosen vcweb localhost URL
25+
stdout '"Subdir": "nested"'
26+
stdout '"TagPrefix": "nested/"'
27+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
28+
stdout '"Ref": "refs/tags/has-nested"'
29+
stdout '"Hash": "08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a"'
30+
stdout '"Reuse": true'
31+
32+
33+
# Experiment case: if the nested module doesn't exist at "latest",
34+
# the Origin metadata should include the ref that we tried to resolve
35+
# (HEAD for a repo without version tags) and the hash to which it refers,
36+
# so that changing the HEAD ref will invalidate the result.
37+
38+
go list -json -m --versions -e vcs-test.golang.org/git/issue61415.git/nested@latest
39+
cp stdout no-nested.json
40+
stdout '"Err": "module vcs-test.golang.org/git/issue61415.git/nested: no matching versions for query \\"latest\\""'
41+
stdout '"URL":' # randomly-chosen vcweb localhost URL
42+
stdout '"Subdir": "nested"'
43+
stdout '"TagPrefix": "nested/"'
44+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
45+
46+
stdout '"Ref": "HEAD"'
47+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
48+
49+
# The error result should be reusable.
50+
51+
go list -reuse=no-nested.json -json -m --versions -e vcs-test.golang.org/git/issue61415.git/nested@latest
52+
53+
stdout '"Err": "module vcs-test.golang.org/git/issue61415.git/nested: no matching versions for query \\"latest\\""'
54+
stdout '"URL":' # randomly-chosen vcweb localhost URL
55+
stdout '"Subdir": "nested"'
56+
stdout '"TagPrefix": "nested/"'
57+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
58+
stdout '"Ref": "HEAD"'
59+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
60+
stdout '"Reuse": true'
61+
62+
63+
# If the hash refers to some other commit instead, the
64+
# result should not be reused.
65+
66+
replace f213069baa68ec26412fb373c7cf6669db1f8e69 08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a no-nested.json
67+
68+
go list -reuse=no-nested.json -json -m --versions -e vcs-test.golang.org/git/issue61415.git/nested@latest
69+
stdout '"Err": "module vcs-test.golang.org/git/issue61415.git/nested: no matching versions for query \\"latest\\""'
70+
stdout '"URL":' # randomly-chosen vcweb localhost URL
71+
stdout '"Subdir": "nested"'
72+
stdout '"TagPrefix": "nested/"'
73+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
74+
stdout '"Ref": "HEAD"'
75+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
76+
! stdout '"Reuse"'

src/cmd/go/testdata/script/reuse_git.txt

+10-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ stdout '"Version": "latest"'
5555
stdout '"Error":.*no matching versions'
5656
! stdout '"TagPrefix"'
5757
stdout '"TagSum": "t1:47DEQpj8HBSa[+]/TImW[+]5JCeuQeRkm5NMpJWZG3hSuFU="'
58-
! stdout '"(Ref|Hash|RepoSum)":'
58+
stdout '"Ref": "HEAD"'
59+
stdout '"Hash": "fc3a09f3dc5cfe0d7a743ea18f1f5226e68b3777"'
60+
! stdout 'RepoSum'
5961

6062
# go mod download vcstest/hello/sub/v9 should also fail, print origin info with TagPrefix
6163
! go mod download -x -json vcs-test.golang.org/git/hello.git/sub/v9@latest
@@ -64,7 +66,9 @@ stdout '"Version": "latest"'
6466
stdout '"Error":.*no matching versions'
6567
stdout '"TagPrefix": "sub/"'
6668
stdout '"TagSum": "t1:47DEQpj8HBSa[+]/TImW[+]5JCeuQeRkm5NMpJWZG3hSuFU="'
67-
! stdout '"(Ref|Hash|RepoSum)":'
69+
stdout '"Ref": "HEAD"'
70+
stdout '"Hash": "fc3a09f3dc5cfe0d7a743ea18f1f5226e68b3777"'
71+
! stdout 'RepoSum'
6872

6973
# go mod download vcstest/hello@nonexist should fail, still print origin info
7074
! go mod download -x -json vcs-test.golang.org/git/hello.git@nonexist
@@ -200,7 +204,8 @@ stdout '"Reuse": true'
200204
stdout '"Error":.*no matching versions'
201205
! stdout '"TagPrefix"'
202206
stdout '"TagSum": "t1:47DEQpj8HBSa[+]/TImW[+]5JCeuQeRkm5NMpJWZG3hSuFU="'
203-
! stdout '"(Ref|Hash)":'
207+
stdout '"Ref": "HEAD"'
208+
stdout '"Hash": "fc3a09f3dc5cfe0d7a743ea18f1f5226e68b3777"'
204209
! stdout '"(Dir|Info|GoMod|Zip)"'
205210

206211
# reuse go mod download vcstest/hello/sub/v9 error result
@@ -210,7 +215,8 @@ stdout '"Reuse": true'
210215
stdout '"Error":.*no matching versions'
211216
stdout '"TagPrefix": "sub/"'
212217
stdout '"TagSum": "t1:47DEQpj8HBSa[+]/TImW[+]5JCeuQeRkm5NMpJWZG3hSuFU="'
213-
! stdout '"(Ref|Hash)":'
218+
stdout '"Ref": "HEAD"'
219+
stdout '"Hash": "fc3a09f3dc5cfe0d7a743ea18f1f5226e68b3777"'
214220
! stdout '"(Dir|Info|GoMod|Zip)"'
215221

216222
# reuse go mod download vcstest/hello@nonexist
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
handle git
2+
3+
env GIT_AUTHOR_NAME='Bryan C. Mills'
4+
env GIT_AUTHOR_EMAIL='[email protected]'
5+
env GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME
6+
env GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL
7+
8+
at 2023-11-14T13:00:00-05:00
9+
10+
git init
11+
12+
git add go.mod nested
13+
git commit -m 'nested: add go.mod'
14+
git branch -m main
15+
16+
git tag has-nested
17+
18+
at 2023-11-14T13:00:01-05:00
19+
20+
git rm -r nested
21+
git commit -m 'nested: delete subdirectory'
22+
23+
git show-ref --tags --heads
24+
cmp stdout .git-refs
25+
26+
git log --pretty=oneline
27+
cmp stdout .git-log
28+
29+
-- .git-refs --
30+
f213069baa68ec26412fb373c7cf6669db1f8e69 refs/heads/main
31+
08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a refs/tags/has-nested
32+
-- .git-log --
33+
f213069baa68ec26412fb373c7cf6669db1f8e69 nested: delete subdirectory
34+
08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a nested: add go.mod
35+
-- go.mod --
36+
module vcs-test.golang.org/git/issue61415.git
37+
38+
go 1.20
39+
-- nested/go.mod --
40+
module vcs-test.golang.org/git/issue61415.git/nested
41+
42+
go 1.20

0 commit comments

Comments
 (0)