fix: run the post-merge restack through the shared engine - #142
Merged
Conversation
`ez merge` kept its own restack loop, written before the shared engine landed
in 0.2.29. It was strictly worse in the ways that engine exists to fix:
- A stack entry whose git branch is gone — deleted by hand, cleaned up in
another worktree — aborted the whole command. The shared engine skips those
(`if !git::branch_exists(branch_name) { continue }`). This is what bit an
actual merge: the PR merged, the remote branch was cleaned up and trunk
fast-forwarded, then the run exited 1 on `fatal: no such branch/commit`
for an unrelated stale entry. Merge succeeded, exit code said failure.
- It replayed `stored_parent_head..branch` directly instead of going through
`effective_old_base`, so a stale `parent_head` resurrected foreign commits
or conflicted for no reason.
- A conflict on any one branch aborted the rest of the stack instead of
isolating that branch and continuing.
- It bypassed the worktree guard, so a branch checked out elsewhere was
rebased in the wrong root rather than via `git -C <worktree>`.
Replace the loop with `restack::restack_branches_with_options`. Local trunk is
fast-forwarded to the fetched remote tip first, because the shared engine reads
local refs where the old loop rev-parsed `<remote>/<trunk>` inline. Failures
now surface as exit 3 via `incomplete_error`, consistent with sync and restack,
and only after the merge is recorded and state is saved.
`RestackReport` gains `restacked_branches` — merge force-pushes each branch it
restacked, so it needs the names, not just the count.
This was referenced Aug 11, 2026
Merged
rohoswagger
added a commit
that referenced
this pull request
Aug 11, 2026
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.
The bug
ez mergeexited 1 after a successful merge:The PR was merged, the remote branch cleaned up, trunk fast-forwarded — then the post-merge restack died on an unrelated stack entry whose git branch no longer exists. An agent branching on the exit code would conclude the merge failed and retry it.
Root cause:
fetch_restack_and_push_remainingis a private restack loop inmerge.rsthat predates the shared engine added in 0.2.29, and never got converged. It was worse in four ways:restack_branches_with_options?on rev-parse → aborts the runif !git::branch_exists)parent_headstored_parent_head..branchverbatimeffective_old_basefalls back to merge-basegit -C <worktree>via the worktree guardThe fix
Delete the loop, call the shared engine. Two supporting changes:
update_branch_to_latest_remote, same asrestack::rundoes). The shared engine reads local refs; the old loop rev-parsed<remote>/<trunk>inline.RestackReportgainsrestacked_branches: Vec<String>. Merge force-pushes each branch it restacked, so a count isn't enough. Both increment sites now go throughrecord_restacked.Restack failures surface as exit 3 via
incomplete_error— consistent withez syncandez restack— and only after the merge is recorded and state is saved, so a partial failure can never again make a completed merge look like it failed.Test
fetch_restack_survives_a_stack_entry_whose_branch_is_gone_from_gitreproduces the exact scenario: a stack with one live branch and one ghost entry, trunk advanced so there is real work behind the ghost. Asserts the live branch still restacks and is pushed, and that the call returnsOk.Full suite green (485 unit + all integration suites), clippy clean.