Title: gh stack rebase --upstack doesn't detect squash-merged branches below the current branch · Issue #31 · github/gh-stack · GitHub
Open Graph Title: gh stack rebase --upstack doesn't detect squash-merged branches below the current branch · Issue #31 · github/gh-stack
X Title: gh stack rebase --upstack doesn't detect squash-merged branches below the current branch · Issue #31 · github/gh-stack
Description: Summary When gh stack rebase --upstack is run from a branch that has a squash-merged PR somewhere below it in the stack, the merged branch is never detected and skipped. Pre-squash commits from the merged branch stay in the history of ev...
Open Graph Description: Summary When gh stack rebase --upstack is run from a branch that has a squash-merged PR somewhere below it in the stack, the merged branch is never detected and skipped. Pre-squash commits from the...
X Description: Summary When gh stack rebase --upstack is run from a branch that has a squash-merged PR somewhere below it in the stack, the merged branch is never detected and skipped. Pre-squash commits from the...
Opengraph URL: https://github.com/github/gh-stack/issues/31
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"gh stack rebase --upstack doesn't detect squash-merged branches below the current branch","articleBody":"## Summary\n\nWhen `gh stack rebase --upstack` is run from a branch that has a squash-merged PR somewhere below it in the stack, the merged branch is never detected and skipped. Pre-squash commits from the merged branch stay in the history of every branch above, which (after push) shows up as bloated PR diffs containing unrelated code from the already-merged PR.\n\n## Reproducer\n\n1. Create a stack `main ← A ← B ← C` with PRs for each.\n2. Squash-merge A's PR into main.\n3. From B or C, run `gh stack rebase --upstack`.\n4. Observe: B and C are rebased but still contain A's pre-squash commits.\n5. The PRs for B and C now show a diff that includes A's changes in addition to their own.\n\nPlain `gh stack rebase` (no flag) handles the same scenario correctly — it detects A as merged, sets `needsOnto`, and rebases B and C with `--onto` to skip A's pre-squash commits.\n\n## Root cause\n\nIn `cmd/rebase.go`:\n\n```go\nif opts.upstack {\n startIdx = currentIdx\n}\nbranchesToRebase := s.Branches[startIdx:endIdx]\n```\n\nThe squash-merge detection runs inside the loop that iterates over `branchesToRebase`:\n\n```go\nif br.IsMerged() {\n ontoOldBase = originalRefs[br.Branch]\n needsOnto = true\n cfg.Successf(\"Skipping %s (PR %s merged)\", br.Branch, ...)\n continue\n}\n```\n\n`--upstack` sets `startIdx = currentIdx`, so any merged branch below `currentIdx` is excluded from the slice. `IsMerged()` never fires on it, `needsOnto` stays `false`, and the subsequent rebases use the old (pre-squash) parent instead of `--onto` to the squashed base.\n\n## Suggested fixes\n\nTwo options:\n\n1. **Seed merge state before the upstack loop.** Scan `s.Branches[:startIdx]` for merged branches before starting the loop and, if any is found, pre-populate `ontoOldBase` / `needsOnto` with the appropriate old base so that the first branch in the upstack slice rebases with `--onto`.\n\n2. **Refuse `--upstack` when a merged branch exists below.** Detect the condition up front and error out with a message directing the user to run plain `gh stack rebase` instead. Simpler and surfaces the problem loudly rather than silently producing a bad rebase.\n\nOption 2 is probably cheapest and prevents silent data corruption. Option 1 is more user-friendly but needs more care around multiple merged branches in sequence.\n\n## Impact\n\nRepeatedly observed in the wild — the bad rebase is silent, the push succeeds, and the only signal is that PRs suddenly show hundreds of lines of unrelated diff. Easy to miss if you don't self-check every push.","author":{"url":"https://github.com/benschoel","@type":"Person","name":"benschoel"},"datePublished":"2026-04-15T20:22:41.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":2},"url":"https://github.com/31/gh-stack/issues/31"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:5d16ff0c-61f7-01a8-67ce-727cdaca3d74 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | DA82:16C3D3:6369003:8BCC7E9:6A5028EA |
| html-safe-nonce | c96dd870cd8ec1723e633814cd3ce7ff135ab8504383a699112e6c27b214296c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEQTgyOjE2QzNEMzo2MzY5MDAzOjhCQ0M3RTk6NkE1MDI4RUEiLCJ2aXNpdG9yX2lkIjoiMzc3ODA1MDg5NDAzNTE2NzQ2NiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 659b5334244e6060e94fab86fcc343a9b5cb4f153f6e96a845af887666e134e0 |
| hovercard-subject-tag | issue:4271473442 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/github/gh-stack/31/issue_layout |
| twitter:image | https://opengraph.githubassets.com/3fdbe03e235a0e644a3d9078222d5dfbd4c071a293fc21dcd322a92e5d6c1b9f/github/gh-stack/issues/31 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/3fdbe03e235a0e644a3d9078222d5dfbd4c071a293fc21dcd322a92e5d6c1b9f/github/gh-stack/issues/31 |
| og:image:alt | Summary When gh stack rebase --upstack is run from a branch that has a squash-merged PR somewhere below it in the stack, the merged branch is never detected and skipped. Pre-squash commits from the... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | benschoel |
| hostname | github.com |
| expected-hostname | github.com |
| None | d6dc8294eb500fa36bbc57472d61fe87c522f9c3c1d64f70f4926f66a66a7efb |
| turbo-cache-control | no-preview |
| go-import | github.com/github/gh-stack git https://github.com/github/gh-stack.git |
| octolytics-dimension-user_id | 9919 |
| octolytics-dimension-user_login | github |
| octolytics-dimension-repository_id | 1151630728 |
| octolytics-dimension-repository_nwo | github/gh-stack |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1151630728 |
| octolytics-dimension-repository_network_root_nwo | github/gh-stack |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | d90666eaf032b7eb8975a378efe38e37f4ebb96c |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width