internal/proxydatasource: support incompatible modules in GetLatestMajorVersion

Currently, GetLatestMajorVersion uses v2 as a base when searching for
latest major versions. This fails to find major versions for
incompatible modules that jump directly from mod/path to mod/path/vN
where N > 2.

To avoid that, update GetLatestMajorVersion to use module's current
version as a base instead.

Add a test case with an incompatible module.

Fixes golang/go#42922

Change-Id: I660d76c3cddf420b85c25b8e7c9f425ba712824c
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/277472
Reviewed-by: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/proxydatasource/datasource.go b/internal/proxydatasource/datasource.go
index 167517a..17b2dcc 100644
--- a/internal/proxydatasource/datasource.go
+++ b/internal/proxydatasource/datasource.go
@@ -12,6 +12,7 @@
 	"fmt"
 	"path"
 	"sort"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -182,13 +183,27 @@
 func (ds *DataSource) GetLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
 	// We are checking if the full path is valid so that we can forward the error if not.
 	seriesPath := internal.SeriesPathForModule(modulePath)
-	_, err = ds.proxyClient.GetInfo(ctx, seriesPath, internal.LatestVersion)
+	info, err := ds.proxyClient.GetInfo(ctx, seriesPath, internal.LatestVersion)
 	if err != nil {
 		return "", "", err
 	}
-	const startVersion = 2
-	// We start checking versions from "/v2", since v1 and v0 versions don't
-	// have a major version at the end of the modulepath.
+
+	// Converting version numbers to integers may cause an overflow, as version
+	// numbers need not fit into machine integers.
+	// While using Atoi is wrong, for it to fail, the version number must reach a
+	// value higher than at least 2^31, which is unlikely.
+	startVersion, err := strconv.Atoi(strings.TrimPrefix(semver.Major(info.Version), "v"))
+	if err != nil {
+		return "", "", err
+	}
+	startVersion++
+
+	// We start checking versions from "/v2" or higher, since v1 and v0 versions
+	// don't have a major version at the end of the modulepath.
+	if startVersion < 2 {
+		startVersion = 2
+	}
+
 	for v := startVersion; ; v++ {
 		query := fmt.Sprintf("%s/v%d", seriesPath, v)
 
diff --git a/internal/proxydatasource/datasource_test.go b/internal/proxydatasource/datasource_test.go
index 6306bc1..5fee0d8 100644
--- a/internal/proxydatasource/datasource_test.go
+++ b/internal/proxydatasource/datasource_test.go
@@ -212,6 +212,13 @@
 		{
 			ModulePath: "bar.com/foo",
 		},
+		{
+			ModulePath: "incompatible.com/bar",
+			Version:    "v2.1.1+incompatible",
+		},
+		{
+			ModulePath: "incompatible.com/bar/v3",
+		},
 	}
 	client, teardownProxy := proxy.SetupTestClient(t, testModules)
 	defer teardownProxy()
@@ -249,6 +256,12 @@
 			wantModulePath:  "foo.com/bar/v3",
 			wantPackagePath: "foo.com/bar/v3",
 		},
+		{
+			fullPath:        "incompatible.com/bar",
+			modulePath:      "incompatible.com/bar",
+			wantModulePath:  "incompatible.com/bar/v3",
+			wantPackagePath: "incompatible.com/bar/v3",
+		},
 	} {
 		gotVersion, gotPath, err := ds.GetLatestMajorVersion(ctx, test.fullPath, test.modulePath)
 		if err != nil {