@@ -253,6 +253,17 @@ fn translate_flags(flags: &StreamFlags, precise: bool, root_path_exists: bool) -
253253 } ) ;
254254 }
255255
256+ // `ITEM_CLONED` can be present alongside other flags (including create/modify/remove).
257+ // Preserve any existing `info` (like "root changed"), but annotate otherwise so downstream
258+ // can detect and filter clone-related events. See https://github.com/notify-rs/notify/issues/465.
259+ if flags. contains ( StreamFlags :: ITEM_CLONED ) {
260+ for ev in & mut evs {
261+ if ev. info ( ) . is_none ( ) {
262+ ev. attrs . set_info ( "is: clone" ) ;
263+ }
264+ }
265+ }
266+
256267 if flags. contains ( StreamFlags :: OWN_EVENT ) {
257268 for ev in & mut evs {
258269 * ev = std:: mem:: take ( ev) . set_process_id ( std:: process:: id ( ) ) ;
@@ -853,6 +864,70 @@ mod tests {
853864 ) ;
854865 }
855866
867+ #[ test]
868+ fn translate_flags_ignores_is_file_only_events ( ) {
869+ assert ! ( translate_flags( & StreamFlags :: IS_FILE , true , false ) . is_empty( ) ) ;
870+ assert ! (
871+ translate_flags(
872+ & ( StreamFlags :: IS_FILE | StreamFlags :: ITEM_CLONED ) ,
873+ true ,
874+ false
875+ )
876+ . is_empty( ) ,
877+ "type-only clone flags should not produce events"
878+ ) ;
879+ }
880+
881+ #[ test]
882+ fn translate_flags_sets_clone_info_for_file_events ( ) {
883+ let create = translate_flags (
884+ & ( StreamFlags :: ITEM_CREATED | StreamFlags :: IS_FILE | StreamFlags :: ITEM_CLONED ) ,
885+ true ,
886+ false ,
887+ ) ;
888+ assert_eq ! ( create. len( ) , 1 ) ;
889+ assert_eq ! ( create[ 0 ] . kind, EventKind :: Create ( CreateKind :: File ) ) ;
890+ assert_eq ! ( create[ 0 ] . info( ) , Some ( "is: clone" ) ) ;
891+
892+ let modify = translate_flags (
893+ & ( StreamFlags :: INODE_META_MOD
894+ | StreamFlags :: ITEM_MODIFIED
895+ | StreamFlags :: IS_FILE
896+ | StreamFlags :: ITEM_CLONED ) ,
897+ true ,
898+ false ,
899+ ) ;
900+ assert_eq ! ( modify. len( ) , 2 ) ;
901+ assert ! (
902+ modify
903+ . iter( )
904+ . any( |e| matches!( e. kind, EventKind :: Modify ( ModifyKind :: Metadata ( _) ) ) )
905+ ) ;
906+ assert ! (
907+ modify
908+ . iter( )
909+ . any( |e| matches!( e. kind, EventKind :: Modify ( ModifyKind :: Data ( _) ) ) )
910+ ) ;
911+ assert ! (
912+ modify. iter( ) . all( |e| e. info( ) == Some ( "is: clone" ) ) ,
913+ "all events should be annotated as clone-related: {modify:?}"
914+ ) ;
915+ }
916+
917+ #[ test]
918+ fn translate_flags_does_not_override_existing_info ( ) {
919+ let evs = translate_flags (
920+ & ( StreamFlags :: ROOT_CHANGED
921+ | StreamFlags :: ITEM_REMOVED
922+ | StreamFlags :: IS_FILE
923+ | StreamFlags :: ITEM_CLONED ) ,
924+ true ,
925+ false ,
926+ ) ;
927+ assert_eq ! ( evs. len( ) , 1 ) ;
928+ assert_eq ! ( evs[ 0 ] . info( ) , Some ( "root changed" ) ) ;
929+ }
930+
856931 #[ test]
857932 fn does_not_crash_with_empty_path ( ) {
858933 let mut watcher = FsEventWatcher :: new ( |_| { } , Config :: default ( ) ) . unwrap ( ) ;
0 commit comments