Git-native CLI for planning and applying cascade rebases across dependent local branch stacks.
When the git-cascade binary is installed on PATH, Git exposes it as:
git cascade <command>Pick the command based on what changed in your branch stack:
git cascade sync
git cascade restack [branch]
git cascade landed <old-tip> [--onto <ref>]Use git cascade replay --old-base <ref> --old-tip <ref> --new-tip <ref> when none of those workflows fit and you can name the exact before/after refs. The lower-level git cascade plan ... commands are for power users who need to inspect, save, or apply plans manually.
git-cascade is built for local branch stacks where one branch is the base for another:
main -- A
\
pr-1 B -- C
\
pr-2 D -- E
\
pr-3 F
The common situations are:
- Your target branch advanced, and local branch stacks should catch up.
- A parent branch gained commits, and child branches should follow it.
- A parent branch landed, and child branches should now start from the landed result.
Use sync after updating your target branch when your local branch stacks should catch up to it.
For example, main advanced from A to G while pr-1 and pr-2 still point at the old stack:
main -- A -- G
\
pr-1 B -- C
\
pr-2 D -- E
Run:
git cascade syncResult:
main -- A -- G
\
pr-1 B' -- C'
\
pr-2 D' -- E'
By default, sync uses the repository default branch, preferring origin/HEAD, then local main, then local master.
After sync, each affected local stack starts from the current tip of the selected target branch.
If you know the oldest local branch that should be included, pass it explicitly to avoid considering older unrelated branches:
git cascade sync --oldest-branch pr-1If this repository targets a non-default integration branch, pass it explicitly:
git cascade sync --base developPreview the operation first:
git cascade sync --dry-runUse restack when a parent branch gained commits and child branches should follow that parent branch.
For example, pr-1 gained commit G:
main -- A
\
pr-1 B -- C -- G
\
pr-2 D -- E
\
pr-3 F
Move pr-2 so it starts from the new tip of pr-1, and move pr-3 so it starts from the new tip of pr-2:
git cascade restack pr-1Result:
main -- A
\
pr-1 B -- C -- G
\
pr-2 D' -- E'
\
pr-3 F'
If you are currently on the updated branch, the branch argument can be omitted:
git switch pr-1
git cascade restackIf the stack is based on a non-default integration branch, pass that base explicitly:
git cascade restack pr-1 --base developPreview the operation first:
git cascade restack pr-1 --dry-runUse landed when a parent branch was merged or squashed into a target branch, and child branches should now start from the landed result instead of the old branch.
For example, pr-1 was squash-merged into main as commit S:
main -- A -- S
\
pr-1 B -- C
\
pr-2 D -- E
\
pr-3 F
Move pr-2 so it starts from the landed result on main, and move pr-3 so it starts from the new tip of pr-2:
git cascade landed pr-1 --onto mainResult:
main -- A -- S
\
pr-2 D' -- E'
\
pr-3 F'
When --onto is omitted, git-cascade uses the default branch, preferring origin/HEAD, then local main, then local master.
For a true merge commit, landed uses the merge commit that introduced the old tip. That keeps child branches attached to the landing point instead of accidentally including unrelated later target-branch commits:
main -- A -------- M
\ /
pr-1 B -- C
\
pr-2 D -- E
Fast-forward landings do not leave enough graph information to tell where the target branch was before the landing. Pass that previous target-branch tip explicitly:
git cascade landed pr-1 --onto main --old-base <previous-main-tip>Preview the operation first:
git cascade landed pr-1 --onto main --dry-runUse replay when none of the named workflows fit and you can identify the exact old range and replacement tip:
git cascade replay --old-base main --old-tip pr-1 --new-tip rewritten-pr-1replay moves the same kind of dependent branch stack as sync, restack, and landed, but it does not infer the situation for you. You provide the before/after refs directly.
The targeted workflow commands and replay default to move-to-current-tips, so each child branch moves to its parent's rewritten apply-time tip. All of them expose --strategy for explicit-control cases.
Preview the generic replay first:
git cascade replay --old-base main --old-tip pr-1 --new-tip rewritten-pr-1 --dry-rungit-cascade works by creating a repository-local plan and then applying it.
A plan records:
- the old root range, expressed as
old-base..old-tip - the local branches that depend on commits in that range
- the parent/child relationships between dependent branches
- each branch's planned commits and fork point
The targeted workflow commands and explicit replay generate plans for you:
synccreates a generated plan undergenerated/sync/...restackcreates a generated plan undergenerated/restack/...landedcreates a generated plan undergenerated/landed/...replaycreates a generated plan undergenerated/replay/...
Generated plans are deleted after a successful apply. If replay stops on a conflict, the generated plan is kept and the active state file points to it, so git cascade continue can recover.
By default, generated one-shot commands replay each child branch onto its parent's rewritten apply-time tip. This is the move-to-current-tips strategy. Pass --strategy ... when you need a different strategy.
Dependent branches are replayed along their first-parent chain, producing linear results:
- A merge of target-branch history (typically a previous
git merge mainto catch up) is flattened away: the merged-in commits are already part of the new base, and the branch becomes linear. - A merge of history that the new tip does not contain (an unrelated local branch, or stacked work) cannot be flattened. Such branches are skipped during plan generation with a warning, or rejected at apply time; rebase them to linearize first.
Two caveats about flattening:
- Conflicts that were resolved in a flattened merge re-fire during replay and are resolved through the normal conflict flow. Edits that were made only in the merge commit itself, to content neither side had touched, are not replayed and disappear with the merge.
- Branches that already start at their replay base are left untouched, including their merges. Linearization happens when a branch is actually replayed, not on no-op runs.
Fork points that are ambiguous because of criss-cross merges make a branch unplannable; it is skipped with a warning.
If replay conflicts, permanent branch refs remain unchanged and the active operation state is preserved.
Inspect the active operation:
git cascade statusResolve conflicts in the reported worktree:
git -C <worktree> status
git -C <worktree> add <resolved-files>Continue after resolving:
git cascade continuecontinue also resumes an operation that was interrupted mid-replay, for example by a crash or Ctrl-C; already-finished branches are not replayed again.
Abort and clean the active operation:
git cascade abortAbort cleans temporary state and leaves the stored plan intact so it can be retried.
Pass --pause-at-checkpoints when you want to run tests or make manual fixes at every point later replay depends on:
git cascade restack pr-1 --pause-at-checkpointsReplay pauses after each rewritten branch. It also pauses at any rewritten commit that a child branch will use as its replay base, such as an intermediate preserved fork point or a planned parent tip before extra parent commits are replayed.
When replay pauses, permanent branch refs are still unchanged. Run checks in the reported worktree. If a branch needs fixes, commit them in that worktree, then continue:
git -C <worktree> status
git -C <worktree> add <files>
git -C <worktree> commit -m "fix after replay"
git cascade continuecontinue records the paused worktree's current HEAD as the paused branch tip or child-base mapping, then replays dependent work using the selected strategy. It refuses to continue if the paused worktree has uncommitted changes or if HEAD no longer descends from the paused rewritten commit.
Use git cascade plan ... when you need to snapshot topology before a destructive rewrite, inspect a plan before applying it, or script the lower-level workflow directly.
Create a named repository-local plan:
git cascade plan create stack --old-base main --old-tip pr-1For a single-commit root rewrite, use that commit's parent as the explicit old base:
git cascade plan create stack --old-base '<old-commit>^' --old-tip <old-commit>Inspect stored plans:
git cascade plan list
git cascade plan show stackDelete a stored plan:
git cascade plan remove stackApply a stored plan:
git cascade plan apply stack --new-tip pr-1Preview a stored plan apply without mutating refs, worktrees, plans, or state:
git cascade plan apply stack --new-tip pr-1 --dry-runUse move-to-current-tips to replay every child onto the parent's rewritten apply-time tip:
git cascade plan apply stack --new-tip pr-1 --strategy move-to-current-tipsUse move-to-planned-tips when children should move to each parent's rewritten planned tip, ignoring commits added to the parent after planning.
The low-level plan apply default strategy is preserve-fork-points:
git cascade plan apply stack --new-tip pr-1 --strategy preserve-fork-pointsReplay in the current worktree instead of a temporary worktree:
git cascade plan apply stack --new-tip pr-1 --in-place--in-place requires a clean worktree. If replay conflicts, the conflict is left in the current worktree and git cascade abort restores the checkout that was active before apply started.
Pause after each replayed branch when applying a stored plan:
git cascade plan apply stack --new-tip pr-1 --pause-at-checkpointsReplace an existing plan:
git cascade plan create stack --old-base main --old-tip pr-1 --replaceGenerate completion scripts with Clap's built-in shell generators:
git cascade completions bash
git cascade completions zsh
git cascade completions fish