From ce6e4310febf5659392a41b543594382441f3681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 11 Mar 2018 18:59:11 +0100 Subject: [PATCH] Refactor the GitInfo into the date handlers Fixes #4495 --- hugolib/gitinfo.go | 48 ++++++------- hugolib/hugo_sites.go | 19 ++++++ hugolib/hugo_sites_build.go | 2 - hugolib/page.go | 28 ++++++-- hugolib/page_test.go | 32 +++++++++ hugolib/pagemeta/page_frontmatter.go | 20 +++++- hugolib/pagemeta/page_frontmatter_test.go | 82 +++++++++-------------- hugolib/testsite/content/first-post.md | 4 ++ 8 files changed, 147 insertions(+), 88 deletions(-) create mode 100644 hugolib/testsite/content/first-post.md diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go index bfcfa9a42..affa9cea8 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 6c2a5c153..4e802270d 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 8f03f589f..c8affe15a 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 2274aa84a..f8f8f9958 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 905793ca6..875c5bdde 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 5e60a47d0..8bfc4e837 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 5372a4f3a..03f4c2f84 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() + 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) { diff --git a/hugolib/testsite/content/first-post.md b/hugolib/testsite/content/first-post.md new file mode 100644 index 000000000..4a8007946 --- /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