Skip to content

Commit 3bea90d

Browse files
author
Bryan C. Mills
committed
cmd/go: allow a fork with path […]/vN to replace gopkg.in/[…].vN
Fixes #34254 Change-Id: Ib4e476d31264342538c2cf381177823183cba890 Reviewed-on: https://go-review.googlesource.com/c/go/+/206761 Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 5e52ca0 commit 3bea90d

File tree

2 files changed

+62
-15
lines changed

2 files changed

+62
-15
lines changed

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -719,9 +719,6 @@ func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err e
719719
// because of replacement modules. This might be a fork of
720720
// the real module, found at a different path, usable only in
721721
// a replace directive.
722-
//
723-
// TODO(bcmills): This doesn't seem right. Investigate further.
724-
// (Notably: why can't we replace foo/v2 with fork-of-foo/v3?)
725722
dir2 := path.Join(r.codeDir, r.pathMajor[1:])
726723
file2 = path.Join(dir2, "go.mod")
727724
gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
@@ -747,11 +744,11 @@ func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err e
747744

748745
// Not v2/go.mod, so it's either go.mod or nothing. Which is it?
749746
if found1 {
750-
// Explicit go.mod with matching module path OK.
747+
// Explicit go.mod with matching major version ok.
751748
return rev, r.codeDir, gomod1, nil
752749
}
753750
if err1 == nil {
754-
// Explicit go.mod with non-matching module path disallowed.
751+
// Explicit go.mod with non-matching major version disallowed.
755752
suffix := ""
756753
if file2 != "" {
757754
suffix = fmt.Sprintf(" (and ...%s/go.mod does not exist)", r.pathMajor)
@@ -762,6 +759,9 @@ func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err e
762759
if r.pathMajor != "" { // ".v1", ".v2" for gopkg.in
763760
return "", "", nil, fmt.Errorf("%s has non-...%s module path %q%s at revision %s", file1, r.pathMajor, mpath1, suffix, rev)
764761
}
762+
if _, _, ok := module.SplitPathVersion(mpath1); !ok {
763+
return "", "", nil, fmt.Errorf("%s has malformed module path %q%s at revision %s", file1, mpath1, suffix, rev)
764+
}
765765
return "", "", nil, fmt.Errorf("%s has post-%s module path %q%s at revision %s", file1, semver.Major(version), mpath1, suffix, rev)
766766
}
767767

@@ -778,24 +778,43 @@ func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err e
778778
return "", "", nil, fmt.Errorf("missing %s/go.mod at revision %s", r.pathPrefix, rev)
779779
}
780780

781+
// isMajor reports whether the versions allowed for mpath are compatible with
782+
// the major version(s) implied by pathMajor, or false if mpath has an invalid
783+
// version suffix.
781784
func isMajor(mpath, pathMajor string) bool {
782785
if mpath == "" {
786+
// If we don't have a path, we don't know what version(s) it is compatible with.
787+
return false
788+
}
789+
_, mpathMajor, ok := module.SplitPathVersion(mpath)
790+
if !ok {
791+
// An invalid module path is not compatible with any version.
783792
return false
784793
}
785794
if pathMajor == "" {
786-
// mpath must NOT have version suffix.
787-
i := len(mpath)
788-
for i > 0 && '0' <= mpath[i-1] && mpath[i-1] <= '9' {
789-
i--
790-
}
791-
if i < len(mpath) && i >= 2 && mpath[i-1] == 'v' && mpath[i-2] == '/' {
792-
// Found valid suffix.
795+
// All of the valid versions for a gopkg.in module that requires major
796+
// version v0 or v1 are compatible with the "v0 or v1" implied by an empty
797+
// pathMajor.
798+
switch module.PathMajorPrefix(mpathMajor) {
799+
case "", "v0", "v1":
800+
return true
801+
default:
793802
return false
794803
}
795-
return true
796804
}
797-
// Otherwise pathMajor is ".v1", ".v2" (gopkg.in), or "/v2", "/v3" etc.
798-
return strings.HasSuffix(mpath, pathMajor)
805+
if mpathMajor == "" {
806+
// Even if pathMajor is ".v0" or ".v1", we can't be sure that a module
807+
// without a suffix is tagged appropriately. Besides, we don't expect clones
808+
// of non-gopkg.in modules to have gopkg.in paths, so a non-empty,
809+
// non-gopkg.in mpath is probably the wrong module for any such pathMajor
810+
// anyway.
811+
return false
812+
}
813+
// If both pathMajor and mpathMajor are non-empty, then we only care that they
814+
// have the same major-version validation rules. A clone fetched via a /v2
815+
// path might replace a module with path gopkg.in/foo.v2-unstable, and that's
816+
// ok.
817+
return pathMajor[1:] == mpathMajor[1:]
799818
}
800819

801820
func (r *codeRepo) GoMod(version string) (data []byte, err error) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Regression test for golang.org/issue/34254:
2+
# a clone of gopkg.in/[…].vN should be replaceable by
3+
# a fork hosted at corp.example.com/[…]/vN,
4+
# even if there is an explicit go.mod file containing the
5+
# gopkg.in path.
6+
7+
[short] skip
8+
[!net] skip
9+
[!exec:git] skip
10+
11+
env GO111MODULE=on
12+
env GOPROXY=direct
13+
env GOSUMDB=off
14+
15+
# Replacing gopkg.in/[…].vN with a repository with a root go.mod file
16+
# specifying […].vN and a compatible version should succeed, even if
17+
# the replacement path is not a gopkg.in path.
18+
cd dot-to-dot
19+
go list gopkg.in/src-d/go-git.v4
20+
21+
-- dot-to-dot/go.mod --
22+
module golang.org/issue/34254
23+
24+
go 1.13
25+
26+
require gopkg.in/src-d/go-git.v4 v4.13.1
27+
28+
replace gopkg.in/src-d/go-git.v4 v4.13.1 => github.com/src-d/go-git/v4 v4.13.1

0 commit comments

Comments
 (0)