fix: clear error when new branch name collides with existing branch namespace - #3528
Conversation
…amespace Creating a branch whose name is a path prefix of (or sits under) an existing branch is a git ref directory/file conflict — 'release' cannot be created while 'release/2026.4' exists, since git stores refs as file paths. This surfaced as git's raw 'cannot lock ref' text. Detect the collision structurally from the cached local-branch inventory (no extra subprocess) and report a clear, actionable error instead. Closes #3527
Bold inside a hint closes with the SGR 22m reset, which clears both bold and the surrounding dim, so the hint text after each branch name lost its dim styling. Per writing-user-outputs, hints use <underline> (SGR 24m resets only underline, preserving dim).
worktrunk-bot
left a comment
There was a problem hiding this comment.
Clean, well-scoped fix — structural detection over error-text parsing matches the "Structured Output Over Error-Message Parsing" guideline, covers both D/F directions, stays on the error path with no extra subprocess, and the snapshot test reproduces #3527. One observation on the failure attribution, inline.
| // refs as file paths, so `release` and `release/2026.4` | ||
| // can't coexist. Surface that as a clear, actionable | ||
| // error instead of git's raw "cannot lock ref" text. | ||
| if *create_branch |
There was a problem hiding this comment.
Minor: this fires on any git worktree add failure once a colliding branch exists, so it can mask an unrelated cause with the namespace message. E.g. wt switch --create release --from badref when release/2026.4 exists — git actually failed on the invalid base ref, but we'd report the namespace collision and swallow invalid reference: badref. The path-occupancy and non-create checks are already guarded earlier in validate_worktree_creation, so the realistic remaining case is a bad --from. Narrow, and the reported message is still a real problem the user would hit next, so not blocking — just flagging the unconditional swap of git's error. Could tighten by only substituting when the collision is the plausible cause, but that risks re-introducing error-text parsing, so the current tradeoff may well be the right call.
There was a problem hiding this comment.
Good flag — I chased down whether the misattribution can actually happen, and for the --base case it can't: --create --base <ref> resolves and validates the base with ref_exists() up front at switch.rs:605, so an invalid base errors with ReferenceNotFound before git worktree add ever runs. Verified end-to-end — wt switch --create release --base badref with release/2026.4 present reports ✗ No branch, tag, or commit named badref, not the namespace collision.
So by the time we reach the failing git worktree add: the path is pre-checked in validate_worktree_creation, the base is either a validated ref or None (→ HEAD, always valid), and --create is set. The only inputs left that can fail there are the D/F collision itself plus rare environmental/race failures — and in the environmental case the swap only fires if a colliding branch also exists, where the namespace conflict is a genuine latent blocker anyway.
I did prototype the tightening you suggested — gate the swap on base_branch.as_deref().is_none_or(|b| repo.ref_exists(b)...) — but since the base is already validated at line 605, its false branch is structurally unreachable, so it'd be speculative dead code (and would trip codecov/patch). Dropped it. The unconditional swap is the right call here; leaving it as-is.
Problem
wt switch --create releasefailed with a raw git error when a branch under therelease/namespace already existed (e.g.release/2026.4):This is a git ref directory/file (D/F) conflict: git stores refs as file paths under
refs/heads/, soreleaseandrelease/2026.4can't coexist — one name can't be both a file and a directory. The branch genuinely can't be created, but the git-jargon error didn't make the cause or the remedy clear. Release-style prefixes (release/*,feature/*) are common, so this is easy to hit.Solution
Detect the collision structurally in the
--createfailure path and surface a clear, actionable error instead of git's raw text. Detection reads the cached local-branch inventory (local_branches()— no extra subprocess, and only on the error path) and covers both directions of the D/F conflict:releasewhenrelease/2026.4exists (new name is a prefix), andrelease/2026.4/foowhenrelease/2026.4exists (an existing branch is a prefix).New output:
Per the repo's "Structured Output Over Error-Message Parsing" guideline, the detection keys on the ref inventory, not on git's locale-dependent "cannot lock ref" prose.
Testing
Added
test_switch_create_branch_namespace_conflictintests/integration_tests/switch.rs, which reproduces #3527 (createsrelease/2026.4, thenwt switch --create release) and snapshots the new error. Verified the fix end-to-end against the issue's minimal reproduction and the reverse-direction case; the normal--createpath is unaffected.cargo clippyand thegit::errorunit tests are clean.Closes #3527 — automated triage