From: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date: Sun, 11 Mar 2018 17:59:11 +0000 (+0100)
Subject: Refactor the GitInfo into the date handlers
X-Git-Tag: v0.38~38
X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=ce6e4310febf5659392a41b543594382441f3681;p=brevno-suite%2Fhugo

Refactor the GitInfo into the date handlers

Fixes #4495
---

diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go
index bfcfa9a4..affa9cea 100644
--- a/hugolib/gitinfo.go
+++ b/hugolib/gitinfo.go
@@ -19,51 +19,41 @@ import (
 	"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
 }
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 6c2a5c15..4e802270 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -47,6 +47,9 @@ type HugoSites struct {
 
 	// 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,9 +149,25 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
 
 	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 {
 	if cfg.TemplateProvider == nil {
 		cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 8f03f589..c8affe15 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -165,8 +165,6 @@ func (h *HugoSites) assemble(config *BuildCfg) error {
 	}
 
 	if config.whatChanged.source {
-		h.assembleGitInfo()
-
 		for _, s := range h.Sites {
 			if err := s.buildSiteMeta(); err != nil {
 				return err
diff --git a/hugolib/page.go b/hugolib/page.go
index 2274aa84..f8f8f995 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1119,13 +1119,20 @@ func (p *Page) update(frontmatter map[string]interface{}) error {
 		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
@@ -1579,6 +1586,15 @@ func (p *Page) parse(reader io.Reader) error {
 		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)
 }
 
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index 905793ca..875c5bdd 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"html/template"
 	"os"
+
 	"path/filepath"
 	"reflect"
 	"sort"
@@ -25,6 +26,11 @@ import (
 	"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"
@@ -904,6 +910,32 @@ func TestPageWithDate(t *testing.T) {
 	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) {
 	t.Parallel()
 
diff --git a/hugolib/pagemeta/page_frontmatter.go b/hugolib/pagemeta/page_frontmatter.go
index 5e60a47d..8bfc4e83 100644
--- a/hugolib/pagemeta/page_frontmatter.go
+++ b/hugolib/pagemeta/page_frontmatter.go
@@ -56,6 +56,9 @@ type FrontMatterDescriptor struct {
 	// 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,13 +178,16 @@ const (
 
 	// Gets date from file OS mod time.
 	fmModTime = ":filemodtime"
+
+	// Gets date from Git
+	fmGitAuthorDate = ":git"
 )
 
 // This is the config you get when doing nothing.
 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 @@ func (f FrontMatterHandler) createDateHandler(identifiers []string, setter func(
 			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))
 		}
@@ -410,3 +418,13 @@ func (f *frontmatterFieldHandlers) newDateModTimeHandler(setter func(d *FrontMat
 		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
+	}
+}
diff --git a/hugolib/pagemeta/page_frontmatter_test.go b/hugolib/pagemeta/page_frontmatter_test.go
index 5372a4f3..03f4c2f8 100644
--- a/hugolib/pagemeta/page_frontmatter_test.go
+++ b/hugolib/pagemeta/page_frontmatter_test.go
@@ -15,6 +15,7 @@ package pagemeta
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 	"time"
 
@@ -94,7 +95,7 @@ func TestFrontMatterNewConfig(t *testing.T) {
 	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 @@ func TestFrontMatterNewConfig(t *testing.T) {
 	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()
-
-	cfg.Set("frontmatter", map[string]interface{}{
-		"date": []string{":fileModTime", "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.ModTime = 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)
+	for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
 
-	cfg := viper.New()
-
-	cfg.Set("frontmatter", map[string]interface{}{
-		"date": []string{":filename", "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.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"])
+		d1, _ := time.Parse("2006-01-02", "2018-02-01")
+		d2, _ := time.Parse("2006-01-02", "2018-02-02")
+
+		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"])
 
-	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) {
diff --git a/hugolib/testsite/content/first-post.md b/hugolib/testsite/content/first-post.md
new file mode 100644
index 00000000..4a800794
--- /dev/null
+++ b/hugolib/testsite/content/first-post.md
@@ -0,0 +1,4 @@
+---
+title: "My First Post"
+lastmod: 2018-02-28
+---
\ No newline at end of file