Clean all symlinks managed by this configuration
(&self, options: &SyncOptions)
| 1227 | |
| 1228 | /// Clean all symlinks managed by this configuration |
| 1229 | pub fn clean(&self, options: &SyncOptions) -> Result<SyncResult> { |
| 1230 | let mut result = SyncResult::default(); |
| 1231 | |
| 1232 | println!("{}", "Cleaning managed symlinks...".cyan()); |
| 1233 | |
| 1234 | for (agent_name, agent_config) in &self.config.agents { |
| 1235 | for target_config in agent_config.targets.values() { |
| 1236 | match target_config.sync_type { |
| 1237 | SyncType::NestedGlob => { |
| 1238 | // SECURITY: Validate destination template for traversal/absolute paths. |
| 1239 | if self |
| 1240 | .ensure_safe_destination(&target_config.destination) |
| 1241 | .is_err() |
| 1242 | { |
| 1243 | continue; |
| 1244 | } |
| 1245 | |
| 1246 | // Re-discover the same files and remove the corresponding symlinks. |
| 1247 | let search_root = self.project_root.join(&target_config.source); |
| 1248 | // SECURITY: Validate search root to prevent traversal/absolute escapes. |
| 1249 | if self.revalidate_path(&search_root).is_err() { |
| 1250 | continue; |
| 1251 | } |
| 1252 | if !search_root.exists() || !search_root.is_dir() { |
| 1253 | continue; |
| 1254 | } |
| 1255 | let glob_pattern = |
| 1256 | target_config.pattern.as_deref().unwrap_or("**/AGENTS.md"); |
| 1257 | let dest_template = &target_config.destination; |
| 1258 | let excludes = &target_config.exclude; |
| 1259 | |
| 1260 | self.for_each_nested_glob_match( |
| 1261 | &search_root, |
| 1262 | glob_pattern, |
| 1263 | excludes, |
| 1264 | options, |
| 1265 | |_, rel_path| { |
| 1266 | let dest_str = |
| 1267 | Self::expand_destination_template(dest_template, rel_path); |
| 1268 | if dest_str.is_empty() { |
| 1269 | return Ok(()); |
| 1270 | } |
| 1271 | |
| 1272 | let dest = match self.ensure_safe_destination(&dest_str) { |
| 1273 | Ok(dest) => dest, |
| 1274 | Err(_) => return Ok(()), |
| 1275 | }; |
| 1276 | if dest.is_symlink() { |
| 1277 | if options.dry_run { |
| 1278 | println!( |
| 1279 | " {} Would remove: {}", |
| 1280 | "→".cyan(), |
| 1281 | dest.display() |
| 1282 | ); |
| 1283 | } else { |
| 1284 | self.revalidate_unlink_path(&dest)?; |
| 1285 | fs::remove_file(&dest)?; |
| 1286 | self.invalidate_discovery_caches(); |