Skip to content

Commit bb8a96f

Browse files
Bryan C. Millsgopherbot
authored andcommitted
cmd/go: check that expected Origin fields are present to reuse module info
When 'go list' or 'go mod download' uses a proxy to resolve a version query like "@latest", it may have origin metadata about the resolved version but not about the inputs that would be needed to resolve the same query without using the proxy. We shouldn't just redact the incomplete information, because it might be useful independent of the -reuse flag. Instead, we examine the query to decide which origin information it ought to need, and avoid reusing it if that information isn't included. Fixes #61423. Change-Id: Ibeaa46ebba284beee285cbb1898e271e5a5b257b Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/543155 Reviewed-by: Michael Matloob <[email protected]> Commit-Queue: Bryan Mills <[email protected]> Auto-Submit: Bryan Mills <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 3ff5632 commit bb8a96f

File tree

4 files changed

+176
-19
lines changed

4 files changed

+176
-19
lines changed

src/cmd/go/internal/modfetch/codehost/codehost.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ type Origin struct {
9595
URL string `json:",omitempty"` // URL of repository
9696
Subdir string `json:",omitempty"` // subdirectory in repo
9797

98+
Hash string `json:",omitempty"` // commit hash or ID
99+
98100
// If TagSum is non-empty, then the resolution of this module version
99101
// depends on the set of tags present in the repo, specifically the tags
100102
// of the form TagPrefix + a valid semver version.
@@ -111,8 +113,7 @@ type Origin struct {
111113
// and the Hash is the Git object hash the ref maps to.
112114
// Other VCS might choose differently, but the idea is that Ref is the name
113115
// with a mutable meaning while Hash is a name with an immutable meaning.
114-
Ref string `json:",omitempty"`
115-
Hash string `json:",omitempty"`
116+
Ref string `json:",omitempty"`
116117

117118
// If RepoSum is non-empty, then the resolution of this module version
118119
// failed due to the repo being available but the version not being present.
@@ -124,7 +125,7 @@ type Origin struct {
124125
// Checkable reports whether the Origin contains anything that can be checked.
125126
// If not, the Origin is purely informational and should fail a CheckReuse call.
126127
func (o *Origin) Checkable() bool {
127-
return o.TagSum != "" || o.Ref != "" || o.Hash != "" || o.RepoSum != ""
128+
return o != nil && (o.TagSum != "" || o.Ref != "" || o.Hash != "" || o.RepoSum != "")
128129
}
129130

130131
// ClearCheckable clears the Origin enough to make Checkable return false.

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ func mergeOrigin(m1, m2 *codehost.Origin) *codehost.Origin {
214214
// Excluded versions will be omitted. If listRetracted is false, retracted
215215
// versions will also be omitted.
216216
func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
217+
// TODO(bcmills): Would it make sense to check for reuse here too?
218+
// Perhaps that doesn't buy us much, though: we would always have to fetch
219+
// all of the version tags to list the available versions anyway.
220+
217221
allowed := CheckAllowed
218222
if listRetracted {
219223
allowed = CheckExclusions
@@ -319,29 +323,30 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
319323
return
320324
}
321325

322-
if old := reuse[module.Version{Path: m.Path, Version: m.Version}]; old != nil {
323-
if err := checkReuse(ctx, m.Path, old.Origin); err == nil {
324-
*m = *old
325-
m.Query = ""
326-
m.Dir = ""
327-
return
328-
}
329-
}
330-
331326
checksumOk := func(suffix string) bool {
332327
return rs == nil || m.Version == "" || !mustHaveSums() ||
333328
modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
334329
}
335330

331+
mod := module.Version{Path: m.Path, Version: m.Version}
332+
336333
if m.Version != "" {
334+
if old := reuse[mod]; old != nil && old.Origin.Checkable() {
335+
if err := checkReuse(ctx, mod, old.Origin); err == nil {
336+
*m = *old
337+
m.Query = ""
338+
m.Dir = ""
339+
return
340+
}
341+
}
342+
337343
if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
338344
m.Error = &modinfo.ModuleError{Err: err.Error()}
339345
} else {
340346
m.Version = q.Version
341347
m.Time = &q.Time
342348
}
343349
}
344-
mod := module.Version{Path: m.Path, Version: m.Version}
345350

346351
if m.GoVersion == "" && checksumOk("/go.mod") {
347352
// Load the go.mod file to determine the Go version, since it hasn't

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

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,80 @@ func queryReuse(ctx context.Context, path, query, current string, allowed Allowe
9898
return info, err
9999
}
100100

101-
// checkReuse checks whether a revision of a given module or a version list
101+
// checkReuse checks whether a revision of a given module
102102
// for a given module may be reused, according to the information in origin.
103-
func checkReuse(ctx context.Context, path string, old *codehost.Origin) error {
103+
func checkReuse(ctx context.Context, m module.Version, old *codehost.Origin) error {
104104
return modfetch.TryProxies(func(proxy string) error {
105-
repo, err := lookupRepo(ctx, proxy, path)
105+
repo, err := lookupRepo(ctx, proxy, m.Path)
106106
if err != nil {
107107
return err
108108
}
109-
return repo.CheckReuse(ctx, old)
109+
return checkReuseRepo(ctx, repo, m.Path, m.Version, old)
110110
})
111111
}
112112

113+
func checkReuseRepo(ctx context.Context, repo versionRepo, path, query string, origin *codehost.Origin) error {
114+
if !origin.Checkable() {
115+
return errors.New("Origin is not checkable")
116+
}
117+
118+
// Ensure that the Origin actually includes enough fields to resolve the query.
119+
// If we got the previous Origin data from a proxy, it may be missing something
120+
// that we would have needed to resolve the query directly from the repo.
121+
switch {
122+
case origin.RepoSum != "":
123+
// A RepoSum is always acceptable, since it incorporates everything
124+
// (and is often associated with an error result).
125+
126+
case query == module.CanonicalVersion(query):
127+
// This query refers to a specific version, and Go module versions
128+
// are supposed to be cacheable and immutable (confirmed with checksums).
129+
// If the version exists at all, we shouldn't need any extra information
130+
// to identify which commit it resolves to.
131+
//
132+
// It may be associated with a Ref for a semantic-version tag, but if so
133+
// we don't expect that tag to change in the future. We also don't need a
134+
// TagSum: if a tag is removed from some ancestor commit, the version may
135+
// change from valid to invalid, but we're ok with keeping stale versions
136+
// as long as they were valid at some point in the past.
137+
//
138+
// If the version did not successfully resolve, the origin may indicate
139+
// a TagSum and/or RepoSum instead of a Hash, in which case we still need
140+
// to check those to ensure that the error is still applicable.
141+
142+
case IsRevisionQuery(path, query):
143+
// This query may refer to a branch, non-version tag, or commit ID.
144+
//
145+
// If it is a commit ID, we expect to see a Hash in the Origin data. On
146+
// the other hand, if it is not a commit ID, we expect to see either a Ref
147+
// (for a positive result) or a RepoSum (for a negative result), since
148+
// we don't expect refs in general to remain stable over time.
149+
if origin.Hash == "" && origin.Ref == "" {
150+
return fmt.Errorf("query %q requires a Hash or Ref", query)
151+
}
152+
// Once we resolve the query to a particular commit, we will need to
153+
// also identify the most appropriate version to assign to that commit.
154+
// (It may correspond to more than one valid version.)
155+
//
156+
// The most appropriate version depends on the tags associated with
157+
// both the commit itself (if the commit is a tagged version)
158+
// and its ancestors (if we need to produce a pseudo-version for it).
159+
if origin.TagSum == "" {
160+
return fmt.Errorf("query %q requires a TagSum", query)
161+
}
162+
163+
default:
164+
// The query may be "latest" or a version inequality or prefix.
165+
// Its result depends on the absence of higher tags matching the query,
166+
// not just the state of an individual ref or tag.
167+
if origin.TagSum == "" {
168+
return fmt.Errorf("query %q requires a TagSum", query)
169+
}
170+
}
171+
172+
return repo.CheckReuse(ctx, origin)
173+
}
174+
113175
// AllowedFunc is used by Query and other functions to filter out unsuitable
114176
// versions, for example, those listed in exclude directives in the main
115177
// module's go.mod file.
@@ -163,8 +225,8 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
163225
return nil, err
164226
}
165227

166-
if old := reuse[module.Version{Path: path, Version: query}]; old != nil {
167-
if err := repo.CheckReuse(ctx, old.Origin); err == nil {
228+
if old := reuse[module.Version{Path: path, Version: query}]; old != nil && old.Origin.Checkable() {
229+
if err := checkReuseRepo(ctx, repo, path, query, old.Origin); err == nil {
168230
info := &modfetch.RevInfo{
169231
Version: old.Version,
170232
Origin: old.Origin,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
[short] skip 'generates a vcstest git repo'
2+
[!git] skip
3+
4+
mkdir $WORK/mod1
5+
mkdir $WORK/mod2
6+
env GONOSUMDB=vcs-test.golang.org
7+
8+
env GOPROXY=direct
9+
env GOMODCACHE=$WORK/mod1
10+
11+
12+
# If we query a module version from a git repo, we expect its
13+
# Origin data to be reusable.
14+
15+
go list -m -json vcs-test.golang.org/git/issue61415.git@latest
16+
cp stdout git-latest.json
17+
stdout '"Version": "v0.0.0-20231114180001-f213069baa68"'
18+
stdout '"Origin":'
19+
stdout '"VCS": "git"'
20+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
21+
stdout '"Ref": "HEAD"'
22+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
23+
24+
go list -reuse=git-latest.json -m -json vcs-test.golang.org/git/issue61415.git@latest
25+
stdout '"Version": "v0.0.0-20231114180001-f213069baa68"'
26+
stdout '"Origin":'
27+
stdout '"VCS": "git"'
28+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
29+
stdout '"Ref": "HEAD"'
30+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
31+
stdout '"Reuse": true'
32+
33+
34+
# Now we construct a filesystem-based module proxy that
35+
# contains only an older commit.
36+
37+
go clean -modcache
38+
39+
go mod download -json vcs-test.golang.org/git/issue61415.git@08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a
40+
stdout '"Version": "v0.0.0-20231114180000-08a4fa6bb9c0"'
41+
stdout '"Origin":'
42+
stdout '"VCS": "git"'
43+
stdout '"Hash": "08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a"'
44+
45+
[GOOS:windows] env GOPROXY=file:///$WORK/mod1/cache/download
46+
[!GOOS:windows] env GOPROXY=file://$WORK/mod1/cache/download
47+
env GOMODCACHE=$WORK/modcache2
48+
49+
50+
# If we resolve the "latest" version query using a proxy,
51+
# it is only going to have Git origin information about the one
52+
# commit — not the other tags that would go into resolving
53+
# the underlying version list.
54+
55+
go list -m -json vcs-test.golang.org/git/issue61415.git@latest
56+
cp stdout proxy-latest.json
57+
stdout '"Version": "v0.0.0-20231114180000-08a4fa6bb9c0"'
58+
stdout '"Origin":'
59+
stdout '"VCS": "git"'
60+
stdout '"Hash": "08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a"'
61+
! stdout '"Ref":'
62+
! stdout '"TagSum":'
63+
64+
# The -reuse flag has no effect with a proxy, since the proxy can serve
65+
# metadata about a given module version cheaply anyway.
66+
go list -reuse=proxy-latest.json -m -json vcs-test.golang.org/git/issue61415.git@latest
67+
stdout '"Version": "v0.0.0-20231114180000-08a4fa6bb9c0"'
68+
stdout '"Origin":'
69+
stdout '"VCS": "git"'
70+
stdout '"Hash": "08a4fa6bb9c04ffba03b26ae427b0d6335d90a2a"'
71+
! stdout '"Ref":'
72+
! stdout '"TagSum":'
73+
! stdout '"Reuse":'
74+
75+
76+
# With GOPROXY=direct, the -reuse flag has an effect, but
77+
# the Origin data from the proxy should not be sufficient
78+
# for the proxy response to be reused.
79+
80+
env GOPROXY=direct
81+
82+
go list -reuse=proxy-latest.json -m -json vcs-test.golang.org/git/issue61415.git@latest
83+
stdout '"Version": "v0.0.0-20231114180001-f213069baa68"'
84+
stdout '"Origin":'
85+
stdout '"VCS": "git"'
86+
stdout '"Hash": "f213069baa68ec26412fb373c7cf6669db1f8e69"'
87+
stdout '"Ref": "HEAD"'
88+
stdout '"TagSum": "t1:47DEQpj8HBSa\+/TImW\+5JCeuQeRkm5NMpJWZG3hSuFU="'
89+
! stdout '"Reuse":'

0 commit comments

Comments
 (0)