Make sure that unreferenced but changed bundle resources gets republished

Fixes #13748
This commit is contained in:
Bjørn Erik Pedersen
2025-05-28 09:18:16 +02:00
parent 13e1617557
commit 766a2e7868
8 changed files with 77 additions and 38 deletions

View File

@@ -21,7 +21,6 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -1310,7 +1309,6 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
} }
po.renderState = 0 po.renderState = 0
po.p.resourcesPublishInit = &sync.Once{}
if r == identity.FinderFoundOneOfMany || po.f.Name == output.HTTPStatus404HTMLFormat.Name { if r == identity.FinderFoundOneOfMany || po.f.Name == output.HTTPStatus404HTMLFormat.Name {
// Will force a re-render even in fast render mode. // Will force a re-render even in fast render mode.
po.renderOnce = false po.renderOnce = false

View File

@@ -905,7 +905,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
handleChange := func(pathInfo *paths.Path, delete, isDir bool) { handleChange := func(pathInfo *paths.Path, delete, isDir bool) {
switch pathInfo.Component() { switch pathInfo.Component() {
case files.ComponentFolderContent: case files.ComponentFolderContent:
logger.Println("Source changed", pathInfo.Path())
isContentDataFile := pathInfo.IsContentData() isContentDataFile := pathInfo.IsContentData()
if !isContentDataFile { if !isContentDataFile {
if ids := h.pageTrees.collectAndMarkStaleIdentities(pathInfo); len(ids) > 0 { if ids := h.pageTrees.collectAndMarkStaleIdentities(pathInfo); len(ids) > 0 {

View File

@@ -19,7 +19,6 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugofs"
@@ -29,6 +28,7 @@ import (
"github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related" "github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/tpl/tplimpl" "github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/spf13/afero" "github.com/spf13/afero"
@@ -111,7 +111,6 @@ type pageState struct {
resource.Staler resource.Staler
dependencyManager identity.Manager dependencyManager identity.Manager
resourcesPublishInit *sync.Once
} }
func (p *pageState) incrPageOutputTemplateVariation() { func (p *pageState) incrPageOutputTemplateVariation() {
@@ -522,13 +521,17 @@ func (p *pageState) initPage() error {
} }
func (p *pageState) renderResources() error { func (p *pageState) renderResources() error {
var initErr error
p.resourcesPublishInit.Do(func() {
for _, r := range p.Resources() { for _, r := range p.Resources() {
if _, ok := r.(page.Page); ok { if _, ok := r.(page.Page); ok {
if p.s.h.buildCounter.Load() == 0 {
// Pages gets rendered with the owning page but we count them here. // Pages gets rendered with the owning page but we count them here.
p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages) p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
}
continue
}
if resources.IsPublished(r) {
continue continue
} }
@@ -540,8 +543,7 @@ func (p *pageState) renderResources() error {
src, ok := r.(resource.Source) src, ok := r.(resource.Source)
if !ok { if !ok {
initErr = fmt.Errorf("resource %T does not support resource.Source", r) return fmt.Errorf("resource %T does not support resource.Source", r)
return
} }
if err := src.Publish(); err != nil { if err := src.Publish(); err != nil {
@@ -552,9 +554,8 @@ func (p *pageState) renderResources() error {
p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files) p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
} }
} }
})
return initErr return nil
} }
func (p *pageState) AlternativeOutputFormats() page.OutputFormats { func (p *pageState) AlternativeOutputFormats() page.OutputFormats {

View File

@@ -16,7 +16,6 @@ package hugolib
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"github.com/gohugoio/hugo/hugofs/files" "github.com/gohugoio/hugo/hugofs/files"
@@ -190,7 +189,6 @@ func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
pid: pid, pid: pid,
pageOutput: nopPageOutput, pageOutput: nopPageOutput,
pageOutputTemplateVariationsState: &atomic.Uint32{}, pageOutputTemplateVariationsState: &atomic.Uint32{},
resourcesPublishInit: &sync.Once{},
Staler: m, Staler: m,
dependencyManager: m.s.Conf.NewIdentityManager(m.Path()), dependencyManager: m.s.Conf.NewIdentityManager(m.Path()),
pageCommon: &pageCommon{ pageCommon: &pageCommon{

View File

@@ -1946,3 +1946,23 @@ tags: ["tag1"]
// But that is a harder problem to tackle. // But that is a harder problem to tackle.
b.AssertFileContent("public/tags/index.html", "All. Tag1|Tag2|") b.AssertFileContent("public/tags/index.html", "All. Tag1|Tag2|")
} }
func TestRebuildEditNonReferencedResourceIssue13748(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- content/mybundle/index.md --
-- content/mybundle/resource.txt --
This is a resource file.
-- layouts/all.html --
All.
`
b := TestRunning(t, files)
b.AssertFileContent("public/mybundle/resource.txt", "This is a resource file.")
b.EditFileReplaceAll("content/mybundle/resource.txt", "This is a resource file.", "This is an edited resource file.").Build()
b.AssertFileContent("public/mybundle/resource.txt", "This is an edited resource file.")
}

View File

@@ -24,6 +24,7 @@ import (
"sync/atomic" "sync/atomic"
"github.com/gohugoio/hugo/identity" "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/lazy"
"github.com/gohugoio/hugo/resources/internal" "github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/common/hashing" "github.com/gohugoio/hugo/common/hashing"
@@ -54,6 +55,7 @@ var (
_ identity.DependencyManagerProvider = (*genericResource)(nil) _ identity.DependencyManagerProvider = (*genericResource)(nil)
_ identity.Identity = (*genericResource)(nil) _ identity.Identity = (*genericResource)(nil)
_ fileInfo = (*genericResource)(nil) _ fileInfo = (*genericResource)(nil)
_ isPublishedProvider = (*genericResource)(nil)
) )
type ResourceSourceDescriptor struct { type ResourceSourceDescriptor struct {
@@ -242,6 +244,7 @@ type baseResourceInternal interface {
fileInfo fileInfo
mediaTypeAssigner mediaTypeAssigner
targetPather targetPather
isPublishedProvider
ReadSeekCloser() (hugio.ReadSeekCloser, error) ReadSeekCloser() (hugio.ReadSeekCloser, error)
@@ -355,7 +358,7 @@ func GetTestInfoForResource(r resource.Resource) GenericResourceTestInfo {
// genericResource represents a generic linkable resource. // genericResource represents a generic linkable resource.
type genericResource struct { type genericResource struct {
publishInit *sync.Once publishInit *lazy.OnceMore
key string key string
keyInit *sync.Once keyInit *sync.Once
@@ -536,6 +539,10 @@ func (l *genericResource) Publish() error {
return err return err
} }
func (l *genericResource) isPublished() bool {
return l.publishInit.Done()
}
func (l *genericResource) RelPermalink() string { func (l *genericResource) RelPermalink() string {
return l.spec.PathSpec.GetBasePath(false) + paths.PathEscape(l.paths.TargetLink()) return l.spec.PathSpec.GetBasePath(false) + paths.PathEscape(l.paths.TargetLink())
} }
@@ -629,7 +636,7 @@ func (rc *genericResource) cloneWithUpdates(u *transformationUpdate) (baseResour
} }
func (l genericResource) clone() *genericResource { func (l genericResource) clone() *genericResource {
l.publishInit = &sync.Once{} l.publishInit = &lazy.OnceMore{}
l.keyInit = &sync.Once{} l.keyInit = &sync.Once{}
return &l return &l
} }
@@ -643,6 +650,10 @@ type targetPather interface {
TargetPath() string TargetPath() string
} }
type isPublishedProvider interface {
isPublished() bool
}
type resourceHash struct { type resourceHash struct {
value uint64 value uint64
size int64 size int64
@@ -702,6 +713,11 @@ func InternalResourceSourcePathBestEffort(r resource.Resource) string {
return InternalResourceTargetPath(r) return InternalResourceTargetPath(r)
} }
// isPublished returns true if the resource is published.
func IsPublished(r resource.Resource) bool {
return r.(isPublishedProvider).isPublished()
}
type targetPathProvider interface { type targetPathProvider interface {
// targetPath is the relative path to this resource. // targetPath is the relative path to this resource.
// In most cases this will be the same as the RelPermalink(), // In most cases this will be the same as the RelPermalink(),

View File

@@ -20,6 +20,7 @@ import (
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig" "github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/lazy"
"github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/internal" "github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/jsconfig" "github.com/gohugoio/hugo/resources/jsconfig"
@@ -189,7 +190,7 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro
gr := &genericResource{ gr := &genericResource{
Staler: &AtomicStaler{}, Staler: &AtomicStaler{},
h: &resourceHash{}, h: &resourceHash{},
publishInit: &sync.Once{}, publishInit: &lazy.OnceMore{},
keyInit: &sync.Once{}, keyInit: &sync.Once{},
includeHashInKey: isImage, includeHashInKey: isImage,
paths: rp, paths: rp,

View File

@@ -61,6 +61,7 @@ var (
_ identity.DependencyManagerProvider = (*resourceAdapter)(nil) _ identity.DependencyManagerProvider = (*resourceAdapter)(nil)
_ identity.IdentityGroupProvider = (*resourceAdapter)(nil) _ identity.IdentityGroupProvider = (*resourceAdapter)(nil)
_ resource.NameNormalizedProvider = (*resourceAdapter)(nil) _ resource.NameNormalizedProvider = (*resourceAdapter)(nil)
_ isPublishedProvider = (*resourceAdapter)(nil)
) )
// These are transformations that need special support in Hugo that may not // These are transformations that need special support in Hugo that may not
@@ -325,6 +326,11 @@ func (r *resourceAdapter) Publish() error {
return r.target.Publish() return r.target.Publish()
} }
func (r *resourceAdapter) isPublished() bool {
r.init(false, false)
return r.target.isPublished()
}
func (r *resourceAdapter) ReadSeekCloser() (hugio.ReadSeekCloser, error) { func (r *resourceAdapter) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
r.init(false, false) r.init(false, false)
return r.target.ReadSeekCloser() return r.target.ReadSeekCloser()