diff --git a/crates/collab/tests/integration/git_tests.rs b/crates/collab/tests/integration/git_tests.rs index cc1b748675d421ae92316d490df243f6d79bbc4f..4af1355352554ee6e3350806cefe0b4cd41cf5d6 100644 --- a/crates/collab/tests/integration/git_tests.rs +++ b/crates/collab/tests/integration/git_tests.rs @@ -401,16 +401,19 @@ async fn test_linked_worktrees_sync( path: PathBuf::from(path!("/project")), ref_name: Some("refs/heads/main".into()), sha: "aaa111".into(), + is_main: false, }); state.worktrees.push(GitWorktree { path: PathBuf::from(path!("/project/feature-branch")), ref_name: Some("refs/heads/feature-branch".into()), sha: "bbb222".into(), + is_main: false, }); state.worktrees.push(GitWorktree { path: PathBuf::from(path!("/project/bugfix-branch")), ref_name: Some("refs/heads/bugfix-branch".into()), sha: "ccc333".into(), + is_main: false, }); }) .unwrap(); @@ -480,6 +483,7 @@ async fn test_linked_worktrees_sync( path: PathBuf::from(path!("/project/hotfix-branch")), ref_name: Some("refs/heads/hotfix-branch".into()), sha: "ddd444".into(), + is_main: false, }); }) .unwrap(); diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 38cb1e6b3c467dba4430767c2f4d6705c1d8b2aa..12a095ffe27aa760623fa2b6ce674fdd9008eef1 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -429,6 +429,7 @@ impl GitRepository for FakeGitRepository { path: work_dir, ref_name: Some(branch_ref.into()), sha: head_sha.into(), + is_main: true, }; let mut all = vec![main_worktree]; all.extend(state.worktrees.iter().cloned()); @@ -470,6 +471,7 @@ impl GitRepository for FakeGitRepository { path, ref_name: Some(ref_name.into()), sha: sha.into(), + is_main: false, }); state.branches.insert(branch_name); Ok::<(), anyhow::Error>(()) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 8b0f189d869fe2438aecbac14895d5f30deaf308..b03fe1b0c63904bfc751ab7946f92a7c8595db00 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -238,6 +238,7 @@ pub struct Worktree { pub ref_name: Option, // todo(git_worktree) This type should be a Oid pub sha: SharedString, + pub is_main: bool, } impl Worktree { @@ -259,6 +260,7 @@ impl Worktree { pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec { let mut worktrees = Vec::new(); + let mut is_first = true; let normalized = raw_worktrees.as_ref().replace("\r\n", "\n"); let entries = normalized.split("\n\n"); for entry in entries { @@ -286,7 +288,9 @@ pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec) -> bool { + !self.is_new + && !self.worktree.is_main + && forbidden_deletion_path != Some(&self.worktree.path) + } +} + pub struct WorktreeListDelegate { matches: Vec, all_worktrees: Option>, @@ -462,7 +470,7 @@ impl WorktreeListDelegate { let Some(entry) = self.matches.get(idx).cloned() else { return; }; - if entry.is_new || self.forbidden_deletion_path.as_ref() == Some(&entry.worktree.path) { + if !entry.can_delete(self.forbidden_deletion_path.as_ref()) { return; } let Some(repo) = self.repo.clone() else { @@ -719,6 +727,7 @@ impl PickerDelegate for WorktreeListDelegate { path: Default::default(), ref_name: Some(format!("refs/heads/{query}").into()), sha: Default::default(), + is_main: false, }, positions: Vec::new(), is_new: true, @@ -805,8 +814,7 @@ 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 can_delete = entry.can_delete(self.forbidden_deletion_path.as_ref()); let delete_button = |entry_ix: usize| { IconButton::new(("delete-worktree", entry_ix), IconName::Trash) @@ -894,9 +902,8 @@ 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 can_delete = selected_entry + .is_some_and(|entry| entry.can_delete(self.forbidden_deletion_path.as_ref())); let footer_container = h_flex() .w_full() diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 1be256a50a1029e54a6002f1301f248b88b80850..36479fb80f561665e01853eba5a214eb84088361 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -7060,6 +7060,7 @@ fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree { .map(|s| s.to_string()) .unwrap_or_default(), sha: worktree.sha.to_string(), + is_main: worktree.is_main, } } @@ -7068,6 +7069,7 @@ fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree { path: PathBuf::from(proto.path.clone()), ref_name: Some(SharedString::from(&proto.ref_name)), sha: proto.sha.clone().into(), + is_main: proto.is_main, } } diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index 4f6b74e9537ac4570c72c3c5319b9819f1e52d0c..cb878cade726002e7e09670cf7c190880d8e66cb 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -575,6 +575,7 @@ message Worktree { string path = 1; string ref_name = 2; string sha = 3; + bool is_main = 4; } message GitCreateWorktree { diff --git a/crates/sidebar/src/project_group_builder.rs b/crates/sidebar/src/project_group_builder.rs index 318dfac0a839e28ceb27c6036b87e6a13d9bc992..0b8e56ac99565218dd827048afdee71e896f2667 100644 --- a/crates/sidebar/src/project_group_builder.rs +++ b/crates/sidebar/src/project_group_builder.rs @@ -255,6 +255,7 @@ mod tests { path: std::path::PathBuf::from("/wt/feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "abc".into(), + is_main: false, }); }) .expect("git state should be set"); diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index d0400d1ae4338ac5de4dfee0daca9196f12030b5..b9bd873d369a44d3e09db9771383c111ead2ccb6 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -2463,6 +2463,7 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -2577,6 +2578,7 @@ async fn test_search_matches_worktree_name(cx: &mut TestAppContext) { path: std::path::PathBuf::from("/wt/rosewood"), ref_name: Some("refs/heads/rosewood".into()), sha: "abc".into(), + is_main: false, }); }) .unwrap(); @@ -2638,6 +2640,7 @@ async fn test_git_worktree_added_live_updates_sidebar(cx: &mut TestAppContext) { path: std::path::PathBuf::from("/wt/rosewood"), ref_name: Some("refs/heads/rosewood".into()), sha: "abc".into(), + is_main: false, }); }) .unwrap(); @@ -2739,11 +2742,13 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); state.worktrees.push(git::repository::Worktree { path: std::path::PathBuf::from("/wt-feature-b"), ref_name: Some("refs/heads/feature-b".into()), sha: "bbb".into(), + is_main: false, }); }) .unwrap(); @@ -2821,11 +2826,13 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); state.worktrees.push(git::repository::Worktree { path: std::path::PathBuf::from("/wt-feature-b"), ref_name: Some("refs/heads/feature-b".into()), sha: "bbb".into(), + is_main: false, }); }) .unwrap(); @@ -2941,6 +2948,7 @@ async fn test_multi_worktree_thread_shows_multiple_chips(cx: &mut TestAppContext path: std::path::PathBuf::from(format!("/worktrees/{repo}/{branch}/{repo}")), ref_name: Some(format!("refs/heads/{branch}").into()), sha: "aaa".into(), + is_main: false, }); } }) @@ -3043,6 +3051,7 @@ async fn test_same_named_worktree_chips_are_deduplicated(cx: &mut TestAppContext path: std::path::PathBuf::from(format!("/worktrees/{repo}/olivetti/{repo}")), ref_name: Some("refs/heads/olivetti".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3133,6 +3142,7 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3248,6 +3258,7 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3354,6 +3365,7 @@ async fn test_clicking_worktree_thread_opens_workspace_when_none_exists(cx: &mut path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3459,6 +3471,7 @@ async fn test_clicking_worktree_thread_does_not_briefly_render_as_separate_proje path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3609,6 +3622,7 @@ async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace( path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -4203,6 +4217,7 @@ async fn test_archive_thread_uses_next_threads_own_workspace(cx: &mut TestAppCon path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -4374,6 +4389,7 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -5180,6 +5196,7 @@ mod property_test { path: worktree_pathbuf, ref_name: Some(format!("refs/heads/{}", worktree_name).into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap();