I've created pr-shadow, a tool that maintains a shadow branch for GitHub PRs that never requires force-pushing. This addresses pain points I described in Reflections on LLVM's switch to GitHub pull requests.
The problem
GitHub structures pull requests around branches, enforcing a
branch-centric workflow. When you force-push a branch after a rebase,
the UI displays "force-pushed the BB branch from X to Y". Clicking
"compare" shows git diff X..Y, which includes unrelated
upstream commits—not the actual patch difference. For a project like
LLVM with 100+ commits daily, this makes the comparison essentially
useless.
Inline comments suffer too: they may become "outdated" or misplaced after force pushes. Due to these difficulties, some recommendations suggest less flexible workflows that only append new commits and discourage rebases. When working with both the latest main branch and the pull request branch, switching between branches results in numerous rebuilds.
In a large repository, avoiding rebases isn't realistic—other commits frequently modify nearby lines, and rebasing is often the only way to discover that your patch needs adjustments due to interactions with other landed changes.
The solution
pr-shadow maintains a separate PR branch (e.g.,
pr/feature) that only receives commits—never force-pushed.
You work freely on your local branch (rebase, amend, squash), then sync
to the PR branch using git commit-tree to create a commit
with the same tree but parented to the previous PR HEAD.
1 | Local branch (feature) PR branch (pr/feature) |
Reviewers see clean diffs between C1 and C2, even though the underlying commits were rewritten.
When a rebase is detected (merge-base with main/master
changed), the new PR commit is created as a merge commit with the new
merge-base as the second parent. GitHub displays these as "condensed"
merges, preserving the diff view for reviewers.
Usage
1 |
|
The tool supports both fork-based workflows (pushing to your fork)
and same-repo workflows (for branches like
user/<name>/feature). It also works with GitHub
Enterprise, auto-detecting the host from the repository URL.
Prior art
The program name "prs" is a tribute to my previous favorite, https://github.com/spacedentist/spr. I actually use
Justin Bogner's fork to have a succinct message (removing the reviewer
list from the commit message). The tool resembles Phabricator's
arc diff and is great.
However, the tool pushes a user branch to the main repository (instead of a fork) to create a PR. While this is necessary for stacked pull requests, abusing it for single pull requests is discouraged. Our Lead maintainer Nikita Popov is not happy with this - but I have abused this for a long time to make my life less miserable (I don't want numerous rebuilds due to switching between old branches).