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!("Worktree '{}' has uncommitted changes", opts.name),
48 Some("Use --force to remove anyway, or commit/stash changes first."),
49 );
50 std::process::exit(1);
51 }
52
53 if is_dirty {
54 print_warning(&format!(
55 "Worktree '{}' has uncommitted changes (--force specified)",
56 opts.name
57 ));
58 }
59
60 if !opts.yes && std::io::stdin().is_terminal() {
61 let confirm = Confirm::new()
62 .with_prompt(format!(
63 "Remove worktree '{}'{}?",
64 opts.name,
65 if opts.delete_branch {
66 " and its branch"
67 } else {
68 ""
69 }
70 ))
71 .default(false)
72 .interact()?;
73
74 if !confirm {
75 eprintln!("Aborted.");
76 std::process::exit(1);
77 }
78 }
79
80 let branch_name = wt.branch_short.clone();
81 let wt_path = wt.path.clone();
82
83 let mut args = vec!["worktree", "remove"];
84 if opts.force {
85 args.push("--force");
86 }
87 args.push(wt_path.to_str().unwrap());
88
89 let output = Command::new("git")
90 .current_dir(&repo.root)
91 .args(&args)
92 .output()
93 .context("Failed to remove worktree")?;
94
95 if !output.status.success() {
96 let stderr = String::from_utf8_lossy(&output.stderr);
97 bail!("Failed to remove worktree: {}", stderr.trim());
98 }
99
100 print_success(&format!("Removed worktree '{}'", opts.name));
101
102 if opts.delete_branch {
103 if let Some(branch) = branch_name {
104 let output = Command::new("git")
105 .current_dir(&repo.root)
106 .args(["branch", "-d", &branch])
107 .output();
108
109 match output {
110 Ok(o) if o.status.success() => {
111 print_success(&format!("Deleted branch '{}'", branch));
112 }
113 Ok(o) => {
114 let stderr = String::from_utf8_lossy(&o.stderr);
115 print_warning(&format!(
116 "Could not delete branch '{}': {}",
117 branch,
118 stderr.trim()
119 ));
120 eprintln!("Hint: Use `git branch -D {}` to force delete.", branch);
121 }
122 Err(e) => {
123 print_warning(&format!("Could not delete branch '{}': {}", branch, e));
124 }
125 }
126 }
127 }
128
129 Ok(())
130}