From 95e5e9f4ab6e2b4565be4e46507aadeada2435a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 11 Jun 2026 11:57:09 +0200 Subject: [PATCH] Fix multi --renderSegments merge behavior Fixes #15024 --- hugolib/integrationtest_builder.go | 9 ++- hugolib/segments/segments.go | 46 +++++++++------ hugolib/segments/segments_integration_test.go | 59 +++++++++++++++++++ 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index c840a5eec..9db0c0ef9 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -559,7 +559,12 @@ func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) { func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) { s.Helper() var buff bytes.Buffer - s.Assert(s.printAndCheckFs(fs, "", &buff), qt.IsNil) + if err := s.printAndCheckFs(fs, "", &buff); err != nil { + // E.g. public not created, treat that as an empty dir. + if !errors.Is(err, os.ErrNotExist) { + s.Fatal(err) + } + } printFsLines := strings.Split(buff.String(), "\n") sort.Strings(printFsLines) content := strings.TrimSpace((strings.Join(printFsLines, "\n"))) @@ -589,7 +594,7 @@ func (s *IntegrationTestBuilder) printAndCheckFs(fs afero.Fs, path string, w io. return afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { if err != nil { - return fmt.Errorf("error: path %q: %s", path, err) + return err } path = filepath.ToSlash(path) if path == "" { diff --git a/hugolib/segments/segments.go b/hugolib/segments/segments.go index 382b8032b..12682048a 100644 --- a/hugolib/segments/segments.go +++ b/hugolib/segments/segments.go @@ -44,17 +44,35 @@ type SegmentFilter interface { ShouldExcludeFine(SegmentQuery) bool } -type segmentFilter struct { - exclude predicate.PR[SegmentQuery] +type segmentPredicate struct { include predicate.PR[SegmentQuery] + exclude predicate.PR[SegmentQuery] } +type segmentFilter struct { + segments []segmentPredicate +} + +// ShouldExcludeCoarse skips a whole site or output format only if every +// segment excludes it; a single segment that doesn't is enough to keep it. func (f segmentFilter) ShouldExcludeCoarse(q SegmentQuery) bool { - return f.exclude(q).OK() + for _, s := range f.segments { + if !s.exclude(q).OK() { + return false + } + } + return true } +// ShouldExcludeFine renders the query if any segment includes it and does not +// exclude it. func (f segmentFilter) ShouldExcludeFine(q SegmentQuery) bool { - return f.exclude(q).OK() || !f.include(q).OK() + for _, s := range f.segments { + if s.include(q).OK() && !s.exclude(q).OK() { + return false + } + } + return true } type segmentsBuilder struct { @@ -204,23 +222,17 @@ func (s *segmentsBuilder) build() (SegmentFilter, error) { if err != nil { return nil, err } - if sf.include == nil { - sf.include = include - } else { - sf.include = sf.include.Or(include) + if include == nil { + include = matchAll } - if sf.exclude == nil { - sf.exclude = exclude - } else { - sf.exclude = sf.exclude.Or(exclude) + if exclude == nil { + exclude = matchNothing } + sf.segments = append(sf.segments, segmentPredicate{include: include, exclude: exclude}) } - if sf.exclude == nil { - sf.exclude = matchNothing - } - if sf.include == nil { - sf.include = matchAll + if len(sf.segments) == 0 { + sf.segments = append(sf.segments, segmentPredicate{include: matchAll, exclude: matchNothing}) } return sf, nil diff --git a/hugolib/segments/segments_integration_test.go b/hugolib/segments/segments_integration_test.go index 89ec92d30..b9c619146 100644 --- a/hugolib/segments/segments_integration_test.go +++ b/hugolib/segments/segments_integration_test.go @@ -14,6 +14,7 @@ package segments_test import ( + "strings" "testing" qt "github.com/frankban/quicktest" @@ -76,6 +77,64 @@ tags: ["tag1", "tag2"] b.AssertFileExists("public/no/index.xml", false) } +// See issue 15024. +func TestSegmentsMultiple(t *testing.T) { + filesTemplate := ` +-- hugo.toml -- +renderSegments = SEGMENTS +disableKinds = ["home", "taxonomy", "term", "page"] +[outputs] +section = ['html', 'json'] +[segments] +[segments.excludeallkinds] +[[segments.excludeallkinds.excludes]] +kind = "**" +[segments.blog] +[[segments.blog.includes]] +path = "{/blog,/blog/**}" +[[segments.blog.excludes]] +output = 'json' +[segments.news] +[[segments.news.includes]] +path = "{/news,/news/**}" +-- layouts/all.html -- +{{ .Kind }}: {{ .Title }}|{{ .RelPermalink }}| +-- layouts/all.json -- +{{ .Kind }}: {{ .Title }}|{{ .RelPermalink }}| +-- content/blog/_index.md -- +-- content/blog/page1.md -- +--- +title: "Blog Page 1" +tags: ["tag1", "tag2"] +--- +-- content/news/_index.md -- +-- content/news/page1.md -- +--- +title: "News Page 1" +tags: ["tag1", "tag2"] +--- +` + files := strings.ReplaceAll(filesTemplate, "SEGMENTS", `["excludeallkinds", "blog", "news"]`) + + b := hugolib.Test(t, files, hugolib.TestOptInfo()) + + b.AssertPublishDir(` +blog/index.html +! blog/index.json +news/index.html +news/index.json +`) + + files = strings.ReplaceAll(filesTemplate, "SEGMENTS", `["excludeallkinds"]`) + + b = hugolib.Test(t, files, hugolib.TestOptInfo()) + + b.AssertPublishDir(` +! json +! html +`) +} + // See issue 14939. func TestRenderSegmentsMergesHugoStatsJSON(t *testing.T) { files := `