github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/commands/static_syncer.go (about) 1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package commands 15 16 import ( 17 "os" 18 "path/filepath" 19 20 "github.com/gohugoio/hugo/hugolib/filesystems" 21 22 "github.com/fsnotify/fsnotify" 23 "github.com/gohugoio/hugo/helpers" 24 "github.com/spf13/fsync" 25 ) 26 27 type staticSyncer struct { 28 c *commandeer 29 } 30 31 func newStaticSyncer(c *commandeer) (*staticSyncer, error) { 32 return &staticSyncer{c: c}, nil 33 } 34 35 func (s *staticSyncer) isStatic(filename string) bool { 36 return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename) 37 } 38 39 func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error { 40 c := s.c 41 42 syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) { 43 publishDir := c.hugo().PathSpec.PublishDir 44 // If root, remove the second '/' 45 if publishDir == "//" { 46 publishDir = helpers.FilePathSeparator 47 } 48 49 if sourceFs.PublishFolder != "" { 50 publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) 51 } 52 53 syncer := fsync.NewSyncer() 54 syncer.NoTimes = c.Cfg.GetBool("noTimes") 55 syncer.NoChmod = c.Cfg.GetBool("noChmod") 56 syncer.ChmodFilter = chmodFilter 57 syncer.SrcFs = sourceFs.Fs 58 syncer.DestFs = c.Fs.Destination 59 60 // prevent spamming the log on changes 61 logger := helpers.NewDistinctErrorLogger() 62 63 for _, ev := range staticEvents { 64 // Due to our approach of layering both directories and the content's rendered output 65 // into one we can't accurately remove a file not in one of the source directories. 66 // If a file is in the local static dir and also in the theme static dir and we remove 67 // it from one of those locations we expect it to still exist in the destination 68 // 69 // If Hugo generates a file (from the content dir) over a static file 70 // the content generated file should take precedence. 71 // 72 // Because we are now watching and handling individual events it is possible that a static 73 // event that occupies the same path as a content generated file will take precedence 74 // until a regeneration of the content takes places. 75 // 76 // Hugo assumes that these cases are very rare and will permit this bad behavior 77 // The alternative is to track every single file and which pipeline rendered it 78 // and then to handle conflict resolution on every event. 79 80 fromPath := ev.Name 81 82 relPath, found := sourceFs.MakePathRelative(fromPath) 83 84 if !found { 85 // Not member of this virtual host. 86 continue 87 } 88 89 // Remove || rename is harder and will require an assumption. 90 // Hugo takes the following approach: 91 // If the static file exists in any of the static source directories after this event 92 // Hugo will re-sync it. 93 // If it does not exist in all of the static directories Hugo will remove it. 94 // 95 // This assumes that Hugo has not generated content on top of a static file and then removed 96 // the source of that static file. In this case Hugo will incorrectly remove that file 97 // from the published directory. 98 if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove { 99 if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) { 100 // If file doesn't exist in any static dir, remove it 101 toRemove := filepath.Join(publishDir, relPath) 102 103 logger.Println("File no longer exists in static dir, removing", toRemove) 104 _ = c.Fs.Destination.RemoveAll(toRemove) 105 } else if err == nil { 106 // If file still exists, sync it 107 logger.Println("Syncing", relPath, "to", publishDir) 108 109 if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { 110 c.logger.Errorln(err) 111 } 112 } else { 113 c.logger.Errorln(err) 114 } 115 116 continue 117 } 118 119 // For all other event operations Hugo will sync static. 120 logger.Println("Syncing", relPath, "to", publishDir) 121 if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { 122 c.logger.Errorln(err) 123 } 124 } 125 126 return 0, nil 127 } 128 129 _, err := c.doWithPublishDirs(syncFn) 130 return err 131 }