shithub: hugo

Download patch

ref: f7375c497239115cd30ae42af6b4d298e4e7ad7d
parent: 7966c0b5b7b2297527f8be9040b793de5e4e3f48
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Apr 10 06:11:51 EDT 2019

Fix paginator refresh on server change

Fixes #5838

--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -761,16 +761,16 @@
 	return nil
 }
 
-func (s *Site) preparePagesForRender(idx int) error {
+func (s *Site) preparePagesForRender(isRenderingSite bool, idx int) error {
 
 	for _, p := range s.workAllPages {
-		if err := p.initOutputFormat(idx); err != nil {
+		if err := p.initOutputFormat(isRenderingSite, idx); err != nil {
 			return err
 		}
 	}
 
 	for _, p := range s.headlessPages {
-		if err := p.initOutputFormat(idx); err != nil {
+		if err := p.initOutputFormat(isRenderingSite, idx); err != nil {
 			return err
 		}
 	}
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -288,7 +288,7 @@
 					// needs this set.
 					s2.rc = &siteRenderingContext{Format: renderFormat}
 
-					if err := s2.preparePagesForRender(siteRenderContext.sitesOutIdx); err != nil {
+					if err := s2.preparePagesForRender(s == s2, siteRenderContext.sitesOutIdx); err != nil {
 						return err
 					}
 				}
--- /dev/null
+++ b/hugolib/hugo_sites_rebuild_test.go
@@ -1,0 +1,77 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+	"testing"
+)
+
+func TestSitesRebuild(t *testing.T) {
+
+	configFile := `
+baseURL = "https://example.com"
+title = "Rebuild this"
+contentDir = "content"
+
+
+`
+
+	contentFilename := "content/blog/page1.md"
+
+	b := newTestSitesBuilder(t).WithConfigFile("toml", configFile)
+
+	// To simulate https://github.com/gohugoio/hugo/issues/5838, the home page
+	// needs a content page.
+	b.WithContent("content/_index.md", `---
+title: Home, Sweet Home!
+---
+
+`)
+
+	b.WithContent(contentFilename, `
+---
+title: "Page 1"
+summary: "Initial summary"
+paginate: 3
+---
+
+Content.
+
+`)
+
+	b.WithTemplatesAdded("index.html", `
+{{ range (.Paginate .Site.RegularPages).Pages }}
+* Page: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }}
+{{ end }}
+`)
+
+	b.Running().Build(BuildCfg{})
+
+	b.AssertFileContent("public/index.html", "* Page: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
+
+	b.EditFiles(contentFilename, `
+---
+title: "Page 1 edit"
+summary: "Edited summary"
+---
+
+Edited content.
+
+`)
+
+	b.Build(BuildCfg{})
+
+	b.AssertFileContent("public/index.html", "* Page: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+
+}
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -332,8 +332,8 @@
 }
 
 // This is serialized
-func (p *pageState) initOutputFormat(idx int) error {
-	if err := p.shiftToOutputFormat(idx); err != nil {
+func (p *pageState) initOutputFormat(isRenderingSite bool, idx int) error {
+	if err := p.shiftToOutputFormat(isRenderingSite, idx); err != nil {
 		return err
 	}
 
@@ -700,7 +700,7 @@
 
 // shiftToOutputFormat is serialized. The output format idx refers to the
 // full set of output formats for all sites.
-func (p *pageState) shiftToOutputFormat(idx int) error {
+func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
 	if err := p.initPage(); err != nil {
 		return err
 	}
@@ -715,6 +715,12 @@
 		panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx))
 	}
 
+	// Reset any built paginator. This will trigger when re-rendering pages in
+	// server mode.
+	if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil {
+		p.pageOutput.paginator.reset()
+	}
+
 	if idx > 0 {
 		// Check if we can reuse content from one of the previous formats.
 		for i := idx - 1; i >= 0; i-- {
@@ -728,7 +734,7 @@
 
 	for _, r := range p.Resources().ByType(pageResourceType) {
 		rp := r.(*pageState)
-		if err := rp.shiftToOutputFormat(idx); err != nil {
+		if err := rp.shiftToOutputFormat(isRenderingSite, idx); err != nil {
 			return errors.Wrap(err, "failed to shift outputformat in Page resource")
 		}
 	}
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -41,7 +41,7 @@
 	var pag *pagePaginator
 
 	if render && ps.IsNode() {
-		pag = &pagePaginator{source: ps}
+		pag = newPagePaginator(ps)
 		paginatorProvider = pag
 	}
 
--- a/hugolib/page__paginator.go
+++ b/hugolib/page__paginator.go
@@ -19,16 +19,31 @@
 	"github.com/gohugoio/hugo/resources/page"
 )
 
-type pagePaginator struct {
-	paginatorInit sync.Once
-	current       *page.Pager
+func newPagePaginator(source *pageState) *pagePaginator {
+	return &pagePaginator{
+		source:            source,
+		pagePaginatorInit: &pagePaginatorInit{},
+	}
+}
 
+type pagePaginator struct {
+	*pagePaginatorInit
 	source *pageState
 }
 
+type pagePaginatorInit struct {
+	init    sync.Once
+	current *page.Pager
+}
+
+// reset resets the paginator to allow for a rebuild.
+func (p *pagePaginator) reset() {
+	p.pagePaginatorInit = &pagePaginatorInit{}
+}
+
 func (p *pagePaginator) Paginate(seq interface{}, options ...interface{}) (*page.Pager, error) {
 	var initErr error
-	p.paginatorInit.Do(func() {
+	p.init.Do(func() {
 		pagerSize, err := page.ResolvePagerSize(p.source.s.Cfg, options...)
 		if err != nil {
 			initErr = err
@@ -56,7 +71,7 @@
 
 func (p *pagePaginator) Paginator(options ...interface{}) (*page.Pager, error) {
 	var initErr error
-	p.paginatorInit.Do(func() {
+	p.init.Do(func() {
 		pagerSize, err := page.ResolvePagerSize(p.source.s.Cfg, options...)
 		if err != nil {
 			initErr = err
@@ -80,8 +95,4 @@
 	}
 
 	return p.current, nil
-}
-
-func (p *pagePaginator) rewind() {
-	p.current = p.current.First()
 }
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -171,12 +171,9 @@
 	f := p.s.rc.Format
 	d.Type = f
 
-	// Rewind
-	p.paginator.rewind()
-	defer func() {
-		// Prepare for any re-rendering in server mode.
-		p.paginator.rewind()
-	}()
+	if p.paginator.current == nil || p.paginator.current != p.paginator.current.First() {
+		panic(fmt.Sprintf("invalid paginator state for %q", p.pathOrTitle()))
+	}
 
 	// Write alias for page 1
 	d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -14,6 +14,7 @@
 	"strings"
 	"text/template"
 
+	"github.com/fsnotify/fsnotify"
 	"github.com/gohugoio/hugo/common/herrors"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/deps"
@@ -45,6 +46,9 @@
 
 	dumper litter.Options
 
+	// Used to test partial rebuilds.
+	changedFiles []string
+
 	// Aka the Hugo server mode.
 	running bool
 
@@ -296,6 +300,19 @@
 	return s
 }
 
+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]
+		changedFiles = append(changedFiles, filename)
+		writeSource(s.T, s.Fs, filename, content)
+
+	}
+	s.changedFiles = changedFiles
+
+	return s
+}
+
 func (s *sitesBuilder) writeFilePairs(folder string, filenameContent []string) *sitesBuilder {
 	if len(filenameContent)%2 != 0 {
 		s.Fatalf("expect filenameContent for %q in pairs (%d)", folder, len(filenameContent))
@@ -376,12 +393,33 @@
 	return s.build(cfg, true)
 }
 
+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{
+			Name: v,
+			Op:   fsnotify.Write,
+		}
+	}
+
+	return events
+}
+
 func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder {
+	defer func() {
+		s.changedFiles = nil
+	}()
+
 	if s.H == nil {
 		s.CreateSites()
 	}
 
-	err := s.H.Build(cfg)
+	err := s.H.Build(cfg, s.changeEvents()...)
 
 	if err == nil {
 		logErrorCount := s.H.NumLogErrors()