The first two launches this week (shared stacks & automatic rebasing for partially-merged stacks) focused on polishing the biggest remaining rough edges of stacking pull requests. For day 3, we’re excited to announce Graphite Protections - a feature designed to improve every team’s code review workflow, regardless of whether or not they’ve adopted stacked PRs.
What needs to happen before a PR can be merged?
As teams grow beyond a handful of engineers, it becomes increasingly important to codify a way to determine whether or not a pull request is ready to merge (i.e. have the right people approved it? has it passed the necessary CI checks?). Today, teams try to achieve this with a combination of CODEOWNERS
(to assign reviewers) and branch protection rules, or by writing custom scripts and GitHub Actions workflows.
These solutions all fall short in one way or another - custom scripts & actions require constant maintenance, and we hear regularly from teams on Graphite that CODEOWNERS
and branch protection rules are too rigid and constrained for modern development practices. CODEOWNERS
files simply assign ownership based on directory paths, and branch protection rules have a limited number of rules and presets. Systems like round-robin reviewer assignment are well-intentioned, but can end up making the problem worse by removing the requirement that a domain expert reviews your code.
The limitations of CODEOWNERS
and branch protection rules are particularly painful for companies using monorepos, where a one-size-fits-all approach reviewer assignment & mergeability just doesn’t work. Development best practices are going to be different between infra and product teams (and even between different product teams), and teams need tooling that supports these more nuanced processes.
We spent a lot of time over the past few months talking about this problem with some of the most active teams on Graphite, and we’re excited to introduce a solution that gives teams the flexibility they need without sacrificing correctness and reliability.
Introducing Graphite Protections
Graphite Protections is the next generation of branch protection rules and codeowners, built for fast-moving development teams. With it:
Individual teams can customize & enforce their own codeowners and PR mergeability requirements, while still remaining compliant with their company’s policies.
Define merge requirements for specific directories, code authors, branches, PR sizes, types of changes, on-call engineers, and anything else — instead of setting overly-broad or overly-strict branch protection requirements.
Let PR authors override specific rules while still enforcing a high quality bar. For example, disallow merging large PRs by default that aren’t code mods or reverts.
How are Graphite customers using Protections?
Protections give Graphite users a powerful and flexible framework for codifying, simplifying, and accelerating their code review processes. For example, here’s a set of a few Protections from a large Graphite customer:
Frontend code requires 1+ approval from a frontend engineer
Production infrastructure changes to Terraform files require approval from 1 infra engineer and 1 security engineer
Data science & analytics PRs require no approvals
CI must be passing to merge, but developers can override this if touching a codepath containing a known-flaky test or when making minor copy changes.
How it works under the hood
To make Graphite work in real-time, we maintain a sophisticated GitHub sync engine that uses a mix of live webhooks and polling. Whenever someone or something updates a PR in GitHub, we get an event (PR approved, CI failing, etc.).
When we detect a PR has changed, we look at the change and decide:
Does one or more Protection apply to this PR? Merge rule criteria are boolean conditions, e.g.
PR_AUTHOR_IS(satoshi) AND FILE_CHANGED(libs/private/bitcoin/**)
If the criteria is true, what merge requirements do we need to enforce? E.g. 3 or more people need to approve the PR
Internally, we represent these criteria as a tree structure, where the leaves are metadata conditions (e.g. FILE_CHANGED(libs/private/bitcoin/**)
) and the internal nodes are boolean operators (AND
, OR
, NOT
, etc.). We then build a query by parsing this tree and use it to decide if the merge rule applies to a PR.
When a merge rule applies to a PR, we need to post a check to GitHub. For that, we compute if the PR meets the merge requirements (e.g. did at least 3 engineers approve the PR?). If check is either failing ❌ (not enough approvals), in progress 🚧 (required CI is running), or passes ✅. And we recompute this check every time we detect a change to the PR!
In Graphite, our frontend constantly polls for changes to a PR and updates mobx objects which represent the state of the PR. Whenever the state of the PR changes, we re-compute its mergeability and the UI updates.
How to get started with Graphite Protections
We’re excited to get Protections in the hands of more teams on Graphite, and we’ll begin rolling out Protections to a cohort of beta customers soon. If you want to be one of the first to try it out, sign up for the beta!