Allow v1,v2 etc. style version names while still supporting full semver in queries

Fixes #14414
This commit is contained in:
Bjørn Erik Pedersen
2026-01-23 18:53:32 +01:00
committed by GitHub
parent ee91c707ee
commit ac2c3fb20f
4 changed files with 97 additions and 7 deletions
+18 -4
View 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)
}
+14
View 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) {
@@ -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|")
}
+18 -3
View 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