When developers work on a large feature, the pull request can quickly become difficult to review. It might contain changes to database models, API endpoints, business logic, tests, and user interface components. Reviewing all these changes together takes time, and finding the cause of a problem becomes harder.
GitHub stacked pull requests offer a practical way to manage this situation. Instead of submitting one large pull request, developers divide the feature into smaller pull requests that depend on one another.
Each pull request focuses on a specific part of the implementation. Reviewers can understand the changes more easily, provide focused feedback, and identify problems before the entire feature is merged.
In this article, you will learn how stacked pull requests work, how to create them with Git, how to manage dependencies, and which practices help keep the workflow manageable.
What Are Stacked Pull Requests?
A stacked pull request is a pull request that builds on another pull request that has not yet been merged.
For example, imagine you are developing an order management feature. The implementation requires changes to database models, an order service, API endpoints, and automated tests.
Instead of submitting all the changes in one pull request, you can organize the work into a stack:
PR 1: Add order models and database configuration.
PR 2: Implement order service logic.
PR 3: Add order API endpoints.
PR 4: Add integration tests.
Each pull request depends on the changes introduced by the previous one.
The dependency relationship looks like this:
main
|
+-- PR 1: Order Models
|
+-- PR 2: Order Service
|
+-- PR 3: Order API
|
+-- PR 4: Integration Tests
The diagram represents the intended dependency structure. In a normal Git workflow, each branch contains the commits inherited from its parent branch.
Stacked pull requests are useful when a feature is too large for one review but contains smaller changes that can be reviewed independently.
Why Use Stacked Pull Requests?
Large pull requests can create several problems for development teams.
Reviewers must understand many unrelated changes at once. Developers may need to wait for extensive feedback before discovering problems in an early part of the implementation. If the pull request changes significantly during review, reviewers might also need to inspect the same areas again.
Stacking reduces these problems by dividing the implementation into smaller units.
Area | Single large pull request | Stacked pull requests |
|---|
Review scope | Many changes together | Smaller, focused changes |
Dependencies | Mixed into one change set | Explicitly represented by branches |
Feedback | Often covers several areas | Can target a specific implementation layer |
Parallel work | Can be difficult to coordinate | Different parts may be reviewed independently |
Maintenance | One large branch to manage | Multiple dependent branches to maintain |
Merge process | Usually one large merge | Multiple merges in dependency order |
However, stacking does not automatically make every pull request independent. If PR 3 requires the code from PR 2, it cannot be merged safely before PR 2.
How to Create Stacked Pull Requests With Git
Consider a feature developed in a repository where the main branch is named main.
You will create three branches:
feature/order-model
feature/order-service
feature/order-api
Each branch will build on the previous branch.
Step 1: Create the First Branch
Start by updating your local main branch and creating the model branch.
git switch main
git pull origin main
git switch -c feature/order-model
Implement the order model and database configuration. Commit the changes:
git add .
git commit -m "Add order model and database configuration"
git push -u origin feature/order-model
Open a pull request from feature/order-model into main.
This pull request should contain only the model and database configuration changes.
Keeping the scope narrow makes it easier for reviewers to verify the data structure and its intended behavior.
Step 2: Create a Branch Based on the First Branch
Now create the service branch from the model branch.
git switch feature/order-model
git switch -c feature/order-service
Implement the service logic and commit it:
git add .
git commit -m "Implement order service"
git push -u origin feature/order-service
Create another pull request, but target feature/order-model instead of main.
This is important because the service implementation depends on the model changes.
The second pull request should display only the changes introduced by the service branch when compared with its base branch.
If you target main while the model pull request is still open, the review may include both the model and service changes. That makes the second pull request harder to review.
Step 3: Add the API Branch
Create the API branch from the service branch:
git switch feature/order-service
git switch -c feature/order-api
Add the API controllers or endpoints and commit the changes.
git add .
git commit -m "Add order API endpoints"
git push -u origin feature/order-api
Open a pull request from feature/order-api into feature/order-service.
The resulting dependency chain is:
main
|
+-- feature/order-model
|
+-- feature/order-service
|
+-- feature/order-api
GitHub pull requests should use the appropriate parent branch as their base until the preceding changes are merged.
How to Manage the Stack When a Pull Request Is Merged
The most important part of stacked pull requests is maintaining the correct branch relationships.
Suppose the first pull request is approved and merged into main.
The service branch originally contains commits based on feature/order-model. After the model branch is merged, you should update the service branch so that it uses main as its base.
First, update the local main branch:
git switch main
git pull origin main
Then rebase the service branch:
git switch feature/order-service
git rebase main
If the rebase succeeds, update the remote branch:
git push --force-with-lease origin feature/order-service
Use --force-with-lease carefully. Rebasing changes commit history, so the remote branch may need a history update. The command checks that the remote reference has not changed unexpectedly before replacing it.
Do not force-push shared branches without coordinating with other contributors.
Next, change the service pull request's base branch to main in GitHub.
Repeat the process when the service pull request is merged, updating the API branch and changing its base to main.
Always resolve conflicts and run the relevant tests after rebasing.
How to Handle Merge Conflicts
Merge conflicts can occur when a parent branch changes while a child branch is under development.
For example, a reviewer might request a change to the order model. The service branch may then need to be rebased onto the updated parent branch.
Use the following workflow:
Fetch the latest repository changes.
Switch to the branch that needs updating.
Rebase it onto the updated parent branch.
Resolve conflicts in the affected files.
Stage the resolved files and continue the rebase.
Run tests and inspect the final diff.
Push the updated branch and review the pull request again.
The Git commands for resolving a conflict during a rebase are:
git status
# Edit the files containing conflict markers.
git add path/to/resolved-file.cs
git rebase --continue
If you need to abandon the rebase, use:
git rebase --abort
This returns the branch to its state before the rebase started.
Avoid resolving conflicts by blindly accepting all changes from one side. That can remove valid work from the other branch.
Best Practices for Stacked Pull Requests
Keep Each Pull Request Focused
Each pull request should represent a meaningful part of the feature.
For example, database models, service logic, and API endpoints are reasonable boundaries when they have clear responsibilities.
Avoid creating many tiny pull requests that require reviewers to switch contexts repeatedly. The goal is to reduce review complexity, not increase the number of reviews unnecessarily.
Make Dependencies Explicit
Use descriptive branch names and explain dependencies in pull request descriptions.
A description might state that the current pull request depends on the order model pull request and should not be merged independently.
This prevents reviewers and maintainers from merging changes in the wrong order.
Run Tests at Each Stage
A child branch should be tested with the changes it depends on.
If the repository uses continuous integration, configure the checks to build and test the pull request's complete proposed change set. Verify how your repository handles pull requests whose base branches are still open.
Tests that pass on a child branch may fail after its parent changes, so rerun relevant checks after rebasing.
Coordinate With Reviewers
Tell reviewers which pull request to review first. If several pull requests are submitted together, they should understand the intended order.
When a parent pull request changes, communicate the impact on child branches rather than allowing reviewers to continue working against outdated diffs.
Avoid Unnecessary History Rewrites
Rebasing can keep the commit history easier to follow, but it changes commit identifiers.
If several developers are working on the same branch, coordinate history changes and avoid rewriting commits that others are actively building on.
Common Mistakes to Avoid
Mistake | Why it causes problems | Better approach |
|---|
Targeting every pull request at main | Child pull requests may include parent changes | Target the immediate parent branch |
Merging a child before its parent | Required changes may be missing | Merge in dependency order |
Rebasing without checking the diff | Changes can be lost or unintentionally included | Review the diff and run tests |
Force-pushing without coordination | Other contributors may lose work | Coordinate and use --force-with-lease when appropriate |
Creating too many small pull requests | Review overhead can increase | Split by meaningful responsibilities |
Ignoring outdated pull requests | Reviewers may examine incorrect changes | Update branches and pull request bases after merges |
Advantages and Disadvantages
Stacked pull requests provide several benefits:
Smaller changes are easier to understand and review.
Feedback can focus on specific parts of a feature.
Teams can begin reviewing earlier implementation stages.
Dependencies become more visible.
Large changes can be divided into manageable development tasks.
There are also trade-offs:
Developers must maintain branch dependencies.
Rebasing and conflict resolution require additional effort.
Pull request bases must be updated as parent branches are merged.
Continuous integration configuration may require extra attention.
Reviewers need to understand the dependency order.
For a small change that fits comfortably into one pull request, stacking may add unnecessary complexity.
Summary
GitHub stacked pull requests help developers divide large features into smaller, dependent changes. By creating branches from their parent branches, developers can keep each pull request focused on a specific part of the implementation.
The key is to maintain the correct dependency order, target each pull request at its immediate parent, and update child branches when earlier changes are merged.
Use stacking when it improves review quality and reduces the risk of large, difficult-to-understand changes. Keep the workflow simple, communicate branch dependencies clearly, and test each stage before merging it into the main branch.
Join the conversation! Your thoughts help the community grow.