diff options
| author | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-06 21:36:29 +0200 |
|---|---|---|
| committer | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2026-07-06 21:36:29 +0200 |
| commit | ad7575e85ebf6332d324ca5997db18ea81065df4 (patch) | |
| tree | d2a2e46e2c0432d4b4437c562b4c70de4a6bebf0 /watcher.go | |
| parent | 8b22a36d1c068f9baee68799b0c1f51ecb999e9d (diff) | |
| download | sourcewatch-ad7575e85ebf6332d324ca5997db18ea81065df4.tar.gz sourcewatch-ad7575e85ebf6332d324ca5997db18ea81065df4.zip | |
Implement nested .gitignore support with GitignoreStack
- Replaced go-gitignore with custom GitignoreStack that tracks
multiple .gitignore files hierarchically
- Patterns are processed in root-to-leaf order, last match wins
- Negation patterns in child dirs override parent ignores
- Patterns only apply within their directory scope (no leaking)
- Added 6 new tests for nested .gitignore:
- Scoped: patterns only affect own directory
- Dir-scoped: different dirs can ignore different things
- Negation override: child !pattern un-ignores parent's *.pattern
- Deep nesting: 3+ levels of .gitignore
- Parent scope doesn't leak to siblings
- All 35 tests passing
Diffstat (limited to 'watcher.go')
| -rw-r--r-- | watcher.go | 39 |
1 files changed, 24 insertions, 15 deletions
@@ -5,15 +5,13 @@ import ( "log" "os" "path/filepath" - "strings" "sync" "time" "github.com/fsnotify/fsnotify" - ignore "github.com/sabhiram/go-gitignore" ) -func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, rules *ignore.GitIgnore, dryRun, verbose bool) error { +func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, stack *GitignoreStack, dryRun, verbose bool) error { watcher, err := fsnotify.NewWatcher() if err != nil { return err @@ -23,7 +21,7 @@ func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, rul var mu sync.Mutex debounce := make(map[string]*time.Timer) - if err := addWatchersRecursively(watcher, hostDir, rules); err != nil { + if err := addWatchersRecursively(watcher, hostDir, stack); err != nil { return err } @@ -42,11 +40,11 @@ func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, rul continue } - if relPath == ".git" || strings.HasPrefix(relPath, ".git"+string(filepath.Separator)) { + if isGitDir(relPath) { continue } - if rules.MatchesPath(relPath) { + if stack.IsIgnored(relPath) { continue } @@ -62,7 +60,7 @@ func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, rul mu.Lock() delete(debounce, relPath) mu.Unlock() - handleEvent(event.Op, hostDir, dataDir, relPath, manifest, rules, dryRun, verbose, watcher) + handleEvent(event.Op, hostDir, dataDir, relPath, manifest, stack, dryRun, verbose, watcher) }) mu.Unlock() @@ -75,13 +73,13 @@ func watch(ctx context.Context, hostDir, dataDir string, manifest *Manifest, rul } } -func handleEvent(op fsnotify.Op, hostDir, dataDir, relPath string, manifest *Manifest, rules *ignore.GitIgnore, dryRun, verbose bool, watcher *fsnotify.Watcher) { +func handleEvent(op fsnotify.Op, hostDir, dataDir, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun, verbose bool, watcher *fsnotify.Watcher) { destPath := filepath.Join(dataDir, relPath) srcPath := filepath.Join(hostDir, relPath) switch { case op&fsnotify.Create != 0: - syncCreate(srcPath, destPath, relPath, manifest, dryRun, verbose, watcher) + syncCreate(srcPath, destPath, relPath, manifest, stack, dryRun, verbose, watcher) case op&fsnotify.Write != 0: syncWrite(srcPath, destPath, relPath, manifest, dryRun, verbose) @@ -94,7 +92,7 @@ func handleEvent(op fsnotify.Op, hostDir, dataDir, relPath string, manifest *Man } } -func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, dryRun, verbose bool, watcher *fsnotify.Watcher) { +func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun, verbose bool, watcher *fsnotify.Watcher) { info, err := os.Lstat(srcPath) if err != nil { if os.IsNotExist(err) { @@ -105,6 +103,9 @@ func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, dryRun, v } if info.IsDir() { + if hasGitignore(srcPath) { + stack.Push(srcPath) + } if err := watcher.Add(srcPath); err != nil { log.Printf("error adding watcher for %s: %v", relPath, err) } @@ -190,7 +191,7 @@ func syncRemove(destPath, relPath string, manifest *Manifest, dryRun, verbose bo os.Remove(destPath) } -func addWatchersRecursively(watcher *fsnotify.Watcher, root string, rules *ignore.GitIgnore) error { +func addWatchersRecursively(watcher *fsnotify.Watcher, root string, stack *GitignoreStack) error { return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -201,12 +202,20 @@ func addWatchersRecursively(watcher *fsnotify.Watcher, root string, rules *ignor return err } - if relPath == ".git" || strings.HasPrefix(relPath, ".git"+string(filepath.Separator)) { - return filepath.SkipDir + if isGitDir(relPath) { + if info.IsDir() { + return filepath.SkipDir + } + return nil } - if info.IsDir() && !rules.MatchesPath(relPath) { - return watcher.Add(path) + if info.IsDir() { + if hasGitignore(path) { + stack.Push(path) + } + if !stack.IsIgnored(relPath) { + return watcher.Add(path) + } } return nil |
