diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index caa9a4bc2..8c80e189c 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -761,16 +761,16 @@ func (h *HugoSites) createPageCollections() error { 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 } } diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 91dfb9a37..d748a0169 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -288,7 +288,7 @@ func (h *HugoSites) render(config *BuildCfg) error { // 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 } } diff --git a/hugolib/hugo_sites_rebuild_test.go b/hugolib/hugo_sites_rebuild_test.go new file mode 100644 index 000000000..c2acbc238 --- /dev/null +++ b/hugolib/hugo_sites_rebuild_test.go @@ -0,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:
Content.
") + + 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:Edited content.
") + +} diff --git a/hugolib/page.go b/hugolib/page.go index 99b771eee..2ed828df6 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -332,8 +332,8 @@ func (p *pageState) getLayouts(layouts ...string) ([]string, error) { } // 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 @@ func (p *pageState) posOffset(offset int) text.Position { // 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 @@ func (p *pageState) shiftToOutputFormat(idx int) error { 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 @@ func (p *pageState) shiftToOutputFormat(idx int) error { 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") } } diff --git a/hugolib/page__output.go b/hugolib/page__output.go index d38d7c852..619ac0d77 100644 --- a/hugolib/page__output.go +++ b/hugolib/page__output.go @@ -41,7 +41,7 @@ func newPageOutput( var pag *pagePaginator if render && ps.IsNode() { - pag = &pagePaginator{source: ps} + pag = newPagePaginator(ps) paginatorProvider = pag } diff --git a/hugolib/page__paginator.go b/hugolib/page__paginator.go index 020d08089..026546742 100644 --- a/hugolib/page__paginator.go +++ b/hugolib/page__paginator.go @@ -19,16 +19,31 @@ import ( "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) Paginate(seq interface{}, options ...interface{}) (*page 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 @@ -81,7 +96,3 @@ func (p *pagePaginator) Paginator(options ...interface{}) (*page.Pager, error) { return p.current, nil } - -func (p *pagePaginator) rewind() { - p.current = p.current.First() -} diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 760704053..407061b67 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -171,12 +171,9 @@ func (s *Site) renderPaginator(p *pageState, layouts []string) error { 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) diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 0a8fbe7f5..d7c972443 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -14,6 +14,7 @@ import ( "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 @@ type sitesBuilder struct { dumper litter.Options + // Used to test partial rebuilds. + changedFiles []string + // Aka the Hugo server mode. running bool @@ -296,6 +300,19 @@ func (s *sitesBuilder) WithI18nAdded(filenameContent ...string) *sitesBuilder { 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 @@ func (s *sitesBuilder) BuildFail(cfg BuildCfg) *sitesBuilder { 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()