Fix server rebuilds when adding a content file on Linux

Fixes #12362
This commit is contained in:
Bjørn Erik Pedersen 2024-04-16 09:32:08 +02:00
parent fe63de3a83
commit fa60a2fbc3
4 changed files with 91 additions and 33 deletions

View file

@ -610,7 +610,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
// For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/. // 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.fileEventsFilter(events)
events = h.fileEventsTranslate(events) events = h.fileEventsTrim(events)
eventInfos := h.fileEventsApplyInfo(events) eventInfos := h.fileEventsApplyInfo(events)
logger := h.Log logger := h.Log

View file

@ -10,6 +10,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -685,8 +686,17 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
return nil return nil
} }
// We simulate the fsnotify events.
// See the test output in https://github.com/bep/fsnotifyeventlister for what events gets produced
// by the different OSes.
func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event { func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
var events []fsnotify.Event var (
events []fsnotify.Event
isLinux = runtime.GOOS == "linux"
isMacOs = runtime.GOOS == "darwin"
isWindows = runtime.GOOS == "windows"
)
for _, v := range s.removedFiles { for _, v := range s.removedFiles {
events = append(events, fsnotify.Event{ events = append(events, fsnotify.Event{
Name: v, Name: v,
@ -713,12 +723,32 @@ func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
Name: v, Name: v,
Op: fsnotify.Write, Op: fsnotify.Write,
}) })
if isLinux || isWindows {
// Duplicate write events, for some reason.
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Write,
})
}
if isMacOs {
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Chmod,
})
}
} }
for _, v := range s.createdFiles { for _, v := range s.createdFiles {
events = append(events, fsnotify.Event{ events = append(events, fsnotify.Event{
Name: v, Name: v,
Op: fsnotify.Create, Op: fsnotify.Create,
}) })
if isLinux || isWindows {
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Write,
})
}
} }
// Shuffle events. // Shuffle events.

View file

@ -1553,3 +1553,27 @@ Single: {{ .Title }}|{{ .Content }}|
b.AssertRenderCountPage(1) b.AssertRenderCountPage(1)
b.AssertRenderCountContent(1) b.AssertRenderCountContent(1)
} }
func TestRebuildEditSingleListChangeUbuntuIssue12362(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['rss','section','sitemap','taxonomy','term']
disableLiveReload = true
-- layouts/_default/list.html --
{{ range .Pages }}{{ .Title }}|{{ end }}
-- layouts/_default/single.html --
{{ .Title }}
-- content/p1.md --
---
title: p1
---
`
b := TestRunning(t, files)
b.AssertFileContent("public/index.html", "p1|")
b.AddFiles("content/p2.md", "---\ntitle: p2\n---").Build()
b.AssertFileContent("public/index.html", "p1|p2|") // this test passes, which doesn't match reality
}

View file

@ -424,7 +424,35 @@ func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event {
events[n] = ev events[n] = ev
n++ n++
} }
return events[:n] events = events[:n]
eventOrdinal := func(e fsnotify.Event) int {
// Pull the structural changes to the top.
if e.Op.Has(fsnotify.Create) {
return 1
}
if e.Op.Has(fsnotify.Remove) {
return 2
}
if e.Op.Has(fsnotify.Rename) {
return 3
}
if e.Op.Has(fsnotify.Write) {
return 4
}
return 5
}
sort.Slice(events, func(i, j int) bool {
// First sort by event type.
if eventOrdinal(events[i]) != eventOrdinal(events[j]) {
return eventOrdinal(events[i]) < eventOrdinal(events[j])
}
// Then sort by name.
return events[i].Name < events[j].Name
})
return events
} }
type fileEventInfo struct { type fileEventInfo struct {
@ -494,41 +522,17 @@ func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo
return infos return infos
} }
func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event { func (h *HugoSites) fileEventsTrim(events []fsnotify.Event) []fsnotify.Event {
eventMap := make(map[string][]fsnotify.Event) seen := make(map[string]bool)
// We often get a Remove etc. followed by a Create, a Create followed by a Write.
// Remove the superfluous events to make the update logic simpler.
for _, ev := range events {
eventMap[ev.Name] = append(eventMap[ev.Name], ev)
}
n := 0 n := 0
for _, ev := range events { for _, ev := range events {
mapped := eventMap[ev.Name] if seen[ev.Name] {
continue
// Keep one
found := false
var kept fsnotify.Event
for i, ev2 := range mapped {
if i == 0 {
kept = ev2
}
if ev2.Op&fsnotify.Write == fsnotify.Write {
kept = ev2
found = true
}
if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
kept = ev2
}
} }
seen[ev.Name] = true
events[n] = kept events[n] = ev
n++ n++
} }
return events return events
} }