Maintaining shadow branches for GitHub PRs
嗯,用户让我总结一篇文章,控制在一百个字以内,而且不需要特定的开头。我先看看文章内容。 文章讲的是pr-shadow这个工具,用于维护GitHub PR的影子分支,避免强制推送。主要问题是在GitHub上强制推送会导致比较混乱,评论失效,链接重复等问题。解决方案是用影子分支来同步提交,保持PR的干净。工具支持fork和同一仓库的工作流。 所以总结的时候要包括工具名称、功能、解决的问题和优势。控制在一百字以内。 可能的结构:pr-shadow工具用于维护GitHub PR的影子分支,避免强制推送带来的问题,保持PR整洁,支持多种工作流。 检查字数:大约70字左右,符合要求。 </think> 该文章介绍了一款名为pr-shadow的工具,用于维护GitHub拉取请求(PR)的影子分支,避免因强制推送导致的问题。通过同步提交到影子分支,该工具解决了比较混乱、评论失效及链接重复等问题,并支持多种工作流程。 2026-1-22 08:0:0 Author: maskray.me(查看原文) 阅读量:0 收藏

I've created pr-shadow, a tool that maintains a shadow branch for GitHub pull requests (PR) 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.

Additionally, if your commit message references an issue or another PR, each force push creates a new link on the referenced page, cluttering it with duplicate mentions. (You can work around this by adding backticks around the link text, but it is not ideal.)

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
2
3
4
5
6
Local branch (feature)     PR branch (pr/feature)
A A
| |
B (amend) C1 "Fix bug"
| |
C (rebase) C2 "Address review"

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

git checkout feature
prs init
prs init --draft


git rebase main
git commit --amend


prs push "Fix bug"
prs push --force "Rewrite"


prs desc


prs gh view
prs gh checks

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.

The name "prs" is a tribute to spr, which implements a similar shadow branch concept. However, spr pushes user branches to the main repository rather than a personal fork. While necessary for stacked pull requests, this approach is discouraged for single PRs as it clutters the upstream repository. pr-shadow avoids this by pushing to your fork by default.

I owe an apology to folks who receive users/MaskRay/feature branches (if they use the default fetch = +refs/heads/*:refs/remotes/origin/* to receive user branches). I had been abusing spr for a long time after LLVM's GitHub transition to avoid unnecessary rebuilds when switching between the main branch and PR branches.


文章来源: https://maskray.me/blog/2026-01-22-maintaining-shadow-branches-for-github-prs
如有侵权请联系:admin#unsafe.sh