]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Allow v1,v2 etc. style version names while still supporting full semver in queries
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 23 Jan 2026 17:53:32 +0000 (18:53 +0100)
committerGitHub <noreply@github.com>
Fri, 23 Jan 2026 17:53:32 +0000 (18:53 +0100)
Fixes #14414

common/version/version.go
common/version/version_test.go
hugolib/sitesmatrix/sitematrix_integration_test.go
hugolib/versions/versions.go

index 83bd4435e63262ff230c601a2cd24f22e0682022..34ed9bfd1a4992485ebffe22feb2fee020efefe3 100644 (file)
@@ -37,6 +37,9 @@ type Version struct {
        // HugoVersionSuffix is the suffix used in the Hugo version string.
        // It will be blank for release versions.
        Suffix string
+
+       // Set when parsed from a string,
+       source string
 }
 
 var (
@@ -51,6 +54,10 @@ func (v Version) IsAlphaBetaOrRC() bool {
        return strings.Contains(s, "alpha.") || strings.Contains(s, "beta.") || strings.Contains(s, "rc.")
 }
 
+func (v Version) IsZero() bool {
+       return v.Major == 0 && v.Minor == 0 && v.PatchLevel == 0 && v.Suffix == ""
+}
+
 func (v Version) String() string {
        return version(v.Major, v.Minor, v.PatchLevel, v.Suffix)
 }
@@ -109,6 +116,7 @@ func ParseVersion(s string) (Version, error) {
                vv.Suffix = suffix
        }
        vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
+       vv.source = s
 
        return vv, nil
 }
@@ -176,6 +184,10 @@ func CompareVersions(v1 Version, v2 any) int {
        case int64:
                c = compareFloatWithVersion(float64(d), v1)
        case Version:
+               if v1.IsZero() && d.IsZero() {
+                       // Fall back to source comparison.
+                       return strings.Compare(v1.source, d.source)
+               }
                if d.Major == v1.Major && d.Minor == v1.Minor && d.PatchLevel == v1.PatchLevel {
                        return strings.Compare(v1.Suffix, d.Suffix)
                }
@@ -200,11 +212,13 @@ func CompareVersions(v1 Version, v2 any) int {
                        return -1
                }
 
-               v, err := ParseVersion(s)
-               if err != nil {
-                       return -1
+               v2v, _ := ParseVersion(s)
+               if v1.IsZero() && v2v.IsZero() {
+                       // Fall back to source comparison.
+                       return strings.Compare(v1.source, v2v.source)
                }
-               return v1.Compare(v)
+
+               return v1.Compare(v2v)
 
        }
 
index f171e4d97a7a59784ea453a79c09f132ac20529d..ed8d2a7cf30efd8a7d086c040b128231da4fb620 100644 (file)
@@ -50,6 +50,11 @@ func TestHugoVersion(t *testing.T) {
 func TestCompareVersions(t *testing.T) {
        c := qt.New(t)
 
+       parseIgnoreErr := func(s string) Version {
+               v, _ := ParseVersion(s)
+               return v
+       }
+
        c.Assert(CompareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
        c.Assert(CompareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
        c.Assert(CompareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
@@ -69,6 +74,15 @@ func TestCompareVersions(t *testing.T) {
        c.Assert(CompareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
        c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
        c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
+
+       c.Assert(CompareVersions(parseIgnoreErr("foobar"), "v1.0.0"), qt.Equals, 1)
+       c.Assert(CompareVersions(parseIgnoreErr("v1.0.0"), "foobar"), qt.Equals, -1)
+       c.Assert(CompareVersions(parseIgnoreErr("foobar"), "foobar"), qt.Equals, 0)
+       c.Assert(CompareVersions(parseIgnoreErr("foobar"), parseIgnoreErr("foobar")), qt.Equals, 0)
+       c.Assert(CompareVersions(parseIgnoreErr("a"), "b"), qt.Equals, -1)
+       c.Assert(CompareVersions(parseIgnoreErr("a"), parseIgnoreErr("b")), qt.Equals, -1)
+       c.Assert(CompareVersions(parseIgnoreErr("b"), "a"), qt.Equals, 1)
+       c.Assert(CompareVersions(parseIgnoreErr("b"), parseIgnoreErr("a")), qt.Equals, 1)
 }
 
 func TestParseHugoVersion(t *testing.T) {
index 8c2e775594a16c727b9287e3400c649f9f6f1a1f..874936931bf09a7a0988138f20d807ccc122edde 100644 (file)
@@ -1590,3 +1590,50 @@ Title: {{ .Title }}|
        b.AssertFileContent("public/v1.0.0/p1/index.html", "Title: P1 v1|")
        b.AssertFileContent("public/v2.0.0/p1/index.html", "Title: P1 v1|")
 }
+
+func TestSitesMatrixFileMountThemeIssue14414(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+
+disableKinds = ["taxonomy", "term", "rss", "sitemap"]
+defaultContentVersion = "v1"
+defaultContentVersionInSubDir = true
+
+[versions]
+[versions."v1"]
+[versions."v2"]
+
+[module]
+[[module.mounts]]
+source = 'content'
+target = 'content'
+[module.mounts.sites.matrix]
+versions = '> v2.0.0'
+[[module.imports]]
+ignoreConfig = true
+path         = 'mytheme'
+[[module.imports.mounts]]
+source = 'content'
+target = 'content'
+[module.imports.mounts.sites.matrix]
+versions = '<= v2.0.0'
+
+-- themes/mytheme/content/p1.md --
+---
+title: "P1 from theme"
+---
+-- content/p1.md --
+---
+title: "P1 from site"
+---
+-- layouts/all.html --
+Title: {{ .Title }}|Version: {{ .Site.Version.Name }}|
+
+`
+
+       b := hugolib.Test(t, files)
+
+       b.AssertFileContent("public/v1/p1/index.html", "Title: P1 from theme|Version: v1|")
+}
index ca372c23effba8432ea6c85d84a01e7e243273b5..19ac181d2a1e83e4690667404df2c4371147c8b6 100644 (file)
@@ -64,6 +64,10 @@ type VersionInternal struct {
        // Name of the version.
        // This is the key from the config.
        Name string
+
+       // Parsed version of Name.
+       parsedVersion version.Version
+
        // Whether this version is the default version.
        // This will be by default rendered in the root.
        // There can only be one default version.
@@ -104,6 +108,15 @@ func (r VersionsInternal) ResolveIndex(name string) int {
                        return i
                }
        }
+       // Compare against parsed versions as well.
+       v, err := version.ParseVersion(name)
+       if err == nil {
+               for i, version := range r.Sorted {
+                       if version.parsedVersion.Compare(v) == 0 {
+                               return i
+                       }
+               }
+       }
        return -1
 }
 
@@ -161,15 +174,17 @@ func (r *VersionsInternal) init(defaultContentVersion string) error {
                        defaultSeen = true
                }
 
-               r.Sorted = append(r.Sorted, VersionInternal{Name: k, Default: isDefault, VersionConfig: v})
+               // Ignore error, this may not be a semver version.
+               parsedVersion, _ := version.ParseVersion(k)
+
+               r.Sorted = append(r.Sorted, VersionInternal{Name: k, parsedVersion: parsedVersion, Default: isDefault, VersionConfig: v})
        }
 
        // Sort by weight if set, then semver descending.
        sort.SliceStable(r.Sorted, func(i, j int) bool {
                ri, rj := r.Sorted[i], r.Sorted[j]
                if ri.Weight == rj.Weight {
-                       v1, v2 := version.MustParseVersion(ri.Name), version.MustParseVersion(rj.Name)
-                       return v1.Compare(v2) < 0
+                       return ri.parsedVersion.Compare(rj.parsedVersion) < 0
                }
                if rj.Weight == 0 {
                        return true