git_workty/commands/
rm.rs

1use 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}