mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
95d62004a0
commit
ce6e4310fe
8 changed files with 147 additions and 88 deletions
|
@ -19,51 +19,41 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bep/gitmap"
|
"github.com/bep/gitmap"
|
||||||
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *HugoSites) assembleGitInfo() {
|
type gitInfo struct {
|
||||||
if !h.Cfg.GetBool("enableGitInfo") {
|
contentDir string
|
||||||
return
|
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 (
|
var (
|
||||||
workingDir = h.Cfg.GetString("workingDir")
|
workingDir = cfg.GetString("workingDir")
|
||||||
contentDir = h.Cfg.GetString("contentDir")
|
contentDir = cfg.GetString("contentDir")
|
||||||
)
|
)
|
||||||
|
|
||||||
gitRepo, err := gitmap.Map(workingDir, "")
|
gitRepo, err := gitmap.Map(workingDir, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Log.ERROR.Printf("Got error reading Git log: %s", err)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gitMap := gitRepo.Files
|
|
||||||
repoPath := filepath.FromSlash(gitRepo.TopLevelAbsPath)
|
repoPath := filepath.FromSlash(gitRepo.TopLevelAbsPath)
|
||||||
|
|
||||||
// The Hugo site may be placed in a sub folder in the Git repo,
|
// The Hugo site may be placed in a sub folder in the Git repo,
|
||||||
// one example being the Hugo docs.
|
// one example being the Hugo docs.
|
||||||
// We have to find the root folder to the Hugo site below the Git root.
|
// We have to find the root folder to the Hugo site below the Git root.
|
||||||
contentRoot := strings.TrimPrefix(workingDir, repoPath)
|
contentRoot := strings.TrimPrefix(workingDir, repoPath)
|
||||||
contentRoot = strings.TrimPrefix(contentRoot, helpers.FilePathSeparator)
|
contentRoot = strings.TrimPrefix(contentRoot, helpers.FilePathSeparator)
|
||||||
|
contentDir = path.Join(filepath.ToSlash(contentRoot), contentDir)
|
||||||
|
|
||||||
s := h.Sites[0]
|
return &gitInfo{contentDir: contentDir, repo: gitRepo}, nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ type HugoSites struct {
|
||||||
|
|
||||||
// Keeps track of bundle directories and symlinks to enable partial rebuilding.
|
// Keeps track of bundle directories and symlinks to enable partial rebuilding.
|
||||||
ContentChanges *contentChangeMap
|
ContentChanges *contentChangeMap
|
||||||
|
|
||||||
|
// If enabled, keeps a revision map for all content.
|
||||||
|
gitInfo *gitInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HugoSites) IsMultihost() bool {
|
func (h *HugoSites) IsMultihost() bool {
|
||||||
|
@ -146,9 +149,25 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
|
||||||
|
|
||||||
h.Deps = sites[0].Deps
|
h.Deps = sites[0].Deps
|
||||||
|
|
||||||
|
if err := h.initGitInfo(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return h, nil
|
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 {
|
func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
|
||||||
if cfg.TemplateProvider == nil {
|
if cfg.TemplateProvider == nil {
|
||||||
cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
|
cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
|
||||||
|
|
|
@ -165,8 +165,6 @@ func (h *HugoSites) assemble(config *BuildCfg) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.whatChanged.source {
|
if config.whatChanged.source {
|
||||||
h.assembleGitInfo()
|
|
||||||
|
|
||||||
for _, s := range h.Sites {
|
for _, s := range h.Sites {
|
||||||
if err := s.buildSiteMeta(); err != nil {
|
if err := s.buildSiteMeta(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1119,13 +1119,20 @@ func (p *Page) update(frontmatter map[string]interface{}) error {
|
||||||
mtime = p.Source.FileInfo().ModTime()
|
mtime = p.Source.FileInfo().ModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gitAuthorDate time.Time
|
||||||
|
if p.GitInfo != nil {
|
||||||
|
gitAuthorDate = p.GitInfo.AuthorDate
|
||||||
|
}
|
||||||
|
|
||||||
descriptor := &pagemeta.FrontMatterDescriptor{
|
descriptor := &pagemeta.FrontMatterDescriptor{
|
||||||
Frontmatter: frontmatter,
|
Frontmatter: frontmatter,
|
||||||
Params: p.params,
|
Params: p.params,
|
||||||
Dates: &p.PageDates,
|
Dates: &p.PageDates,
|
||||||
PageURLs: &p.URLPath,
|
PageURLs: &p.URLPath,
|
||||||
BaseFilename: p.BaseFileName(),
|
BaseFilename: p.BaseFileName(),
|
||||||
ModTime: mtime}
|
ModTime: mtime,
|
||||||
|
GitAuthorDate: gitAuthorDate,
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the date separately
|
// Handle the date separately
|
||||||
// TODO(bep) we need to "do more" in this area so this can be split up and
|
// 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{}{}
|
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)
|
return p.update(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -25,6 +26,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -904,6 +910,32 @@ func TestPageWithDate(t *testing.T) {
|
||||||
checkPageDate(t, p, d)
|
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) {
|
func TestPageWithFrontMatterConfig(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,9 @@ type FrontMatterDescriptor struct {
|
||||||
// The content file's mod time.
|
// The content file's mod time.
|
||||||
ModTime time.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.
|
// The below are pointers to values on Page and will be modified.
|
||||||
|
|
||||||
// This is the Page's params.
|
// This is the Page's params.
|
||||||
|
@ -175,13 +178,16 @@ const (
|
||||||
|
|
||||||
// Gets date from file OS mod time.
|
// Gets date from file OS mod time.
|
||||||
fmModTime = ":filemodtime"
|
fmModTime = ":filemodtime"
|
||||||
|
|
||||||
|
// Gets date from Git
|
||||||
|
fmGitAuthorDate = ":git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is the config you get when doing nothing.
|
// This is the config you get when doing nothing.
|
||||||
func newDefaultFrontmatterConfig() frontmatterConfig {
|
func newDefaultFrontmatterConfig() frontmatterConfig {
|
||||||
return frontmatterConfig{
|
return frontmatterConfig{
|
||||||
date: []string{fmDate, fmPubDate, fmLastmod},
|
date: []string{fmDate, fmPubDate, fmLastmod},
|
||||||
lastmod: []string{fmLastmod, fmDate, fmPubDate},
|
lastmod: []string{fmGitAuthorDate, fmLastmod, fmDate, fmPubDate},
|
||||||
publishDate: []string{fmPubDate, fmDate},
|
publishDate: []string{fmPubDate, fmDate},
|
||||||
expiryDate: []string{fmExpiryDate},
|
expiryDate: []string{fmExpiryDate},
|
||||||
}
|
}
|
||||||
|
@ -348,6 +354,8 @@ func (f FrontMatterHandler) createDateHandler(identifiers []string, setter func(
|
||||||
handlers = append(handlers, h.newDateFilenameHandler(setter))
|
handlers = append(handlers, h.newDateFilenameHandler(setter))
|
||||||
case fmModTime:
|
case fmModTime:
|
||||||
handlers = append(handlers, h.newDateModTimeHandler(setter))
|
handlers = append(handlers, h.newDateModTimeHandler(setter))
|
||||||
|
case fmGitAuthorDate:
|
||||||
|
handlers = append(handlers, h.newDateGitAuthorDateHandler(setter))
|
||||||
default:
|
default:
|
||||||
handlers = append(handlers, h.newDateFieldHandler(identifier, setter))
|
handlers = append(handlers, h.newDateFieldHandler(identifier, setter))
|
||||||
}
|
}
|
||||||
|
@ -410,3 +418,13 @@ func (f *frontmatterFieldHandlers) newDateModTimeHandler(setter func(d *FrontMat
|
||||||
return true, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ package pagemeta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ func TestFrontMatterNewConfig(t *testing.T) {
|
||||||
fc, err = newFrontmatterConfig(cfg)
|
fc, err = newFrontmatterConfig(cfg)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal([]string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"}, fc.date)
|
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{"expirydate", "unpublishdate"}, fc.expiryDate)
|
||||||
assert.Equal([]string{"publishdate", "pubdate", "published", "date"}, fc.publishDate)
|
assert.Equal([]string{"publishdate", "pubdate", "published", "date"}, fc.publishDate)
|
||||||
|
|
||||||
|
@ -108,69 +109,50 @@ func TestFrontMatterNewConfig(t *testing.T) {
|
||||||
fc, err = newFrontmatterConfig(cfg)
|
fc, err = newFrontmatterConfig(cfg)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal([]string{"d1", "date", "publishdate", "pubdate", "published", "lastmod", "modified"}, fc.date)
|
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{"d3", "expirydate", "unpublishdate"}, fc.expiryDate)
|
||||||
assert.Equal([]string{"d4", "publishdate", "pubdate", "published", "date"}, fc.publishDate)
|
assert.Equal([]string{"d4", "publishdate", "pubdate", "published", "date"}, fc.publishDate)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFrontMatterDatesFilenameModTime(t *testing.T) {
|
func TestFrontMatterDatesHandlers(t *testing.T) {
|
||||||
assert := require.New(t)
|
assert := require.New(t)
|
||||||
|
|
||||||
cfg := viper.New()
|
for _, handlerID := range []string{":filename", ":fileModTime", ":git"} {
|
||||||
|
|
||||||
cfg.Set("frontmatter", map[string]interface{}{
|
cfg := viper.New()
|
||||||
"date": []string{":fileModTime", "date"},
|
|
||||||
})
|
|
||||||
|
|
||||||
handler, err := NewFrontmatterHandler(nil, cfg)
|
cfg.Set("frontmatter", map[string]interface{}{
|
||||||
assert.NoError(err)
|
"date": []string{handlerID, "date"},
|
||||||
|
})
|
||||||
|
|
||||||
d1, _ := time.Parse("2006-01-02", "2018-02-01")
|
handler, err := NewFrontmatterHandler(nil, cfg)
|
||||||
d2, _ := time.Parse("2006-01-02", "2018-02-02")
|
assert.NoError(err)
|
||||||
|
|
||||||
d := newTestFd()
|
d1, _ := time.Parse("2006-01-02", "2018-02-01")
|
||||||
d.ModTime = d1
|
d2, _ := time.Parse("2006-01-02", "2018-02-02")
|
||||||
d.Frontmatter["date"] = d2
|
|
||||||
assert.NoError(handler.HandleDates(d))
|
|
||||||
assert.Equal(d1, d.Dates.Date)
|
|
||||||
assert.Equal(d2, d.Params["date"])
|
|
||||||
|
|
||||||
d = newTestFd()
|
d := newTestFd()
|
||||||
d.Frontmatter["date"] = d2
|
switch strings.ToLower(handlerID) {
|
||||||
assert.NoError(handler.HandleDates(d))
|
case ":filename":
|
||||||
assert.Equal(d2, d.Dates.Date)
|
d.BaseFilename = "2018-02-01-page.md"
|
||||||
assert.Equal(d2, d.Params["date"])
|
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) {
|
func TestFrontMatterDatesCustomConfig(t *testing.T) {
|
||||||
|
|
4
hugolib/testsite/content/first-post.md
Normal file
4
hugolib/testsite/content/first-post.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: "My First Post"
|
||||||
|
lastmod: 2018-02-28
|
||||||
|
---
|
Loading…
Reference in a new issue