Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "signal",
# Jiff is local-only: it reads the OS timezone database (with a Windows bundle)
# and performs no runtime network access.
jiff = { version = "0.2", default-features = false, features = ["std", "tz-system", "tzdb-zoneinfo", "tzdb-bundle-platform"] }
# `libc` (setsid + macOS proc_pidinfo) is only needed on Unix.
# Unix APIs; 0.2.187 adds musl renameat2 for exclusive worktree restoration.
[target.'cfg(unix)'.dependencies]
libc = "0.2"
libc = "0.2.187"

# Native Windows terminal-backend safety: owner verification for named-pipe
# peers, PID-reuse-safe process creation timestamps, and one bounded ToolHelp
Expand Down
40 changes: 29 additions & 11 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6753,11 +6753,11 @@ impl App {
// Keep this helper useful to callers that inspect rows before
// opening a menu. The renderer always takes the cached branch.
.unwrap_or_else(|| ws.is_some_and(|w| crate::git::local::is_repo(&w.cwd)));
// A linked worktree (a `git worktree add` checkout) can be deleted; a main
// checkout or plain workspace cannot — only closed.
let is_worktree = ws
// Only offer deletion when the confirmed checkout can be identified.
// Missing identity fails closed; opening the menu must not recapture it.
let can_delete_worktree = ws
.and_then(|w| w.worktree.as_ref())
.is_some_and(|m| m.linked);
.is_some_and(|m| m.linked && m.directory_identity.is_some());
let pin = if ws.is_some_and(|w| w.pinned) {
WsMenuItem::Unpin
} else {
Expand All @@ -6770,7 +6770,7 @@ impl App {
WsMenuItem::TogglePath,
WsMenuItem::QuickActions,
];
if is_worktree {
if can_delete_worktree {
items.push(WsMenuItem::DeleteWorktree);
}
if is_repo {
Expand Down Expand Up @@ -7253,12 +7253,7 @@ impl App {
.and_then(|_| linked_worktree_branch(&repo, &path))
.and_then(|_| ensure_worktree_identity(&path, identity))
.and_then(|_| {
crate::git::local::worktree_remove_force(&repo, &path)?;
if path.exists() {
ensure_worktree_identity(&path, identity)?;
std::fs::remove_dir_all(&path)
.map_err(|error| error.to_string())?;
}
crate::git::removal::remove_confirmed(&repo, &path, identity)?;
cleanup_empty_worktree_parent(&path);
Ok(())
});
Expand Down Expand Up @@ -10268,6 +10263,29 @@ mod tests {
assert!(items.contains(&WsMenuItem::Close), "but it can be closed");
}

#[test]
fn delete_worktree_menu_requires_captured_directory_identity() {
let _env = crate::persist::test_env("wt-del-menu-identity");
let (base, _repo, wt) = repo_with_sibling_worktree("wt-del-menu-identity");
let (tx, _rx) = mpsc::channel();
let mut app = App::new(80, 24, tx).unwrap();
assert!(app.create_workspace_at(wt));
let index = app.active_ws;
assert!(app
.ws_menu_items(index)
.contains(&WsMenuItem::DeleteWorktree));
app.workspaces[index]
.worktree
.as_mut()
.unwrap()
.directory_identity = None;
let items = app.ws_menu_items(index);
assert!(!items.contains(&WsMenuItem::DeleteWorktree));
assert!(items.contains(&WsMenuItem::Close));
app.drain_io_jobs();
let _ = std::fs::remove_dir_all(&base);
}

/// The worktree-delete confirm cancels on any non-confirm key, and confirming
/// on a non-worktree node is a guarded no-op (never deletes a plain workspace).
#[test]
Expand Down
1 change: 1 addition & 0 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
pub mod github;
pub mod local;
pub mod model;
pub(crate) mod removal;

use std::collections::HashMap;
use std::ops::Range;
Expand Down
Loading
Loading