Handling GitHub PR dependencies without breaking a sweat

Greg Foster
Greg Foster
Graphite software engineer


Note

This guide explains this concept in vanilla Git. For Graphite documentation, see our CLI docs.


Managing complex dependencies across pull requests can turn into a complex mess.

Teams get bogged down with blocked merges. Tricky reviews pile up. Tedious conflict resolution wears people down. Progress slows to a crawl.

However, it doesn't have to be this way—using the right workflows, we'll explore how you can smooth out PR dependencies. We’ll see techniques and workflows designed to streamline your process, moving from a state of complex dependencies to a more manageable and efficient system. Let’s get right into it.

the 3 main challenges with dependent PRs: blocked PRs; hard to rest/review; merge conflicts

Before we get into solutions, let’s look at some of the issues you may have experienced with PRs that have dependencies.

_“Biggest pain point at the moment in npm dependencies. Occasionally the size of the deployment for one of our projects balloons and we have to go around tracking down what's going on. What I'd like to be able to do is to centrally manage dependencies across multiple projects.” — _A Reddit user

With dependent PRs, you often run into situations where one or more PRs are blocked and cannot be merged until other PRs are merged first.

Blocked PRs are problematic for a few key reasons:

  • They clutter the pull request queue and make it difficult to see what code is actually ready to merge.

  • The blocked code risks getting stale and outdated as the main branch progresses.

  • Engineers feel blocked from completing their work while waiting for dependencies to merge first.

  • Long-blocked PR queues signal code review process issues that slow delivery.

_“One thing that I have noticed both in internships and full-time work is how getting your PR reviewed feels like pulling teeth. If I just make a PR and don’t message anyone asking them to review it, it won’t get reviewed until the end of the sprint, and if they request changes, then I won’t have time to complete the ticket before the end of the sprint.” — _Reddit user

When a parent PR has multiple dependent PRs, it becomes exceedingly difficult to test and review properly:

  • There is no integrated environment to validate if all the interconnected changes spanning PRs work correctly end-to-end.

  • Parsing complex code across many PR dependencies strains cognitive capacity as it requires significant mental overhead to maintain full context across branches.

  • Chasing issues across isolated branches lacks efficiency, and the test and review velocity slows down.

  • The code awaits quality testing and feedback longer, so downstream bugs and tech debt continue to accumulate.

  • Confidence in testing suffers since validation scenarios are split across branches mid-development.

As the number of dependent PRs increases, so does the complexity reviewers must juggle, both in testing systems and analyzing changes. This tangled process exaggerates problems and frustrations around delivering quality code.

If a dependent PR gets merged before the parent, you end up with code that will not work anymore. Merging the parent PR after the dependent one can cause merge conflicts, which can be extremely tricky to resolve correctly:

  • The dependent PR changed files or code that the parent PR also touches. Git sees both PRs trying to edit the same sections of code and can't reconcile the changes.

  • New functions, classes, imports, etc., added in the dependency PR now make the old unchanged parent PR code out of date. The mismatches in the codebase result in conflicts.

  • Progress on the main branch since the parent PR was opened—bug fixes, refactors, new features—clashes with the old state of the code in the still-open parent PR.

Essentially, changes from the dependency PR and on main branch diverge from the parent PR's code. Git runs into conflicting edits across the branches and can't merge them cleanly. This results in merge conflicts that block automatically merging the parent PR.

All of the above issues become even more evident when the number of unorganized, dependent PRs increases.

A lack of support for properly defining and managing dependencies between PRs can seriously hinder your workflow! Let's delve into some solutions.

Much of the frustration from dependency issues can be eliminated by having a sensible Git branching model in place. While there are many flavors of Git workflows you could use, one very effective and popular workflow for managing dependencies is the Gitflow branching model. This model was first published and made popular by Vincent Driessen at NVIE.

At a high level, Gitflow separates work into two primary branches:

  • Main: The mainline branch for production-ready code.

  • Develop: The main integration branch for new feature work.

Some downsides to this approach:

  • Having a separate develop branch can isolate engineers from changes going directly into production—lengthening feedback loops.

  • Long-lived feature branches encourage siloed work and increase merge conflicts when finally integrating.

  • Code in develop may diverge significantly from main over time, increasing the risk of code conflicts.

  • More branches, in general, add overhead in testing and tracking different lines of work.

To mitigate these risks, features should be integrated early and often into “develop” to keep them small and incremental. Releases from develop to main should also happen regularly to sync the branches. For GitFlow workflows to succeed, you need to ensure that the integration happens frequently.

main branch is a straight line with the develop branch sprouting off into its own direction and the feature branched off and then merged right back in to the develop

We generally work on short-lived feature branches created from a “develop” branch. When a feature branch is ready, a PR is submitted to the develop branch for review and integration testing.

Once approved and integrated into “develop” branch, subsequent features can continue to get code from the updated code base.

This way, each developer can work independently on a given feature while building on integrated chunks that slowly make their way from develop to main. Use right, the Gitflow workflow can massively reduce friction when working on new releases.

Even with a streamlined branching model, you’ll likely still have some situations where PRs relate to or build on others. Many developers experienced this issue, which is why Greg Dennis built Dependencies Action. This uses the GitHub Actions feature to detect words like “Depends On” or “Blocked By.”

screenshot of merge conversation showing "this branch has no conflicts with the base branch"

You can indicate a dependency with a simple Depends On statement when you open a PR:

Terminal
Depends On #123

Where #123 is the PR number. GitHub will automatically link the PRs on the interface to clarify the relationship. They’ll also block auto-merging on a dependent PR until dependencies are merged.

You can add multiple dependencies by adding comments that this GitHub action can recognize. This may work just right for simpler workflows—you get automated dependency detection, and it’s all good for the team.

When reviewing pull requests with multiple interdependent changes, it can be difficult to thoroughly test and analyze the code in the context of the other ongoing changes.

Instead, create disposable local branches to isolate just the code you need to review. Let’s consider that Sally is the developer who created a new payment system. Jane is the code reviewer. Here’s what a typical flow would look like.

Terminal
# Sally opens PR for new feature
git checkout -b feature/new-payment-system develop
git commit -m "Introduce payment system"
git push origin feature/new-payment-system
gh pr create --title "New payment system"
# Jane checks out just Sally's branch to review
git fetch origin
git checkout -b review-payment-feature origin/feature/new-payment-system
# Jane can freely experiment and test on her branch
git commit --amend # Saves comments without changing remote branch
# When done, open the GitHub PR dialog to leave feedback
gh pr comment #123 -- "Overall looks good! One question about..."

Some downsides to this isolated review approach:

  • Creating and managing short-lived feature branches can be more time-consuming than fewer long-lived ones.

  • If multiple reviewers all create isolated review branches, it can be tricky to consolidate feedback from various perspectives.

  • With many active review branches, it can become confusing tracking which feedback relates to which feature branch.

  • Since features are not integrated until late in the process, more significant integration issues may slip through reviews, and merge conflicts could still happen when merging the reviewed branch.

Branch isolation strategies and continuous integration testing give you safety nets at multiple levels when working with dependent changes. They help isolate riskier new code while incrementally merging stable features.

Some key advantages to isolating reviews:

  • Avoid tangles with code from other in-progress features.

  • Test functionality more easily without dependencies.

  • Craft targeted feedback for that specific change.

  • Eliminate the risk of accidental commits to the shared branch.

When working on an isolated code review, communicate to your team to set expectations for the review process.

If you think this method may suit your workflow, it’s best to try it on a small scale as it can get messy with many people working in isolation.

The git rerere command records how you've fixed conflicts in the past, and reuses recorded resolutions to automate future clashes that look the same.

To enable it:

Terminal
git config --global rerere.enabled true

When Git encounters a recorded conflict, it will resolve it the same way without user intervention.

Bots like Dependabot can be set up to automatically rebase branches, resolve conflicts, and merge approved pull requests

Bots like Dependabot can be set up to automatically rebase branches, resolve conflicts, and merge approved pull requests for you.

These bots can be useful for:

  • Keeping dependency update PRs moving swiftly.

  • Reducing the tedium of human-driven merges.

  • Enforcing custom merge rules and quality checks.

Some downsides to consider:

  • Bots may not handle all edge cases and conflict scenarios correctly.

  • Could lead to lower code quality if merges aren't thoroughly tested.

  • Loosens human oversight of changes entering the codebase.

  • More initial setup and maintenance of bot configuration.

Setup takes more initial configuration, but then the bots automatically shoulder the burden of repetitive conflict resolution.

Between built-in Git capabilities like rerere and dedicated merge bots, you can eliminate a large portion of tedious conflict resolution work over time.

While defining dependencies and using rebase can help, pull request management is still tedious and error-prone, especially as the complexity grows.

Stacking is a way to structure branches that allows you to break large, difficult-to-review pull requests into a sequence of small, iterative changes.

Here's how it works at a high level—considering that Sally, our developer, is working on an ecommerce checkout flow:

ecommerce checkout flow with stacked PRS: pr1 checkout button, pr2 chekcout form, pr3 integrate checkout with payment gateway, pr4 handle order submission and completion

  1. Sally creates a branch for the checkout button based on code from main and creates a PR 1.

  2. Rather than waiting for the review/merge, Sally creates PR 2 for the checkout form. This PR is built on top of PR 1.

  3. PR 2 naturally inherits PR 1 as a dependency. The additional PR 3 and 4, are chained on top of them.

  4. When PR 1 merges and the checkout button code has been added, PR 2 is already adjusted to the newly merged code as it depends on PR 1.

If a developer has to maintain stack relationships and handle rebases manually, it’s easy for merge conflicts to creep in. Solving these can be extremely tedious.

Graphite helps developers create, review, and merge code without being blocked. It automates a lot of the steps required when it comes to stacking PRs.

screenshot of graphite interactive showing an active pull request with a "you are here" prior to the first change

Some of the key capabilities of Graphite include:

  • Automatic rebasing of dependent PRs.

  • One-click stack updates when one of the PRs gets merged.

  • Flexible editing, reordering, splitting, and dropping of changes.

  • VS Code integration for IDE stacking.

  • Integration with GitHub workflow (reviews and merging).

Using Graphite for stacking, you get all the benefits of incremental development without the headaches of manual maintenance.

Streams of small, iterative changes can flow swiftly from work in progress to production. Quality stays high while sustaining a rapid pace of innovation.

Now, if you’re ready to improve your pull request workflow, you need to implement the below points.

Rather than dumping large change lists, you may find value in pushing up work-in-progress changes incrementally to get feedback cooking early. Keep scopes small and ask focused questions on specific pieces. Making reviews more ongoing could increase quality over time.

When issues occur, consider directing energy into learning rather than blaming. You should establish a no-blame culture focused on team improvement and knowledge sharing.

If someone fears breaking things, they may hesitate. The idea is to ensure psychological safety could enable people to take appropriate, preventative actions.

Rather than accepting GitHub's out-of-box workflow, you may get better results by tailoring elements like branching strategies, code review processes, etc. to amplify outcomes for your business.

Well-configured workflows could remove roadblocks and allow developers to focus more on shipping value. The leverage in getting these fundamentals right may be substantial.

As you know, managing dependencies across GitHub pull requests requires careful attention and effort. However, with the right workflows and tools, what seems challenging can become a strong asset for your team.

The techniques we've covered allow you to structure branches sensibly, isolate risk, encourage early feedback, resolve conflicts automatically, and promote a learning culture for your team. For the optimum results, though, you need to experiment with each workflow on a smaller scale and identify the best one.

Graphite eliminates grunt work for stacking workflows, so your team can fully benefit from the power of incremental development—lightning-fast iteration speed without compromising on code quality, stability, or reviews.

Sign up for a free Graphite account and experience how stacking can benefit your existing workflows—with minimal disruption.

Graphite
Git stacked on GitHub

Stacked pull requests are easier to read, easier to write, and easier to manage.
Teams that stack ship better software, faster.

Or install our CLI.
Product Screenshot 1
Product Screenshot 2