Skip to main content

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!("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}