* 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