mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
fix(worktree): skip the cleanup offer while another pane still lives in the checkout
New tabs inherit the current cwd, so two tabs sharing one managed worktree is common; removing the checkout on the first close would yank the directory out from under the surviving shell. Before offering, scan every leaf of every surviving tab (splits included) and stay quiet if any cwd still sits inside the worktree.
This commit is contained in:
@@ -166,6 +166,16 @@ pub fn managed(cwd: &Path) -> Option<ManagedWorktree> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether any of `cwds` still lives inside the worktree at `path` — removing
|
||||
/// the checkout then would pull the directory out from under a live shell (new
|
||||
/// tabs inherit the current cwd, so two tabs sharing one worktree is common).
|
||||
/// Each cwd is canonicalized before the ancestor test to match the physical
|
||||
/// path git reported; a vanished cwd never counts as occupying.
|
||||
pub fn occupied(path: &Path, cwds: &[PathBuf]) -> bool {
|
||||
cwds.iter()
|
||||
.any(|c| std::fs::canonicalize(c).is_ok_and(|c| c.starts_with(path)))
|
||||
}
|
||||
|
||||
/// Remove a managed worktree (`git worktree remove`, `--force` to discard
|
||||
/// uncommitted changes), then best-effort delete its branch with `-d` — so a
|
||||
/// branch carrying unmerged commits survives the cleanup.
|
||||
@@ -481,6 +491,20 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&own);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn occupied_detects_live_cwds_inside_the_worktree() {
|
||||
let repo = temp_repo("occ");
|
||||
let wt = create(&repo, &req("occ-wt")).unwrap();
|
||||
let inside = wt.path.join("deep");
|
||||
std::fs::create_dir_all(&inside).unwrap();
|
||||
assert!(occupied(&wt.path, &[repo.clone(), inside]));
|
||||
// Cwds elsewhere in the repo don't count…
|
||||
assert!(!occupied(&wt.path, &[repo.clone()]));
|
||||
// …and neither does a cwd that no longer exists.
|
||||
assert!(!occupied(&wt.path, &[wt.path.join("gone")]));
|
||||
let _ = std::fs::remove_dir_all(&repo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_outside_a_repo_errors() {
|
||||
let plain = scratch("plain");
|
||||
|
||||
+15
-1
@@ -1968,12 +1968,26 @@ impl Tty7App {
|
||||
/// clean worktree gets a plain keep/remove prompt; one with uncommitted
|
||||
/// changes defaults to keeping and makes discarding explicit. Removal also
|
||||
/// deletes the branch when it carries no unmerged commits (`branch -d`).
|
||||
/// No offer while any surviving pane still has its cwd inside the checkout
|
||||
/// (new tabs inherit the current cwd, so shared worktrees are common) —
|
||||
/// removal would yank the directory out from under a live shell.
|
||||
/// Detection, the dirty probe, and removal all run off the UI thread.
|
||||
fn offer_worktree_cleanup(&mut self, cwd: Option<std::path::PathBuf>, cx: &mut Context<Self>) {
|
||||
let Some(cwd) = cwd else { return };
|
||||
// Every leaf of every surviving tab, not just focused panes — a shell
|
||||
// tucked away in a split occupies the worktree all the same.
|
||||
let open_cwds: Vec<std::path::PathBuf> = self
|
||||
.tabs
|
||||
.iter()
|
||||
.flat_map(|tab| tab.pane.leaves())
|
||||
.filter_map(|leaf| leaf.read(cx).cwd())
|
||||
.collect();
|
||||
cx.spawn(async move |this, cx| {
|
||||
let Some(wt) = cx
|
||||
.background_spawn(async move { crate::core::worktree::managed(&cwd) })
|
||||
.background_spawn(async move {
|
||||
crate::core::worktree::managed(&cwd)
|
||||
.filter(|wt| !crate::core::worktree::occupied(&wt.path, &open_cwds))
|
||||
})
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user