Skip to content

finish --rebase: support a topic branch checked out in its own worktree #246

Description

@alexrinass

finish refuses the rebase strategy outright when the topic branch has its own separate linked worktree (#245), rather than support it: git flow feature finish x --rebase from a repo where feature/x lives in a linked worktree fails with a clear, actionable error naming --merge/--squash as alternatives, or removing the worktree first. Merge and squash both work correctly from inside such a worktree (#175); only rebase is unsupported.

Why it's not supported yet

A first attempt (tried and reverted in #245) ran the rebase directly in the topic's own worktree, since the branch is already checked out there and refs are shared across worktrees of one repository — avoiding the "already used by worktree" checkout failure that a naive redirect hits. This worked for the conflict-free path, but broke on conflict: reproduced by building and running it directly, not just reasoning about it.

  • mergestate's JSON state lands in the redirected worktree (the parent's own worktree, or main), since that is where handleMergeStep calls SaveMergeState. Git's own rebase state (rebase-merge/REBASE_HEAD) lands in the topic worktree, since that is where the rebase itself ran. One operation, split across two git-dirs.
  • Mid-rebase, the topic worktree is on a detached HEAD (rebase always operates that way internally, reattaching only on success). WorktreeForBranch matches on the porcelain branch field, so it returns nothing for a detached worktree — the lookup that --continue/--abort would need, to locate the redirected state, goes blind exactly when it matters.
  • Concretely: --continue after resolving the conflict fails with "no merge in progress" (the state lookup never finds it, not even the exit-6 refusal a narrower reading of the bug would predict). --abort exits 0 having silently done nothing — the rebase, the detached HEAD, and the stranded merge.json are all left exactly as they were.

What real support needs

At least six places currently assume the operation's mergestate, git's own rebase markers, and the conflicted working tree all live in the same worktree, and would each need to account for a split location:

  1. The --continue/--abort state-lookup fallback in executeFinish (needs to find state whether it's local or redirected, and separately needs to handle the topic worktree's detached-HEAD blindness — not specific to this feature, worth fixing as its own primitive improvement).
  2. mergestate's staleness check (isStateValid), which currently checks IsGitRebaseInProgress() against the state-holding repo's own git-dir.
  3. handleContinue's HasConflicts() call.
  4. RebaseContinue()/RebaseAbort()'s dispatch — needs to run against whichever repo actually holds the rebase.
  5. preflightWorktreeCleanup's in-progress detection, which would otherwise see the operation's own rebase as a foreign blocker.
  6. handleAbort's topic-worktree lookup, which also goes blind mid-rebase for the same detached-HEAD reason as (1).

The likely shape of a real fix: persist the rebase's actual worktree path in MergeState at the point the rebase starts, and thread a second repo handle through handleContinue/handleAbort for the strategy-specific git calls, while the redirected repo continues to own everything else (the merge-into-parent step, cleanup). The detached-HEAD blindness in (1)/(6) needs its own fix — likely keying the lookup off mergestate's persisted branch name rather than the worktree's current (possibly detached) HEAD — and is worth landing as a small standalone fix regardless of this issue, since any future feature that keys off "which worktree holds branch X" during an in-progress operation would hit the same trap.

Relationship to #175 and #230

#175 established worktree cleanup for finish/delete generally; #230 fixed finish being unable to run at all from inside a topic's own worktree, for merge/squash. This issue is the one combination neither closed: rebase strategy specifically, from inside a worktree, surviving a conflict.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions