1use crate::engine::cleanup::cleanup_orphaned_worktree;
2use std::path::Path;
3use std::sync::Arc;
4
5pub async fn run(dry_run: bool) -> anyhow::Result<()> {
12 let store = Arc::new(crate::cli::init_store().await?);
13 let all_tasks = store.list_all_global().await?;
14
15 let worktrees_dir = crate::home::worktrees_dir()?;
16
17 let owned: std::collections::HashSet<String> = all_tasks
19 .iter()
20 .filter(|t| !t.worktree.is_empty())
21 .map(|t| t.worktree.clone())
22 .collect();
23
24 let mut removed = 0usize;
25 let mut failed = 0usize;
26
27 let project_dirs = match std::fs::read_dir(&worktrees_dir) {
28 Ok(d) => d,
29 Err(e) => {
30 eprintln!("cannot read worktrees directory: {e}");
31 return Ok(());
32 }
33 };
34
35 for project_entry in project_dirs {
36 let project_entry = match project_entry {
37 Ok(e) => e,
38 Err(e) => {
39 eprintln!("error reading worktrees directory entry: {e}");
40 continue;
41 }
42 };
43 if !project_entry
44 .file_type()
45 .map(|t| t.is_dir())
46 .unwrap_or(false)
47 {
48 continue;
49 }
50 let branch_dirs = match std::fs::read_dir(project_entry.path()) {
51 Ok(d) => d,
52 Err(_) => continue,
53 };
54 for branch_entry in branch_dirs {
55 let branch_entry = match branch_entry {
56 Ok(e) => e,
57 Err(e) => {
58 eprintln!("error reading project directory entry: {e}");
59 continue;
60 }
61 };
62 if !branch_entry
63 .file_type()
64 .map(|t| t.is_dir())
65 .unwrap_or(false)
66 {
67 continue;
68 }
69 let wt_path = branch_entry.path().to_string_lossy().to_string();
70 if owned.contains(&wt_path) {
71 continue;
72 }
73
74 if dry_run {
75 println!("would remove: {}", wt_path);
76 removed += 1;
77 continue;
78 }
79
80 let ok = cleanup_orphaned_worktree("prune", Path::new(&wt_path)).await;
81 if ok {
82 println!("removed: {}", wt_path);
83 removed += 1;
84 } else {
85 eprintln!("failed to remove: {}", wt_path);
86 failed += 1;
87 }
88 }
89 }
90
91 if removed == 0 && failed == 0 {
92 println!("No orphaned worktrees found.");
93 return Ok(());
94 }
95
96 if dry_run {
97 println!(
98 "\n{} orphaned worktree(s) would be removed (dry run)",
99 removed
100 );
101 } else {
102 println!(
103 "\nRemoved {} orphaned worktree(s){}",
104 removed,
105 if failed > 0 {
106 format!(", {} failed", failed)
107 } else {
108 String::new()
109 }
110 );
111 }
112
113 Ok(())
114}