shithub: hugo

Download patch

ref: 94f0f7e59788e802e706a55cac0d52a9e70ff745
parent: 3c29c5af8ee865ef20741f576088e031e940c3d2
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Nov 14 12:18:32 EST 2018

cache/filecache: Add a :project placeholder

This allows for "cache per Hugo project", making `hugo --gc` work as expected, even if you have several Hugo projects running on the same PC.

See #5439

--- a/cache/filecache/filecache.go
+++ b/cache/filecache/filecache.go
@@ -272,7 +272,10 @@
 }
 
 func (c *Cache) isExpired(modTime time.Time) bool {
-	return c.maxAge >= 0 && time.Now().Sub(modTime) > c.maxAge
+	if c.maxAge < 0 {
+		return false
+	}
+	return c.maxAge == 0 || time.Now().Sub(modTime) > c.maxAge
 }
 
 // For testing
--- a/cache/filecache/filecache_config.go
+++ b/cache/filecache/filecache_config.go
@@ -35,7 +35,7 @@
 
 var defaultCacheConfig = cacheConfig{
 	MaxAge: -1, // Never expire
-	Dir:    ":cacheDir",
+	Dir:    ":cacheDir/:project",
 }
 
 const (
@@ -139,26 +139,33 @@
 	disabled := cfg.GetBool("ignoreCache")
 
 	for k, v := range c {
-		v.Dir = filepath.Clean(v.Dir)
-		dir := filepath.ToSlash(v.Dir)
+		dir := filepath.ToSlash(filepath.Clean(v.Dir))
+		hadSlash := strings.HasPrefix(dir, "/")
 		parts := strings.Split(dir, "/")
-		first := parts[0]
 
-		if strings.HasPrefix(first, ":") {
-			resolved, err := resolveDirPlaceholder(p, first)
-			if err != nil {
-				return c, err
+		for i, part := range parts {
+			if strings.HasPrefix(part, ":") {
+				resolved, err := resolveDirPlaceholder(p, part)
+				if err != nil {
+					return c, err
+				}
+				parts[i] = resolved
 			}
-			resolved = filepath.ToSlash(resolved)
+		}
 
-			v.Dir = filepath.FromSlash(path.Join((append([]string{resolved}, parts[1:]...))...))
+		dir = path.Join(parts...)
+		if hadSlash {
+			dir = "/" + dir
+		}
+		v.Dir = filepath.Clean(filepath.FromSlash(dir))
 
-		} else if isOsFs && !path.IsAbs(dir) {
-			return c, errors.Errorf("%q must either start with a placeholder (e.g. :cacheDir, :resourceDir) or be absolute", v.Dir)
+		if isOsFs && !filepath.IsAbs(v.Dir) {
+			return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
 		}
 
-		if len(v.Dir) < 5 {
-			return c, errors.Errorf("%q is not a valid cache dir", v.Dir)
+		// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
+		if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
+			return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
 		}
 
 		if disabled {
@@ -178,6 +185,8 @@
 		return p.AbsResourcesDir, nil
 	case ":cachedir":
 		return helpers.GetCacheDir(p.Fs.Source, p.Cfg)
+	case ":project":
+		return filepath.Base(p.WorkingDir), nil
 	}
 
 	return "", errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
--- a/cache/filecache/filecache_config_test.go
+++ b/cache/filecache/filecache_config_test.go
@@ -16,6 +16,7 @@
 import (
 	"path/filepath"
 	"runtime"
+	"strings"
 	"testing"
 	"time"
 
@@ -107,6 +108,8 @@
 func TestDecodeConfigDefault(t *testing.T) {
 	assert := require.New(t)
 	cfg := viper.New()
+	cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
+
 	if runtime.GOOS == "windows" {
 		cfg.Set("resourceDir", "c:\\cache\\resources")
 		cfg.Set("cacheDir", "c:\\cache\\thecache")
@@ -130,5 +133,34 @@
 		assert.Equal("c:\\cache\\resources\\_gen", decoded[cacheKeyImages].Dir)
 	} else {
 		assert.Equal("/cache/resources/_gen", decoded[cacheKeyImages].Dir)
+		assert.Equal("/cache/thecache/hugoproject", decoded[cacheKeyGetJSON].Dir)
 	}
+}
+
+func TestDecodeConfigInvalidDir(t *testing.T) {
+	t.Parallel()
+
+	assert := require.New(t)
+
+	configStr := `
+resourceDir = "myresources"
+[caches]
+[caches.getJSON]
+maxAge = "10m"
+dir = "/"
+
+`
+	if runtime.GOOS == "windows" {
+		configStr = strings.Replace(configStr, "/", "c:\\\\", 1)
+	}
+
+	cfg, err := config.FromConfigString(configStr, "toml")
+	assert.NoError(err)
+	fs := hugofs.NewMem(cfg)
+	p, err := paths.New(fs, cfg)
+	assert.NoError(err)
+
+	_, err = decodeConfig(p)
+	assert.Error(err)
+
 }
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -413,10 +413,10 @@
 ```toml
 [caches]
 [caches.getjson]
-dir = ":cacheDir"
+dir = ":cacheDir/:project"
 maxAge = -1
 [caches.getcsv]
-dir = ":cacheDir"
+dir = ":cacheDir/:project"
 maxAge = -1
 [caches.images]
 dir = ":resourceDir/_gen"
@@ -434,6 +434,9 @@
 :cacheDir
 : This is the value of the `cacheDir` config option if set (can also be set via OS env variable `HUGO_CACHEDIR`). It will fall back to `/opt/build/cache/hugo_cache/` on Netlify, or a `hugo_cache` directory below the OS temp dir for the others. This means that if you run your builds on Netlify, all caches configured with `:cacheDir` will be saved and restored on the next build. For other CI vendors, please read their documentation. For an CircleCI example, see [this configuration](https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml).
 
+:project
+
+The base directory name of the current Hugo project. This means that, in its default setting, every project will have separated file caches, which means that when you do `hugo --gc` you will not touch files related to other Hugo projects running on the same PC.
 
 :resourceDir
 : This is the value of the `resourceDir` config option.
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -15,7 +15,6 @@
 
 import (
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -25,7 +24,6 @@
 	"github.com/gohugoio/hugo/helpers"
 
 	"github.com/gohugoio/hugo/deps"
-	"github.com/gohugoio/hugo/hugofs"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -349,15 +347,6 @@
 		}
 	}
 
-}
-
-func TestNewSiteDefaultLang(t *testing.T) {
-	t.Parallel()
-	defer os.Remove("resources")
-	s, err := NewSiteDefaultLang()
-	require.NoError(t, err)
-	require.Equal(t, hugofs.Os, s.Fs.Source)
-	require.Equal(t, hugofs.Os, s.Fs.Destination)
 }
 
 // Issue #3355