You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## README – Updating Stacked PRs After a Squash‑Merge
1
+
## Stacked PRs with squash & merge
2
2
3
-
### Why this script exists
4
-
When you work with **stacked pull‑requests**—a chain of feature branches where each PR is based on the previous one—life is good right up to the moment the bottom PR is merged with **"Squash & Merge."**
5
-
A squash merge rewrites history and then deletes the source branch, which breaks every open PR that depended on it.
3
+
### The problem
6
4
7
-
**Pain points the script eliminates**
5
+
If you want stacked pull requests on GitHub, one way to do it that stays easy for people who aren't rebase wizards is to use a simple `git push` / `git merge` workflow while working on your PRs.
8
6
9
-
| Pain | Why it happens | Consequence in GitHub UI |
| 1. **Descendant branches lose their base**| The commit they were branched from no longer exists on the target branch. | GitHub shows a red *"This branch is out‑of‑date with the base branch"* banner. |
12
-
| 2. **Diffs are garbage**| GitHub compares the child‑branch to `main`, not to the commit it actually diverged from. | Reviewers see a giant diff containing code from *all* earlier PRs, making review impossible. |
13
-
| 3. **"Update branch" button explodes**| The missing commits mean Git can't perform a clean rebase or merge. | Clicking *Update branch* opens a web conflict‑editor with dozens of unrelated hunks. |
14
-
| 4. **Manual recovery is tedious**| Each branch must be rebased/merged and force‑pushed one‑by‑one. | Hours of menial work and risk of errors. |
7
+
When you merge the lower PR in the stack, you just need to update the upper PR. This works fine if you use regular merge commits, but your trunk history becomes very hard to read with normal tooling like the GitHub commit history page (though you can still navigate it with `git log --first-parent`).
15
8
16
-
This action automates that recovery:
17
-
1. Replays the missing history onto every direct child PR with a synthetic three‑parent merge.
18
-
2. Recursively updates indirect descendants so they stay clean and reviewable.
19
-
3. Updates each PR's base branch so GitHub's diff & merge logic are correct.
20
-
4. Deletes the merged branch.
9
+
If you use squash & merge instead, your main branch history stays nice and clean, but now the upper PR in the stack gets a garbage diff and merge conflicts when you try to update it. This happens because the squash commit rewrites history, and GitHub can't figure out what the PR is actually trying to change.
21
10
22
-
The net result: **your stack stays green and reviewers only see the intended diff**.
11
+
### The solution
12
+
13
+
This action tries to fix that in a transparent way. Install it, and hopefully the workflow of stacking + merge during dev + squash merge when landing works.
23
14
24
15
---
25
16
26
-
### How the action works (high level)
27
-
1.**Trigger** – Fires when a PR is closed *and*`merged == true`*and* has `merge_commit_sha` (i.e. a squash merge).
28
-
2.**Discover hierarchy** – Uses `gh pr list` to find PRs whose `base` was the merged branch; walks the tree recursively.
29
-
3.**Direct children** – For each child branch it creates a synthetic merge commit that records three parents: (a) the child's old tip, (b) the deleted branch tip, (c) the squash commit. This preserves history without re‑introducing code.
30
-
4.**Indirect descendants** – Simply merge the now‑updated parent branch; no custom commit needed.
31
-
5.**PR metadata** – Switches each direct child PR's base to the trunk branch (or next living base).
32
-
6.**Push & clean up** – Force‑pushes updated branches where necessary and deletes the obsolete branch on the remote.
17
+
### How it works
18
+
19
+
1. Triggers when a PR is squash merged
20
+
2. Finds PRs that were based on the merged branch
21
+
3. For direct children: creates a synthetic merge commit with three parents (child tip, deleted branch tip, squash commit) to preserve history without re-introducing code
22
+
4. For indirect descendants: merges the updated parent branch
23
+
5. Updates each PR's base branch to point to trunk
24
+
6. Force-pushes updated branches and deletes the merged branch
33
25
34
26
---
35
27
36
-
### Using this action in your repository
28
+
### Setup
37
29
38
-
#### As a GitHub Action workflow
39
-
1. Create a `.github/workflows/update-pr-stack.yml` file with the following content:
30
+
Create a `.github/workflows/update-pr-stack.yml` file:
40
31
```yaml
41
32
name: Update Stacked PRs on Squash Merge
42
33
@@ -60,27 +51,19 @@ jobs:
60
51
fetch-depth: 0
61
52
62
53
- name: Update PR stack
63
-
uses: username/test-stack@v1
54
+
uses: Phlogistique/autorestack-action@main
64
55
env:
65
56
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66
57
```
67
-
2. Replace `username/test-stack@v1` with the appropriate repository reference (e.g., `your-username/your-repo@main` or use a version tag).
68
58
69
-
#### Repository contents
70
-
| File | Purpose |
71
-
|------|---------|
72
-
| `update-pr-stack.sh` | Bash script that performs all git/gh operations. |
73
-
| `.github/workflows/update-pr-stack.yml` | GitHub Actions workflow that runs the script after every squash merge. |
74
-
| `action.yml` | GitHub Action definition for reuse in other repositories. |
59
+
### Notes
75
60
76
-
---
77
-
78
-
### Caveats & Tips
79
-
* Only supports **squash merges** for the base PR.
80
-
* TODO: If a merge in the chain hits a conflict the workflow exits neutral, comments on the PR, and waits for the developer to resolve & push. A follow‑up label or comment automatically resumes the stack update.
81
-
* Very large stacks may hit GitHub rate limits; TODO: throttle or batch API calls if needed.---
61
+
* Currently only supports squash merges
62
+
* If a merge hits a conflict, you'll need to resolve it manually
63
+
* Very large stacks might hit GitHub rate limits
82
64
83
65
---
84
66
85
67
### Credits
86
-
Inspired by *Graphite* and *Gerrit* workflows but implemented with plain git + GitHub CLI.
68
+
69
+
Inspired by Graphite and Gerrit workflows but implemented with plain git + GitHub CLI.
0 commit comments