Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle symlink change event #2273

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 37 additions & 3 deletions helpers/path.go
Expand Up @@ -481,17 +481,17 @@ func SymbolicWalk(fs afero.Fs, root string, walker filepath.WalkFunc) error {
}

// Handle the root first
fileInfo, err := lstatIfOs(fs, root)
fileInfo, realPath, err := getRealFileInfo(fs, root)

if err != nil {
return walker(root, nil, err)
}

if !fileInfo.IsDir() {
return nil
return fmt.Errorf("Cannot walk regular file %s", root)
}

if err := walker(root, fileInfo, err); err != nil && err != filepath.SkipDir {
if err := walker(realPath, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}

Expand All @@ -511,6 +511,40 @@ func SymbolicWalk(fs afero.Fs, root string, walker filepath.WalkFunc) error {

}

func getRealFileInfo(fs afero.Fs, path string) (os.FileInfo, string, error) {
fileInfo, err := lstatIfOs(fs, path)
realPath := path

if err != nil {
return nil, "", err
}

if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
link, err := filepath.EvalSymlinks(path)
if err != nil {
return nil, "", fmt.Errorf("Cannot read symbolic link '%s', error was: %s", path, err)
}
fileInfo, err = lstatIfOs(fs, link)
if err != nil {
return nil, "", fmt.Errorf("Cannot stat '%s', error was: %s", link, err)
}
realPath = link
}
return fileInfo, realPath, nil
}

// GetRealPath returns the real file path for the given path, whether it is a
// symlink or not.
func GetRealPath(fs afero.Fs, path string) (string, error) {
_, realPath, err := getRealFileInfo(fs, path)

if err != nil {
return "", err
}

return realPath, nil
}

// Code copied from Afero's path.go
// if the filesystem is OsFs use Lstat, else use fs.Stat
func lstatIfOs(fs afero.Fs, path string) (info os.FileInfo, err error) {
Expand Down
25 changes: 25 additions & 0 deletions helpers/path_test.go
Expand Up @@ -25,6 +25,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/spf13/afero"
"github.com/spf13/viper"
)
Expand Down Expand Up @@ -141,6 +143,29 @@ func TestGetRelativePath(t *testing.T) {
}
}

func TestGetRealPath(t *testing.T) {
d1, err := ioutil.TempDir("", "d1")
defer os.Remove(d1)
fs := afero.NewOsFs()

rp1, err := GetRealPath(fs, d1)
assert.NoError(t, err)
assert.Equal(t, d1, rp1)

sym := filepath.Join(os.TempDir(), "d1sym")
err = os.Symlink(d1, sym)
defer os.Remove(sym)
assert.NoError(t, err)

rp2, err := GetRealPath(fs, sym)
assert.NoError(t, err)

// On OS X, the temp folder is itself a symbolic link (to /private...)
// This has to do for now.
assert.True(t, strings.HasSuffix(rp2, d1))

}

func TestMakePathRelative(t *testing.T) {
type test struct {
inPath, path1, path2, output string
Expand Down
69 changes: 62 additions & 7 deletions hugolib/site.go
Expand Up @@ -476,16 +476,15 @@ func (s *Site) reBuild(events []fsnotify.Event) (whatChanged, error) {
logger := helpers.NewDistinctFeedbackLogger()

for _, ev := range events {
// Need to re-read source
if strings.HasPrefix(ev.Name, s.absContentDir()) {
if s.isContentDirEvent(ev) {
logger.Println("Source changed", ev.Name)
sourceChanged = append(sourceChanged, ev)
}
if strings.HasPrefix(ev.Name, s.absLayoutDir()) || strings.HasPrefix(ev.Name, s.absThemeDir()) {
if s.isLayoutDirEvent(ev) || s.isThemeDirEvent(ev) {
logger.Println("Template changed", ev.Name)
tmplChanged = append(tmplChanged, ev)
}
if strings.HasPrefix(ev.Name, s.absDataDir()) {
if s.isDataDirEvent(ev) {
logger.Println("Data changed", ev.Name)
dataChanged = append(dataChanged, ev)
}
Expand Down Expand Up @@ -546,7 +545,7 @@ func (s *Site) reBuild(events []fsnotify.Event) (whatChanged, error) {
// so we do this first to prevent races.
if ev.Op&fsnotify.Remove == fsnotify.Remove {
//remove the file & a create will follow
path, _ := helpers.GetRelativePath(ev.Name, s.absContentDir())
path, _ := helpers.GetRelativePath(ev.Name, s.getContentDir(ev.Name))
s.removePageByPath(path)
continue
}
Expand All @@ -557,7 +556,7 @@ func (s *Site) reBuild(events []fsnotify.Event) (whatChanged, error) {
if ev.Op&fsnotify.Rename == fsnotify.Rename {
// If the file is still on disk, it's only been updated, if it's not, it's been moved
if ex, err := afero.Exists(hugofs.Source(), ev.Name); !ex || err != nil {
path, _ := helpers.GetRelativePath(ev.Name, s.absContentDir())
path, _ := helpers.GetRelativePath(ev.Name, s.getContentDir(ev.Name))
s.removePageByPath(path)
continue
}
Expand Down Expand Up @@ -941,18 +940,74 @@ func (s *Site) absI18nDir() string {
return helpers.AbsPathify(viper.GetString("I18nDir"))
}

func (s *Site) isDataDirEvent(e fsnotify.Event) bool {
return s.getDataDir(e.Name) != ""
}

func (s *Site) getDataDir(path string) string {
return getRealDir(s.absDataDir(), path)
}

func (s *Site) absThemeDir() string {
return helpers.AbsPathify(viper.GetString("themesDir") + "/" + viper.GetString("theme"))
}

func (s *Site) isThemeDirEvent(e fsnotify.Event) bool {
return s.getThemeDir(e.Name) != ""
}

func (s *Site) getThemeDir(path string) string {
return getRealDir(s.absThemeDir(), path)
}

func (s *Site) absLayoutDir() string {
return helpers.AbsPathify(viper.GetString("LayoutDir"))
}

func (s *Site) isLayoutDirEvent(e fsnotify.Event) bool {
return s.getLayoutDir(e.Name) != ""
}

func (s *Site) getLayoutDir(path string) string {
return getRealDir(s.absLayoutDir(), path)
}

func (s *Site) absContentDir() string {
return helpers.AbsPathify(viper.GetString("ContentDir"))
}

func (s *Site) isContentDirEvent(e fsnotify.Event) bool {
return s.getContentDir(e.Name) != ""
}

func (s *Site) getContentDir(path string) string {
return getRealDir(s.absContentDir(), path)
}

// getRealDir gets the base path of the given path, also handling the case where
// base is a symlinked folder.
func getRealDir(base, path string) string {

if strings.HasPrefix(path, base) {
return base
}

realDir, err := helpers.GetRealPath(hugofs.Source(), base)

if err != nil {
if !os.IsNotExist(err) {
jww.ERROR.Printf("Failed to get real path for %s: %s", path, err)
}
return ""
}

if strings.HasPrefix(path, realDir) {
return realDir
}

return ""
}

func (s *Site) absPublishDir() string {
return helpers.AbsPathify(viper.GetString("PublishDir"))
}
Expand All @@ -973,7 +1028,7 @@ func (s *Site) reReadFile(absFilePath string) (*source.File, error) {
if err != nil {
return nil, err
}
file, err = source.NewFileFromAbs(s.absContentDir(), absFilePath, reader)
file, err = source.NewFileFromAbs(s.getContentDir(absFilePath), absFilePath, reader)

if err != nil {
return nil, err
Expand Down