From 02332bb79f82a6033a1f0fb912f884dfece68fb5 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Mon, 26 May 2025 21:18:54 -0300 Subject: [PATCH] fix(watcher): fix some v3.43.x regressions --- watch.go | 120 +++++++++++++++++++++++++++++--------------------- watch_test.go | 19 ++++---- 2 files changed, 81 insertions(+), 58 deletions(-) diff --git a/watch.go b/watch.go index 515bde0af9..651dc300c1 100644 --- a/watch.go +++ b/watch.go @@ -19,6 +19,8 @@ import ( "github.com/go-task/task/v3/internal/fingerprint" "github.com/go-task/task/v3/internal/fsnotifyext" "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/internal/slicesext" + "github.com/go-task/task/v3/taskfile/ast" ) const defaultWaitTime = 100 * time.Millisecond @@ -85,17 +87,22 @@ func (e *Executor) watchTasks(calls ...*Call) error { for _, c := range calls { c := c go func() { + if ShouldIgnore(event.Name) { + e.Logger.VerboseErrf(logger.Magenta, "task: event skipped for being an ignored dir: %s\n", event.Name) + return + } t, err := e.GetTask(c) if err != nil { e.Logger.Errf(logger.Red, "%v\n", err) return } baseDir := filepathext.SmartJoin(e.Dir, t.Dir) - files, err := fingerprint.Globs(baseDir, t.Sources) + files, err := e.collectSources(calls) if err != nil { e.Logger.Errf(logger.Red, "%v\n", err) return } + if !event.Has(fsnotify.Remove) && !slices.Contains(files, event.Name) { relPath, _ := filepath.Rel(baseDir, event.Name) e.Logger.VerboseErrf(logger.Magenta, "task: skipped for file not in sources: %s\n", relPath) @@ -158,69 +165,84 @@ func closeOnInterrupt(w *fsnotify.Watcher) { } func (e *Executor) registerWatchedDirs(w *fsnotify.Watcher, calls ...*Call) error { - var registerTaskDirs func(*Call) error - registerTaskDirs = func(c *Call) error { - task, err := e.CompiledTask(c) - if err != nil { + files, err := e.collectSources(calls) + if err != nil { + return err + } + for _, f := range files { + d := filepath.Dir(f) + if isSet, ok := e.watchedDirs.Load(d); ok && isSet { + continue + } + if ShouldIgnore(d) { + continue + } + if err := w.Add(d); err != nil { return err } + e.watchedDirs.Store(d, true) + relPath, _ := filepath.Rel(e.Dir, d) + e.Logger.VerboseOutf(logger.Green, "task: watching new dir: %v\n", relPath) + } + return nil +} - for _, d := range task.Deps { - if err := registerTaskDirs(&Call{Task: d.Task, Vars: d.Vars}); err != nil { - return err - } - } - for _, c := range task.Cmds { - if c.Task != "" { - if err := registerTaskDirs(&Call{Task: c.Task, Vars: c.Vars}); err != nil { - return err - } - } +var ignorePaths = []string{ + "/.task", + "/.git", + "/.hg", + "/node_modules", +} + +func ShouldIgnore(path string) bool { + for _, p := range ignorePaths { + if strings.Contains(path, fmt.Sprintf("%s/", p)) || strings.HasSuffix(path, p) { + return true } + } + return false +} + +func (e *Executor) collectSources(calls []*Call) ([]string, error) { + var sources []string + err := e.traverse(calls, func(task *ast.Task) error { files, err := fingerprint.Globs(task.Dir, task.Sources) if err != nil { return err } + sources = append(sources, files...) + return nil + }) - for _, f := range files { - d := filepath.Dir(f) - if isSet, ok := e.watchedDirs.Load(d); ok && isSet { - continue - } - if ShouldIgnoreFile(d) { - continue + return slicesext.UniqueJoin(sources), err +} + +type traverseFunc func(*ast.Task) error + +func (e *Executor) traverse(calls []*Call, yield traverseFunc) error { + for _, c := range calls { + task, err := e.CompiledTask(c) + if err != nil { + return err + } + for _, dep := range task.Deps { + if dep.Task != "" { + if err := e.traverse([]*Call{{Task: dep.Task, Vars: dep.Vars}}, yield); err != nil { + return err + } } - if err := w.Add(d); err != nil { - return err + } + for _, cmd := range task.Cmds { + if cmd.Task != "" { + if err := e.traverse([]*Call{{Task: cmd.Task, Vars: cmd.Vars}}, yield); err != nil { + return err + } } - e.watchedDirs.Store(d, true) - relPath, _ := filepath.Rel(e.Dir, d) - w.Events <- fsnotify.Event{Name: f, Op: fsnotify.Create} - e.Logger.VerboseOutf(logger.Green, "task: watching new dir: %v\n", relPath) } - return nil - } - - for _, c := range calls { - if err := registerTaskDirs(c); err != nil { + if err := yield(task); err != nil { return err } } return nil } - -func ShouldIgnoreFile(path string) bool { - ignorePaths := []string{ - "/.task", - "/.git", - "/.hg", - "/node_modules", - } - for _, p := range ignorePaths { - if strings.Contains(path, fmt.Sprintf("%s/", p)) || strings.HasSuffix(path, p) { - return true - } - } - return false -} diff --git a/watch_test.go b/watch_test.go index 16e23ecdf4..8bef86ed49 100644 --- a/watch_test.go +++ b/watch_test.go @@ -31,16 +31,17 @@ task: Started watching for tasks: default task: [default] echo "Task running!" Task running! task: task "default" finished running -task: Task "default" is up to date +task: [default] echo "Task running!" +Task running! task: task "default" finished running `) var buff bytes.Buffer e := task.NewExecutor( - task.ExecutorWithDir(dir), - task.ExecutorWithStdout(&buff), - task.ExecutorWithStderr(&buff), - task.ExecutorWithWatch(true), + task.WithDir(dir), + task.WithStdout(&buff), + task.WithStderr(&buff), + task.WithWatch(true), ) require.NoError(t, e.Setup()) @@ -71,16 +72,16 @@ task: task "default" finished running } }() - time.Sleep(10 * time.Millisecond) + time.Sleep(200 * time.Millisecond) err = os.WriteFile(filePath, []byte("test updated"), 0o644) require.NoError(t, err) - time.Sleep(150 * time.Millisecond) + time.Sleep(200 * time.Millisecond) cancel() assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String())) } -func TestShouldIgnoreFile(t *testing.T) { +func TestShouldIgnore(t *testing.T) { t.Parallel() tt := []struct { @@ -95,7 +96,7 @@ func TestShouldIgnoreFile(t *testing.T) { ct := ct t.Run(fmt.Sprintf("ignore - %d", k), func(t *testing.T) { t.Parallel() - require.Equal(t, task.ShouldIgnoreFile(ct.path), ct.expect) + require.Equal(t, task.ShouldIgnore(ct.path), ct.expect) }) } }