Files
Sameen Karim adbc3bd206 Stop reporting rebases that git refused to run as successes
`gh stack rebase` and `gh stack sync` could print a full success report
while leaving the stack exactly where it was:

    ✓ Trunk main fast-forwarded to 84bacb3
    ✓ Rebased phil/trd-mark-edge-drills onto main
    ✓ Rebased phil/implement-in-place-apis onto phil/trd-mark-edge-drills
    All branches in stack rebased locally with main

…after which `git merge-base --is-ancestor main HEAD` still reported that
main was not an ancestor.

The cause is in `tryAutoResolveRebase`, the shared error path for every
rebase: it returned nil whenever no rebase was in progress. That is only a
valid success signal *after* an auto-`--continue`. On the first check it
means `git rebase` exited non-zero without ever starting — a dirty working
tree, a branch checked out in another worktree, a missing local trunk, or a
stale rebase still in progress. All of those were silently swallowed and the
cascade carried on reporting success.

- Return a typed `*git.RebaseStartError` for a rebase that never started, and
  detect an already-running rebase up front so its leftover conflicts are not
  mistaken for this rebase's.
- Treat that error as fatal in `cascadeRebase` (and in `modify`) rather than a
  conflict, so no bogus rebase state is written and git's own message shows.
- Add the preflight checks `modify` already had — no rebase in progress, clean
  working tree — to `rebase` and `sync`, with `--autostash` to opt out of the
  clean-tree requirement by passing `--autostash` through to git.
- Verify after every cascade that each branch really does sit on top of its
  parent, and fail instead of printing the success summary. `sync` checks
  before pushing so an unrebased stack is never force-pushed.

Also fixes duplicate commits: the cascade passed the parent's current tip as
the `--onto` upstream with no staleness guard, so a parent amended, reordered,
or squash-merged out of band had its old commits replayed onto the child. The
merged-PR path guarded this but fell back to `merge-base(newBase, branch)`,
which replays squashed commits. Both paths now use the latest commit the child
genuinely contains.

Smaller fixes found alongside:

- `git rebase <option> --continue` is a usage error, so `--preserve-dates`
  made `rebase --continue` fail every time. git persists the option in the
  rebase state, so `--continue` alone honors it.
- `sync` never ensured the local trunk existed before rebasing onto it, and
  printed "Fetched latest changes" even when the fetch failed.
- A trunk whose remote branch is gone now says so instead of silently rebasing
  onto a stale local trunk (partial diagnostic for #225; migrating the trunk
  is a separate change).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f4355094-2f25-4532-a17d-2b88fbf68131
2026-07-26 14:30:42 -04:00
..
2026-04-10 03:32:08 -04:00
2026-04-10 03:32:08 -04:00
2026-07-26 11:26:04 -04:00
2026-07-26 11:26:04 -04:00
2026-04-10 03:32:08 -04:00