Repository navigation
fix: open a worktree on a branch that already exists - #335
Merged
Merged
Conversation
Every worktree creation went to git as `worktree add -b`, which creates the branch — so a branch that was already there failed with "A branch or worktree with that name already exists", and the only way through was to delete it first. A branch prepared before the session cannot afford that: cut from a freshly fetched remote, pushed, and linked to a GitHub issue. An existing branch is now checked out as it stands, on the New worktree dialog, `lich open --worktree` and the `open_session` tool alike — all three route through project.CreateWorktree, and the GUI reaches it without passing spawn's resolveCheckout, so the guard belongs here. Naming a branch is naming the work on it, which is already how spawn.resolveCheckout reads a branch that holds a worktree. The base drops out with it: it says where a branch starts, and that one already started. That opens a path CreateWorktree could not reach before — a branch held by another checkout — and git reworded that refusal in 2.36, from "is already checked out at" to "is already used by worktree at". Only the old wording was mapped, so modern git fell through to "git could not complete the operation". Both are matched now; both are live, depending on the distribution's git. TestCreateWorktreeErrors covered the old refusal as contract. The contract changed, so that case moved to TestCreateWorktreeExistingBranch and the table now pins what git still refuses. Fixes #334
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #334.
The bug
CreateWorktreebuilt every call asgit worktree add -b <name>, and-bmeans create the branch. git refuses a name it already knows, so opening a worktree on a branch that existed failed with "A branch or worktree with that name already exists" — even with the branch free, checked out nowhere.The reporter's case is the one that has no way out: the branch was cut from a freshly fetched
origin/main, pushed, and linked to a GitHub issue. Deleting it to get past lich was not an option.There were three states and only two had an answer:
-bspawn.resolveCheckout)The fix
The guard is in
project.CreateWorktree, not inspawn.resolveCheckout, because the New worktree dialog callsproject.CreateWorktreeover RPC directly and never passes through spawn. All three surfaces — the dialog,lich open --worktree, and theopen_sessionMCP tool — route through this one function.Naming a branch is naming the work on it. That is already how
spawn.resolveCheckoutreads a branch holding a worktree; this extends the same reading to a branch that merely exists. The base goes unused in that case: a base says where a branch starts, and that one already started.A second hole the fix exposed
Reusing an existing branch makes a git failure reachable that
CreateWorktreecould never hit before: the branch is held by another checkout. git reworded that refusal in 2.36 —fatal: 'x' is already checked out at '...'fatal: 'x' is already used by worktree at '...'giterror.goonly knew the old one, so on any current git this fell through to the useless "git could not complete the operation." Verified against git 2.55 locally. Both wordings are matched now, andgiterror_test.gopins each.Contract change, named
TestCreateWorktreeErrorscarried{"duplicate branch", "taken", "already exists"}— a test that documented the bug as contract. Per hard invariant #2 this is the "the contract changed" case, not a test bent to green: the expectation moved toTestCreateWorktreeExistingBranch, and the error table now pins what git still refuses (a branch held elsewhere).Test plan
TestCreateWorktreeExistingBranchreplicates the report verbatim — prepared branch plus--base origin/mainwithbaseIsRemote=true. It plants a commit reachable only from the prepared branch and rewindsmain, so one HEAD assertion proves both that the branch was reused and that the base was ignored. Fails onmainwith the reported error.gofmt -l .clean,go vet ./...cleango test ./...— 1738 passed, 32 packages-count=50on the new test, no flakeAlso updated
docs/cli.md, the--worktree/--baseflag help, and theopen_sessionparameter descriptions — all four promised a worktree "created off--base", which is no longer the whole truth.CHANGELOG.md[Unreleased]› Fixed.No
docs/ceilings.mdentry: there is no provider gap, all five reach the samespawn.Open.🤖 Generated with Claude Code