9
9
"fmt"
10
10
"os"
11
11
pathpkg "path"
12
+ "path/filepath"
12
13
"strings"
13
14
"sync"
14
15
@@ -90,10 +91,20 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
90
91
badVersion := func (v string ) (* modfetch.RevInfo , error ) {
91
92
return nil , fmt .Errorf ("invalid semantic version %q in range %q" , v , query )
92
93
}
93
- var ok func (module.Version ) bool
94
- var prefix string
95
- var preferOlder bool
96
- var mayUseLatest bool
94
+ matchesMajor := func (v string ) bool {
95
+ _ , pathMajor , ok := module .SplitPathVersion (path )
96
+ if ! ok {
97
+ return false
98
+ }
99
+ return module .CheckPathMajor (v , pathMajor ) == nil
100
+ }
101
+ var (
102
+ ok func (module.Version ) bool
103
+ prefix string
104
+ preferOlder bool
105
+ mayUseLatest bool
106
+ preferIncompatible bool = strings .HasSuffix (current , "+incompatible" )
107
+ )
97
108
switch {
98
109
case query == "latest" :
99
110
ok = allowed
@@ -126,6 +137,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
126
137
ok = func (m module.Version ) bool {
127
138
return semver .Compare (m .Version , v ) <= 0 && allowed (m )
128
139
}
140
+ if ! matchesMajor (v ) {
141
+ preferIncompatible = true
142
+ }
129
143
130
144
case strings .HasPrefix (query , "<" ):
131
145
v := query [len ("<" ):]
@@ -135,6 +149,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
135
149
ok = func (m module.Version ) bool {
136
150
return semver .Compare (m .Version , v ) < 0 && allowed (m )
137
151
}
152
+ if ! matchesMajor (v ) {
153
+ preferIncompatible = true
154
+ }
138
155
139
156
case strings .HasPrefix (query , ">=" ):
140
157
v := query [len (">=" ):]
@@ -145,6 +162,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
145
162
return semver .Compare (m .Version , v ) >= 0 && allowed (m )
146
163
}
147
164
preferOlder = true
165
+ if ! matchesMajor (v ) {
166
+ preferIncompatible = true
167
+ }
148
168
149
169
case strings .HasPrefix (query , ">" ):
150
170
v := query [len (">" ):]
@@ -159,12 +179,18 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
159
179
return semver .Compare (m .Version , v ) > 0 && allowed (m )
160
180
}
161
181
preferOlder = true
182
+ if ! matchesMajor (v ) {
183
+ preferIncompatible = true
184
+ }
162
185
163
186
case semver .IsValid (query ) && isSemverPrefix (query ):
164
187
ok = func (m module.Version ) bool {
165
188
return matchSemverPrefix (query , m .Version ) && allowed (m )
166
189
}
167
190
prefix = query + "."
191
+ if ! matchesMajor (query ) {
192
+ preferIncompatible = true
193
+ }
168
194
169
195
default :
170
196
// Direct lookup of semantic version or commit identifier.
@@ -217,6 +243,10 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
217
243
if err != nil {
218
244
return nil , err
219
245
}
246
+ releases , prereleases , err := filterVersions (path , versions , ok , preferIncompatible )
247
+ if err != nil {
248
+ return nil , err
249
+ }
220
250
221
251
lookup := func (v string ) (* modfetch.RevInfo , error ) {
222
252
rev , err := repo .Stat (v )
@@ -237,28 +267,18 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
237
267
}
238
268
239
269
if preferOlder {
240
- for _ , v := range versions {
241
- if semver .Prerelease (v ) == "" && ok (module.Version {Path : path , Version : v }) {
242
- return lookup (v )
243
- }
270
+ if len (releases ) > 0 {
271
+ return lookup (releases [0 ])
244
272
}
245
- for _ , v := range versions {
246
- if semver .Prerelease (v ) != "" && ok (module.Version {Path : path , Version : v }) {
247
- return lookup (v )
248
- }
273
+ if len (prereleases ) > 0 {
274
+ return lookup (prereleases [0 ])
249
275
}
250
276
} else {
251
- for i := len (versions ) - 1 ; i >= 0 ; i -- {
252
- v := versions [i ]
253
- if semver .Prerelease (v ) == "" && ok (module.Version {Path : path , Version : v }) {
254
- return lookup (v )
255
- }
277
+ if len (releases ) > 0 {
278
+ return lookup (releases [len (releases )- 1 ])
256
279
}
257
- for i := len (versions ) - 1 ; i >= 0 ; i -- {
258
- v := versions [i ]
259
- if semver .Prerelease (v ) != "" && ok (module.Version {Path : path , Version : v }) {
260
- return lookup (v )
261
- }
280
+ if len (prereleases ) > 0 {
281
+ return lookup (prereleases [len (prereleases )- 1 ])
262
282
}
263
283
}
264
284
@@ -302,6 +322,52 @@ func matchSemverPrefix(p, v string) bool {
302
322
return len (v ) > len (p ) && v [len (p )] == '.' && v [:len (p )] == p && semver .Prerelease (v ) == ""
303
323
}
304
324
325
+ // filterVersions classifies versions into releases and pre-releases, filtering
326
+ // out:
327
+ // 1. versions that do not satisfy the 'ok' predicate, and
328
+ // 2. "+incompatible" versions, if a compatible one satisfies the predicate
329
+ // and the incompatible version is not preferred.
330
+ func filterVersions (path string , versions []string , ok func (module.Version ) bool , preferIncompatible bool ) (releases , prereleases []string , err error ) {
331
+ var lastCompatible string
332
+ for _ , v := range versions {
333
+ if ! ok (module.Version {Path : path , Version : v }) {
334
+ continue
335
+ }
336
+
337
+ if ! preferIncompatible {
338
+ if ! strings .HasSuffix (v , "+incompatible" ) {
339
+ lastCompatible = v
340
+ } else if lastCompatible != "" {
341
+ // If the latest compatible version is allowed and has a go.mod file,
342
+ // ignore any version with a higher (+incompatible) major version. (See
343
+ // https://golang.org/issue/34165.) Note that we even prefer a
344
+ // compatible pre-release over an incompatible release.
345
+
346
+ ok , err := versionHasGoMod (module.Version {Path : path , Version : lastCompatible })
347
+ if err != nil {
348
+ return nil , nil , err
349
+ }
350
+ if ok {
351
+ break
352
+ }
353
+
354
+ // No acceptable compatible release has a go.mod file, so the versioning
355
+ // for the module might not be module-aware, and we should respect
356
+ // legacy major-version tags.
357
+ preferIncompatible = true
358
+ }
359
+ }
360
+
361
+ if semver .Prerelease (v ) != "" {
362
+ prereleases = append (prereleases , v )
363
+ } else {
364
+ releases = append (releases , v )
365
+ }
366
+ }
367
+
368
+ return releases , prereleases , nil
369
+ }
370
+
305
371
type QueryResult struct {
306
372
Mod module.Version
307
373
Rev * modfetch.RevInfo
@@ -590,3 +656,12 @@ func ModuleHasRootPackage(m module.Version) (bool, error) {
590
656
_ , ok := dirInModule (m .Path , m .Path , root , isLocal )
591
657
return ok , nil
592
658
}
659
+
660
+ func versionHasGoMod (m module.Version ) (bool , error ) {
661
+ root , _ , err := fetch (m )
662
+ if err != nil {
663
+ return false , err
664
+ }
665
+ fi , err := os .Stat (filepath .Join (root , "go.mod" ))
666
+ return err == nil && ! fi .IsDir (), nil
667
+ }
0 commit comments