URI: 
       Fix server rebuilds when adding sub sections especially on Windows - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 07b2e535be469f0a95619edb2b0bd5ea569bca3e
   DIR parent f038a51b3e448286287b56bed6dd76e9b00138d5
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Fri, 15 Mar 2024 10:57:51 +0100
       
       Fix server rebuilds when adding sub sections especially on Windows
       
       This commit also optimizes for the case where change events for both file (e.g. `_index.md`) and the container directory comes in the same event batch.
       
       While testing this on Windows 11 (ARM64), I notice that Windows behaves a little oddly when dumping a folder of files into the content tree; it works (at least after this commit), but it seems like the event batching behaves differently compared to other OSes (even older Win versions).
       
       A related tip would be to try starting the server with polling, to see if that improves the situation, e.g.:
       
       ```
       hugo server --poll 700ms
       ```
       
       Fixes #12230
       
       Diffstat:
         M hugolib/hugo_sites_build.go         |      36 ++++++-------------------------
         M hugolib/pages_capture.go            |       2 +-
         M hugolib/site.go                     |      68 +++++++++++++++++++++++++++++++
       
       3 files changed, 76 insertions(+), 30 deletions(-)
       ---
   DIR diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
       @@ -595,8 +595,10 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
                        return sb.String()
                }))
        
       +        // For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/.
                events = h.fileEventsFilter(events)
                events = h.fileEventsTranslate(events)
       +        eventInfos := h.fileEventsApplyInfo(events)
        
                logger := h.Log
        
       @@ -631,36 +633,12 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
                        addedContentPaths []*paths.Path
                )
        
       -        for _, ev := range events {
       -                removed := false
       -                added := false
       -
       -                if ev.Op&fsnotify.Remove == fsnotify.Remove {
       -                        removed = true
       -                }
       -
       -                fi, statErr := h.Fs.Source.Stat(ev.Name)
       -
       -                // Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
       -                // Sometimes a rename operation means that file has been renamed other times it means
       -                // it's been updated.
       -                if ev.Op.Has(fsnotify.Rename) {
       -                        // If the file is still on disk, it's only been updated, if it's not, it's been moved
       -                        if statErr != nil {
       -                                removed = true
       -                        }
       -                }
       -                if ev.Op.Has(fsnotify.Create) {
       -                        added = true
       -                }
       -
       -                isChangedDir := statErr == nil && fi.IsDir()
       -
       +        for _, ev := range eventInfos {
                        cpss := h.BaseFs.ResolvePaths(ev.Name)
                        pss := make([]*paths.Path, len(cpss))
                        for i, cps := range cpss {
                                p := cps.Path
       -                        if removed && !paths.HasExt(p) {
       +                        if ev.removed && !paths.HasExt(p) {
                                        // Assume this is a renamed/removed directory.
                                        // For deletes, we walk up the tree to find the container (e.g. branch bundle),
                                        // so we will catch this even if it is a file without extension.
       @@ -671,7 +649,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
                                }
        
                                pss[i] = h.Configs.ContentPathParser.Parse(cps.Component, p)
       -                        if added && !isChangedDir && cps.Component == files.ComponentFolderContent {
       +                        if ev.added && !ev.isChangedDir && cps.Component == files.ComponentFolderContent {
                                        addedContentPaths = append(addedContentPaths, pss[i])
                                }
        
       @@ -683,9 +661,9 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
                                }
                        }
        
       -                if removed {
       +                if ev.removed {
                                changedPaths.deleted = append(changedPaths.deleted, pss...)
       -                } else if isChangedDir {
       +                } else if ev.isChangedDir {
                                changedPaths.changedDirs = append(changedPaths.changedDirs, pss...)
                        } else {
                                changedPaths.changedFiles = append(changedPaths.changedFiles, pss...)
   DIR diff --git a/hugolib/pages_capture.go b/hugolib/pages_capture.go
       @@ -161,7 +161,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
                                        // We always start from a directory.
                                        collectErr = c.collectDir(id.p, id.isDir, func(fim hugofs.FileMetaInfo) bool {
                                                if id.delete || id.isDir {
       -                                                if id.isDir {
       +                                                if id.isDir && fim.Meta().PathInfo.IsLeafBundle() {
                                                                return strings.HasPrefix(fim.Meta().PathInfo.Path(), paths.AddTrailingSlash(id.p.Path()))
                                                        }
        
   DIR diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -19,6 +19,7 @@ import (
                "io"
                "mime"
                "net/url"
       +        "os"
                "path/filepath"
                "runtime"
                "sort"
       @@ -426,6 +427,73 @@ func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event {
                return events[:n]
        }
        
       +type fileEventInfo struct {
       +        fsnotify.Event
       +        fi           os.FileInfo
       +        added        bool
       +        removed      bool
       +        isChangedDir bool
       +}
       +
       +func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo {
       +        var infos []fileEventInfo
       +        for _, ev := range events {
       +                removed := false
       +                added := false
       +
       +                if ev.Op&fsnotify.Remove == fsnotify.Remove {
       +                        removed = true
       +                }
       +
       +                fi, statErr := h.Fs.Source.Stat(ev.Name)
       +
       +                // Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
       +                // Sometimes a rename operation means that file has been renamed other times it means
       +                // it's been updated.
       +                if ev.Op.Has(fsnotify.Rename) {
       +                        // If the file is still on disk, it's only been updated, if it's not, it's been moved
       +                        if statErr != nil {
       +                                removed = true
       +                        }
       +                }
       +                if ev.Op.Has(fsnotify.Create) {
       +                        added = true
       +                }
       +
       +                isChangedDir := statErr == nil && fi.IsDir()
       +
       +                infos = append(infos, fileEventInfo{
       +                        Event:        ev,
       +                        fi:           fi,
       +                        added:        added,
       +                        removed:      removed,
       +                        isChangedDir: isChangedDir,
       +                })
       +        }
       +
       +        n := 0
       +
       +        for _, ev := range infos {
       +                // Remove any directories that's also represented by a file.
       +                keep := true
       +                if ev.isChangedDir {
       +                        for _, ev2 := range infos {
       +                                if ev2.fi != nil && !ev2.fi.IsDir() && filepath.Dir(ev2.Name) == ev.Name {
       +                                        keep = false
       +                                        break
       +                                }
       +                        }
       +                }
       +                if keep {
       +                        infos[n] = ev
       +                        n++
       +                }
       +        }
       +        infos = infos[:n]
       +
       +        return infos
       +}
       +
        func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event {
                eventMap := make(map[string][]fsnotify.Event)