Skip to content

Commit 55e58bf

Browse files
feat: rename cascade command to restack, keep cascade as alias
The `cascade` command is now `restack`, with `cascade` retained as a Cobra alias for backward compatibility. All user-facing strings (help text, output messages, error messages) and documentation updated accordingly. The `--no-cascade` flag on `sync` is now `--no-restack`. Internal identifiers (function names, `STACK_CASCADE_STATE`, `OperationCascade`, etc.) are intentionally unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 61471f3 commit 55e58bf

11 files changed

Lines changed: 71 additions & 70 deletions

File tree

ARCHITECTURE.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,22 @@ When a multi-branch rebase is interrupted by a conflict, gh-stack saves the oper
8686
| `current` | Branch where the conflict occurred |
8787
| `pending` | Remaining branches to rebase |
8888
| `original_head` | HEAD before the operation started (for abort) |
89-
| `operation` | `"cascade"` or `"submit"` |
89+
| `operation` | `"cascade"` or `"submit"` (`"cascade"` is used by the `restack` command) |
9090
| `stash_ref` | Commit hash of auto-stashed uncommitted changes |
9191
| `branches` | Full branch list (submit only; used to rebuild the set for push/PR phases) |
9292
| `update_only`, `web`, `push_only` | Submit-specific flags preserved across continue |
9393

9494
### Cascade State Lifecycle
9595

96-
1. **Created** when a rebase conflict interrupts `cascade`, `submit`, or `sync`.
96+
1. **Created** when a rebase conflict interrupts `restack`, `submit`, or `sync`.
9797
2. **Removed** before `continue` resumes (will be recreated if another conflict occurs).
9898
3. **Removed** on successful completion or `abort`.
9999

100100
### Cascade State Trade-offs
101101

102102
This is an ephemeral, single-operation state file. It is not designed to survive beyond the operation that created it.
103103

104-
- **Single-operation scope.** Only one cascade/submit/sync can be in progress at a time. The code checks for this file and refuses to start a new operation if one exists.
104+
- **Single-operation scope.** Only one restack/submit/sync can be in progress at a time. The code checks for this file and refuses to start a new operation if one exists.
105105
- **Best-effort persistence.** Save errors are ignored (`//nolint:errcheck`) because if we can't save state, the user can still recover manually by aborting the rebase and re-running the command.
106106

107107
## Undo Snapshots
@@ -116,7 +116,7 @@ Before any destructive operation, gh-stack captures a snapshot of every affected
116116
{
117117
"timestamp": "2026-02-05T12:00:00Z",
118118
"operation": "cascade",
119-
"command": "gh stack cascade",
119+
"command": "gh stack restack",
120120
"original_head": "abc123...",
121121
"stash_ref": "",
122122
"branches": {
@@ -133,7 +133,7 @@ Before any destructive operation, gh-stack captures a snapshot of every affected
133133

134134
### Snapshot Lifecycle
135135

136-
1. **Created** before destructive operations (`cascade`, `submit`, `sync`).
136+
1. **Created** before destructive operations (`restack`, `submit`, `sync`).
137137
2. **Used** by `undo`, which restores branch refs and config keys from the snapshot.
138138
3. **Archived** to `done/` after a successful undo.
139139
4. **Pruned** automatically: max 50 active snapshots and 50 archived. Oldest are removed first.
@@ -153,7 +153,7 @@ flowchart TD
153153
init[init]
154154
create[create / adopt]
155155
submit[submit / link]
156-
cascade[cascade / sync]
156+
restack[restack / sync]
157157
undoCmd[undo]
158158
end
159159
@@ -166,8 +166,8 @@ flowchart TD
166166
init -->|set trunk| config
167167
create -->|set parent, fork point| config
168168
submit -->|set PR number| config
169-
cascade -->|on conflict| state
170-
cascade -->|before start| snapshots
169+
restack -->|on conflict| state
170+
restack -->|before start| snapshots
171171
undoCmd -->|restore from| snapshots
172172
undoCmd -->|restore| config
173173
```

README.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,26 @@ main
7676
Say we've made changes in `feature-auth`. To keep the stack in sync, we will need to rebase `feature-auth-tests` onto `feature-auth`. From branch `feature-auth`, execute:
7777

7878
```bash
79-
gh stack cascade
79+
gh stack restack
8080
```
8181

82-
If you run into conflicts, resolve them and run `gh stack continue` to resume the cascade (or `gh stack abort` to cancel). Once complete, your local stacks will be in sync. _They won't yet be pushed to the remote repository._
82+
If you run into conflicts, resolve them and run `gh stack continue` to resume the restack (or `gh stack abort` to cancel). Once complete, your local stacks will be in sync. _They won't yet be pushed to the remote repository._
8383

8484
#### Scenario 2: Changes in the local trunk
8585

8686
Maybe we pulled down `main` and it has new commits. We'll use the same strategy as above, but this time from the `main` branch:
8787

8888
```bash
89-
gh stack cascade
89+
gh stack restack
9090
```
9191

9292
> [!NOTE]
9393
>
94-
> Since `main` (the trunk) is the parent of every stack, `gh stack cascade` will naturally cascade _all_ stacks.
94+
> Since `main` (the trunk) is the parent of every stack, `gh stack restack` will naturally restack _all_ stacks.
9595
9696
#### Scenario 3: Upstream changes
9797

98-
Say `feature-auth` has been merged into the remote `main`. We now need to cascade the changes, but also retarget `feature-auth-tests` to `main` from `feature-auth`. You'll want to run:
98+
Say `feature-auth` has been merged into the remote `main`. We now need to restack the changes, but also retarget `feature-auth-tests` to `main` from `feature-auth`. You'll want to run:
9999

100100
```bash
101101
gh stack sync
@@ -108,7 +108,7 @@ This will:
108108
3. Detect merged PRs
109109
4. Clean up merged branches
110110
5. Retarget orphaned children to trunk
111-
6. Cascade all branches
111+
6. Restack all branches
112112

113113
What it _won't_ do is push back up to the remote; see the [next section](#creating--updating-prs) for that.
114114

@@ -124,7 +124,7 @@ Whenever you need to push these branches again, or update the PRs, you can run `
124124

125125
> [!TIP]
126126
>
127-
> `gh stack submit` does everything `gh stack cascade` does, and then some. Generally, if you want to make local mid-stack changes _without_ pushing to the remote, you'll want `gh stack cascade`; otherwise just use `gh stack submit`.
127+
> `gh stack submit` does everything `gh stack restack` does, and then some. Generally, if you want to make local mid-stack changes _without_ pushing to the remote, you'll want `gh stack restack`; otherwise just use `gh stack submit`.
128128
129129
## Commands
130130

@@ -137,11 +137,11 @@ Whenever you need to push these branches again, or update the PRs, you can run `
137137
| `orphan` | Stop tracking a branch |
138138
| `link` | Associate PR number with branch |
139139
| `unlink` | Remove PR association |
140-
| `submit` | Cascade, push, and create/update PRs in one command |
141-
| `cascade` | Rebase branch and descendants onto parents |
140+
| `submit` | Restack, push, and create/update PRs in one command |
141+
| `restack` | Rebase branch and descendants onto parents |
142142
| `continue` | Resume operation after conflict resolution |
143143
| `abort` | Cancel in-progress operation |
144-
| `sync` | Full sync: fetch, cleanup merged PRs, cascade all |
144+
| `sync` | Full sync: fetch, cleanup merged PRs, restack all |
145145
| `undo` | Undo the last destructive operation |
146146

147147
## Command Reference
@@ -254,11 +254,11 @@ The PR itself is not affected; this only removes the local tracking.
254254

255255
### submit
256256

257-
Cascade, push, and create/update PRs for current branch and descendants.
257+
Restack, push, and create/update PRs for current branch and descendants.
258258

259259
This is the primary workflow command. It performs three phases:
260260

261-
1. **Cascade**: Rebase current branch and descendants onto their parents
261+
1. **Restack**: Rebase current branch and descendants onto their parents
262262
2. **Push**: Force-push all affected branches (using `--force-with-lease`)
263263
3. **PR**: Create PRs for branches without them; update PR bases for existing PRs
264264

@@ -273,51 +273,51 @@ If a rebase conflict occurs, resolve it and run `gh stack continue`.
273273
| `--dry-run` | Show what would happen without doing it |
274274
| `--current-only` | Only submit the current branch, not descendants |
275275
| `--update-only` | Only update existing PRs, don't create new ones |
276-
| `--push-only` | Skip PR creation/update, only cascade and push |
276+
| `--push-only` | Skip PR creation/update, only restack and push |
277277

278-
### cascade
278+
### restack
279279

280-
Rebase the current branch and its descendants onto their parents.
280+
Rebase the current branch and its descendants onto their parents. Aliased as `cascade`.
281281

282282
Use this when you've made local changes and want to keep your stack in sync without pushing or creating PRs. For a full submit workflow, use `gh stack submit` instead.
283283

284284
If a rebase conflict occurs, resolve it and run `gh stack continue`.
285285

286-
#### cascade Flags
286+
#### restack Flags
287287

288-
| Flag | Description |
289-
| ----------- | -------------------------------------------- |
290-
| `--only` | Only cascade current branch, not descendants |
291-
| `--dry-run` | Show what would be done |
288+
| Flag | Description |
289+
| ----------- | --------------------------------------------- |
290+
| `--only` | Only restack current branch, not descendants |
291+
| `--dry-run` | Show what would be done |
292292

293293
### continue
294294

295-
Continue a cascade or submit operation after resolving rebase conflicts.
295+
Continue a restack or submit operation after resolving rebase conflicts.
296296

297297
After resolving conflicts and staging the changes, run this command to resume the operation.
298298

299299
### abort
300300

301-
Abort a cascade or submit operation in progress.
301+
Abort a restack or submit operation in progress.
302302

303303
This aborts any in-progress rebase and cleans up the operation state. Your branches will be left in their pre-operation state.
304304

305305
### sync
306306

307-
Full sync: fetch from origin, detect merged PRs, clean up merged branches, retarget orphaned children, and cascade all branches.
307+
Full sync: fetch from origin, detect merged PRs, clean up merged branches, retarget orphaned children, and restack all branches.
308308

309309
This is the command to run when upstream changes have occurred (e.g., a PR in your stack was merged). It handles the bookkeeping of updating your local stack to match remote state.
310310

311311
#### sync Flags
312312

313313
| Flag | Description |
314314
| -------------- | ----------------------- |
315-
| `--no-cascade` | Skip cascading branches |
315+
| `--no-restack` | Skip restacking branches |
316316
| `--dry-run` | Show what would be done |
317317

318318
### undo
319319

320-
Undo the last destructive operation (cascade, submit, or sync) by restoring branches to their pre-operation state.
320+
Undo the last destructive operation (restack, submit, or sync) by restoring branches to their pre-operation state.
321321

322322
Before any destructive operation, gh-stack automatically captures a snapshot of affected branches. If something goes wrong or you change your mind, `undo` restores:
323323

cmd/abort.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313

1414
var abortCmd = &cobra.Command{
1515
Use: "abort",
16-
Short: "Abort a cascade in progress",
17-
Long: `Abort a cascade operation and restore the original state.`,
16+
Short: "Abort a restack in progress",
17+
Long: `Abort a restack operation and restore the original state.`,
1818
RunE: runAbort,
1919
}
2020

@@ -35,7 +35,7 @@ func runAbort(cmd *cobra.Command, args []string) error {
3535
// Check if cascade in progress
3636
st, err := state.Load(g.GetGitDir())
3737
if err != nil {
38-
return fmt.Errorf("no cascade in progress")
38+
return fmt.Errorf("no restack in progress")
3939
}
4040

4141
// Abort rebase if in progress
@@ -57,6 +57,6 @@ func runAbort(cmd *cobra.Command, args []string) error {
5757
}
5858
}
5959

60-
fmt.Printf("%s Cascade aborted. Original HEAD was %s\n", s.WarningIcon(), st.OriginalHead)
60+
fmt.Printf("%s Restack aborted. Original HEAD was %s\n", s.WarningIcon(), st.OriginalHead)
6161
return nil
6262
}

cmd/cascade.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ import (
1919
var ErrConflict = errors.New("rebase conflict: resolve and run 'gh stack continue', or 'gh stack abort'")
2020

2121
var cascadeCmd = &cobra.Command{
22-
Use: "cascade",
23-
Short: "Rebase current branch and descendants onto their parents",
24-
Long: `Rebase the current branch onto its parent, then recursively cascade to descendants.`,
25-
RunE: runCascade,
22+
Use: "restack",
23+
Aliases: []string{"cascade"},
24+
Short: "Rebase current branch and descendants onto their parents",
25+
Long: `Rebase the current branch onto its parent, then recursively restack descendants.`,
26+
RunE: runCascade,
2627
}
2728

2829
var (
@@ -53,7 +54,7 @@ func runCascade(cmd *cobra.Command, args []string) error {
5354

5455
// Check if cascade already in progress
5556
if state.Exists(g.GetGitDir()) {
56-
return fmt.Errorf("cascade already in progress; use 'gh stack continue' or 'gh stack abort'")
57+
return fmt.Errorf("restack already in progress; use 'gh stack continue' or 'gh stack abort'")
5758
}
5859

5960
currentBranch, err := g.CurrentBranch()
@@ -84,7 +85,7 @@ func runCascade(cmd *cobra.Command, args []string) error {
8485
var stashRef string
8586
if !cascadeDryRunFlag {
8687
var saveErr error
87-
stashRef, saveErr = saveUndoSnapshot(g, cfg, branches, nil, "cascade", "gh stack cascade", s)
88+
stashRef, saveErr = saveUndoSnapshot(g, cfg, branches, nil, "cascade", "gh stack restack", s)
8889
if saveErr != nil {
8990
fmt.Printf("%s could not save undo state: %v\n", s.WarningIcon(), saveErr)
9091
}
@@ -129,7 +130,7 @@ func doCascadeWithState(g *git.Git, cfg *config.Config, branches []*tree.Node, d
129130
}
130131

131132
if !needsRebase {
132-
fmt.Printf("Cascading %s... %s\n", s.Branch(b.Name), s.Muted("already up to date"))
133+
fmt.Printf("Restacking %s... %s\n", s.Branch(b.Name), s.Muted("already up to date"))
133134
continue
134135
}
135136

@@ -153,9 +154,9 @@ func doCascadeWithState(g *git.Git, cfg *config.Config, branches []*tree.Node, d
153154
}
154155

155156
if useOnto {
156-
fmt.Printf("Cascading %s onto %s %s...\n", s.Branch(b.Name), s.Branch(parent), s.Muted("(using fork point)"))
157+
fmt.Printf("Restacking %s onto %s %s...\n", s.Branch(b.Name), s.Branch(parent), s.Muted("(using fork point)"))
157158
} else {
158-
fmt.Printf("Cascading %s onto %s...\n", s.Branch(b.Name), s.Branch(parent))
159+
fmt.Printf("Restacking %s onto %s...\n", s.Branch(b.Name), s.Branch(parent))
159160
}
160161

161162
// Checkout and rebase
@@ -198,7 +199,7 @@ func doCascadeWithState(g *git.Git, cfg *config.Config, branches []*tree.Node, d
198199
return ErrConflict
199200
}
200201

201-
fmt.Printf("Cascading %s... %s\n", s.Branch(b.Name), s.Success("ok"))
202+
fmt.Printf("Restacking %s... %s\n", s.Branch(b.Name), s.Success("ok"))
202203

203204
// Update fork point to current parent tip
204205
parentTip, tipErr := g.GetTip(parent)

cmd/continue.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
var continueCmd = &cobra.Command{
1717
Use: "continue",
1818
Short: "Continue an operation after resolving conflicts",
19-
Long: `Continue a cascade or submit operation after resolving rebase conflicts.`,
19+
Long: `Continue a restack or submit operation after resolving rebase conflicts.`,
2020
RunE: runContinue,
2121
}
2222

@@ -133,6 +133,6 @@ func runContinue(cmd *cobra.Command, args []string) error {
133133
}
134134
}
135135

136-
fmt.Println(s.SuccessMessage("Cascade complete!"))
136+
fmt.Println(s.SuccessMessage("Restack complete!"))
137137
return nil
138138
}

cmd/submit.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import (
1919

2020
var submitCmd = &cobra.Command{
2121
Use: "submit",
22-
Short: "Cascade, push, and create/update PRs for current branch and descendants",
22+
Short: "Restack, push, and create/update PRs for current branch and descendants",
2323
Long: `Submit rebases the current branch and its descendants onto their parents,
2424
pushes all affected branches, and creates or updates pull requests.
2525
2626
This is the typical workflow command after making changes in a stack:
27-
1. Cascade: rebase current branch + descendants onto their parents
27+
1. Restack: rebase current branch + descendants onto their parents
2828
2. Push: force-push all affected branches (with --force-with-lease)
2929
3. PR: create PRs for branches without them, update PR bases for those that have them
3030
@@ -45,7 +45,7 @@ func init() {
4545
submitCmd.Flags().BoolVar(&submitDryRunFlag, "dry-run", false, "show what would be done without doing it")
4646
submitCmd.Flags().BoolVar(&submitCurrentOnlyFlag, "current-only", false, "only submit current branch, not descendants")
4747
submitCmd.Flags().BoolVar(&submitUpdateOnlyFlag, "update-only", false, "only update existing PRs, don't create new ones")
48-
submitCmd.Flags().BoolVar(&submitPushOnlyFlag, "push-only", false, "skip PR creation/update, only cascade and push")
48+
submitCmd.Flags().BoolVar(&submitPushOnlyFlag, "push-only", false, "skip PR creation/update, only restack and push")
4949
submitCmd.Flags().BoolVarP(&submitYesFlag, "yes", "y", false, "skip interactive prompts and use auto-generated title/description for PRs")
5050
submitCmd.Flags().BoolVarP(&submitWebFlag, "web", "w", false, "open created/updated PRs in web browser")
5151
rootCmd.AddCommand(submitCmd)
@@ -135,8 +135,8 @@ func runSubmit(cmd *cobra.Command, args []string) error {
135135
}
136136
}
137137

138-
// Phase 1: Cascade
139-
fmt.Println(s.Bold("=== Phase 1: Cascade ==="))
138+
// Phase 1: Restack
139+
fmt.Println(s.Bold("=== Phase 1: Restack ==="))
140140
if cascadeErr := doCascadeWithState(g, cfg, branches, submitDryRunFlag, state.OperationSubmit, submitUpdateOnlyFlag, submitWebFlag, submitPushOnlyFlag, branchNames, stashRef, s); cascadeErr != nil {
141141
// Stash is saved in state for conflicts; restore on other errors
142142
if cascadeErr != ErrConflict && stashRef != "" {

cmd/sync.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import (
1717

1818
var syncCmd = &cobra.Command{
1919
Use: "sync",
20-
Short: "Fetch, detect merged PRs, retarget orphaned branches, cascade all",
21-
Long: `Fetch from origin, detect merged PRs, retarget orphaned branches to trunk, and cascade all branches.`,
20+
Short: "Fetch, detect merged PRs, retarget orphaned branches, restack all",
21+
Long: `Fetch from origin, detect merged PRs, retarget orphaned branches to trunk, and restack all branches.`,
2222
RunE: runSync,
2323
}
2424

@@ -28,7 +28,7 @@ var (
2828
)
2929

3030
func init() {
31-
syncCmd.Flags().BoolVar(&syncNoCascadeFlag, "no-cascade", false, "skip cascading branches")
31+
syncCmd.Flags().BoolVar(&syncNoCascadeFlag, "no-restack", false, "skip restacking branches")
3232
syncCmd.Flags().BoolVar(&syncDryRunFlag, "dry-run", false, "show what would be done")
3333
rootCmd.AddCommand(syncCmd)
3434
}
@@ -311,7 +311,7 @@ func runSync(cmd *cobra.Command, args []string) error {
311311
}
312312
fmt.Printf("Rebasing %s onto %s (from fork point %s)...\n", s.Branch(rt.childName), s.Branch(trunk), displayForkPoint)
313313
if rebaseErr := g.RebaseOnto(trunk, rt.forkPoint, rt.childName); rebaseErr != nil {
314-
fmt.Printf("%s --onto rebase failed, will try normal cascade: %v\n", s.WarningIcon(), rebaseErr)
314+
fmt.Printf("%s --onto rebase failed, will try normal restack: %v\n", s.WarningIcon(), rebaseErr)
315315
// Don't return error - let cascade try
316316
} else {
317317
fmt.Printf("%s Rebased %s successfully\n", s.SuccessIcon(), s.Branch(rt.childName))
@@ -332,7 +332,7 @@ func runSync(cmd *cobra.Command, args []string) error {
332332

333333
// Cascade all (if not disabled)
334334
if !syncNoCascadeFlag {
335-
fmt.Println(s.Bold("\nCascading all branches..."))
335+
fmt.Println(s.Bold("\nRestacking all branches..."))
336336
// Rebuild tree after modifications
337337
root, err = tree.Build(cfg)
338338
if err != nil {

0 commit comments

Comments
 (0)