diff --git a/src/output/handlers.rs b/src/output/handlers.rs index 0dc64c0a8..5222858eb 100644 --- a/src/output/handlers.rs +++ b/src/output/handlers.rs @@ -1606,10 +1606,20 @@ fn refresh_removal_safety_after_pre_remove( fn prepare_remove_directory_change( main_path: &Path, + worktree_path: &Path, changed_directory: bool, ) -> anyhow::Result<()> { if changed_directory { - super::change_directory(main_path)?; + // Preserve the user's subdirectory position, mirroring `wt switch` + // (#3343). The removal hasn't run yet, so the current directory still + // exists inside the worktree being removed — if the user is in + // `worktree/apps/gateway/` and `main/apps/gateway/` exists, cd there + // instead of the main worktree root. Falls back to the root when the + // subdir is absent in the destination or the cwd can't be read. + let cd_target = std::env::current_dir() + .map(|cwd| resolve_subdir_in_target(main_path, Some(worktree_path), &cwd)) + .unwrap_or_else(|_| main_path.to_path_buf()); + super::change_directory(&cd_target)?; stderr().flush()?; // Force flush to ensure shell processes the cd // Mark that the CWD worktree is being removed, so the error handler // can show a hint if a subsequent command (e.g., post-merge hook) fails. @@ -1831,7 +1841,7 @@ fn handle_removed_worktree_output( return remove_removed_worktree_silently(&repo, &ctx, &safety, announcer); } - prepare_remove_directory_change(ctx.main_path, ctx.changed_directory)?; + prepare_remove_directory_change(ctx.main_path, ctx.worktree_path, ctx.changed_directory)?; // Handle detached HEAD case (no branch known) let Some(branch_name) = ctx.branch_name else { diff --git a/tests/integration_tests/directives.rs b/tests/integration_tests/directives.rs index aba6505c2..fd7f9a428 100644 --- a/tests/integration_tests/directives.rs +++ b/tests/integration_tests/directives.rs @@ -384,6 +384,79 @@ fn test_switch_create_preserves_subdir(#[from(repo_with_remote)] repo: TestRepo) ); } +#[rstest] +fn test_remove_preserves_subdir(#[from(repo_with_remote)] mut repo: TestRepo) { + let feature_wt = repo.add_worktree("feature"); + let (cd_path, exec_path, _guard) = directive_files(); + + // Create the same subdirectory in both the feature worktree (where the + // user is) and the main worktree (where removal lands). + let subdir = "apps/gateway"; + fs::create_dir_all(repo.root_path().join(subdir)).unwrap(); + fs::create_dir_all(feature_wt.join(subdir)).unwrap(); + + let mut cmd = wt_command(); + repo.configure_wt_cmd(&mut cmd); + configure_directive_files(&mut cmd, &cd_path, &exec_path); + cmd.arg("remove").current_dir(feature_wt.join(subdir)); + + let output = cmd.output().unwrap(); + assert!(output.status.success(), "wt remove failed: {:?}", output); + + // Verify cd file lands in the equivalent subdirectory of the main + // worktree, not at its root — mirroring `wt switch` (issue #3343). + let cd_content = fs::read_to_string(&cd_path).unwrap_or_default(); + let expected_subdir = repo.root_path().join(Path::new("apps").join("gateway")); + let expected_str = expected_subdir.to_string_lossy(); + assert!( + cd_content.contains(&*expected_str), + "CD file should contain subdirectory path {}, got: {}", + expected_str, + cd_content + ); +} + +#[rstest] +fn test_remove_falls_back_to_root_when_subdir_missing( + #[from(repo_with_remote)] mut repo: TestRepo, +) { + let feature_wt = repo.add_worktree("feature"); + let (cd_path, exec_path, _guard) = directive_files(); + + // Create the subdirectory only in the feature worktree (where the user + // is), not in the main worktree where removal lands. + let subdir = "apps/gateway"; + fs::create_dir_all(feature_wt.join(subdir)).unwrap(); + // Intentionally NOT creating the subdir in the main worktree. + + let mut cmd = wt_command(); + repo.configure_wt_cmd(&mut cmd); + configure_directive_files(&mut cmd, &cd_path, &exec_path); + cmd.arg("remove").current_dir(feature_wt.join(subdir)); + + let output = cmd.output().unwrap(); + assert!(output.status.success(), "wt remove failed: {:?}", output); + + // Verify cd file lands at the main worktree root (the subdir is absent + // there, so preservation falls back). + let cd_content = fs::read_to_string(&cd_path).unwrap_or_default(); + let root_str = repo.root_path().to_string_lossy(); + assert!( + cd_content.contains(&*root_str), + "CD file should contain main worktree root {}, got: {}", + root_str, + cd_content + ); + let subdir_path = repo.root_path().join(Path::new("apps").join("gateway")); + let subdir_str = subdir_path.to_string_lossy(); + assert!( + !cd_content.contains(&*subdir_str), + "CD file should NOT contain missing subdirectory path {}, got: {}", + subdir_str, + cd_content + ); +} + // ============================================================================ // --no-cd Tests // ============================================================================