hugolib: Improve error and reload handling of hook templates in server mode

Fixes #6635
This commit is contained in:
Bjørn Erik Pedersen 2019-12-20 08:11:36 +01:00
parent 0453683816
commit 8a58ebb311
8 changed files with 121 additions and 20 deletions

View file

@ -88,6 +88,7 @@ type commandeer struct {
doLiveReload bool
fastRenderMode bool
showErrorInBrowser bool
wasError bool
configured bool
paused bool

View file

@ -718,6 +718,9 @@ func (c *commandeer) handleBuildErr(err error, msg string) {
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 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
}
}
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) {

View file

@ -334,6 +334,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
// 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 {

View file

@ -158,6 +158,60 @@ SHORT3|
}
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)

View file

@ -564,6 +564,9 @@ type BuildCfg struct {
// 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 @@ func (h *HugoSites) findPagesByKindIn(kind string, inPages page.Pages) page.Page
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 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
for id, _ := range idset {
if po.cp.dependencyTracker.Search(id) != nil {
po.cp.Reset()
p.forceRender = true
continue OUTPUTS
}
}
@ -834,7 +848,6 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
po.cp.Reset()
}
}
p.forceRender = true
continue PAGES
}
}

View file

@ -629,9 +629,12 @@ func (p *pageState) Render(layout ...string) (template.HTML, error) {
}
// 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()

View file

@ -909,6 +909,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
contentFilesChanged []string
tmplChanged bool
tmplAdded bool
dataChanged bool
i18nChanged bool
@ -934,8 +935,16 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
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 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
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

View file

@ -68,6 +68,7 @@ type sitesBuilder struct {
// Used to test partial rebuilds.
changedFiles []string
removedFiles []string
// Aka the Hugo server mode.
running bool
@ -386,16 +387,22 @@ func (s *sitesBuilder) WithI18nAdded(filenameContent ...string) *sitesBuilder {
}
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) BuildFail(cfg BuildCfg) *sitesBuilder {
}
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