@@ -189,6 +189,14 @@ func (a *App) applyEvent(evt fileEvent) error {
189189 if isExcluded (rel , a .cfg .Excludes ) {
190190 return nil
191191 }
192+ if shouldIgnoreStopMarker (rel ) {
193+ // receiver-local control file; never mutate it from remote
194+ return nil
195+ }
196+ if stopRoot , stopped := a .findStopRoot (rel ); stopped {
197+ logf ("<- skipped by .stop (%s): %s %s\n " , stopRoot , evt .Type , rel )
198+ return nil
199+ }
192200 full := filepath .Join (a .cfg .Dir , rel )
193201 if ! pathWithinBase (full , a .cfg .Dir ) {
194202 return fmt .Errorf ("path escape: %s" , rel )
@@ -354,6 +362,50 @@ func pathWithinBase(path, base string) bool {
354362 return true
355363}
356364
365+ // findStopRoot returns the nearest ancestor directory (or self, if dir)
366+ // that contains a ".stop" marker file on receiver side.
367+ func (a * App ) findStopRoot (rel string ) (string , bool ) {
368+ rel = filepath .ToSlash (filepath .Clean (rel ))
369+ if rel == "" || rel == "." {
370+ if fileExists (filepath .Join (a .cfg .Dir , ".stop" )) {
371+ return "." , true
372+ }
373+ return "" , false
374+ }
375+
376+ parts := strings .Split (rel , "/" )
377+ for i := len (parts ); i >= 1 ; i -- {
378+ cand := strings .Join (parts [:i ], "/" )
379+ full := filepath .Join (a .cfg .Dir , filepath .FromSlash (cand ))
380+ if st , err := os .Stat (full ); err == nil && ! st .IsDir () {
381+ // if current candidate is a file path, check parent directory marker
382+ if i == len (parts ) {
383+ continue
384+ }
385+ }
386+ if fileExists (filepath .Join (full , ".stop" )) {
387+ return cand , true
388+ }
389+ }
390+ if fileExists (filepath .Join (a .cfg .Dir , ".stop" )) {
391+ return "." , true
392+ }
393+ return "" , false
394+ }
395+
396+ func fileExists (path string ) bool {
397+ st , err := os .Stat (path )
398+ return err == nil && ! st .IsDir ()
399+ }
400+
401+ func shouldIgnoreStopMarker (rel string ) bool {
402+ rel = filepath .ToSlash (filepath .Clean (rel ))
403+ if rel == ".stop" {
404+ return true
405+ }
406+ return strings .HasSuffix (rel , "/.stop" )
407+ }
408+
357409func (a * App ) suppress (rel string , d time.Duration ) {
358410 a .mu .Lock ()
359411 defer a .mu .Unlock ()
@@ -424,6 +476,16 @@ func (a *App) snapshotEnd(id string) error {
424476 }
425477 return nil
426478 }
479+ if shouldIgnoreStopMarker (rel ) {
480+ // receiver-local stop markers should never be removed by reconcile
481+ return nil
482+ }
483+ if stopRoot , stopped := a .findStopRoot (rel ); stopped {
484+ if d .IsDir () && rel == stopRoot {
485+ return filepath .SkipDir
486+ }
487+ return nil
488+ }
427489 if d .IsDir () {
428490 return nil
429491 }
0 commit comments