use crate::cli::DropArgs;
use crate::commands::remove::{RemoveOptions, remove_resolved};
use crate::commands::{candidate_label, open_session};
use crate::cx::Cx;
use crate::error::{Error, Result};
use crate::hooks::HookRunner;
use crate::model::RemovedResult;
use crate::worktree_service::build_worktrees;
pub(crate) fn run(cx: &mut Cx, hooks: &dyn HookRunner, args: &DropArgs, json: bool) -> Result<u8> {
let git = cx.git.clone();
let git = git.as_ref();
let session = open_session(cx, git)?;
let root = session.primary_root.clone();
let worktrees = build_worktrees(&session.repo, git)?;
let index = worktrees
.iter()
.position(|w| w.is_current)
.ok_or(Error::NoCurrentWorktree)?;
let worktree = worktrees[index].clone();
if worktree.is_main {
return Err(Error::operation("refusing to drop the primary worktree"));
}
let opts = RemoveOptions {
force_remove: args.force,
force_branch: false,
keep_branch: true,
no_hooks: args.no_hooks,
};
remove_resolved(cx, git, hooks, &session, &root, &worktree, &opts)?;
if json {
let result = RemovedResult {
worktree: worktree.clone(),
removed: true,
};
cx.out.line(&serde_json::to_string(&result)?)?;
} else {
cx.err
.line(&format!("dropped worktree {}", candidate_label(&worktree)))?;
cx.out.line(&root.to_string_lossy())?;
}
Ok(0)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::cli::DropArgs;
use crate::error::Result;
use crate::hooks::RealHookRunner;
use crate::testutil::{TestRepo, give_upstream, make_wt, wt_dir};
fn args(force: bool) -> DropArgs {
DropArgs {
force,
no_hooks: true,
}
}
fn run_in(cwd: &Path, a: &DropArgs, json: bool) -> Result<(u8, String, String)> {
let mut t = crate::testutil::test_cx(&[], cwd.to_str().unwrap());
let code = super::run(&mut t.cx, &RealHookRunner, a, json)?;
Ok((code, t.out.contents(), t.err.contents()))
}
#[test]
fn drops_current_worktree_and_keeps_branch() {
let repo = TestRepo::init();
make_wt(&repo, "featurex");
give_upstream(&repo, "featurex"); let wt = wt_dir(&repo, "featurex");
let (code, out, err) = run_in(&wt, &args(false), false).unwrap();
assert_eq!(code, 0);
assert!(err.contains("dropped worktree featurex"));
assert!(!repo.git(&["worktree", "list"]).contains("featurex"));
assert!(
!repo
.git(&["branch", "--list", "featurex"])
.trim()
.is_empty()
);
assert!(crate::commands::same_path(
Path::new(out.trim()),
repo.root()
));
}
#[test]
fn drops_from_arbitrarily_deep_subdir() {
let repo = TestRepo::init();
make_wt(&repo, "deepwt");
give_upstream(&repo, "deepwt");
let deep = wt_dir(&repo, "deepwt").join("a/b/c");
std::fs::create_dir_all(&deep).unwrap();
let (code, _, err) = run_in(&deep, &args(false), false).unwrap();
assert_eq!(code, 0);
assert!(err.contains("dropped worktree deepwt"));
assert!(!repo.git(&["worktree", "list"]).contains("deepwt"));
assert!(!repo.git(&["branch", "--list", "deepwt"]).trim().is_empty());
}
#[test]
fn refuses_primary_worktree() {
let repo = TestRepo::init();
let err = run_in(repo.root(), &args(false), false).unwrap_err();
assert!(err.to_string().contains("primary"));
assert!(repo.git(&["worktree", "list"]).contains("main"));
}
#[test]
fn dirty_blocks_without_force_then_force_removes() {
let repo = TestRepo::init();
make_wt(&repo, "dirtywt");
give_upstream(&repo, "dirtywt");
let wt = wt_dir(&repo, "dirtywt");
std::fs::write(wt.join("README.md"), "changed\n").unwrap();
let err = run_in(&wt, &args(false), false).unwrap_err();
assert!(err.to_string().contains("uncommitted"));
assert!(err.to_string().contains("--force"));
let (code, _, e) = run_in(&wt, &args(true), false).unwrap();
assert_eq!(code, 0);
assert!(e.contains("data may be lost"));
assert!(!repo.git(&["worktree", "list"]).contains("dirtywt"));
assert!(!repo.git(&["branch", "--list", "dirtywt"]).trim().is_empty());
}
#[test]
fn json_emits_removed_flag_and_keeps_branch() {
let repo = TestRepo::init();
make_wt(&repo, "featurej");
give_upstream(&repo, "featurej");
let wt = wt_dir(&repo, "featurej");
let (code, out, _) = run_in(&wt, &args(false), true).unwrap();
assert_eq!(code, 0);
let v: serde_json::Value = serde_json::from_str(out.trim()).unwrap();
assert_eq!(v["removed"], serde_json::json!(true));
assert_eq!(v["branch"], serde_json::json!("featurej"));
assert!(
!repo
.git(&["branch", "--list", "featurej"])
.trim()
.is_empty()
);
}
}