Skip to main content

git_workty/commands/
rm.rs

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