esync

Directory watching and remote syncing
Log | Files | Refs | README | LICENSE

commit 3290272c523f71239e030be9e15a7732264381bd
parent 681a0ad0de494ad4672acbf9019395131b04d73d
Author: Erik Loualiche <[email protected]>
Date:   Sun, 22 Mar 2026 10:13:45 -0500

feat: add findBrokenSymlinks helper for detecting dangling symlinks

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

Diffstat:
Minternal/watcher/watcher.go | 37+++++++++++++++++++++++++++++++++++++
Minternal/watcher/watcher_test.go | 30++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+), 0 deletions(-)

diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go @@ -13,6 +13,43 @@ import ( ) // --------------------------------------------------------------------------- +// BrokenSymlink +// --------------------------------------------------------------------------- + +// BrokenSymlink records a symlink whose target does not exist. +type BrokenSymlink struct { + Path string // absolute path to the symlink + Target string // what the symlink points to +} + +// findBrokenSymlinks scans a directory for symlinks whose targets do not exist. +func findBrokenSymlinks(dir string) []BrokenSymlink { + entries, err := os.ReadDir(dir) + if err != nil { + return nil + } + var broken []BrokenSymlink + for _, e := range entries { + if e.Type()&os.ModeSymlink == 0 { + continue + } + full := filepath.Join(dir, e.Name()) + target, err := os.Readlink(full) + if err != nil { + continue + } + // Resolve relative targets against the directory + if !filepath.IsAbs(target) { + target = filepath.Join(dir, target) + } + if _, err := os.Stat(target); err != nil { + broken = append(broken, BrokenSymlink{Path: full, Target: target}) + } + } + return broken +} + +// --------------------------------------------------------------------------- // EventHandler // --------------------------------------------------------------------------- diff --git a/internal/watcher/watcher_test.go b/internal/watcher/watcher_test.go @@ -1,6 +1,8 @@ package watcher import ( + "os" + "path/filepath" "sync/atomic" "testing" "time" @@ -165,3 +167,31 @@ func TestShouldIncludeEmptyMeansAll(t *testing.T) { } } } + +// --------------------------------------------------------------------------- +// 7. TestFindBrokenSymlinks — detects broken symlinks in a directory +// --------------------------------------------------------------------------- +func TestFindBrokenSymlinks(t *testing.T) { + dir := t.TempDir() + + // Create a valid file + os.WriteFile(filepath.Join(dir, "good.txt"), []byte("ok"), 0644) + + // Create a broken symlink + os.Symlink("/nonexistent/target", filepath.Join(dir, "bad.txt")) + + // Create a valid symlink + os.Symlink(filepath.Join(dir, "good.txt"), filepath.Join(dir, "also-good.txt")) + + broken := findBrokenSymlinks(dir) + + if len(broken) != 1 { + t.Fatalf("findBrokenSymlinks found %d, want 1", len(broken)) + } + if broken[0].Target != "/nonexistent/target" { + t.Errorf("target = %q, want %q", broken[0].Target, "/nonexistent/target") + } + if filepath.Base(broken[0].Path) != "bad.txt" { + t.Errorf("path base = %q, want %q", filepath.Base(broken[0].Path), "bad.txt") + } +}