aboutsummaryrefslogtreecommitdiffstats
path: root/watcher_test.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_test.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_test.go')
-rw-r--r--watcher_test.go126
1 files changed, 81 insertions, 45 deletions
diff --git a/watcher_test.go b/watcher_test.go
index 0ea6ca2..db0defb 100644
--- a/watcher_test.go
+++ b/watcher_test.go
@@ -6,23 +6,21 @@ import (
"path/filepath"
"testing"
"time"
-
- ignore "github.com/sabhiram/go-gitignore"
)
func TestWatcher_Create(t *testing.T) {
host := t.TempDir()
data := t.TempDir()
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
@@ -36,15 +34,15 @@ func TestWatcher_CreateInSubdir(t *testing.T) {
writeFile(t, host, "src/existing.go", "old")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
@@ -58,8 +56,8 @@ func TestWatcher_Write(t *testing.T) {
writeFile(t, host, "file.txt", "original")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
@@ -68,7 +66,7 @@ func TestWatcher_Write(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
@@ -82,8 +80,8 @@ func TestWatcher_Remove(t *testing.T) {
writeFile(t, host, "file.txt", "content")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
@@ -92,7 +90,7 @@ func TestWatcher_Remove(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
@@ -106,8 +104,8 @@ func TestWatcher_Rename(t *testing.T) {
writeFile(t, host, "Foo.txt", "content")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
@@ -116,11 +114,10 @@ func TestWatcher_Rename(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
- // Rename: delete old, create new (Linux is case-sensitive)
os.Rename(filepath.Join(host, "Foo.txt"), filepath.Join(host, "foo.txt"))
writeFile(t, host, "foo.txt", "content")
@@ -134,23 +131,18 @@ func TestWatcher_IgnoredFileEvents(t *testing.T) {
writeFile(t, host, ".gitignore", "*.log\nbuild/\n")
- rules, err := loadGitignore(host)
- if err != nil {
- t.Fatal(err)
- }
-
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
- // Create ignored files - should NOT sync
writeFile(t, host, "debug.log", "log data")
writeFile(t, host, "build/output.o", "binary")
@@ -159,7 +151,6 @@ func TestWatcher_IgnoredFileEvents(t *testing.T) {
assertFileNotExists(t, data, "debug.log")
assertFileNotExists(t, data, "build/output.o")
- // Create non-ignored file - should sync
writeFile(t, host, "app.go", "package main")
assertFileEventually(t, data, "app.go", "package main", 2*time.Second)
}
@@ -168,28 +159,25 @@ func TestWatcher_Debouncing(t *testing.T) {
host := t.TempDir()
data := t.TempDir()
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
- // Rapid writes to same file
writeFile(t, host, "file.txt", "v1")
writeFile(t, host, "file.txt", "v2")
writeFile(t, host, "file.txt", "v3")
writeFile(t, host, "file.txt", "final")
- // Wait for debounce + sync
assertFileEventually(t, data, "file.txt", "final", 2*time.Second)
- // Check that intermediate states don't matter - final content is correct
content, err := os.ReadFile(filepath.Join(data, "file.txt"))
if err != nil {
t.Fatal(err)
@@ -203,23 +191,22 @@ func TestWatcher_CreateDirectory(t *testing.T) {
host := t.TempDir()
data := t.TempDir()
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
- // Create directory and file inside it
if err := os.MkdirAll(filepath.Join(host, "newdir"), 0755); err != nil {
t.Fatal(err)
}
- time.Sleep(300 * time.Millisecond) // let watcher register new dir
+ time.Sleep(300 * time.Millisecond)
writeFile(t, host, "newdir/file.txt", "nested")
assertDirExistsEventually(t, data, "newdir", 2*time.Second)
@@ -232,8 +219,8 @@ func TestWatcher_RemoveDirectory(t *testing.T) {
writeFile(t, host, "dir/file.txt", "content")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
@@ -242,7 +229,7 @@ func TestWatcher_RemoveDirectory(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
@@ -256,8 +243,8 @@ func TestWatcher_VerifyManifestUpdated(t *testing.T) {
writeFile(t, host, "file.txt", "v1")
- rules := ignore.CompileIgnoreLines()
- manifest, err := initialSync(host, data, rules, false)
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
if err != nil {
t.Fatal(err)
}
@@ -268,13 +255,12 @@ func TestWatcher_VerifyManifestUpdated(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- go watch(ctx, host, data, manifest, rules, false, false)
+ go watch(ctx, host, data, manifest, stack, false, false)
time.Sleep(100 * time.Millisecond)
writeFile(t, host, "file.txt", "version two")
- // Wait for sync
time.Sleep(500 * time.Millisecond)
if manifest.Files["file.txt"].Size != 11 {
@@ -282,6 +268,56 @@ func TestWatcher_VerifyManifestUpdated(t *testing.T) {
}
}
+func TestWatcher_NestedGitignore_CreateIgnored(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, "sub/.gitignore", "*.tmp\n")
+
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ go watch(ctx, host, data, manifest, stack, false, false)
+
+ time.Sleep(100 * time.Millisecond)
+
+ writeFile(t, host, "sub/cache.tmp", "tmp")
+ time.Sleep(500 * time.Millisecond)
+
+ assertFileNotExists(t, data, "sub/cache.tmp")
+
+ writeFile(t, host, "sub/file.go", "code")
+ assertFileEventually(t, data, "sub/file.go", "code", 2*time.Second)
+}
+
+func TestWatcher_NestedGitignore_CreateUnignore(t *testing.T) {
+ host := t.TempDir()
+ data := t.TempDir()
+
+ writeFile(t, host, ".gitignore", "*.txt\n")
+ writeFile(t, host, "sub/.gitignore", "!important.txt\n")
+
+ stack := NewGitignoreStack(host)
+ manifest, err := initialSync(host, data, stack, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ go watch(ctx, host, data, manifest, stack, false, false)
+
+ time.Sleep(100 * time.Millisecond)
+
+ writeFile(t, host, "sub/important.txt", "keep me")
+ assertFileEventually(t, data, "sub/important.txt", "keep me", 2*time.Second)
+}
+
// Helpers
func assertFileEventually(t *testing.T, dir, path, expectedContent string, timeout time.Duration) {