shithub: hugo

Download patch

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