Refactor the GitInfo into the date handlers
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 11 Mar 2018 17:59:11 +0000 (18:59 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 11 Mar 2018 20:32:05 +0000 (21:32 +0100)
Fixes #4495

hugolib/gitinfo.go
hugolib/hugo_sites.go
hugolib/hugo_sites_build.go
hugolib/page.go
hugolib/page_test.go
hugolib/pagemeta/page_frontmatter.go
hugolib/pagemeta/page_frontmatter_test.go
hugolib/testsite/content/first-post.md [new file with mode: 0644]

index bfcfa9a42feabfb9dea681a371ca773c69a465b9..affa9cea8d41399eb516ae451764583f9f459e8c 100644 (file)
@@ -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
 }
index 6c2a5c1538f2de9caa3e0c1c43afd3fd65a03baf..4e802270d524cd908e00bc27627a9cb224e8711c 100644 (file)
@@ -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
index 8f03f589f30d1b3ce83dc4e5205002747c9c3789..c8affe15aa19983a17eb6753cf17420a4f1bd6cd 100644 (file)
@@ -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
index 2274aa84ae8b9c8fd4938e10d420301bd1dbd93b..f8f8f9958bcccede2409fc1e2cbcd9c3799987bd 100644 (file)
@@ -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)
 }
 
index 905793ca60181f951b8c51763a253edd8ce2d910..875c5bddedaf8f5f8cb6ef5424451d2c702e45c9 100644 (file)
@@ -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()
 
index 5e60a47d0288e6a12f3e0b505f19b580233da965..8bfc4e837e844769c6ae997c42d2232a88fbdd0c 100644 (file)
@@ -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
+       }
+}
index 5372a4f3af0efe4151739087ccd7e344c3bd27e5..03f4c2f84a4708455569175d63beb3927f894278 100644 (file)
@@ -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 (file)
index 0000000..4a80079
--- /dev/null
@@ -0,0 +1,4 @@
+---
+title: "My First Post"
+lastmod: 2018-02-28
+---
\ No newline at end of file