@@ -15,7 +15,6 @@ import (
1515 "github.com/puzpuzpuz/xsync/v4"
1616
1717 "github.com/go-task/task/v3/errors"
18- "github.com/go-task/task/v3/internal/filepathext"
1918 "github.com/go-task/task/v3/internal/fingerprint"
2019 "github.com/go-task/task/v3/internal/fsnotifyext"
2120 "github.com/go-task/task/v3/internal/logger"
@@ -25,6 +24,8 @@ import (
2524
2625const defaultWaitTime = 100 * time .Millisecond
2726
27+ var refreshChan = make (chan string )
28+
2829// watchTasks start watching the given tasks
2930func (e * Executor ) watchTasks (calls ... * Call ) error {
3031 tasks := make ([]string , len (calls ))
@@ -68,44 +69,82 @@ func (e *Executor) watchTasks(calls ...*Call) error {
6869
6970 closeOnInterrupt (w )
7071
72+ watchFiles , err := e .collectSources (calls )
73+ if err != nil {
74+ cancel ()
75+ return err
76+ }
7177 go func () {
7278 for {
7379 select {
80+ case path := <- refreshChan :
81+ // If a path is added its necessary to refresh the sources, otherwise the
82+ // watcher may not pick up any changes in that new path.
83+ _ = path
84+ watchFiles , err = e .collectSources (calls )
85+ if err != nil {
86+ e .Logger .Errf (logger .Red , "%v\n " , err )
87+ continue
88+ }
89+
7490 case event , ok := <- eventsChan :
7591 if ! ok {
7692 cancel ()
7793 return
7894 }
7995 e .Logger .VerboseErrf (logger .Magenta , "task: received watch event: %v\n " , event )
8096
81- cancel ()
82- ctx , cancel = context . WithCancel ( context . Background ())
83-
84- e . Compiler . ResetCache ()
85-
86- for _ , c := range calls {
87- go func ( ) {
88- if ShouldIgnore ( event .Name ) {
89- e . Logger . VerboseErrf ( logger . Magenta , "task: event skipped for being an ignored dir: %s \n " , event .Name )
90- return
97+ // Check if this watch event should be ignored.
98+ if ShouldIgnore ( event . Name ) {
99+ e . Logger . VerboseErrf ( logger . Magenta , "task: event skipped for being an ignored dir: %s \n " , event . Name )
100+ continue
101+ }
102+ if event . Has ( fsnotify . Remove ) || event . Has ( fsnotify . Rename ) || event . Has ( fsnotify . Write ) {
103+ if ! slices . Contains ( watchFiles , event . Name ) {
104+ relPath := event .Name
105+ if rel , err := filepath . Rel ( e . Dir , event .Name ); err == nil {
106+ relPath = rel
91107 }
92- t , err := e .GetTask (c )
93- if err != nil {
94- e .Logger .Errf (logger .Red , "%v\n " , err )
95- return
108+ e .Logger .VerboseErrf (logger .Magenta , "task: skipped for file not in sources: %s\n " , relPath )
109+ continue
110+ }
111+ }
112+ if event .Has (fsnotify .Create ) {
113+ createDir := false
114+ if info , err := os .Stat (event .Name ); err == nil {
115+ if info .IsDir () {
116+ createDir = true
96117 }
97- baseDir := filepathext .SmartJoin (e .Dir , t .Dir )
98- files , err := e .collectSources (calls )
99- if err != nil {
118+ }
119+ watchFiles , err = e .collectSources (calls )
120+ if err != nil {
121+ e .Logger .Errf (logger .Red , "%v\n " , err )
122+ continue
123+ }
124+
125+ if createDir {
126+ // If the CREATE relates to a folder, update the registered watch dirs (immediately).
127+ if err := e .registerWatchedDirs (w , calls ... ); err != nil {
100128 e .Logger .Errf (logger .Red , "%v\n " , err )
101- return
102129 }
103-
104- if ! event .Has (fsnotify .Remove ) && ! slices .Contains (files , event .Name ) {
105- relPath , _ := filepath .Rel (baseDir , event .Name )
130+ } else {
131+ if ! slices .Contains (watchFiles , event .Name ) {
132+ relPath := event .Name
133+ if rel , err := filepath .Rel (e .Dir , event .Name ); err == nil {
134+ relPath = rel
135+ }
106136 e .Logger .VerboseErrf (logger .Magenta , "task: skipped for file not in sources: %s\n " , relPath )
107- return
137+ continue
108138 }
139+ }
140+ }
141+
142+ // The watch event is good, restart the task calls.
143+ cancel ()
144+ ctx , cancel = context .WithCancel (context .Background ())
145+ e .Compiler .ResetCache ()
146+ for _ , c := range calls {
147+ go func () {
109148 err = e .RunTask (ctx , c )
110149 if err == nil {
111150 e .Logger .Errf (logger .Green , "task: task \" %s\" finished running\n " , c .Task )
@@ -167,8 +206,25 @@ func (e *Executor) registerWatchedDirs(w *fsnotify.Watcher, calls ...*Call) erro
167206 if err != nil {
168207 return err
169208 }
209+ dirs := []string {}
170210 for _ , f := range files {
171- d := filepath .Dir (f )
211+ dir := filepath .Dir (f )
212+ if ! slices .Contains (dirs , dir ) {
213+ dirs = append (dirs , dir )
214+ }
215+ }
216+
217+ // Remove dirs from the watch, otherwise the watched dir may become stale and
218+ // if the dir is recreated, it will not trigger any watch events.
219+ e .watchedDirs .Range (func (dir string , value bool ) bool {
220+ if ! slices .Contains (dirs , dir ) {
221+ e .watchedDirs .Delete (dir )
222+ }
223+ return true
224+ })
225+
226+ // Add new dirs to the watch.
227+ for _ , d := range dirs {
172228 if isSet , ok := e .watchedDirs .Load (d ); ok && isSet {
173229 continue
174230 }
@@ -181,6 +237,9 @@ func (e *Executor) registerWatchedDirs(w *fsnotify.Watcher, calls ...*Call) erro
181237 e .watchedDirs .Store (d , true )
182238 relPath , _ := filepath .Rel (e .Dir , d )
183239 e .Logger .VerboseOutf (logger .Green , "task: watching new dir: %v\n " , relPath )
240+
241+ // Signal that the watcher should refresh its watch file list.
242+ refreshChan <- d
184243 }
185244 return nil
186245}
0 commit comments