diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index d9adc079bf470e121c99b2d13c2ef25ffea7a68f..ce1504dd60f0ea1a18c6cb827ee14a8f5b411695 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -486,6 +486,10 @@ impl BranchListDelegate { let is_remote; let result = match &entry { Entry::Branch { branch, .. } => { + if branch.is_head { + return Ok(()); + } + is_remote = branch.is_remote(); repo.update(cx, |repo, _| { repo.delete_branch(is_remote, branch.name().to_string()) @@ -1151,20 +1155,29 @@ impl PickerDelegate for BranchListDelegate { let delete_and_select_btns = h_flex() .gap_1() - .child( - Button::new("delete-branch", "Delete") - .key_binding( - KeyBinding::for_action_in( - &branch_picker::DeleteBranch, - &focus_handle, - cx, - ) - .map(|kb| kb.size(rems_from_px(12.))), + .when( + !selected_entry + .and_then(|entry| entry.as_branch()) + .is_some_and(|branch| branch.is_head), + |this| { + this.child( + Button::new("delete-branch", "Delete") + .key_binding( + KeyBinding::for_action_in( + &branch_picker::DeleteBranch, + &focus_handle, + cx, + ) + .map(|kb| kb.size(rems_from_px(12.))), + ) + .on_click(|_, window, cx| { + window.dispatch_action( + branch_picker::DeleteBranch.boxed_clone(), + cx, + ); + }), ) - .on_click(|_, window, cx| { - window - .dispatch_action(branch_picker::DeleteBranch.boxed_clone(), cx); - }), + }, ) .child( Button::new("select_branch", "Select") diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs index 2cbba9347f973aa084413b03af7314f30d22fc99..d7488cc1bddb7e9d2825b8d21d3dc6c4c4fdde5a 100644 --- a/crates/git_ui/src/worktree_picker.rs +++ b/crates/git_ui/src/worktree_picker.rs @@ -19,7 +19,7 @@ use remote_connection::{RemoteConnectionModal, connect}; use settings::Settings; use std::{path::PathBuf, sync::Arc}; use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*}; -use util::ResultExt; +use util::{ResultExt, debug_panic}; use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr}; use crate::git_panel::show_error_toast; @@ -115,6 +115,7 @@ impl WorktreeList { this.picker.update(cx, |picker, cx| { picker.delegate.all_worktrees = Some(all_worktrees); picker.delegate.default_branch = default_branch; + picker.delegate.refresh_forbidden_deletion_path(cx); picker.refresh(window, cx); }) })?; @@ -261,6 +262,7 @@ pub struct WorktreeListDelegate { modifiers: Modifiers, focus_handle: FocusHandle, default_branch: Option, + forbidden_deletion_path: Option, } impl WorktreeListDelegate { @@ -280,6 +282,7 @@ impl WorktreeListDelegate { modifiers: Default::default(), focus_handle: cx.focus_handle(), default_branch: None, + forbidden_deletion_path: None, } } @@ -452,7 +455,7 @@ impl WorktreeListDelegate { let Some(entry) = self.matches.get(idx).cloned() else { return; }; - if entry.is_new { + if entry.is_new || self.forbidden_deletion_path.as_ref() == Some(&entry.worktree.path) { return; } let Some(repo) = self.repo.clone() else { @@ -486,6 +489,7 @@ impl WorktreeListDelegate { if let Some(all_worktrees) = &mut picker.delegate.all_worktrees { all_worktrees.retain(|w| w.path != path); } + picker.delegate.refresh_forbidden_deletion_path(cx); if picker.delegate.matches.is_empty() { picker.delegate.selected_index = 0; } else if picker.delegate.selected_index >= picker.delegate.matches.len() { @@ -498,6 +502,29 @@ impl WorktreeListDelegate { }) .detach(); } + + fn refresh_forbidden_deletion_path(&mut self, cx: &App) { + let Some(workspace) = self.workspace.upgrade() else { + debug_panic!("Workspace should always be available or else the picker would be closed"); + self.forbidden_deletion_path = None; + return; + }; + + let visible_worktree_paths = workspace.read_with(cx, |workspace, cx| { + workspace + .project() + .read(cx) + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).abs_path().to_path_buf()) + .collect::>() + }); + + self.forbidden_deletion_path = if visible_worktree_paths.len() == 1 { + visible_worktree_paths.into_iter().next() + } else { + None + }; + } } async fn open_remote_worktree( @@ -771,6 +798,9 @@ impl PickerDelegate for WorktreeListDelegate { let focus_handle = self.focus_handle.clone(); + let can_delete = + !entry.is_new && self.forbidden_deletion_path.as_ref() != Some(&entry.worktree.path); + let delete_button = |entry_ix: usize| { IconButton::new(("delete-worktree", entry_ix), IconName::Trash) .icon_size(IconSize::Small) @@ -839,7 +869,7 @@ impl PickerDelegate for WorktreeListDelegate { } })), ) - .when(!entry.is_new, |this| { + .when(can_delete, |this| { if selected { this.end_slot(delete_button(ix)) } else { @@ -857,6 +887,9 @@ impl PickerDelegate for WorktreeListDelegate { let focus_handle = self.focus_handle.clone(); let selected_entry = self.matches.get(self.selected_index); let is_creating = selected_entry.is_some_and(|entry| entry.is_new); + let can_delete = selected_entry.is_some_and(|entry| { + !entry.is_new && self.forbidden_deletion_path.as_ref() != Some(&entry.worktree.path) + }); let footer_container = h_flex() .w_full() @@ -904,16 +937,18 @@ impl PickerDelegate for WorktreeListDelegate { } else { Some( footer_container - .child( - Button::new("delete-worktree", "Delete") - .key_binding( - KeyBinding::for_action_in(&DeleteWorktree, &focus_handle, cx) - .map(|kb| kb.size(rems_from_px(12.))), - ) - .on_click(|_, window, cx| { - window.dispatch_action(DeleteWorktree.boxed_clone(), cx) - }), - ) + .when(can_delete, |this| { + this.child( + Button::new("delete-worktree", "Delete") + .key_binding( + KeyBinding::for_action_in(&DeleteWorktree, &focus_handle, cx) + .map(|kb| kb.size(rems_from_px(12.))), + ) + .on_click(|_, window, cx| { + window.dispatch_action(DeleteWorktree.boxed_clone(), cx) + }), + ) + }) .child( Button::new("open-in-new-window", "Open in New Window") .key_binding(