Large PRs are a problem: properly reviewing them becomes incredibly difficult, their size makes it easier for bugs to slip through the cracks...
PR stacking offers a solution: breaking big features into a chain of incremental code changes:
PR stacking is a development approach where large features are broken down into a series of small, dependent pull requests that build upon each other. Instead of one monolithic PR, you create a "stack" of focused changes, each addressing a specific aspect of the feature.
Once all incremental pull requests have been approved, they get merged with main.
Improved review quality is the headline benefit: each branch becomes more focused and is easier to review and fix.
Continuous Progress: Engineers stay unblocked. While PR #1 awaits review, work continues on PRs #2, #3, and beyond.
Let's walk through creating a stack. Suppose we're building a feature that requires API changes, authentication, and user management endpoints.
Start as usual by branching of main:
# From main branch
git checkout -b feature/api-base main
# Implement base API structure
git commit -m "Add base API router and middleware setup"
git commit -m "Add error handling middleware"
git commit -m "Add request logging and monitoring hooks"
Push and create your first PR.
Then, to continue working, create a new branch out of feature/api-base
:
# Branch from api-base, not main
git checkout -b feature/api-auth
# Implement authentication layer
git commit -m "Add JWT token generation and validation"
git commit -m "Add auth middleware with role checking"
git commit -m "Add refresh token rotation logic"
Push this branch and create PR #2. The PR will show only the auth-related commits, making it easy to review in isolation. Continue the pattern:
# Branch from api-auth
git checkout -b feature/api-users
# Implement user management
git commit -m "Add user CRUD endpoints"
git commit -m "Add user validation and sanitization"
git commit -m "Add user permission management"
You now have a stack:
main
└── feature/api-base (PR #1)
└── feature/api-auth (PR #2)
└── feature/api-users (PR #3)
You have to take care to keep the PRs in the stack in sync.
If a reviewer identifies issues in PR #1, for example, those fixes must propagate through the entire stack.
This can be done with git's rebase and the new --update-refs
flag (added on Git 2.38). Here's a practical example:
Fix issues in the base PR:
git checkout feature/api-base
# address review feedback
git commit -m "Fix: Use proper status codes"
git commit -m "Fix: Add input sanitization"
Update the entire stack:
Starting from the topmost branch in the stack:
git checkout feature/api-users # topmost branch
git rebase --update-refs feature/api-base
This rebases your entire stack while preserving all branch references.
Then push all updates:
git push --force-with-lease origin feature/api-base feature/api-auth feature/api-users
When main gets new commits that you need, you can use the same pattern to sync your stack:
git checkout feature/api-users # Topmost branch
git rebase --update-refs origin/main
git push --force-with-lease origin feature/api-base feature/api-auth feature/api-users
Conflicts during rebase are handled normally, but --update-refs
ensures branch pointers stay correct:
# During rebase, if conflicts occur
# Fix conflicts in your editor
git add .
git rebase --continue
# Repeat for each conflict
The stack structure remains intact throughout the conflict resolution process.
Save yourself typing - add this to your global git config:
git config --global rebase.updateRefs true
Now git rebase
automatically includes --update-refs
behavior when applicable.
Understanding your stack structure prevents mistakes:
# See the structure
git log --graph --pretty=oneline --abbrev-commit feature/api-users
# See which branches point where
git branch -v --contains feature/api-base
Before rebasing, see what's out of sync:
# What commits does api-auth have that api-users doesn't?
git log feature/api-users..feature/api-auth --oneline
# What's in main but not in your stack?
git log feature/api-users..origin/main --oneline
Make it clear in PR descriptions that they're stacked. Something like:
## Stacked PR:
* #5085 👈 (This PR)
* #5083
* #4989
* `main`
which GitHub will render like:
Uploading image...