Maintaining shadow branches for GitHub PRs
I've created
The problem
GitHub structures pull requests around branches, enforcing abranch-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 unrelatedupstream commits—not the actual patch difference. For a project likeLLVM with 100+ commits daily, this makes the comparison essentiallyuseless.
Inline comments suffer too: they may become "outdated" or misplacedafter force pushes.
Additionally, if your commit message references an issue or anotherPR, each force push creates a new link on the referenced page,cluttering it with duplicate mentions. (You can work around this byadding backticks around the link text, but it is not ideal.)
Due to these difficulties, some recommendations suggest
In a large repository, avoiding rebases isn't realistic—other commitsfrequently modify nearby lines, and rebasing is often the only way todiscover that your patch needs adjustments due to interactions withother 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 syncto the PR branch using git commit-tree to create a commitwith 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 theunderlying commits were rewritten.
When a rebase is detected (git merge-base withmain/master changed), the new PR commit is created as a merge commitwith the new merge-base as the second parent. GitHub displays these as"condensed" merges, preserving the diff view for reviewers.
Usage
1 |
# Initialize and create PR |
The tool supports both fork-based workflows (pushing to your fork)and same-repo workflows (for branches likeuser/<name>/feature). It also works with GitHubEnterprise, auto-detecting the host from the repository URL.
Related work
The name "prs" is a tribute to
I owe an apology to folks who receiveusers/MaskRay/feature branches (if they use the defaultfetch = +refs/heads/*:refs/remotes/origin/* to receive userbranches). I had been abusing spr for a long time after
Additionally, spr embeds a PR URL in commit messages (e.g.,Pull Request: https://github.com/llvm/llvm-project/pull/150816),which can cause downstream forks to add unwanted backlinks to theoriginal PR.
If I need stacked pull requests, I will probably use pr-shadow withthe base patch and just rebase stacked ones - it's unclear how sprhandles stacked PRs.