* Rebase stacks onto the latest remote trunk
Fetch the configured trunk explicitly before sync or rebase and use that
fetched ref whenever the local trunk cannot be safely updated, while
preserving local-only and locally-ahead trunks. Fail instead of reporting
success when the fetch or rebase never starts, carry the resolved trunk
through conflict recovery, and verify the resulting ancestry before sync
pushes or either command reports success.
* Restore stacks after incomplete cascade rebases
Roll back branches already rewritten when a later rebase cannot start or
final ancestry verification fails, preventing retries from replaying stale
history. Preserve retryable modify state without repeating completed work,
and add regression coverage for remote-qualified trunk normalization.
* Don't treat queued PRs as merged when rebasing the stack
gh stack rebase and gh stack sync share cascadeRebase, which skipped
branches via IsSkipped() (merged or queued) and then switched to a
`git rebase --onto` that drops the skipped branch's commits from every
downstream branch. That is right for a merged PR — its commits are
already in trunk — but wrong for a queued PR: its commits only exist on
its own branch, which is frozen in the merge queue, so the branches
above it were rebased onto trunk and lost work they depend on.
Handle the two cases separately. A merged branch still activates --onto
so its commits are dropped. A queued branch is still skipped (its branch
is frozen and is not rebased or pushed), but onto mode is reset so
downstream branches rebase normally onto the queued branch, keeping its
commits underneath. The --onto target search, the runRebase --onto seed,
and the continueRebase display base now key on IsMerged() instead of
IsSkipped(), so a queued predecessor no longer forces downstream
branches onto trunk. gh stack sync is fixed through the same shared
helper.
Add rebase coverage for a queued branch mid-stack, a merged branch below
a queued branch, and --upstack above a queued branch, plus a sync test
that also asserts the queued branch is excluded from the push. The
transient queued state is injected through the GitHub mock's merge-queue
entry.
* Refresh queued PR state when continuing a stack rebase
continueRebase reloads the stack from disk, where the Queued flag is
transient (json:"-") and therefore lost, and it only called syncStackPRs
after the cascade. So if the initial rebase conflicted on a branch below
a queued branch, `gh stack rebase --continue` resumed with that branch
seen as active: it rebased the frozen merge-queue branch and rebuilt the
downstream branches on a local history that differs from the queued
branch.
Call syncStackPRs right after resolving the stack — before selecting the
base and cascading the remaining branches — mirroring the refresh
runRebase already does before its cascade. The queued flag is
repopulated, so queued branches stay skipped and downstream branches
stay stacked on them.
Add TestRebase_Continue_QueuedBranchBelowConflict, which conflicts below
a queued branch and asserts the frozen branch is not rebased and the
branch above stays stacked on it. Verified to fail without the refresh.
* Ensure trunk branch exists locally before commands that need it
When a user starts a stack after renaming their initial branch
(e.g. `git branch -m newbranch`), the trunk branch (e.g. main) may
not exist as a local branch. Commands that pass the trunk name to git
operations like merge-base, rebase, or rev-parse then fail with:
fatal: Not a valid object name main
Add an `ensureLocalTrunk` helper that checks whether the trunk branch
exists locally and, if not, fetches it from the remote and creates a
local tracking branch. This mirrors the pattern already used in the
checkout command for importing stacks.
Commands updated:
- modify: call ensureLocalTrunk before the linearity check in
CheckStackLinearity, which uses IsAncestor(trunk, branch). This was
the originally reported failure.
- rebase: call ensureLocalTrunk after fetch and before fastForwardTrunk
and the cascade rebase. git rebase requires a locally resolvable ref;
the remote tracking ref alone is not sufficient.
- trunk: call ensureLocalTrunk before CheckoutBranch so that
`gh stack trunk` works even when trunk was never created locally.
- checkout: refactor the existing inline BranchExists + CreateBranch
block to use the shared helper.
Also fix an incorrect comment in fastForwardTrunk that claimed "the
remote tracking ref is sufficient for rebasing" — verified empirically
that `git rebase main` fails when main has no local branch, even after
fetching origin/main.
Commands that were already safe and required no changes:
- sync: fetches trunk explicitly and fastForwardTrunk guards with
BranchExists
- push, switch, navigate, unstack: do not reference trunk
- add, submit: do not require trunk as a local git ref
- view: handles IsAncestor errors gracefully (false positive is
acceptable since rebase will fix it)
* add check to avoid unnecessary remote selection prompt
* Add --committer-date-is-author-date flag to gh stack rebase
Introduce an opt-in `--committer-date-is-author-date` flag (with
`--preserve-dates` alias) for `gh stack rebase`. The flag is passed
through to every underlying `git rebase` invocation in the cascade,
keeping committer dates equal to author dates so that identical content
rebased onto an identical parent produces stable SHAs. This reduces
spurious force-push notifications and noisy review timelines, especially
in deep stacks where bottom branches get re-rebased on every merge.
Git layer changes:
- Add `RebaseOpts` struct with `CommitterDateIsAuthorDate` field to
`internal/git/gitops.go`
- Update `Ops` interface, `defaultOps`, public wrappers, and
`rebaseContinueOnce`/`tryAutoResolveRebase` helpers to accept and
forward the flag
- Update `MockOps` to match the new signatures
Command layer changes:
- Register `--committer-date-is-author-date` and `--preserve-dates`
flags on the cobra command in `cmd/rebase.go`
- Add `CommitterDateIsAuthorDate` to `cascadeRebaseOpts` and thread it
to all `git.Rebase`/`git.RebaseOnto` calls in `cmd/utils.go`
- Persist the flag in `rebaseState` JSON so `--continue` resumes with
the same behavior; pass it to `RebaseContinue` and subsequent cascade
calls
- Update `internal/modify/apply.go` callers to pass zero-value
`RebaseOpts{}`
Tests:
- Update all existing mock signatures in rebase, sync, and modify tests
- Add tests for flag passthrough, `--preserve-dates` alias, state
round-trip, `--continue` flag restoration, and conflict state
persistence
Docs:
- Update flag tables and examples in README.md and
docs/src/content/docs/reference/cli.md
* clearer wording in docs and help text
* fix: sync performs cascade rebase even when trunk is already up-to-date
Previously, `gh stack sync` gated the cascade rebase on whether trunk
or stack branches were fast-forwarded during the current run. This meant
that if the user had already updated trunk locally (e.g., `git pull`),
sync would skip the rebase entirely even though stack branches hadn't
been rebased onto the current trunk.
This change:
- Adds `stackNeedsRebase()` to detect stale branches regardless of
whether trunk was updated in this run
- Extracts shared helpers (`fastForwardTrunk`, `cascadeRebase`,
`resolveOriginalRefs`) from duplicated code in sync.go and rebase.go
into utils.go, reducing ~450 lines of duplication
- Fixes rebase.go to skip queued branches (was only skipping merged),
consistent with sync's behavior via `IsSkipped()`
- Refactors rebase --continue to reuse the shared cascade helper
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address review feedback: error handling and index validation
- resolveOriginalRefs now returns (map, error) instead of silently
swallowing RevParseMap failures; sync warns and skips rebase, rebase
aborts with a clear error
- cascadeRebase uses a new Err field on the result struct to distinguish
fatal errors (e.g. checkout failure) from recoverable conflicts;
callers no longer enter conflict-recovery flow for non-conflict errors
- continueRebase validates that remaining branch indices are contiguous
in stack order, erroring out if the stack was reordered between
conflict and --continue
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Deduplicate API calls: syncStackPRs now returns PRDetails for
LoadBranchNodes to reuse, eliminating redundant FindPRDetailsForBranch calls
- Parallelize API calls in syncStackPRs (capped at 6 concurrent requests)
- Parallelize git operations in LoadBranchNodes (capped at 4 concurrent)
- Show "Loading stack..." indicator for interactive sessions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* git primitives for modify cmd
* extract reusable TUI parts
* modify cmd
* recreate stack after modify
* add checks to prevent other modifications while modify is applying
* modify continue for resuming after resolving conflicts
* fix bug with duplicate stack entries after modifying
* reuse conflict resolution help msg from rebase
* additional confirmation before overwriting stack on remote
* fix recreate order of operations
Co-authored-by: Copilot <copilot@github.com>
* move base commit instead of cherry picking for fold up
* check to ensure we aren't left with zero branches
* unify and dedupe across view and modify tui
* more detailed help instructions
Co-authored-by: Copilot <copilot@github.com>
* only recommend submit if stack exists on remote
Co-authored-by: Copilot <copilot@github.com>
* tests for modify tui, apply modifications, submit modifications
* refactor submit for regular and pending modifications
* rename recover to abort
Co-authored-by: Copilot <copilot@github.com>
* docs for modify cmd
* tui styling updates
* updated tui screenshot
* addressing review comments
* Fix 4 bugs from code review
Bug 1: Move RevParseMap error check before using originalRefs.
The error from git.RevParseMap() was deferred past iteration of
originalRefs, which could panic on a nil map.
Bug 2: Differentiate cherry-pick vs rebase conflicts in modify.
Cherry-pick conflicts don't save state as 'conflict' phase, so
--continue won't work. Now prints --abort-only instructions for
cherry-pick conflicts.
Bug 3: Unwind now cleans up branches created by renames.
After restoring snapshot branches, Unwind deletes renamed branch
names that don't belong to the original snapshot.
Bug 4: Simplify push message in submit command.
Changed from 'Pushing N branches to remote...' to 'Pushing to
remote...' since individual branches may fail.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix 7 nit issues from code review
11: Add named constants for phase strings (PhaseApplying, PhaseConflict,
PhasePendingSubmit) in state.go; replace remaining raw literals in
state.go CheckStateGuard.
14: Fix bottomLines comment mismatch — listed 3 items but value is 2.
15: Extract magic number 88 to MinWidthForArt constant in header.go.
16: Remove unused stackview import anchor in model.go — the import
is used via types.go where BranchNode is embedded.
17: Simplify CheckStackLinearity parent resolution — ActiveBaseBranch
already handles skipping merged branches.
18: Fix rename undo matching any rename — add NewName check so only
the specific rename being undone is matched.
20: Add TestUndoRename and TestUndoRename_DoesNotAffectOtherRenames
to validate rename undo behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make cherry-pick conflicts recoverable via --continue
Previously, cherry-pick conflicts during fold-down operations could only
be resolved with --abort. Now they save full conflict state (phase,
conflict type, fold branch/target, remaining branches) to the state file,
enabling recovery via 'gh stack modify --continue'.
Changes:
- Add ConflictType field to StateFile (rebase or cherry_pick)
- Add FoldBranch/FoldTarget fields for cherry-pick context
- Add CherryPickContinue to git package (cherry-pick --continue)
- Save cherry-pick conflict state in ApplyPlan with remaining branches
- ContinueApply handles both rebase and cherry-pick conflicts
- Unified conflict messaging in cmd/modify.go (both types show --continue)
- Updated test to verify cherry-pick conflict state is saved correctly
* Apply suggestions from code review
Co-authored-by: Luke Ghenco <lukeghenco@github.com>
Co-authored-by: Sameen Karim <skarim@github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Luke Ghenco <lukeghenco@github.com>
* backfill ontoOldBase for deleted merged branches
* ensure upstack rebase checks immediate predecessor
* fix for stale ontoOldBase causing rebase conflicts