git_workty/commands/
rm.rs1use crate::git::GitRepo;
2use crate::status::is_worktree_dirty;
3use crate::ui::{print_error, print_success, print_warning};
4use crate::worktree::{find_worktree, list_worktrees};
5use anyhow::{bail, Context, Result};
6use dialoguer::Confirm;
7use is_terminal::IsTerminal;
8use std::process::Command;
9
10pub struct RmOptions {
11 pub name: String,
12 pub force: bool,
13 pub delete_branch: bool,
14 pub yes: bool,
15}
16
17pub fn execute(repo: &GitRepo, opts: RmOptions) -> Result<()> {
18 let worktrees = list_worktrees(repo)?;
19
20 let wt = find_worktree(&worktrees, &opts.name).ok_or_else(|| {
21 anyhow::anyhow!(
22 "Worktree '{}' not found. Use `git workty list` to see available worktrees.",
23 opts.name
24 )
25 })?;
26
27 let current_path = std::env::current_dir().unwrap_or_default();
28 if wt.path == current_path {
29 print_error(
30 "Cannot remove the current worktree",
31 Some("Change to a different worktree first with `wcd` or `git workty go`."),
32 );
33 std::process::exit(1);
34 }
35
36 if wt.is_main_worktree(repo) {
37 print_error(
38 "Cannot remove the main worktree",
39 Some("The main worktree is the original repository clone."),
40 );
41 std::process::exit(1);
42 }
43
44 let is_dirty = is_worktree_dirty(wt);
45 if is_dirty && !opts.force {
46 print_error(
47 &format!(
48 "Worktree '{}' has uncommitted changes",
49 opts.name
50 ),
51 Some("Use --force to remove anyway, or commit/stash changes first."),
52 );
53 std::process::exit(1);
54 }
55
56 if is_dirty {
57 print_warning(&format!(
58 "Worktree '{}' has uncommitted changes (--force specified)",
59 opts.name
60 ));
61 }
62
63 if !opts.yes && std::io::stdin().is_terminal() {
64 let confirm = Confirm::new()
65 .with_prompt(format!(
66 "Remove worktree '{}'{}?",
67 opts.name,
68 if opts.delete_branch { " and its branch" } else { "" }
69 ))
70 .default(false)
71 .interact()?;
72
73 if !confirm {
74 eprintln!("Aborted.");
75 std::process::exit(1);
76 }
77 }
78
79 let branch_name = wt.branch_short.clone();
80 let wt_path = wt.path.clone();
81
82 let mut args = vec!["worktree", "remove"];
83 if opts.force {
84 args.push("--force");
85 }
86 args.push(wt_path.to_str().unwrap());
87
88 let output = Command::new("git")
89 .current_dir(&repo.root)
90 .args(&args)
91 .output()
92 .context("Failed to remove worktree")?;
93
94 if !output.status.success() {
95 let stderr = String::from_utf8_lossy(&output.stderr);
96 bail!("Failed to remove worktree: {}", stderr.trim());
97 }
98
99 print_success(&format!("Removed worktree '{}'", opts.name));
100
101 if opts.delete_branch {
102 if let Some(branch) = branch_name {
103 let output = Command::new("git")
104 .current_dir(&repo.root)
105 .args(["branch", "-d", &branch])
106 .output();
107
108 match output {
109 Ok(o) if o.status.success() => {
110 print_success(&format!("Deleted branch '{}'", branch));
111 }
112 Ok(o) => {
113 let stderr = String::from_utf8_lossy(&o.stderr);
114 print_warning(&format!(
115 "Could not delete branch '{}': {}",
116 branch,
117 stderr.trim()
118 ));
119 eprintln!("Hint: Use `git branch -D {}` to force delete.", branch);
120 }
121 Err(e) => {
122 print_warning(&format!("Could not delete branch '{}': {}", branch, e));
123 }
124 }
125 }
126 }
127
128 Ok(())
129}