Skip to content

Commit 5b66f05

Browse files
committed
fix: match run_watch patterns only against events from their own watched directory
Patterns are made relative to the directory they are watched in (for example /var/tmp/chart/** becomes **), but events from every watched directory were matched against every pattern. A pattern such as ** from one path therefore matched all events from the other paths and restarted the command on unrelated changes. Track the base directory per pattern and per event and only match patterns with the same base. Resolves #3222 Signed-off-by: Srinivas Chippagiri <cvas22@gmail.com>
1 parent 8ff6260 commit 5b66f05

2 files changed

Lines changed: 66 additions & 15 deletions

File tree

‎pkg/devspace/pipeline/engine/basichandler/commands/run_watch.go‎

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func (w *watcher) Watch(ctx context.Context, action func(ctx context.Context) er
8888

8989
// get folders from patterns
9090
pathsToWatch := map[string]bool{}
91+
watchPatterns := make([]watchPattern, 0, len(patterns))
9192
for i, p := range patterns {
9293
patternsSplitted := strings.Split(filepath.ToSlash(p), "/")
9394
lastIndex := len(patternsSplitted) - 1
@@ -116,12 +117,13 @@ func (w *watcher) Watch(ctx context.Context, action func(ctx context.Context) er
116117
}
117118

118119
pathsToWatch[absolutePath] = true
120+
watchPatterns = append(watchPatterns, watchPattern{base: absolutePath, pattern: patterns[i]})
119121
}
120122

121123
watchTree := notify.NewTree()
122124
defer watchTree.Close()
123125

124-
globalChannel := make(chan string, 100)
126+
globalChannel := make(chan watchEvent, 100)
125127
for targetPath := range pathsToWatch {
126128
stat, err := os.Stat(targetPath)
127129
if err != nil {
@@ -160,18 +162,45 @@ func (w *watcher) Watch(ctx context.Context, action func(ctx context.Context) er
160162
if err != nil {
161163
log.Debugf("error converting path %s: %v", e.Path(), err)
162164
} else {
163-
globalChannel <- filepath.ToSlash(relPath)
165+
globalChannel <- watchEvent{base: base, path: filepath.ToSlash(relPath)}
164166
}
165167
}
166168
}
167169
}(targetPath, eventChannel)
168170
}
169171

170172
// start command
171-
return w.handleCommand(ctx, patterns, excludes, action, globalChannel)
173+
return w.handleCommand(ctx, watchPatterns, excludes, action, globalChannel)
172174
}
173175

174-
func (w *watcher) handleCommand(ctx context.Context, patterns []string, excludes []string, action func(ctx context.Context) error, events chan string) error {
176+
// watchPattern is a glob pattern that is relative to the directory (base) it is watched in.
177+
type watchPattern struct {
178+
base string
179+
pattern string
180+
}
181+
182+
// watchEvent is a file system event with a path relative to the watched base directory.
183+
type watchEvent struct {
184+
base string
185+
path string
186+
}
187+
188+
// matchesPatterns reports whether the event matches a pattern that belongs to the
189+
// same base directory as the event. Patterns are relative to their own base, so they
190+
// must not be matched against events coming from another watched directory.
191+
func matchesPatterns(patterns []watchPattern, e watchEvent) bool {
192+
for _, p := range patterns {
193+
if p.base != e.base {
194+
continue
195+
}
196+
if matched, _ := doublestar.Match(p.pattern, e.path); matched {
197+
return true
198+
}
199+
}
200+
return false
201+
}
202+
203+
func (w *watcher) handleCommand(ctx context.Context, patterns []watchPattern, excludes []string, action func(ctx context.Context) error, events chan watchEvent) error {
175204
hc := interp.HandlerCtx(ctx)
176205
var t *tomb.Tomb
177206
if !w.options.SkipInitial {
@@ -190,26 +219,18 @@ func (w *watcher) handleCommand(ctx context.Context, patterns []string, excludes
190219
return nil
191220
case e := <-events:
192221
// check if match
193-
hasMatched := false
194-
for _, p := range patterns {
195-
hasMatched, _ = doublestar.Match(p, e)
196-
if hasMatched {
197-
break
198-
}
199-
}
200-
201-
if hasMatched {
222+
if matchesPatterns(patterns, e) {
202223
excluded := false
203224
for _, excludePath := range excludes {
204-
excluded, _ = doublestar.Match(excludePath, e)
225+
excluded, _ = doublestar.Match(excludePath, e.path)
205226
if excluded {
206227
break
207228
}
208229
}
209230

210231
if !excluded {
211232
numEvents++
212-
lastChange = e
233+
lastChange = e.path
213234
}
214235
}
215236
case <-time.After(time.Millisecond * 1200):
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package commands
2+
3+
import "testing"
4+
5+
func TestMatchesPatterns(t *testing.T) {
6+
patterns := []watchPattern{
7+
{base: "/project", pattern: "devspace.yaml"},
8+
{base: "/var/tmp/chart", pattern: "**"},
9+
}
10+
11+
testCases := []struct {
12+
name string
13+
event watchEvent
14+
want bool
15+
}{
16+
{"exact file in its own base", watchEvent{base: "/project", path: "devspace.yaml"}, true},
17+
{"unrelated file in project base", watchEvent{base: "/project", path: "main.go"}, false},
18+
{"nested unrelated file in project base", watchEvent{base: "/project", path: "src/main.go"}, false},
19+
{"any file in chart base", watchEvent{base: "/var/tmp/chart", path: "templates/deploy.yaml"}, true},
20+
{"unknown base", watchEvent{base: "/other", path: "devspace.yaml"}, false},
21+
}
22+
23+
for _, tc := range testCases {
24+
t.Run(tc.name, func(t *testing.T) {
25+
if got := matchesPatterns(patterns, tc.event); got != tc.want {
26+
t.Fatalf("matchesPatterns(%v) = %v, want %v", tc.event, got, tc.want)
27+
}
28+
})
29+
}
30+
}

0 commit comments

Comments
 (0)