ref: 8a58ebb311fd079f65068e7e37725e4d43f17ab5
parent: 0453683816cfbc94e1e19c644f5f84213bb8cf35
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Fri Dec 20 03:11:36 EST 2019
hugolib: Improve error and reload handling of hook templates in server mode Fixes #6635
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -88,6 +88,7 @@
doLiveReload bool
fastRenderMode bool
showErrorInBrowser bool
+ wasError bool
configured bool
paused bool
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -718,6 +718,9 @@
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
defer c.timeTrack(time.Now(), "Total")
+ defer func() {
+ c.wasError = false
+ }()
c.buildErr = nil
visited := c.visitedURLs.PeekAllSet()
@@ -734,16 +737,19 @@
}
}
- return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited}, events...)
+ return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, ErrRecovery: c.wasError}, events...)
}
func (c *commandeer) partialReRender(urls ...string) error {
+ defer func() {
+ c.wasError = false
+ }()
c.buildErr = nil
visited := make(map[string]bool)
for _, url := range urls {
visited[url] = true
}
- return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true})
+ return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError})
}
func (c *commandeer) fullRebuild(changeType string) {
--- a/commands/server.go
+++ b/commands/server.go
@@ -334,6 +334,7 @@
// First check the error state
err := f.c.getErrorWithContext()
if err != nil {
+ f.c.wasError = true
w.WriteHeader(500)
r, err := f.errorTemplate(err)
if err != nil {
--- a/hugolib/content_render_hooks_test.go
+++ b/hugolib/content_render_hooks_test.go
@@ -158,6 +158,60 @@
}
+func TestRenderHooksDeleteTemplate(t *testing.T) {
+ config := `
+baseURL="https://example.org"
+workingDir="/mywork"
+`
+ b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
+ b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
+ b.WithTemplatesAdded("_default/_markup/render-link.html", `html-render-link`)
+
+ b.WithContent("p1.md", `---
+title: P1
+---
+[First Link](https://www.google.com "Google's Homepage")
+
+`)
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/p1/index.html", `<p>html-render-link</p>`)
+
+ b.RemoveFiles(
+ "layouts/_default/_markup/render-link.html",
+ )
+
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/p1/index.html", `<p><a href="https://www.google.com" title="Google's Homepage">First Link</a></p>`)
+
+}
+
+func TestRenderHookAddTemplate(t *testing.T) {
+ config := `
+baseURL="https://example.org"
+workingDir="/mywork"
+`
+ b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
+ b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
+
+ b.WithContent("p1.md", `---
+title: P1
+---
+[First Link](https://www.google.com "Google's Homepage")
+
+`)
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/p1/index.html", `<p><a href="https://www.google.com" title="Google's Homepage">First Link</a></p>`)
+
+ b.EditFiles("layouts/_default/_markup/render-link.html", `html-render-link`)
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/p1/index.html", `<p>html-render-link</p>`)
+
+}
+
func TestRenderHooksRSS(t *testing.T) {
b := newTestSitesBuilder(t)
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -564,6 +564,9 @@
// we should skip most of the processing.
PartialReRender bool
+ // Set in server mode when the last build failed for some reason.
+ ErrRecovery bool
+
// Recently visited URLs. This is used for partial re-rendering.
RecentlyVisited map[string]bool
}
@@ -807,8 +810,20 @@
return h.Sites[0].findPagesByKindIn(kind, inPages)
}
-func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
+func (h *HugoSites) resetPageState() {
+ for _, s := range h.Sites {
+ for _, p := range s.rawAllPages {
+ for _, po := range p.pageOutputs {
+ if po.cp == nil {
+ continue
+ }
+ po.cp.Reset()
+ }
+ }
+ }
+}
+func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
for _, s := range h.Sites {
PAGES:
for _, p := range s.rawAllPages {
@@ -820,7 +835,6 @@
for id, _ := range idset {
if po.cp.dependencyTracker.Search(id) != nil {
po.cp.Reset()
- p.forceRender = true
continue OUTPUTS
}
}
@@ -834,7 +848,6 @@
po.cp.Reset()
}
}
- p.forceRender = true
continue PAGES
}
}
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -629,9 +629,12 @@
}
-// wrapError adds some more context to the given error if possible
+// wrapError adds some more context to the given error if possible/needed
func (p *pageState) wrapError(err error) error {
-
+ if _, ok := err.(*herrors.ErrorWithFileContext); ok {
+ // Preserve the first file context.
+ return err
+ }
var filename string
if !p.File().IsZero() {
filename = p.File().Filename()
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -909,6 +909,7 @@
contentFilesChanged []string
tmplChanged bool
+ tmplAdded bool
dataChanged bool
i18nChanged bool
@@ -934,8 +935,16 @@
logger.Println("Source changed", ev)
sourceChanged = append(sourceChanged, ev)
case files.ComponentFolderLayouts:
- logger.Println("Template changed", ev)
tmplChanged = true
+ if _, found := s.Tmpl.Lookup(id.Path); !found {
+ tmplAdded = true
+ }
+ if tmplAdded {
+ logger.Println("Template added", ev)
+ } else {
+ logger.Println("Template changed", ev)
+ }
+
case files.ComponentFolderData:
logger.Println("Data changed", ev)
dataChanged = true
@@ -1021,7 +1030,11 @@
sourceFilesChanged[ev.Name] = true
}
- h.resetPageStateFromEvents(changeIdentities)
+ if config.ErrRecovery || tmplAdded {
+ h.resetPageState()
+ } else {
+ h.resetPageStateFromEvents(changeIdentities)
+ }
if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
var filenamesChanged []string
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -68,6 +68,7 @@
// Used to test partial rebuilds.
changedFiles []string
+ removedFiles []string
// Aka the Hugo server mode.
running bool
@@ -386,16 +387,22 @@
}
func (s *sitesBuilder) EditFiles(filenameContent ...string) *sitesBuilder {
- var changedFiles []string
for i := 0; i < len(filenameContent); i += 2 {
filename, content := filepath.FromSlash(filenameContent[i]), filenameContent[i+1]
absFilename := s.absFilename(filename)
- changedFiles = append(changedFiles, absFilename)
+ s.changedFiles = append(s.changedFiles, absFilename)
writeSource(s.T, s.Fs, absFilename, content)
}
- s.changedFiles = changedFiles
+ return s
+}
+func (s *sitesBuilder) RemoveFiles(filenames ...string) *sitesBuilder {
+ for _, filename := range filenames {
+ absFilename := s.absFilename(filename)
+ s.removedFiles = append(s.removedFiles, absFilename)
+ s.Assert(s.Fs.Source.Remove(absFilename), qt.IsNil)
+ }
return s
}
@@ -523,17 +530,20 @@
}
func (s *sitesBuilder) changeEvents() []fsnotify.Event {
- if len(s.changedFiles) == 0 {
- return nil
- }
- events := make([]fsnotify.Event, len(s.changedFiles))
- // TODO(bep) remove?
- for i, v := range s.changedFiles {
- events[i] = fsnotify.Event{
+ var events []fsnotify.Event
+
+ for _, v := range s.changedFiles {
+ events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Write,
- }
+ })
+ }
+ for _, v := range s.removedFiles {
+ events = append(events, fsnotify.Event{
+ Name: v,
+ Op: fsnotify.Remove,
+ })
}
return events