diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index db0222d4095..36a25ec5f7d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2041,10 +2041,8 @@ impl ProjectPanel { return; }; - if let Some(worktree) = self - .project - .read(cx) - .worktree_for_id(edit_state.worktree_id, cx) + let project = self.project.read(cx); + if let Some(worktree) = project.worktree_for_id(edit_state.worktree_id, cx) && let Some(entry) = worktree.read(cx).entry_for_id(edit_state.entry_id) { let mut already_exists = false; @@ -2054,12 +2052,18 @@ impl ProjectPanel { already_exists = true; } } else { - let new_path = if let Some(parent) = entry.path.clone().parent() { - parent.join(&filename) - } else { - filename.to_owned() + let new_path = match entry.path.clone().parent() { + Some(parent) => parent.join(&filename), + None => filename.to_owned(), }; - if let Some(existing) = worktree.read(cx).entry_for_path(&new_path) + + // We skip the collision check for worktree roots as the + // lookup resolves paths relative to the root itself, + // whereas renaming the root should resolve it against its + // parent directory. Otherwise, renaming `bar/` to `foo/` + // would report a false collision with `bar/foo/`. + if !project.entry_is_worktree_root(entry.id, cx) + && let Some(existing) = worktree.read(cx).entry_for_path(&new_path) && existing.id != entry.id { already_exists = true; @@ -2142,12 +2146,19 @@ impl ProjectPanel { }); changes = vec![Change::Created(new_project_path)]; } else { - let new_path = if let Some(parent) = entry.path.parent() { - parent.join(&filename).into() - } else { - filename.clone() + let new_path = match entry.path.parent() { + Some(parent) => parent.join(&filename).into(), + None => filename.clone(), }; - if let Some(existing) = worktree.read(cx).entry_for_path(&new_path) { + + // We skip the collision check for worktree roots as the lookup + // resolves paths relative to the root itself, whereas renaming the + // root should resolve it against its parent directory. Otherwise, + // renaming `bar/` to `foo/` would report a false collision with + // `bar/foo/`. + if !self.project.read(cx).entry_is_worktree_root(entry.id, cx) + && let Some(existing) = worktree.read(cx).entry_for_path(&new_path) + { if existing.id == entry.id && refocus { window.focus(&self.focus_handle, cx); } diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 607e385f988..d75248288bc 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -4620,7 +4620,7 @@ async fn test_rename_root_of_worktree(cx: &mut gpui::TestAppContext) { &["v root1 <== selected", " v dir1", " file1.txt",], ); - // Rename root1 to new_root1 + // Rename root1 to match the name of its child directory. panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx)); assert_eq!( @@ -4635,30 +4635,25 @@ async fn test_rename_root_of_worktree(cx: &mut gpui::TestAppContext) { let confirm = panel.update_in(cx, |panel, window, cx| { panel .filename_editor - .update(cx, |editor, cx| editor.set_text("new_root1", window, cx)); - panel.confirm_edit(true, window, cx).unwrap() + .update(cx, |editor, cx| editor.set_text("dir1", window, cx)); + + panel + .confirm_edit(true, window, cx) + .expect("should be able to rename `root1` to `dir1`") }); confirm.await.unwrap(); cx.run_until_parked(); assert_eq!( visible_entries_as_strings(&panel, 0..20, cx), - &[ - "v new_root1 <== selected", - " v dir1", - " file1.txt", - ], + &["v dir1 <== selected", " v dir1", " file1.txt",], "Should update worktree name" ); // Ensure internal paths have been updated - select_path(&panel, "new_root1/dir1/file1.txt", cx); + select_path(&panel, "dir1/dir1/file1.txt", cx); assert_eq!( visible_entries_as_strings(&panel, 0..20, cx), - &[ - "v new_root1", - " v dir1", - " file1.txt <== selected", - ], + &["v dir1", " v dir1", " file1.txt <== selected",], "Files in renamed worktree are selectable" ); }