diff --git a/cmd/selfupdate/selfupdate.go b/cmd/selfupdate/selfupdate.go
index 0f5c9694a..26519fba1 100644
--- a/cmd/selfupdate/selfupdate.go
+++ b/cmd/selfupdate/selfupdate.go
@@ -133,18 +133,28 @@ func GetVersion(ctx context.Context, beta bool, version string) (newVersion, sit
if err != nil {
return "", siteURL, fmt.Errorf("failed to get list of releases: %w", err)
}
- reSubver := fmt.Sprintf(`href="\./%s\.\d+/"`, regexp.QuoteMeta(newVersion))
- allSubvers := regexp.MustCompile(reSubver).FindAllString(string(html), -1)
- if allSubvers == nil {
- return "", siteURL, errors.New("could not find the minor release")
+ newVersion, err = latestMicroRelease(html, newVersion)
+ if err != nil {
+ return "", siteURL, err
}
- // Use the fact that releases in the index are sorted by date
- lastSubver := allSubvers[len(allSubvers)-1]
- newVersion = lastSubver[8 : len(lastSubver)-2]
}
return
}
+// latestMicroRelease returns the latest micro release of the
+// major.minor release version linked to from the HTML index of
+// releases.
+func latestMicroRelease(html []byte, version string) (string, error) {
+ // The links may or may not start with ./ depending on what made the index
+ reSubver := fmt.Sprintf(`href="(?:\./)?(%s\.\d+)/"`, regexp.QuoteMeta(version))
+ allSubvers := regexp.MustCompile(reSubver).FindAllSubmatch(html, -1)
+ if allSubvers == nil {
+ return "", errors.New("could not find the minor release")
+ }
+ // Use the fact that releases in the index are sorted by date
+ return string(allSubvers[len(allSubvers)-1][1]), nil
+}
+
// InstallUpdate performs rclone self-update
func InstallUpdate(ctx context.Context, opt *Options) error {
// Find the latest release number
diff --git a/cmd/selfupdate/selfupdate_test.go b/cmd/selfupdate/selfupdate_test.go
index 3c22ee6ef..5161ef96a 100644
--- a/cmd/selfupdate/selfupdate_test.go
+++ b/cmd/selfupdate/selfupdate_test.go
@@ -45,6 +45,50 @@ func TestGetVersion(t *testing.T) {
assert.Equal(t, "v1.52.3", resultVer)
}
+func TestLatestMicroRelease(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ html string
+ want string
+ }{
+ {
+ name: "caddy",
+ html: `v1.51.0/
+v1.52.0/
+v1.52.3/
+v1.53.0/
+v1.52-DEV/
+rclone`,
+ want: "v1.52.3",
+ },
+ {
+ name: "no prefix",
+ html: `v1.51.0/
+v1.52.0/
+v1.52.3/
+v1.53.0/`,
+ want: "v1.52.3",
+ },
+ {
+ name: "other prefix",
+ html: `v1.52.0/
+v1.52.1/
+v1.52.2/
+v1x52.3/`,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := latestMicroRelease([]byte(test.html), "v1.52")
+ if test.want == "" {
+ assert.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+ assert.Equal(t, test.want, got)
+ })
+ }
+}
+
func TestInstallOnLinux(t *testing.T) {
testy.SkipUnreliable(t)
if runtime.GOOS != "linux" {