@@ -305,17 +305,46 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
305305 //
306306 // (If the version is +incompatible, then the go.mod file must not exist:
307307 // +incompatible is not an ongoing opt-out from semantic import versioning.)
308- var canUseIncompatible func () bool
309- canUseIncompatible = func () bool {
310- var ok bool
311- if r .codeDir == "" && r .pathMajor == "" {
308+ incompatibleOk := map [string ]bool {}
309+ canUseIncompatible := func (v string ) bool {
310+ if r .codeDir != "" || r .pathMajor != "" {
311+ // A non-empty codeDir indicates a module within a subdirectory,
312+ // which necessarily has a go.mod file indicating the module boundary.
313+ // A non-empty pathMajor indicates a module path with a major-version
314+ // suffix, which must match.
315+ return false
316+ }
317+
318+ ok , seen := incompatibleOk ["" ]
319+ if ! seen {
312320 _ , errGoMod := r .code .ReadFile (info .Name , "go.mod" , codehost .MaxGoMod )
313- if errGoMod != nil {
314- ok = true
321+ ok = (errGoMod != nil )
322+ incompatibleOk ["" ] = ok
323+ }
324+ if ! ok {
325+ // A go.mod file exists at the repo root.
326+ return false
327+ }
328+
329+ // Per https://go.dev/issue/51324, previous versions of the 'go' command
330+ // didn't always check for go.mod files in subdirectories, so if the user
331+ // requests a +incompatible version explicitly, we should continue to allow
332+ // it. Otherwise, if vN/go.mod exists, expect that release tags for that
333+ // major version are intended for the vN module.
334+ if v != "" && ! strings .HasSuffix (statVers , "+incompatible" ) {
335+ major := semver .Major (v )
336+ ok , seen = incompatibleOk [major ]
337+ if ! seen {
338+ _ , errGoModSub := r .code .ReadFile (info .Name , path .Join (major , "go.mod" ), codehost .MaxGoMod )
339+ ok = (errGoModSub != nil )
340+ incompatibleOk [major ] = ok
341+ }
342+ if ! ok {
343+ return false
315344 }
316345 }
317- canUseIncompatible = func () bool { return ok }
318- return ok
346+
347+ return true
319348 }
320349
321350 // checkCanonical verifies that the canonical version v is compatible with the
@@ -367,7 +396,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
367396 base := strings .TrimSuffix (v , "+incompatible" )
368397 var errIncompatible error
369398 if ! module .MatchPathMajor (base , r .pathMajor ) {
370- if canUseIncompatible () {
399+ if canUseIncompatible (base ) {
371400 v = base + "+incompatible"
372401 } else {
373402 if r .pathMajor != "" {
@@ -495,7 +524,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
495524 // Save the highest non-retracted canonical tag for the revision.
496525 // If we don't find a better match, we'll use it as the canonical version.
497526 if tagIsCanonical && semver .Compare (highestCanonical , v ) < 0 && ! isRetracted (v ) {
498- if module .MatchPathMajor (v , r .pathMajor ) || canUseIncompatible () {
527+ if module .MatchPathMajor (v , r .pathMajor ) || canUseIncompatible (v ) {
499528 highestCanonical = v
500529 }
501530 }
@@ -513,12 +542,12 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
513542 // retracted versions.
514543 allowedMajor := func (major string ) func (v string ) bool {
515544 return func (v string ) bool {
516- return (major == "" || semver .Major (v ) == major ) && ! isRetracted (v )
545+ return (( major == "" && canUseIncompatible ( v )) || semver .Major (v ) == major ) && ! isRetracted (v )
517546 }
518547 }
519548 if pseudoBase == "" {
520549 var tag string
521- if r .pseudoMajor != "" || canUseIncompatible () {
550+ if r .pseudoMajor != "" || canUseIncompatible ("" ) {
522551 tag , _ = r .code .RecentTag (info .Name , tagPrefix , allowedMajor (r .pseudoMajor ))
523552 } else {
524553 // Allow either v1 or v0, but not incompatible higher versions.
0 commit comments