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