| name | stacked-diffs |
| description | Implement large features as a stack of small, reviewable pull requests (GitHub) or merge requests (GitLab) where each PR/MR targets the previous one instead of main. Use for stacked PRs, stacked MRs, stacked diffs, stacked branches, incremental review, multi-phase features, or breaking a large change into focused commits. Covers the full workflow for both GitHub and GitLab: branch naming, the reset pattern, PR/MR targeting, description templates, sequential merging, rebasing the stack, and recovery. Prefer main-based PRs unless the feature clearly has multiple logical phases for incremental review. |
| license | MIT |
| allowed-tools | Bash |
Stacked Diffs
Break large features into a chain of small, focused pull requests (GitHub) or merge requests (GitLab). Each PR/MR targets the previous one, so reviewers see only the new delta — not the entire feature at once.
main
└── feature/001-schema → PR/MR #1 → main
└── feature/002-api → PR/MR #2 → feature/001-schema
└── feature/003-ui → PR/MR #3 → feature/002-api
When to Stack (and When Not To)
Stack when:
- The feature has multiple distinct logical phases (schema → API → UI)
- You want Phase 1 reviewed while building Phase 2
- The total diff would be too large to review in one pass (>400 lines)
- Each phase could be deployed or rolled back independently
Don't stack — use independent PRs from main — when:
- Changes are unrelated (separate bugfixes, separate features)
- The feature is small enough to review in one PR
- Reviewers don't need the incremental story
Default rule: prefer main-based PRs unless stacking is clearly warranted.
Planning the Stack
Before writing code, sketch the ordered phases. Each phase should:
- Have a single, clear responsibility
- Pass all tests independently
- Be ≤400 lines of diff (split earlier if a natural boundary appears)
Branch naming — use sequential numbers so the order is obvious:
feature/001-user-model
feature/002-oauth-provider
feature/003-login-ui
feature/004-tests-and-docs
Building the Stack
Phase 1 (targets main)
git checkout main && git pull
git checkout -b feature/001-user-model
git add -p
git commit -m "feat: Phase 1 — user model and DB schema"
git push -u origin feature/001-user-model
Open PR (GitHub) or MR (GitLab): feature/001-user-model → main
- Title:
[1/3] Add OAuth: user model
- Mark as draft
- Include stack overview in description (template below)
Phase 2 (targets Phase 1)
git checkout feature/001-user-model
git checkout -b feature/002-oauth-provider
git commit -m "feat: Phase 2 — OAuth provider integration"
git push -u origin feature/002-oauth-provider
Open PR/MR: feature/002-oauth-provider → feature/001-user-model
⚠️ Target is the previous phase branch, NOT main
Then reset Phase 1 to exclude Phase 2 commits:
git checkout feature/001-user-model
git reset --hard <last-phase-1-commit>
git push --force-with-lease
This is the most-missed step. Without it, Phase 1's diff shows Phase 2 work.
Phase N (repeat)
git checkout feature/00{N-1}-previous
git checkout -b feature/00{N}-next
PR/MR Description Template
## Summary
Phase N of M: [what this phase adds].
**Stack:**
| Phase | Branch | PR/MR | Status |
|---|---|---|---|
| 1 | feature/001-user-model | #111 | ✅ merged |
| 2 | feature/002-oauth-provider | #112 | 👀 in review |
| **3** | **feature/003-login-ui** | **this** | 📝 draft |
| 4 | feature/004-tests | — | not yet open |
## Changes
- [commit 1]
- [commit 2]
## Test plan
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual: [specific scenario]
Merging
Always merge bottom-up — Phase 1 before Phase 2 before Phase 3.
Two strategies:
Sequential — merge each phase as it's approved:
- Phase 1 approved → merge into
main
- Retarget/rebase Phase 2 onto
main (see platform notes below)
- Phase 2 approved → merge into
main
Hold-the-stack — get all phases approved, then merge in order:
- Get approvals on all phases
- Merge Phase 1 →
main
- Retarget Phase 2 →
main, merge
- Repeat
Sequential is better for long-running stacks. Hold-the-stack works for small stacks where review happens fast.
GitHub
GitHub automatically retargets a PR when its base branch merges. After Phase 1 merges:
- Verify Phase 2 PR now targets
main (check the PR UI)
- Rebase if there are conflicts:
git rebase main feature/002-oauth-provider
GitLab
GitLab does not auto-retarget. After Phase 1 merges:
- Manually change Phase 2 MR's target branch to
main in the MR UI
- Rebase:
git rebase main feature/002-oauth-provider && git push --force-with-lease
Rebasing the Whole Stack
When main has moved significantly:
git checkout feature/001-user-model && git rebase main && git push --force-with-lease
git checkout feature/002-oauth-provider && git rebase feature/001-user-model && git push --force-with-lease
git checkout feature/003-login-ui && git rebase feature/002-oauth-provider && git push --force-with-lease
Or rebase a phase onto a new base entirely:
git rebase --onto <new-base> <old-base> feature/003-login-ui
git push --force-with-lease
Always use --force-with-lease, never --force.
Checklist Before Opening Each PR/MR
Common Mistakes
All PRs target main (Phase 2 diff shows all commits, not just Phase 2):
feature/002-oauth-provider → main
feature/002-oauth-provider → feature/001-user-model
Forgot to reset the base branch (Phase 1 diff leaks Phase 2 work):
git checkout feature/001-user-model
git reset --hard <last-phase-1-commit>
git push --force-with-lease
Merged out of order — always go bottom-up.
Recovery
| Problem | Fix |
|---|
| Wrong PR/MR target | Change target in the PR/MR UI |
| Messy commits | git rebase -i <base> |
| Phase has wrong commits | git reset --hard <correct-commit> + git push --force-with-lease |
| Stack diverged from main | Rebase each phase bottom-up |
| Conflict mid-rebase | Resolve → git add → git rebase --continue |
| GitLab didn't retarget | Change target manually in MR UI, then rebase |
Related Skills
git-worktrees — work on multiple phases simultaneously in separate directories
code-review — review strategies for incremental diffs