shithub: hugo

Download patch

ref: ce6e4310febf5659392a41b543594382441f3681
parent: 95d62004a07d8bb6d2b94a56112fd419db7eeb65
author: Bjørn Erik Pedersen <[email protected]>
date: Sun Mar 11 14:59:11 EDT 2018

Refactor the GitInfo into the date handlers

Fixes #4495

--- a/hugolib/gitinfo.go
+++ b/hugolib/gitinfo.go
@@ -19,51 +19,41 @@
 	"strings"
 
 	"github.com/bep/gitmap"
+	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/helpers"
 )
 
-func (h *HugoSites) assembleGitInfo() {
-	if !h.Cfg.GetBool("enableGitInfo") {
-		return
+type gitInfo struct {
+	contentDir string
+	repo       *gitmap.GitRepo
+}
+
+func (g *gitInfo) forPage(p *Page) (*gitmap.GitInfo, bool) {
+	if g == nil {
+		return nil, false
 	}
+	name := path.Join(g.contentDir, filepath.ToSlash(p.Path()))
+	return g.repo.Files[name], true
+}
 
+func newGitInfo(cfg config.Provider) (*gitInfo, error) {
 	var (
-		workingDir = h.Cfg.GetString("workingDir")
-		contentDir = h.Cfg.GetString("contentDir")
+		workingDir = cfg.GetString("workingDir")
+		contentDir = cfg.GetString("contentDir")
 	)
 
 	gitRepo, err := gitmap.Map(workingDir, "")
 	if err != nil {
-		h.Log.ERROR.Printf("Got error reading Git log: %s", err)
-		return
+		return nil, err
 	}
 
-	gitMap := gitRepo.Files
 	repoPath := filepath.FromSlash(gitRepo.TopLevelAbsPath)
-
 	// The Hugo site may be placed in a sub folder in the Git repo,
 	// one example being the Hugo docs.
 	// We have to find the root folder to the Hugo site below the Git root.
 	contentRoot := strings.TrimPrefix(workingDir, repoPath)
 	contentRoot = strings.TrimPrefix(contentRoot, helpers.FilePathSeparator)
+	contentDir = path.Join(filepath.ToSlash(contentRoot), contentDir)
 
-	s := h.Sites[0]
-
-	for _, p := range s.AllPages {
-		if p.Path() == "" {
-			// Home page etc. with no content file.
-			continue
-		}
-		// Git normalizes file paths on this form:
-		filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path()))
-		g, ok := gitMap[filename]
-		if !ok {
-			h.Log.WARN.Printf("Failed to find GitInfo for %q", filename)
-			continue
-		}
-
-		p.GitInfo = g
-		p.Lastmod = p.GitInfo.AuthorDate
-	}
-
+	return &gitInfo{contentDir: contentDir, repo: gitRepo}, nil
 }
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -47,6 +47,9 @@
 
 	// Keeps track of bundle directories and symlinks to enable partial rebuilding.
 	ContentChanges *contentChangeMap
+
+	// If enabled, keeps a revision map for all content.
+	gitInfo *gitInfo
 }
 
 func (h *HugoSites) IsMultihost() bool {
@@ -146,7 +149,23 @@
 
 	h.Deps = sites[0].Deps
 
+	if err := h.initGitInfo(); err != nil {
+		return nil, err
+	}
+
 	return h, nil
+}
+
+func (h *HugoSites) initGitInfo() error {
+	if h.Cfg.GetBool("enableGitInfo") {
+		gi, err := newGitInfo(h.Cfg)
+		if err != nil {
+			h.Log.ERROR.Println("Failed to read Git log:", err)
+		} else {
+			h.gitInfo = gi
+		}
+	}
+	return nil
 }
 
 func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -165,8 +165,6 @@
 	}
 
 	if config.whatChanged.source {
-		h.assembleGitInfo()
-
 		for _, s := range h.Sites {
 			if err := s.buildSiteMeta(); err != nil {
 				return err
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1119,13 +1119,20 @@
 		mtime = p.Source.FileInfo().ModTime()
 	}
 
+	var gitAuthorDate time.Time
+	if p.GitInfo != nil {
+		gitAuthorDate = p.GitInfo.AuthorDate
+	}
+
 	descriptor := &pagemeta.FrontMatterDescriptor{
-		Frontmatter:  frontmatter,
-		Params:       p.params,
-		Dates:        &p.PageDates,
-		PageURLs:     &p.URLPath,
-		BaseFilename: p.BaseFileName(),
-		ModTime:      mtime}
+		Frontmatter:   frontmatter,
+		Params:        p.params,
+		Dates:         &p.PageDates,
+		PageURLs:      &p.URLPath,
+		BaseFilename:  p.BaseFileName(),
+		ModTime:       mtime,
+		GitAuthorDate: gitAuthorDate,
+	}
 
 	// Handle the date separately
 	// TODO(bep) we need to "do more" in this area so this can be split up and
@@ -1577,6 +1584,15 @@
 	if meta == nil {
 		// missing frontmatter equivalent to empty frontmatter
 		meta = map[string]interface{}{}
+	}
+
+	if p.s != nil && p.s.owner != nil {
+		gi, enabled := p.s.owner.gitInfo.forPage(p)
+		if gi != nil {
+			p.GitInfo = gi
+		} else if enabled {
+			p.s.Log.WARN.Printf("Failed to find GitInfo for page %q", p.Path())
+		}
 	}
 
 	return p.update(meta)
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"html/template"
 	"os"
+
 	"path/filepath"
 	"reflect"
 	"sort"
@@ -25,6 +26,11 @@
 	"testing"
 	"time"
 
+	"github.com/gohugoio/hugo/hugofs"
+	"github.com/spf13/afero"
+
+	"github.com/spf13/viper"
+
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/helpers"
 	"github.com/spf13/cast"
@@ -902,6 +908,32 @@
 	d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z")
 
 	checkPageDate(t, p, d)
+}
+
+func TestPageWithLastmodFromGitInfo(t *testing.T) {
+	assrt := require.New(t)
+
+	// We need to use the OS fs for this.
+	cfg := viper.New()
+	fs := hugofs.NewFrom(hugofs.Os, cfg)
+	fs.Destination = &afero.MemMapFs{}
+
+	cfg.Set("frontmatter", map[string]interface{}{
+		"lastmod": []string{":git", "lastmod"},
+	})
+
+	cfg.Set("enableGitInfo", true)
+
+	assrt.NoError(loadDefaultSettingsFor(cfg))
+
+	wd, err := os.Getwd()
+	assrt.NoError(err)
+	cfg.Set("workingDir", filepath.Join(wd, "testsite"))
+
+	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
+
+	assrt.Len(s.RegularPages, 1)
+	assrt.Equal("2018-02-28", s.RegularPages[0].Lastmod.Format("2006-01-02"))
 }
 
 func TestPageWithFrontMatterConfig(t *testing.T) {
--- a/hugolib/pagemeta/page_frontmatter.go
+++ b/hugolib/pagemeta/page_frontmatter.go
@@ -56,6 +56,9 @@
 	// The content file's mod time.
 	ModTime time.Time
 
+	// May be set from the author date in Git.
+	GitAuthorDate time.Time
+
 	// The below are pointers to values on Page and will be modified.
 
 	// This is the Page's params.
@@ -175,6 +178,9 @@
 
 	// Gets date from file OS mod time.
 	fmModTime = ":filemodtime"
+
+	// Gets date from Git
+	fmGitAuthorDate = ":git"
 )
 
 // This is the config you get when doing nothing.
@@ -181,7 +187,7 @@
 func newDefaultFrontmatterConfig() frontmatterConfig {
 	return frontmatterConfig{
 		date:        []string{fmDate, fmPubDate, fmLastmod},
-		lastmod:     []string{fmLastmod, fmDate, fmPubDate},
+		lastmod:     []string{fmGitAuthorDate, fmLastmod, fmDate, fmPubDate},
 		publishDate: []string{fmPubDate, fmDate},
 		expiryDate:  []string{fmExpiryDate},
 	}
@@ -348,6 +354,8 @@
 			handlers = append(handlers, h.newDateFilenameHandler(setter))
 		case fmModTime:
 			handlers = append(handlers, h.newDateModTimeHandler(setter))
+		case fmGitAuthorDate:
+			handlers = append(handlers, h.newDateGitAuthorDateHandler(setter))
 		default:
 			handlers = append(handlers, h.newDateFieldHandler(identifier, setter))
 		}
@@ -407,6 +415,16 @@
 			return false, nil
 		}
 		setter(d, d.ModTime)
+		return true, nil
+	}
+}
+
+func (f *frontmatterFieldHandlers) newDateGitAuthorDateHandler(setter func(d *FrontMatterDescriptor, t time.Time)) frontMatterFieldHandler {
+	return func(d *FrontMatterDescriptor) (bool, error) {
+		if d.GitAuthorDate.IsZero() {
+			return false, nil
+		}
+		setter(d, d.GitAuthorDate)
 		return true, nil
 	}
 }
--- a/hugolib/pagemeta/page_frontmatter_test.go
+++ b/hugolib/pagemeta/page_frontmatter_test.go
@@ -15,6 +15,7 @@
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 	"time"
 
@@ -94,7 +95,7 @@
 	fc, err = newFrontmatterConfig(cfg)
 	assert.NoError(err)
 	assert.Equal([]string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"}, fc.date)
-	assert.Equal([]string{"lastmod", "modified", "date", "publishdate", "pubdate", "published"}, fc.lastmod)
+	assert.Equal([]string{":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"}, fc.lastmod)
 	assert.Equal([]string{"expirydate", "unpublishdate"}, fc.expiryDate)
 	assert.Equal([]string{"publishdate", "pubdate", "published", "date"}, fc.publishDate)
 
@@ -108,69 +109,50 @@
 	fc, err = newFrontmatterConfig(cfg)
 	assert.NoError(err)
 	assert.Equal([]string{"d1", "date", "publishdate", "pubdate", "published", "lastmod", "modified"}, fc.date)
-	assert.Equal([]string{"d2", "lastmod", "modified", "date", "publishdate", "pubdate", "published"}, fc.lastmod)
+	assert.Equal([]string{"d2", ":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"}, fc.lastmod)
 	assert.Equal([]string{"d3", "expirydate", "unpublishdate"}, fc.expiryDate)
 	assert.Equal([]string{"d4", "publishdate", "pubdate", "published", "date"}, fc.publishDate)
 
 }
 
-func TestFrontMatterDatesFilenameModTime(t *testing.T) {
+func TestFrontMatterDatesHandlers(t *testing.T) {
 	assert := require.New(t)
 
-	cfg := viper.New()
+	for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
 
-	cfg.Set("frontmatter", map[string]interface{}{
-		"date": []string{":fileModTime", "date"},
-	})
+		cfg := viper.New()
 
-	handler, err := NewFrontmatterHandler(nil, cfg)
-	assert.NoError(err)
+		cfg.Set("frontmatter", map[string]interface{}{
+			"date": []string{handlerID, "date"},
+		})
 
-	d1, _ := time.Parse("2006-01-02", "2018-02-01")
-	d2, _ := time.Parse("2006-01-02", "2018-02-02")
+		handler, err := NewFrontmatterHandler(nil, cfg)
+		assert.NoError(err)
 
-	d := newTestFd()
-	d.ModTime = d1
-	d.Frontmatter["date"] = d2
-	assert.NoError(handler.HandleDates(d))
-	assert.Equal(d1, d.Dates.Date)
-	assert.Equal(d2, d.Params["date"])
+		d1, _ := time.Parse("2006-01-02", "2018-02-01")
+		d2, _ := time.Parse("2006-01-02", "2018-02-02")
 
-	d = newTestFd()
-	d.Frontmatter["date"] = d2
-	assert.NoError(handler.HandleDates(d))
-	assert.Equal(d2, d.Dates.Date)
-	assert.Equal(d2, d.Params["date"])
+		d := newTestFd()
+		switch strings.ToLower(handlerID) {
+		case ":filename":
+			d.BaseFilename = "2018-02-01-page.md"
+		case ":filemodtime":
+			d.ModTime = d1
+		case ":git":
+			d.GitAuthorDate = d1
+		}
+		d.Frontmatter["date"] = d2
+		assert.NoError(handler.HandleDates(d))
+		assert.Equal(d1, d.Dates.Date)
+		assert.Equal(d2, d.Params["date"])
 
-}
+		d = newTestFd()
+		d.Frontmatter["date"] = d2
+		assert.NoError(handler.HandleDates(d))
+		assert.Equal(d2, d.Dates.Date)
+		assert.Equal(d2, d.Params["date"])
 
-func TestFrontMatterDatesFilename(t *testing.T) {
-	assert := require.New(t)
-
-	cfg := viper.New()
-
-	cfg.Set("frontmatter", map[string]interface{}{
-		"date": []string{":filename", "date"},
-	})
-
-	handler, err := NewFrontmatterHandler(nil, cfg)
-	assert.NoError(err)
-
-	d1, _ := time.Parse("2006-01-02", "2018-02-01")
-	d2, _ := time.Parse("2006-01-02", "2018-02-02")
-
-	d := newTestFd()
-	d.BaseFilename = "2018-02-01-page.md"
-	d.Frontmatter["date"] = d2
-	assert.NoError(handler.HandleDates(d))
-	assert.Equal(d1, d.Dates.Date)
-	assert.Equal(d2, d.Params["date"])
-
-	d = newTestFd()
-	d.Frontmatter["date"] = d2
-	assert.NoError(handler.HandleDates(d))
-	assert.Equal(d2, d.Dates.Date)
-	assert.Equal(d2, d.Params["date"])
+	}
 }
 
 func TestFrontMatterDatesCustomConfig(t *testing.T) {
--- /dev/null
+++ b/hugolib/testsite/content/first-post.md
@@ -1,0 +1,4 @@
+---
+title: "My First Post"
+lastmod: 2018-02-28
+---
\ No newline at end of file