aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2024-02-09 13:52:36 +0200
committerBjørn Erik Pedersen <[email protected]>2024-02-16 13:17:53 +0100
commit639073e4fee8fd4235c1002b076e110fad4c82f2 (patch)
tree55f4c392bc4f156f8605993d3d416bd5b105cc40
parent21d9057dbfe64f668d5fc6a7f458e0984fbf7e56 (diff)
downloadhugo-639073e4fee8fd4235c1002b076e110fad4c82f2.tar.gz
hugo-639073e4fee8fd4235c1002b076e110fad4c82f2.zip
Fix rebuild with resources.Concat
Fixes #12017
-rw-r--r--common/paths/pathparser.go71
-rw-r--r--common/paths/pathparser_test.go6
-rw-r--r--config/allconfig/allconfig.go4
-rw-r--r--config/allconfig/configlanguage.go10
-rw-r--r--config/configProvider.go4
-rw-r--r--hugofs/component_fs.go2
-rw-r--r--hugolib/content_map.go9
-rw-r--r--hugolib/content_map_page.go11
-rw-r--r--hugolib/page.go4
-rw-r--r--hugolib/page__new.go9
-rw-r--r--hugolib/page__output.go7
-rw-r--r--hugolib/rebuild_test.go34
-rw-r--r--identity/finder.go44
-rw-r--r--identity/identity.go83
-rw-r--r--resources/resource.go2
-rw-r--r--resources/resource_cache.go2
-rw-r--r--resources/resource_factories/bundler/bundler.go29
-rw-r--r--resources/resource_factories/create/create.go18
18 files changed, 229 insertions, 120 deletions
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
index eceb46b3d..898eee341 100644
--- a/common/paths/pathparser.go
+++ b/common/paths/pathparser.go
@@ -18,9 +18,11 @@ import (
"path/filepath"
"runtime"
"strings"
+ "sync"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/identity"
)
var defaultPathParser PathParser
@@ -50,19 +52,42 @@ func NormalizePathStringBasic(s string) string {
return s
}
+// ParseIdentity parses component c with path s into a StringIdentity.
+func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
+ s = NormalizePathStringBasic(s)
+ p := getPath()
+ p.component = c
+ defer putPath(p)
+ p, err := pp.doParse(c, s, p)
+ if err != nil {
+ panic(err)
+ }
+ return identity.StringIdentity(p.IdentifierBase())
+}
+
// Parse parses component c with path s into Path using Hugo's content path rules.
-func (parser PathParser) Parse(c, s string) *Path {
- p, err := parser.parse(c, s)
+func (pp *PathParser) Parse(c, s string) *Path {
+ p, err := pp.parse(c, s)
if err != nil {
panic(err)
}
return p
}
+func (pp *PathParser) newPath(component string) *Path {
+ return &Path{
+ component: component,
+ posContainerLow: -1,
+ posContainerHigh: -1,
+ posSectionHigh: -1,
+ posIdentifierLanguage: -1,
+ }
+}
+
func (pp *PathParser) parse(component, s string) (*Path, error) {
ss := NormalizePathStringBasic(s)
- p, err := pp.doParse(component, ss)
+ p, err := pp.doParse(component, ss, pp.newPath(component))
if err != nil {
return nil, err
}
@@ -70,7 +95,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
if s != ss {
var err error
// Preserve the original case for titles etc.
- p.unnormalized, err = pp.doParse(component, s)
+ p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
if err != nil {
return nil, err
@@ -82,15 +107,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
return p, nil
}
-func (pp *PathParser) doParse(component, s string) (*Path, error) {
- p := &Path{
- component: component,
- posContainerLow: -1,
- posContainerHigh: -1,
- posSectionHigh: -1,
- posIdentifierLanguage: -1,
- }
-
+func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
hasLang := pp.LanguageIndex != nil
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
@@ -220,6 +237,7 @@ const (
)
type Path struct {
+ // Note: Any additions to this struct should also be added to the pathPool.
s string
posContainerLow int
@@ -239,6 +257,31 @@ type Path struct {
unnormalized *Path
}
+var pathPool = &sync.Pool{
+ New: func() any {
+ return &Path{}
+ },
+}
+
+func getPath() *Path {
+ return pathPool.Get().(*Path)
+}
+
+func putPath(p *Path) {
+ p.s = ""
+ p.posContainerLow = -1
+ p.posContainerHigh = -1
+ p.posSectionHigh = -1
+ p.component = ""
+ p.bundleType = 0
+ p.identifiers = p.identifiers[:0]
+ p.posIdentifierLanguage = -1
+ p.disabled = false
+ p.trimLeadingSlash = false
+ p.unnormalized = nil
+ pathPool.Put(p)
+}
+
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
func (p Path) TrimLeadingSlash() *Path {
p.trimLeadingSlash = true
@@ -254,7 +297,7 @@ func (p *Path) norm(s string) string {
// IdentifierBase satifies identity.Identity.
func (p *Path) IdentifierBase() string {
- return p.Base()[1:]
+ return p.Base()
}
// Component returns the component for this path (e.g. "content").
diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
index 27d0b45e9..8c89ddd41 100644
--- a/common/paths/pathparser_test.go
+++ b/common/paths/pathparser_test.go
@@ -349,3 +349,9 @@ func TestHasExt(t *testing.T) {
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
}
+
+func BenchmarkParseIdentity(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
+ }
+}
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index f22fe8a7e..21d12b0c7 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -663,7 +663,7 @@ type Configs struct {
// All below is set in Init.
Languages langs.Languages
LanguagesDefaultFirst langs.Languages
- ContentPathParser paths.PathParser
+ ContentPathParser *paths.PathParser
configLangs []config.AllProvider
}
@@ -735,7 +735,7 @@ func (c *Configs) Init() error {
c.Languages = languages
c.LanguagesDefaultFirst = languagesDefaultFirst
- c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
+ c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
diff --git a/config/allconfig/configlanguage.go b/config/allconfig/configlanguage.go
index 2cc80caa8..9971be65f 100644
--- a/config/allconfig/configlanguage.go
+++ b/config/allconfig/configlanguage.go
@@ -19,6 +19,7 @@ import (
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/langs"
)
@@ -42,7 +43,7 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
return c.m.LanguagesDefaultFirst
}
-func (c ConfigLanguage) PathParser() paths.PathParser {
+func (c ConfigLanguage) PathParser() *paths.PathParser {
return c.m.ContentPathParser
}
@@ -133,6 +134,13 @@ func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}
+func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
+ if !c.Watching() {
+ return identity.NopManager
+ }
+ return identity.NewManager(name)
+}
+
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
func (c ConfigLanguage) GetConfigSection(s string) any {
switch s {
diff --git a/config/configProvider.go b/config/configProvider.go
index 38dde3bb4..586a9b758 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -20,6 +20,7 @@ import (
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/langs"
)
@@ -31,7 +32,7 @@ type AllProvider interface {
LanguagePrefix() string
BaseURL() urls.BaseURL
BaseURLLiveReload() urls.BaseURL
- PathParser() paths.PathParser
+ PathParser() *paths.PathParser
Environment() string
IsMultihost() bool
IsMultiLingual() bool
@@ -57,6 +58,7 @@ type AllProvider interface {
BuildDrafts() bool
Running() bool
Watching() bool
+ NewIdentityManager(name string) identity.Manager
FastRenderMode() bool
PrintUnusedTemplates() bool
EnableMissingTranslationPlaceholders() bool
diff --git a/hugofs/component_fs.go b/hugofs/component_fs.go
index c3c64b18f..b6eeb89e5 100644
--- a/hugofs/component_fs.go
+++ b/hugofs/component_fs.go
@@ -241,7 +241,7 @@ type ComponentFsOptions struct {
DefaultContentLanguage string
// The parser used to parse paths provided by this filesystem.
- PathParser paths.PathParser
+ PathParser *paths.PathParser
}
func (fs *componentFs) Open(name string) (afero.File, error) {
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index 85300e3db..23b74e1c7 100644
--- a/hugolib/content_map.go
+++ b/hugolib/content_map.go
@@ -93,8 +93,8 @@ func (r *resourceSource) GetIdentity() identity.Identity {
return r.path
}
-func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) {
- f(r.GetIdentity())
+func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) bool {
+ return f(r.GetIdentity())
}
func (r *resourceSource) Path() string {
@@ -142,14 +142,15 @@ func (n resourceSources) GetIdentity() identity.Identity {
return nil
}
-func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) {
+func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) bool {
for _, r := range n {
if r != nil {
if f(r.GetIdentity()) {
- return
+ return true
}
}
}
+ return false
}
func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index 6273870b7..576ee5e08 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -605,12 +605,15 @@ func (n contentNodeIs) GetIdentity() identity.Identity {
return n[0].GetIdentity()
}
-func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) {
+func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) bool {
for _, nn := range n {
if nn != nil {
- nn.ForEeachIdentity(f)
+ if nn.ForEeachIdentity(f) {
+ return true
+ }
}
}
+ return false
}
func (n contentNodeIs) resetBuildState() {
@@ -1151,7 +1154,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
// First check the top level dependency manager.
for _, id := range changes {
checkedCounter.Add(1)
- if r := depsFinder.Contains(id, p.dependencyManager, 100); r > identity.FinderFoundOneOfManyRepetition {
+ if r := depsFinder.Contains(id, p.dependencyManager, 2); r > identity.FinderFoundOneOfManyRepetition {
for _, po := range p.pageOutputs {
resetPo(po, r)
}
@@ -1167,7 +1170,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
}
for _, id := range changes {
checkedCounter.Add(1)
- if r := depsFinder.Contains(id, po.dependencyManagerOutput, 2); r > identity.FinderFoundOneOfManyRepetition {
+ if r := depsFinder.Contains(id, po.dependencyManagerOutput, 50); r > identity.FinderFoundOneOfManyRepetition {
resetPo(po, r)
continue OUTPUTS
}
diff --git a/hugolib/page.go b/hugolib/page.go
index 10ecdcad2..c743d4282 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -120,8 +120,8 @@ func (p *pageState) GetIdentity() identity.Identity {
return p
}
-func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) {
- f(p)
+func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) bool {
+ return f(p)
}
func (p *pageState) GetDependencyManager() identity.Manager {
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 2c2d92ab8..9dd2f2cdf 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -20,7 +20,6 @@ import (
"sync/atomic"
"github.com/gohugoio/hugo/hugofs/files"
- "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/common/maps"
@@ -160,12 +159,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
return nil, nil
}
- var dependencyManager identity.Manager = identity.NopManager
-
- if m.s.conf.Internal.Watch {
- dependencyManager = identity.NewManager(m.Path())
- }
-
// Parse the rest of the page content.
m.content, err = m.newCachedContent(h, pi)
if err != nil {
@@ -178,7 +171,7 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
pageOutputTemplateVariationsState: &atomic.Uint32{},
resourcesPublishInit: &sync.Once{},
Staler: m,
- dependencyManager: dependencyManager,
+ dependencyManager: m.s.Conf.NewIdentityManager(m.Path()),
pageCommon: &pageCommon{
FileProvider: m,
AuthorProvider: m,
diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 9050766d1..2f4d6c205 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -51,11 +51,6 @@ func newPageOutput(
})
}
- var dependencyManager identity.Manager = identity.NopManager
- if ps.s.conf.Internal.Watch {
- dependencyManager = identity.NewManager(ps.Path() + "/" + f.Name)
- }
-
providers := struct {
page.PaginatorProvider
resource.ResourceLinksProvider
@@ -75,7 +70,7 @@ func newPageOutput(
TableOfContentsProvider: page.NopPage,
render: render,
paginator: pag,
- dependencyManagerOutput: dependencyManager,
+ dependencyManagerOutput: ps.s.Conf.NewIdentityManager((ps.Path() + "/" + f.Name)),
}
return po
diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
index b1ebe14d5..9b15fc3e2 100644
--- a/hugolib/rebuild_test.go
+++ b/hugolib/rebuild_test.go
@@ -1131,7 +1131,7 @@ Single.
Running: true,
NeedsOsFS: true,
NeedsNpmInstall: true,
- // LogLevel: logg.LevelTrace,
+ // LogLevel: logg.LevelDebug,
},
).Build()
@@ -1261,3 +1261,35 @@ func BenchmarkRebuildContentFileChange(b *testing.B) {
// fmt.Println(bb.LogString())
}
}
+
+func TestRebuildConcat(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+disableLiveReload = true
+disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
+-- assets/a.css --
+a
+-- assets/b.css --
+b
+-- assets/c.css --
+c
+-- assets/common/c1.css --
+c1
+-- assets/common/c2.css --
+c2
+-- layouts/index.html --
+{{ $a := resources.Get "a.css" }}
+{{ $b := resources.Get "b.css" }}
+{{ $common := resources.Match "common/*.css" | resources.Concat "common.css" | minify }}
+{{ $ab := slice $a $b $common | resources.Concat "ab.css" }}
+all: {{ $ab.RelPermalink }}
+`
+ b := TestRunning(t, files)
+
+ b.AssertFileContent("public/ab.css", "abc1c2")
+ b.EditFileReplaceAll("assets/common/c2.css", "c2", "c2 edited").Build()
+ b.AssertFileContent("public/ab.css", "abc1c2 edited")
+ b.AddFiles("assets/common/c3.css", "c3").Build()
+ b.AssertFileContent("public/ab.css", "abc1c2 editedc3")
+}
diff --git a/identity/finder.go b/identity/finder.go
index bd23d698e..91fac7237 100644
--- a/identity/finder.go
+++ b/identity/finder.go
@@ -169,12 +169,7 @@ func (f *Finder) checkManager(sid *searchID, m Manager, level int) FinderResult
return r
}
- ids := m.getIdentities()
- if len(ids) == 0 {
- r = FinderNotFound
- } else {
- r = f.search(sid, ids, level)
- }
+ r = f.search(sid, m, level)
if r == FinderFoundOneOfMany {
// Don't cache this one.
@@ -270,11 +265,7 @@ func (f *Finder) doCheckOne(sid *searchID, v Identity, depth int) FinderResult {
}
// search searches for id in ids.
-func (f *Finder) search(sid *searchID, ids Identities, depth int) FinderResult {
- if len(ids) == 0 {
- return FinderNotFound
- }
-
+func (f *Finder) search(sid *searchID, m Manager, depth int) FinderResult {
id := sid.id
if id == Anonymous {
@@ -285,19 +276,24 @@ func (f *Finder) search(sid *searchID, ids Identities, depth int) FinderResult {
return FinderNotFound
}
- for v := range ids {
- r := f.checkOne(sid, v, depth)
- if r > 0 {
- return r
- }
-
- m := GetDependencyManager(v)
- if r := f.checkManager(sid, m, depth+1); r > 0 {
- return r
- }
- }
-
- return FinderNotFound
+ var r FinderResult
+ m.forEeachIdentity(
+ func(v Identity) bool {
+ if r > 0 {
+ panic("should be terminated")
+ }
+ r = f.checkOne(sid, v, depth)
+ if r > 0 {
+ return true
+ }
+ m := GetDependencyManager(v)
+ if r = f.checkManager(sid, m, depth+1); r > 0 {
+ return true
+ }
+ return false
+ },
+ )
+ return r
}
// FinderConfig provides configuration for the Finder.
diff --git a/identity/identity.go b/identity/identity.go
index ccb2f6e79..c799759df 100644
--- a/identity/identity.go
+++ b/identity/identity.go
@@ -55,8 +55,8 @@ func NewManager(name string, opts ...ManagerOption) Manager {
// CleanString cleans s to be suitable as an identifier.
func CleanString(s string) string {
s = strings.ToLower(s)
- s = strings.TrimPrefix(filepath.ToSlash(s), "/")
- return path.Clean(s)
+ s = strings.Trim(filepath.ToSlash(s), "/")
+ return "/" + path.Clean(s)
}
// CleanStringIdentity cleans s to be suitable as an identifier and wraps it in a StringIdentity.
@@ -77,23 +77,6 @@ func GetDependencyManager(v any) Manager {
return nil
}
-// GetDependencyManagerForScope returns the DependencyManager for the given scope from v or nil if none found.
-// Note that it will fall back to an unscoped manager if none found for the given scope.
-func GetDependencyManagerForScope(v any, scope int) Manager {
- switch vv := v.(type) {
- case DependencyManagerScopedProvider:
- return vv.GetDependencyManagerForScope(scope)
- case types.Unwrapper:
- return GetDependencyManagerForScope(vv.Unwrapv(), scope)
- case Manager:
- return vv
- case DependencyManagerProvider:
- return vv.GetDependencyManager()
-
- }
- return nil
-}
-
// FirstIdentity returns the first Identity in v, Anonymous if none found
func FirstIdentity(v any) Identity {
var result Identity = Anonymous
@@ -169,7 +152,15 @@ type DependencyManagerScopedProvider interface {
type ForEeachIdentityProvider interface {
// ForEeachIdentityProvider calls cb for each Identity.
// If cb returns true, the iteration is terminated.
- ForEeachIdentity(cb func(id Identity) bool)
+ // The return value is whether the iteration was terminated.
+ ForEeachIdentity(cb func(id Identity) bool) bool
+}
+
+// ForEeachIdentityProviderFunc is a function that implements the ForEeachIdentityProvider interface.
+type ForEeachIdentityProviderFunc func(func(id Identity) bool) bool
+
+func (f ForEeachIdentityProviderFunc) ForEeachIdentity(cb func(id Identity) bool) bool {
+ return f(cb)
}
// ForEeachIdentityByNameProvider provides a way to look up identities by name.
@@ -279,9 +270,10 @@ type IsProbablyDependencyProvider interface {
type Manager interface {
Identity
AddIdentity(ids ...Identity)
+ AddIdentityForEach(ids ...ForEeachIdentityProvider)
GetIdentity() Identity
Reset()
- getIdentities() Identities
+ forEeachIdentity(func(id Identity) bool) bool
}
type ManagerOption func(m *identityManager)
@@ -301,8 +293,9 @@ type identityManager struct {
// mu protects _changes_ to this manager,
// reads currently assumes no concurrent writes.
- mu sync.RWMutex
- ids Identities
+ mu sync.RWMutex
+ ids Identities
+ forEachIds []ForEeachIdentityProvider
// Hooks used in debugging.
onAddIdentity func(id Identity)
@@ -312,7 +305,7 @@ func (im *identityManager) AddIdentity(ids ...Identity) {
im.mu.Lock()
for _, id := range ids {
- if id == Anonymous {
+ if id == nil || id == Anonymous {
continue
}
if _, found := im.ids[id]; !found {
@@ -325,6 +318,12 @@ func (im *identityManager) AddIdentity(ids ...Identity) {
im.mu.Unlock()
}
+func (im *identityManager) AddIdentityForEach(ids ...ForEeachIdentityProvider) {
+ im.mu.Lock()
+ im.forEachIds = append(im.forEachIds, ids...)
+ im.mu.Unlock()
+}
+
func (im *identityManager) ContainsIdentity(id Identity) FinderResult {
if im.Identity != Anonymous && id == im.Identity {
return FinderFound
@@ -355,10 +354,20 @@ func (im *identityManager) String() string {
return fmt.Sprintf("IdentityManager(%s)", im.name)
}
-// TODO(bep) these identities are currently only read on server reloads
-// so there should be no concurrency issues, but that may change.
-func (im *identityManager) getIdentities() Identities {
- return im.ids
+func (im *identityManager) forEeachIdentity(fn func(id Identity) bool) bool {
+ // The absense of a lock here is debliberate. This is currently opnly used on server reloads
+ // in a single-threaded context.
+ for id := range im.ids {
+ if fn(id) {
+ return true
+ }
+ }
+ for _, fe := range im.forEachIds {
+ if fe.ForEeachIdentity(fn) {
+ return true
+ }
+ }
+ return false
}
type nopManager int
@@ -366,6 +375,9 @@ type nopManager int
func (m *nopManager) AddIdentity(ids ...Identity) {
}
+func (m *nopManager) AddIdentityForEach(ids ...ForEeachIdentityProvider) {
+}
+
func (m *nopManager) IdentifierBase() string {
return ""
}
@@ -377,8 +389,8 @@ func (m *nopManager) GetIdentity() Identity {
func (m *nopManager) Reset() {
}
-func (m *nopManager) getIdentities() Identities {
- return nil
+func (m *nopManager) forEeachIdentity(func(id Identity) bool) bool {
+ return false
}
// returns whether further walking should be terminated.
@@ -401,11 +413,9 @@ func walkIdentities(v any, level int, deep bool, seen map[Identity]bool, cb func
if deep {
if m := GetDependencyManager(id); m != nil {
- for id2 := range m.getIdentities() {
- if walkIdentitiesShallow(id2, level+1, cbRecursive) {
- return true
- }
- }
+ m.forEeachIdentity(func(id2 Identity) bool {
+ return walkIdentitiesShallow(id2, level+1, cbRecursive)
+ })
}
}
return false
@@ -420,6 +430,9 @@ func walkIdentitiesShallow(v any, level int, cb func(level int, id Identity) boo
if id == Anonymous {
return false
}
+ if id == nil {
+ return false
+ }
return cb(level, id)
}
diff --git a/resources/resource.go b/resources/resource.go
index 900b05a5f..73a0ad56e 100644
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -171,7 +171,7 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
fd.MediaType = mediaType
if fd.DependencyManager == nil {
- fd.DependencyManager = identity.NopManager
+ fd.DependencyManager = r.Cfg.NewIdentityManager("resource")
}
return nil
diff --git a/resources/resource_cache.go b/resources/resource_cache.go
index a76a51b1c..bf930c71d 100644
--- a/resources/resource_cache.go
+++ b/resources/resource_cache.go
@@ -39,7 +39,7 @@ func newResourceCache(rs *Spec, memCache *dynacache.Cache) *ResourceCache {
cacheResources: dynacache.GetOrCreatePartition[string, resource.Resources](
memCache,
"/ress",
- dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
+ dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnRebuild, Weight: 40},
),
cacheResourceTransformation: dynacache.GetOrCreatePartition[string, *resourceAdapterInner](
memCache,
diff --git a/resources/resource_factories/bundler/bundler.go b/resources/resource_factories/bundler/bundler.go
index c255da601..dd0f1a4e1 100644
--- a/resources/resource_factories/bundler/bundler.go
+++ b/resources/resource_factories/bundler/bundler.go
@@ -20,6 +20,7 @@ import (
"path"
"github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
@@ -86,13 +87,32 @@ func (c *Client) Concat(targetPath string, r resource.Resources) (resource.Resou
// The given set of resources must be of the same Media Type.
// We may improve on that in the future, but then we need to know more.
- for i, r := range r {
- if i > 0 && r.MediaType().Type != resolvedm.Type {
- return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", r.MediaType().Type, resolvedm.Type)
+ for i, rr := range r {
+ if i > 0 && rr.MediaType().Type != resolvedm.Type {
+ return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", rr.MediaType().Type, resolvedm.Type)
}
- resolvedm = r.MediaType()
+ resolvedm = rr.MediaType()
}
+ idm := c.rs.Cfg.NewIdentityManager("concat")
+ // Add the concatenated resources as dependencies to the composite resource
+ // so that we can track changes to the individual resources.
+ idm.AddIdentityForEach(identity.ForEeachIdentityProviderFunc(
+ func(f func(identity.Identity) bool) bool {
+ var terminate bool
+ for _, rr := range r {
+ identity.WalkIdentitiesShallow(rr, func(depth int, id identity.Identity) bool {
+ terminate = f(id)
+ return terminate
+ })
+ if terminate {
+ break
+ }
+ }
+ return terminate
+ },
+ ))
+
concatr := func() (hugio.ReadSeekCloser, error) {
var rcsources []hugio.ReadSeekCloser
for _, s := range r {
@@ -136,6 +156,7 @@ func (c *Client) Concat(targetPath string, r resource.Resources) (resource.Resou
LazyPublish: true,
OpenReadSeekCloser: concatr,
TargetPath: targetPath,
+ DependencyManager: idm,
})
if err != nil {
return nil, err
diff --git a/resources/resource_factories/create/create.go b/resources/resource_factories/create/create.go
index e98eb7425..0cf84c49b 100644
--- a/resources/resource_factories/create/create.go
+++ b/resources/resource_factories/create/create.go
@@ -63,13 +63,6 @@ func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource
})
}
-func (c *Client) newDependencyManager() identity.Manager {
- if c.rs.Cfg.Running() {
- return identity.NewManager("resources")
- }
- return identity.NopManager
-}
-
// Get creates a new Resource by opening the given pathname in the assets filesystem.
func (c *Client) Get(pathname string) (resource.Resource, error) {
pathname = path.Clean(pathname)
@@ -79,7 +72,8 @@ func (c *Client) Get(pathname string) (resource.Resource, error) {
// The resource file will not be read before it gets used (e.g. in .Content),
// so we need to check that the file exists here.
filename := filepath.FromSlash(pathname)
- if _, err := c.rs.BaseFs.Assets.Fs.Stat(filename); err != nil {
+ fi, err := c.rs.BaseFs.Assets.Fs.Stat(filename)
+ if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
@@ -87,14 +81,16 @@ func (c *Client) Get(pathname string) (resource.Resource, error) {
return nil, err
}
+ pi := fi.(hugofs.FileMetaInfo).Meta().PathInfo
+
return c.rs.NewResource(resources.ResourceSourceDescriptor{
LazyPublish: true,
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
return c.rs.BaseFs.Assets.Fs.Open(filename)
},
- GroupIdentity: identity.StringIdentity(key),
- DependencyManager: c.newDependencyManager(),
- TargetPath: pathname,
+ Path: pi,
+ GroupIdentity: pi,
+ TargetPath: pathname,
})
})
}