Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/output/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
73 changes: 73 additions & 0 deletions tests/integration_tests/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ============================================================================
Expand Down
Loading