diff --git a/moves.go b/moves.go index e5e1913..b07de55 100644 --- a/moves.go +++ b/moves.go @@ -1,8 +1,8 @@ package main import ( + "bytes" "fmt" - "io/fs" "log" "net/http" "os" @@ -44,20 +44,21 @@ func (h *handler) handleMove(w http.ResponseWriter, r *http.Request, srcURL, src return } - // Phase 1: walk the tree and rewrite every index.md whose content changes. - // Keep the pre-rewrite bytes in memory so we can revert on failure. + // Phase 1: walk the tree and rewrite every index.md that references the + // moved path. Keep the pre-rewrite bytes in memory so we can revert on + // failure. The walker only reads directory listings and files literally + // named index.md; hidden directories are pruned. A cheap substring check + // skips parsing files that cannot contain a relevant link. rewritten := map[string][]byte{} - walkErr := filepath.WalkDir(h.root, func(fsPath string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() || d.Name() != "index.md" { - return nil - } + needle := []byte("[[" + oldPath) + walkErr := walkIndexFiles(h.root, func(fsPath string) error { orig, err := os.ReadFile(fsPath) if err != nil { return err } + if !bytes.Contains(orig, needle) { + return nil + } updated, changed := rewriteWikiLinks(orig, oldPath, newPath) if !changed { return nil @@ -186,6 +187,36 @@ func rollbackRewrites(rewritten map[string][]byte) { } } +// walkIndexFiles visits every `index.md` under root, skipping hidden +// directories (names beginning with `.`). Unlike filepath.WalkDir this does +// not stat each regular file — on spinning disks that saves the bulk of the +// traversal cost when folders contain many non-page files (photos, archives). +func walkIndexFiles(root string, visit func(fsPath string) error) error { + entries, err := os.ReadDir(root) + if err != nil { + return err + } + for _, e := range entries { + name := e.Name() + if strings.HasPrefix(name, ".") { + continue + } + full := filepath.Join(root, name) + if e.IsDir() { + if err := walkIndexFiles(full, visit); err != nil { + return err + } + continue + } + if name == "index.md" { + if err := visit(full); err != nil { + return err + } + } + } + return nil +} + // writeFileAtomic writes data to a temp file in the same directory as path // and renames it into place so readers never observe a partial file. func writeFileAtomic(path string, data []byte, perm os.FileMode) error {