fix: preserve subdirectory position on wt remove#3344
Conversation
When switching, wt preserves the user's subdirectory position (e.g. apps/gateway/ in a monorepo). wt remove landed at the destination worktree root instead, forcing a manual cd back after every removal. Mirror the switch behavior: when the removed worktree is the current one, compute the cwd's position relative to the removed worktree root and cd into the equivalent subdirectory of the destination if it exists, falling back to the root otherwise. Reuses the existing resolve_subdir_in_target helper. Since wt merge lands via the same handler, it gains the same subdirectory preservation. Closes #3343 Co-Authored-By: Claude <noreply@anthropic.com>
worktrunk-bot
left a comment
There was a problem hiding this comment.
Reviewed as a self-authored triage PR (reviewer role). The fix cleanly reuses the existing resolve_subdir_in_target helper rather than reimplementing the subpath mapping, mirrors the wt switch behavior it's modeled on, and is covered by two integration tests (subdir-present and fallback-to-root). An independent correctness pass over the cd-target computation — including the cwd == worktree root, subdir, and bare-repo (main_path == current_path) edges — surfaced nothing: the resolved target is always under main_path (the destination worktree, distinct from the removed one), and the destination check is read-only (is_dir()), so there's no new deletion behavior.
Flagging for a human look per the repo's data-loss review policy (review-pr.md): this edits src/output/handlers.rs, which lives on the wt remove surface. The change runs before the git removal and only computes where the shell cds to afterward, so it doesn't touch the removal path itself — but a change on that file is maintainer's-eyes-only by policy. @max-sixty, a quick sanity check when you have a moment.
|
great! can we consider unifying these functions so, for example, we get the same behavior with |
max-sixty asked (in #3344) whether the subdirectory-preservation logic could be unified so every path-switching command behaves the same. `wt switch` and `wt remove`/`wt merge` already share `resolve_subdir_in_target` (merge lands via the same handler). The one remaining path that hand-rolled its own `dest.join(relative)` was `wt step relocate`. Route relocate's shell `cd` through the same helper, keeping its "only cd when the user is inside the moving worktree" guard. This also gains the helper's symlink canonicalization (relocate previously did a raw non-canonical `strip_prefix`) and the read-only `is_dir()` fallback (a no-op here since `git worktree move` carries the whole subtree, but keeps behavior identical across commands). Adds `test_relocate_preserves_subdir`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Good call. I traced every site that emits a shell
So Unified it in #3346: relocate now routes through the same |
Release v0.65.0. Version bump plus the 0.65.0 changelog section. **User-facing changes in this release:** - **Picker `alt-x` flashes why a worktree wasn't removed** (#3336, #3350) — the keep/failure reason now appears in the picker header instead of only draining to stderr on exit. - **`-vv` diagnostics consolidate on `diagnostic.md`, led by the performance profile** (#3329). This entry's bullet was previously misfiled under the already-released `0.63.0` section (the PR merged after `0.64.0` was tagged); this release moves it into `0.65.0`, where the change actually ships, and drops a misleading "removed `profile.txt`" clause for a file that never existed. - **`wt remove` preserves your subdirectory position** (#3344, closes #3343, thanks @caillou for reporting). **Also included:** a fix to the release skill's reporter-finding grep — `sort -un` on `#`-prefixed lines collapsed the whole issue-reference list to a single entry (every line sorts as numeric 0), which nearly dropped @caillou's credit. Now sorts on the numeric field after `#`. The nightly cross-platform suite (full-tests linux/macos/windows, feature-powerset, release-target, nix-flake, minimal-versions) passed green on the cut-from tip before this PR was opened. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
When switching worktrees,
wtpreserves the user's subdirectory position — if you're inapps/gateway/and switch, you land inapps/gateway/of the target worktree (implemented for #937).wt removedid not: it always landed at the destination worktree root, so a user working deep in a monorepo sub-tree had to manuallycdback after every removal.Reported in #3343.
Root cause
prepare_remove_directory_changeinsrc/output/handlers.rsunconditionallycd'd to the destination worktree root (main_path), ignoring the user's position within the worktree being removed. Switch, by contrast, routes itscdthroughresolve_subdir_in_target, which maps the relative subpath onto the target.Solution
Reuse the existing
resolve_subdir_in_targethelper in the remove path. Becauseprepare_remove_directory_changeruns before the git removal, the current directory still exists inside the worktree being removed, sostd::env::current_dir()yields the actual sub-position. When the equivalent subdirectory exists in the destination it becomes thecdtarget; otherwise it falls back to the root (and to the root if the cwd can't be read). The check on the destination is read-only (is_dir()), so there's no data-safety impact.wt mergelands via the same handler, so it gains the same subdirectory preservation — a consistency win with switch.This mirrors the always-on behavior chosen for switch in #937 (no new flag/config), keeping the two commands consistent.
Testing
Added two integration tests in
tests/integration_tests/directives.rs, alongside the existing switch subdir tests:test_remove_preserves_subdir— removing fromfeature/apps/gateway/whenmain/apps/gateway/exists lands thecddirective inmain/apps/gateway/. Fails before the fix (lands atmain/), passes after.test_remove_falls_back_to_root_when_subdir_missing— when the subdir is absent in the destination, thecddirective lands at the main worktree root.Verified the reproduction test fails on the unfixed code and passes with the fix. The
directives::,remove::, andmerge::integration modules all pass (262 tests).Closes #3343 — automated triage