feat: sweep merged worktree branches that outlived their worktree - #51
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6129f33030
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!branch || !head) continue; | ||
| if (!branch.startsWith(prefix)) continue; | ||
| if (attached.has(branch)) continue; | ||
| if (branch === base) continue; |
There was a problem hiding this comment.
Exclude the configured local base branch
When worktrees.baseBranch itself starts with branchPrefix and origin/<baseBranch> exists, base is the remote-tracking name, so this comparison does not exclude the corresponding local base branch. If that local branch is not checked out and is contained in the remote ref, --apply deletes it; subsequent worktree acquisition then cannot find the configured local base and may fall back to the caller's current HEAD. Compare against config.worktrees.baseBranch as well as the resolved merge-check ref.
Useful? React with 👍 / 👎.
| // try the next candidate | ||
| } | ||
| } | ||
| return baseBranch; |
There was a problem hiding this comment.
Handle an absent configured base explicitly
When neither origin/<baseBranch> nor the local base exists, returning the unresolved name makes every isAncestor call quietly return false, so sweep reports success with no candidates. This conflicts with the existing worktree creation path, which deliberately falls back to the current branch when the configured base is absent; branches created and later merged through that path can therefore never be swept. Reuse the lifecycle fallback or return a runtime error instead of silently treating the repository as clean.
Useful? React with 👍 / 👎.
Releasing a worktree removes the directory and its record but leaves the branch behind, so every completed epic and sprint deposits a ref that nothing ever collects. Add `rk worktree sweep`, which lists or deletes local branches under the configured prefix that are fully merged into the base and no longer back a checked-out worktree. One of --preview or --apply is required; a bare `sweep` has no safe default reading. The base resolves to the remote-tracking copy when one exists, since a stale local main would leave finished branches looking unmerged. Deletion re-runs that ancestry check immediately before removing the ref rather than relying on `git branch -d`, which measures the merge against HEAD: a branch merged into the base is refused whenever HEAD sits on a divergent branch, so preview would list refs that apply then rejected. Ownership is narrow because the delete is forced. The prefix must match on a path boundary, so a short `branchPrefix` cannot adopt `release/*` or `refactor/*`. The base is excluded under both its local and its remote-tracking name, and again inside the delete itself, so a base that sits under the prefix never sweeps itself away. A failure to list worktrees propagates instead of defaulting to an empty set, which would make every checked-out branch look sweepable. Deleted branches print their full commit, the only way back from a mistake. Leaked worktree directories stay with `rk fix`. Two commands able to remove the same directory under different safety rules is worse than one.
6129f33 to
c53ad58
Compare
Releasing a worktree removes the directory but leaves its branch behind, so every completed epic and sprint deposits a ref that nothing ever collects.
Adds
rk worktree sweep --preview | --apply: lists or deletes local branches under the configuredworktrees.branchPrefixthat are fully merged intoworktrees.baseBranchand no longer back a checked-out worktree. Leaked worktree directories stay withrk fix.Deletion deliberately avoids
git branch -d, which measures the merge against HEAD rather than against the base the listing used: a branch merged into main is refused whenever HEAD sits on a divergent branch, so preview would list refs that apply then rejected. It re-runs the listing's own ancestry check immediately before removing the ref instead, keeping the guard against a ref that moved between listing and deletion while measuring it against the ref the decision was made on.Test plan
branch -dbug before the fix).rk close T-001leavesrk/epic/E-001behind; sweep lists it with its SHA and deletes it.