aboutsummaryrefslogtreecommitdiffstats
path: root/watcher.go
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-06 21:36:29 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-06 21:36:29 +0200
commitad7575e85ebf6332d324ca5997db18ea81065df4 (patch)
treed2a2e46e2c0432d4b4437c562b4c70de4a6bebf0 /watcher.go
parent8b22a36d1c068f9baee68799b0c1f51ecb999e9d (diff)
downloadsourcewatch-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.go39
1 files changed, 24 insertions, 15 deletions
diff --git a/watcher.go b/watcher.go
index 2b56bb4..2067887 100644
--- a/watcher.go
+++ b/watcher.go
@@ -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