@@ -606,54 +606,97 @@ impl Linker {
606606 return Ok ( result) ;
607607 }
608608
609+ self . for_each_nested_glob_match (
610+ search_root,
611+ glob_pattern,
612+ excludes,
613+ options,
614+ |full_path, rel_path| {
615+ let dest_str = Self :: expand_destination_template ( dest_template, rel_path) ;
616+ if dest_str. is_empty ( ) {
617+ if options. verbose {
618+ println ! (
619+ " {} Destination template produced empty path for: {}" ,
620+ "!" . yellow( ) ,
621+ full_path. display( )
622+ ) ;
623+ }
624+ result. skipped += 1 ;
625+ return Ok ( ( ) ) ;
626+ }
627+
628+ let dest = self . project_root . join ( & dest_str) ;
629+
630+ let resolved = ResolvedSource {
631+ path : full_path. to_path_buf ( ) ,
632+ exists : true ,
633+ } ;
634+
635+ let item_result = self . create_symlink ( & resolved, & dest, options) ?;
636+ result. created += item_result. created ;
637+ result. updated += item_result. updated ;
638+ result. skipped += item_result. skipped ;
639+
640+ Ok ( ( ) )
641+ } ,
642+ ) ?;
643+
644+ Ok ( result)
645+ }
646+
647+ fn for_each_nested_glob_match < F > (
648+ & self ,
649+ search_root : & Path ,
650+ glob_pattern : & str ,
651+ excludes : & [ String ] ,
652+ options : & SyncOptions ,
653+ mut on_match : F ,
654+ ) -> Result < ( ) >
655+ where
656+ F : FnMut ( & Path , & Path ) -> Result < ( ) > ,
657+ {
609658 let mut it = WalkDir :: new ( search_root) . follow_links ( false ) . into_iter ( ) ;
610659
611660 while let Some ( entry) = it. next ( ) {
612661 let entry = match entry {
613- Ok ( e ) => e ,
662+ Ok ( entry ) => entry ,
614663 Err ( err) => {
615664 if options. verbose {
616665 let path = err
617666 . path ( )
618667 . map ( |p| p. display ( ) . to_string ( ) )
619668 . unwrap_or_else ( || "<unknown path>" . to_string ( ) ) ;
620669 println ! (
621- " {} WalkDir error while scanning {}: {}" ,
670+ " {} WalkDir error while traversing {}: {}" ,
622671 "!" . yellow( ) ,
623672 path,
624673 err
625674 ) ;
626675 }
627- tracing:: debug!( error = %err, path = ?err. path( ) , "WalkDir entry skipped during nested-glob sync " ) ;
676+ tracing:: debug!( error = %err, path = ?err. path( ) , "WalkDir entry skipped during nested-glob traversal " ) ;
628677 continue ;
629678 }
630679 } ;
631680
632- // Compute path relative to search root
633681 let full_path = entry. path ( ) ;
634682 let rel_path = match full_path. strip_prefix ( search_root) {
635- Ok ( p ) => p ,
683+ Ok ( path ) => path ,
636684 Err ( _) => continue ,
637685 } ;
638686
639- // Convert to forward-slash string for pattern matching
640687 let rel_str = rel_path
641688 . components ( )
642- . map ( |c| c . as_os_str ( ) . to_string_lossy ( ) . into_owned ( ) )
689+ . map ( |component| component . as_os_str ( ) . to_string_lossy ( ) . into_owned ( ) )
643690 . collect :: < Vec < _ > > ( )
644691 . join ( "/" ) ;
645692
646693 if rel_str. is_empty ( ) {
647- continue ; // Skip root itself
694+ continue ;
648695 }
649696
650- // Check exclusion patterns.
651- // Uses `find()` instead of `any()` to:
652- // 1. Skip iteration when excludes list is empty (short-circuit)
653- // 2. Determine which pattern matched (for verbose logging)
654697 if let Some ( matched_exclude) = excludes
655698 . iter ( )
656- . find ( |excl | matches_path_glob ( & rel_str, excl ) )
699+ . find ( |exclude | matches_path_glob ( & rel_str, exclude ) )
657700 {
658701 if options. verbose {
659702 println ! (
@@ -663,52 +706,25 @@ impl Linker {
663706 full_path. display( )
664707 ) ;
665708 }
666- // Optimization: if this is a directory and it matches an exclude pattern,
667- // skip its entire subtree to avoid unnecessary I/O.
709+
668710 if entry. file_type ( ) . is_dir ( ) {
669711 it. skip_current_dir ( ) ;
670712 }
671713 continue ;
672714 }
673715
674- // Only process files
675716 if !entry. file_type ( ) . is_file ( ) {
676717 continue ;
677718 }
678719
679- // Match against the glob pattern
680720 if !matches_path_glob ( & rel_str, glob_pattern) {
681721 continue ;
682722 }
683723
684- // Expand the destination template
685- let dest_str = Self :: expand_destination_template ( dest_template, rel_path) ;
686- if dest_str. is_empty ( ) {
687- if options. verbose {
688- println ! (
689- " {} Destination template produced empty path for: {}" ,
690- "!" . yellow( ) ,
691- full_path. display( )
692- ) ;
693- }
694- result. skipped += 1 ;
695- continue ;
696- }
697-
698- let dest = self . project_root . join ( & dest_str) ;
699-
700- let resolved = ResolvedSource {
701- path : full_path. to_path_buf ( ) ,
702- exists : true ,
703- } ;
704-
705- let item_result = self . create_symlink ( & resolved, & dest, options) ?;
706- result. created += item_result. created ;
707- result. updated += item_result. updated ;
708- result. skipped += item_result. skipped ;
724+ on_match ( full_path, rel_path) ?;
709725 }
710726
711- Ok ( result )
727+ Ok ( ( ) )
712728 }
713729
714730 /// Process a `module-map` target: iterate mappings and create a symlink
@@ -840,78 +856,36 @@ impl Linker {
840856 let dest_template = & target_config. destination ;
841857 let excludes = & target_config. exclude ;
842858
843- let mut it = WalkDir :: new ( & search_root) . follow_links ( false ) . into_iter ( ) ;
844-
845- while let Some ( entry) = it. next ( ) {
846- let entry = match entry {
847- Ok ( e) => e,
848- Err ( err) => {
849- if options. verbose {
850- let path = err
851- . path ( )
852- . map ( |p| p. display ( ) . to_string ( ) )
853- . unwrap_or_else ( || "<unknown path>" . to_string ( ) ) ;
859+ self . for_each_nested_glob_match (
860+ & search_root,
861+ glob_pattern,
862+ excludes,
863+ options,
864+ |_, rel_path| {
865+ let dest_str =
866+ Self :: expand_destination_template ( dest_template, rel_path) ;
867+ if dest_str. is_empty ( ) {
868+ return Ok ( ( ) ) ;
869+ }
870+
871+ let dest = self . project_root . join ( & dest_str) ;
872+ if dest. is_symlink ( ) {
873+ if options. dry_run {
854874 println ! (
855- " {} WalkDir error while cleaning {}: {}" ,
856- "!" . yellow( ) ,
857- path,
858- err
875+ " {} Would remove: {}" ,
876+ "→" . cyan( ) ,
877+ dest. display( )
859878 ) ;
879+ } else {
880+ fs:: remove_file ( & dest) ?;
881+ println ! ( " {} Removed: {}" , "✔" . green( ) , dest. display( ) ) ;
860882 }
861- tracing:: debug!( error = %err, path = ?err. path( ) , "WalkDir entry skipped during nested-glob clean" ) ;
862- continue ;
863- }
864- } ;
865-
866- let rel_path = match entry. path ( ) . strip_prefix ( & search_root) {
867- Ok ( p) => p,
868- Err ( _) => continue ,
869- } ;
870- let rel_str = rel_path
871- . components ( )
872- . map ( |c| c. as_os_str ( ) . to_string_lossy ( ) . into_owned ( ) )
873- . collect :: < Vec < _ > > ( )
874- . join ( "/" ) ;
875-
876- if rel_str. is_empty ( ) {
877- continue ;
878- }
879-
880- if excludes
881- . iter ( )
882- . any ( |excl| matches_path_glob ( & rel_str, excl) )
883- {
884- // Optimization: if this is a directory and it matches an exclude pattern,
885- // skip its entire subtree to avoid unnecessary I/O.
886- if entry. file_type ( ) . is_dir ( ) {
887- it. skip_current_dir ( ) ;
883+ result. removed += 1 ;
888884 }
889- continue ;
890- }
891885
892- if !entry. file_type ( ) . is_file ( ) {
893- continue ;
894- }
895-
896- if !matches_path_glob ( & rel_str, glob_pattern) {
897- continue ;
898- }
899- let dest_str =
900- Self :: expand_destination_template ( dest_template, rel_path) ;
901- if dest_str. is_empty ( ) {
902- continue ;
903- }
904- let dest = self . project_root . join ( & dest_str) ;
905- if dest. is_symlink ( ) {
906- if options. dry_run {
907- println ! ( " {} Would remove: {}" , "→" . cyan( ) , dest. display( ) ) ;
908- } else {
909- fs:: remove_file ( & dest) ?;
910- println ! ( " {} Removed: {}" , "✔" . green( ) , dest. display( ) ) ;
911- }
912- result. removed += 1 ;
913- }
914- }
886+ Ok ( ( ) )
887+ } ,
888+ ) ?;
915889 }
916890 SyncType :: SymlinkContents => {
917891 let dest = self . project_root . join ( & target_config. destination ) ;
0 commit comments