git_workty/commands/
rm.rs1use crate::git::GitRepo;
2use crate::status::is_worktree_dirty;
3use crate::ui::{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().context("Failed to get current directory")?;
28 if wt.path == current_path {
29 bail!("Cannot remove the current worktree. Change to a different worktree first.");
30 }
31
32 if wt.is_main_worktree(repo) {
33 bail!("Cannot remove the main worktree (original repository clone)");
34 }
35
36 let is_dirty = is_worktree_dirty(wt);
37 if is_dirty && !opts.force {
38 bail!(
39 "Worktree '{}' has uncommitted changes. Use --force to remove anyway.",
40 opts.name
41 );
42 }
43
44 if is_dirty {
45 print_warning(&format!(
46 "Worktree '{}' has uncommitted changes (--force specified)",
47 opts.name
48 ));
49 }
50
51 if !opts.yes && std::io::stdin().is_terminal() {
52 let confirm = Confirm::new()
53 .with_prompt(format!(
54 "Remove worktree '{}'{}?",
55 opts.name,
56 if opts.delete_branch {
57 " and its branch"
58 } else {
59 ""
60 }
61 ))
62 .default(false)
63 .interact()?;
64
65 if !confirm {
66 bail!("Aborted");
67 }
68 }
69
70 let branch_name = wt.branch_short.clone();
71 let wt_path = wt.path.clone();
72 let path_str = wt_path
73 .to_str()
74 .ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8: {:?}", wt_path))?;
75
76 let mut args = vec!["worktree", "remove"];
77 if opts.force {
78 args.push("--force");
79 }
80 args.push(path_str);
81
82 let output = Command::new("git")
83 .current_dir(&repo.root)
84 .args(&args)
85 .output()
86 .context("Failed to remove worktree")?;
87
88 if !output.status.success() {
89 let stderr = String::from_utf8_lossy(&output.stderr);
90 bail!("Failed to remove worktree: {}", stderr.trim());
91 }
92
93 print_success(&format!("Removed worktree '{}'", opts.name));
94
95 if opts.delete_branch {
96 if let Some(branch) = branch_name {
97 let output = Command::new("git")
98 .current_dir(&repo.root)
99 .args(["branch", "-d", &branch])
100 .output();
101
102 match output {
103 Ok(o) if o.status.success() => {
104 print_success(&format!("Deleted branch '{}'", branch));
105 }
106 Ok(o) => {
107 let stderr = String::from_utf8_lossy(&o.stderr);
108 print_warning(&format!(
109 "Could not delete branch '{}': {}",
110 branch,
111 stderr.trim()
112 ));
113 eprintln!("Hint: Use `git branch -D {}` to force delete.", branch);
114 }
115 Err(e) => {
116 print_warning(&format!("Could not delete branch '{}': {}", branch, e));
117 }
118 }
119 }
120 }
121
122 Ok(())
123}