Skip to content

restack: rewrite as one rebase --update-refs per leaf #118

@boneskull

Description

@boneskull

Background

Spun out of the investigation in #112 (see docs/update-refs.md for full context).

The headline benefit of git rebase --update-refs is that one rebase from the leaf of a stack updates every intermediate branch ref in one operation. gh-stack today does the opposite: it walks the tree parent-first and runs one rebase per branch.

For a linear stack of N branches, this means we go from N subprocess invocations + N possible conflict interruption points down to 1 + 1.

Proposal

Refactor cmd/restack.go to walk leaves and do one git rebase --update-refs per chain, instead of the current per-branch loop.

Scope

  • Identify leaves in the tree, not just descendants.
  • For each leaf, find the path from trunk and run one rebase from the leaf's tip.
  • After the rebase, refresh stackForkPoint for every branch in the rebased chain (currently we update fork point per branch immediately after each rebase).
  • Update conflict-state machinery to map .git/rebase-merge/stopped-sha back to a gh-stack branch (today's state file records Current: <branch> because we know exactly which branch is mid-rebase; with leaf-path rebase, we'll need a lookup).
  • Continue to suppress --update-refs when --worktrees mode is active (see investigate git rebase --update-refs #112 finding 4: silent stack corruption when intermediate refs are checked out elsewhere).
  • Update cmd/continue.go resume path to handle the new state shape.
  • E2E coverage for: linear chain, branching tree (multiple leaves), conflict on intermediate commit + continue, conflict on intermediate commit + abort, interaction with --onto fork-point flow after a squash merge upstream.

Trade-offs

Wins

  • Fewer subprocess invocations for linear chains.
  • Fewer conflict interruption points per stack.
  • No more "skipped previously applied commit" advisory noise when users have rebase.updateRefs = true set globally.

Costs

  • Per-branch progress messaging (Restacking <branch>... ok) is replaced by git's end-of-rebase summary. UX shift.
  • Conflict-to-branch mapping requires new lookup logic.
  • Per-leaf fork-point refresh becomes a post-rebase pass instead of an inline update.
  • Changes blast radius across cmd/restack.go, cmd/continue.go, internal/state, e2e tests.

Out of scope for this issue

  • The opportunistic --update-refs opt-in in the existing per-branch loop. That's the recommendation from investigate git rebase --update-refs #112 and ships with that issue's PR.
  • Changes to worktree dispatch beyond the existing --no-update-refs suppression.

Acceptance criteria

  • Restack of a linear stack runs exactly one git rebase invocation.
  • Restack of a branching tree runs one git rebase per leaf.
  • Mid-chain conflict + gh stack continue produces correct final refs.
  • Mid-chain conflict + gh stack abort restores all branches to pre-operation state.
  • --worktrees mode falls back to per-branch behavior automatically.
  • No regression in existing e2e suites.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions