Fix panic on server rebuilds when using both base templates and template.Defer
Fixes #12963
This commit is contained in:
parent
565c30eac9
commit
a5e5be234c
5 changed files with 110 additions and 65 deletions
|
@ -65,6 +65,9 @@ func (q *EvictingStringQueue) Len() int {
|
|||
|
||||
// Contains returns whether the queue contains v.
|
||||
func (q *EvictingStringQueue) Contains(v string) bool {
|
||||
if q == nil {
|
||||
return false
|
||||
}
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return q.set[v]
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
|
@ -466,6 +467,28 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
|||
return s
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildPartial(urls ...string) *IntegrationTestBuilder {
|
||||
if _, err := s.BuildPartialE(urls...); err != nil {
|
||||
s.Fatal(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildPartialE(urls ...string) (*IntegrationTestBuilder, error) {
|
||||
if s.buildCount == 0 {
|
||||
panic("BuildPartial can only be used after a full build")
|
||||
}
|
||||
if !s.Cfg.Running {
|
||||
panic("BuildPartial can only be used in server mode")
|
||||
}
|
||||
visited := types.NewEvictingStringQueue(len(urls))
|
||||
for _, url := range urls {
|
||||
visited.Add(url)
|
||||
}
|
||||
buildCfg := BuildCfg{RecentlyVisited: visited, PartialReRender: true}
|
||||
return s, s.build(buildCfg)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) Close() {
|
||||
s.Helper()
|
||||
s.Assert(s.H.Close(), qt.IsNil)
|
||||
|
@ -747,10 +770,6 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
|||
s.counters = &buildCounters{}
|
||||
cfg.testCounters = s.counters
|
||||
|
||||
if s.buildCount > 0 && (len(changeEvents) == 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.buildCount++
|
||||
|
||||
err := s.H.Build(cfg, changeEvents...)
|
||||
|
|
|
@ -721,43 +721,3 @@ console.log("config.params.id", id3);
|
|||
b.EditFileReplaceAll("assets/other/bar.css", ".bar-edit {", ".bar-edit2 {").Build()
|
||||
b.AssertFileContent("public/mybundle/reactbatch.css", ".bar-edit2 {")
|
||||
}
|
||||
|
||||
func TestEditBaseofManyTimes(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
-- layouts/_default/baseof.html --
|
||||
Baseof.
|
||||
{{ block "main" . }}{{ end }}
|
||||
{{ with (templates.Defer (dict "key" "global")) }}
|
||||
Now. {{ now }}
|
||||
{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ define "main" }}
|
||||
Single.
|
||||
{{ end }}
|
||||
--
|
||||
-- layouts/_default/list.html --
|
||||
{{ define "main" }}
|
||||
List.
|
||||
{{ end }}
|
||||
-- content/mybundle/index.md --
|
||||
---
|
||||
title: "My Bundle"
|
||||
---
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
`
|
||||
|
||||
b := hugolib.TestRunning(t, files)
|
||||
b.AssertFileContent("public/index.html", "Baseof.")
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
b.EditFileReplaceAll("layouts/_default/baseof.html", "Now", "Now.").Build()
|
||||
b.AssertFileContent("public/index.html", "Now..")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/output/layouts"
|
||||
|
||||
|
@ -193,6 +194,8 @@ func newTemplateNamespace(funcs map[string]any) *templateNamespace {
|
|||
return &templateNamespace{
|
||||
prototypeHTML: htmltemplate.New("").Funcs(funcs),
|
||||
prototypeText: texttemplate.New("").Funcs(funcs),
|
||||
prototypeHTMLCloneCache: maps.NewCache[prototypeCloneID, *htmltemplate.Template](),
|
||||
prototypeTextCloneCache: maps.NewCache[prototypeCloneID, *texttemplate.Template](),
|
||||
templateStateMap: &templateStateMap{
|
||||
templates: make(map[string]*templateState),
|
||||
},
|
||||
|
@ -688,7 +691,7 @@ func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace
|
|||
func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
|
||||
if overlay.isText {
|
||||
var (
|
||||
templ = t.main.prototypeTextClone.New(overlay.name)
|
||||
templ = t.main.getPrototypeText(prototypeCloneIDBaseof).New(overlay.name)
|
||||
err error
|
||||
)
|
||||
|
||||
|
@ -713,7 +716,7 @@ func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Tem
|
|||
}
|
||||
|
||||
var (
|
||||
templ = t.main.prototypeHTMLClone.New(overlay.name)
|
||||
templ = t.main.getPrototypeHTML(prototypeCloneIDBaseof).New(overlay.name)
|
||||
err error
|
||||
)
|
||||
|
||||
|
@ -953,28 +956,38 @@ func (t *templateHandler) postTransform() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type prototypeCloneID uint16
|
||||
|
||||
const (
|
||||
prototypeCloneIDBaseof prototypeCloneID = iota + 1
|
||||
prototypeCloneIDDefer
|
||||
)
|
||||
|
||||
type templateNamespace struct {
|
||||
prototypeText *texttemplate.Template
|
||||
prototypeHTML *htmltemplate.Template
|
||||
prototypeTextClone *texttemplate.Template
|
||||
prototypeHTMLClone *htmltemplate.Template
|
||||
|
||||
prototypeHTMLCloneCache *maps.Cache[prototypeCloneID, *htmltemplate.Template]
|
||||
prototypeTextCloneCache *maps.Cache[prototypeCloneID, *texttemplate.Template]
|
||||
|
||||
*templateStateMap
|
||||
}
|
||||
|
||||
func (t *templateNamespace) getPrototypeText() *texttemplate.Template {
|
||||
if t.prototypeTextClone != nil {
|
||||
return t.prototypeTextClone
|
||||
}
|
||||
func (t *templateNamespace) getPrototypeText(id prototypeCloneID) *texttemplate.Template {
|
||||
v, ok := t.prototypeTextCloneCache.Get(id)
|
||||
if !ok {
|
||||
return t.prototypeText
|
||||
}
|
||||
|
||||
func (t *templateNamespace) getPrototypeHTML() *htmltemplate.Template {
|
||||
if t.prototypeHTMLClone != nil {
|
||||
return t.prototypeHTMLClone
|
||||
return v
|
||||
}
|
||||
|
||||
func (t *templateNamespace) getPrototypeHTML(id prototypeCloneID) *htmltemplate.Template {
|
||||
v, ok := t.prototypeHTMLCloneCache.Get(id)
|
||||
if !ok {
|
||||
return t.prototypeHTML
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
|
||||
t.mu.RLock()
|
||||
|
@ -989,9 +1002,10 @@ func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
|
|||
}
|
||||
|
||||
func (t *templateNamespace) createPrototypes() error {
|
||||
t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone())
|
||||
t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone())
|
||||
|
||||
for _, id := range []prototypeCloneID{prototypeCloneIDBaseof, prototypeCloneIDDefer} {
|
||||
t.prototypeHTMLCloneCache.Set(id, htmltemplate.Must(t.prototypeHTML.Clone()))
|
||||
t.prototypeTextCloneCache.Set(id, texttemplate.Must(t.prototypeText.Clone()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1021,7 +1035,7 @@ func (t *templateNamespace) addDeferredTemplate(owner *templateState, name strin
|
|||
var templ tpl.Template
|
||||
|
||||
if owner.isText() {
|
||||
prototype := t.getPrototypeText()
|
||||
prototype := t.getPrototypeText(prototypeCloneIDDefer)
|
||||
tt, err := prototype.New(name).Parse("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse empty text template %q: %w", name, err)
|
||||
|
@ -1029,7 +1043,7 @@ func (t *templateNamespace) addDeferredTemplate(owner *templateState, name strin
|
|||
tt.Tree.Root = n
|
||||
templ = tt
|
||||
} else {
|
||||
prototype := t.getPrototypeHTML()
|
||||
prototype := t.getPrototypeHTML(prototypeCloneIDDefer)
|
||||
tt, err := prototype.New(name).Parse("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse empty HTML template %q: %w", name, err)
|
||||
|
|
|
@ -649,3 +649,52 @@ E: An _emphasized_ word.
|
|||
"<details>\n <summary>Details</summary>\n <p>D: An <em>emphasized</em> word.</p>\n</details>",
|
||||
)
|
||||
}
|
||||
|
||||
// Issue 12963
|
||||
func TestEditBaseofParseAfterExecute(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
disableKinds = ["taxonomy", "term", "rss", "404", "sitemap"]
|
||||
[internal]
|
||||
fastRenderMode = true
|
||||
-- layouts/_default/baseof.html --
|
||||
Baseof!
|
||||
{{ block "main" . }}default{{ end }}
|
||||
{{ with (templates.Defer (dict "key" "global")) }}
|
||||
Now. {{ now }}
|
||||
{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ define "main" }}
|
||||
Single.
|
||||
{{ end }}
|
||||
-- layouts/_default/list.html --
|
||||
{{ define "main" }}
|
||||
List.
|
||||
{{ .Content }}
|
||||
{{ range .Pages }}{{ .Title }}{{ end }}|
|
||||
{{ end }}
|
||||
-- content/mybundle1/index.md --
|
||||
---
|
||||
title: "My Bundle 1"
|
||||
---
|
||||
-- content/mybundle2/index.md --
|
||||
---
|
||||
title: "My Bundle 2"
|
||||
---
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
Home!
|
||||
`
|
||||
|
||||
b := hugolib.TestRunning(t, files)
|
||||
b.AssertFileContent("public/index.html", "Home!")
|
||||
b.EditFileReplaceAll("layouts/_default/baseof.html", "Baseof", "Baseof!").Build()
|
||||
b.BuildPartial("/")
|
||||
b.AssertFileContent("public/index.html", "Baseof!!")
|
||||
b.BuildPartial("/mybundle1/")
|
||||
b.AssertFileContent("public/mybundle1/index.html", "Baseof!!")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue