@@ -247,78 +247,9 @@ async fn remove_root_after_worktree_removal(
// alive until the repo removes the worktree
drop(project);
result.context("git worktree metadata cleanup failed")?;
-
- // Empty-parent cleanup uses local std::fs — skip for remote projects.
- if root.remote_connection.is_none() {
- remove_empty_parent_dirs_up_to_worktrees_base(
- root.root_path.clone(),
- root.main_repo_path.clone(),
- cx,
- )
- .await;
- }
-
Ok(())
}
-/// After `git worktree remove` deletes the worktree directory, clean up any
-/// empty parent directories between it and the Zed-managed worktrees base
-/// directory (configured via `git.worktree_directory`). The base directory
-/// itself is never removed.
-///
-/// If the base directory is not an ancestor of `root_path`, no parent
-/// directories are removed.
-async fn remove_empty_parent_dirs_up_to_worktrees_base(
- root_path: PathBuf,
- main_repo_path: PathBuf,
- cx: &mut AsyncApp,
-) {
- let worktrees_base = cx.update(|cx| worktrees_base_for_repo(&main_repo_path, cx));
-
- if let Some(worktrees_base) = worktrees_base {
- cx.background_executor()
- .spawn(async move {
- remove_empty_ancestors(&root_path, &worktrees_base);
- })
- .await;
- }
-}
-
-/// Removes empty directories between `child_path` and `base_path`.
-///
-/// Walks upward from `child_path`, removing each empty parent directory,
-/// stopping before `base_path` itself is removed. If `base_path` is not
-/// an ancestor of `child_path`, nothing is removed. If any directory is
-/// non-empty (i.e. `std::fs::remove_dir` fails), the walk stops.
-fn remove_empty_ancestors(child_path: &Path, base_path: &Path) {
- let mut current = child_path;
- while let Some(parent) = current.parent() {
- if parent == base_path {
- break;
- }
- if !parent.starts_with(base_path) {
- break;
- }
- match std::fs::remove_dir(parent) {
- Ok(()) => {
- log::info!("Removed empty parent directory: {}", parent.display());
- }
- Err(err) if err.kind() == std::io::ErrorKind::DirectoryNotEmpty => break,
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
- // Already removed by a concurrent process; keep walking upward.
- }
- Err(err) => {
- log::error!(
- "Failed to remove parent directory {}: {err}",
- parent.display()
- );
- break;
- }
- }
- current = parent;
- }
-}
-
/// Finds a live `Repository` entity for the given path, or creates a temporary
/// project to obtain one.
///
@@ -913,7 +844,6 @@ mod tests {
use project::Project;
use serde_json::json;
use settings::SettingsStore;
- use tempfile::TempDir;
use workspace::MultiWorkspace;
fn init_test(cx: &mut TestAppContext) {
@@ -926,117 +856,6 @@ mod tests {
});
}
- #[test]
- fn test_remove_empty_ancestors_single_empty_parent() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- let branch_dir = base.join("my-branch");
- let child = branch_dir.join("zed");
-
- std::fs::create_dir_all(&child).unwrap();
- // Simulate git worktree remove having deleted the child.
- std::fs::remove_dir(&child).unwrap();
-
- assert!(branch_dir.exists());
- remove_empty_ancestors(&child, &base);
- assert!(!branch_dir.exists(), "empty parent should be removed");
- assert!(base.exists(), "base directory should be preserved");
- }
-
- #[test]
- fn test_remove_empty_ancestors_nested_empty_parents() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- // Branch name with slash creates nested dirs: fix/thing/zed
- let child = base.join("fix").join("thing").join("zed");
-
- std::fs::create_dir_all(&child).unwrap();
- std::fs::remove_dir(&child).unwrap();
-
- assert!(base.join("fix").join("thing").exists());
- remove_empty_ancestors(&child, &base);
- assert!(!base.join("fix").join("thing").exists());
- assert!(
- !base.join("fix").exists(),
- "all empty ancestors should be removed"
- );
- assert!(base.exists(), "base directory should be preserved");
- }
-
- #[test]
- fn test_remove_empty_ancestors_stops_at_non_empty_parent() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- let branch_dir = base.join("my-branch");
- let child = branch_dir.join("zed");
- let sibling = branch_dir.join("other-file.txt");
-
- std::fs::create_dir_all(&child).unwrap();
- std::fs::write(&sibling, "content").unwrap();
- std::fs::remove_dir(&child).unwrap();
-
- remove_empty_ancestors(&child, &base);
- assert!(branch_dir.exists(), "non-empty parent should be preserved");
- assert!(sibling.exists());
- }
-
- #[test]
- fn test_remove_empty_ancestors_not_an_ancestor() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- let unrelated = tmp.path().join("other-place").join("branch").join("zed");
-
- std::fs::create_dir_all(&base).unwrap();
- std::fs::create_dir_all(&unrelated).unwrap();
- std::fs::remove_dir(&unrelated).unwrap();
-
- let parent = unrelated.parent().unwrap();
- assert!(parent.exists());
- remove_empty_ancestors(&unrelated, &base);
- assert!(parent.exists(), "should not remove dirs outside base");
- }
-
- #[test]
- fn test_remove_empty_ancestors_child_is_direct_child_of_base() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- let child = base.join("zed");
-
- std::fs::create_dir_all(&child).unwrap();
- std::fs::remove_dir(&child).unwrap();
-
- remove_empty_ancestors(&child, &base);
- assert!(base.exists(), "base directory should be preserved");
- }
-
- #[test]
- fn test_remove_empty_ancestors_partially_non_empty_chain() {
- let tmp = TempDir::new().unwrap();
- let base = tmp.path().join("worktrees");
- // Structure: base/a/b/c/zed where a/ has another child besides b/
- let child = base.join("a").join("b").join("c").join("zed");
- let other_in_a = base.join("a").join("other-branch");
-
- std::fs::create_dir_all(&child).unwrap();
- std::fs::create_dir_all(&other_in_a).unwrap();
- std::fs::remove_dir(&child).unwrap();
-
- remove_empty_ancestors(&child, &base);
- assert!(
- !base.join("a").join("b").join("c").exists(),
- "c/ should be removed (empty)"
- );
- assert!(
- !base.join("a").join("b").exists(),
- "b/ should be removed (empty)"
- );
- assert!(
- base.join("a").exists(),
- "a/ should be preserved (has other-branch sibling)"
- );
- assert!(other_in_a.exists());
- }
-
#[gpui::test]
async fn test_build_root_plan_returns_none_for_main_worktree(cx: &mut TestAppContext) {
init_test(cx);