我的主分支历史必须是线性的,每个提交都得挂上绿色的 Verified。GitHub 给了三个合并按钮,但没一个能同时满足这两点。问题出在签名机制本身:签名签的不是改动内容,而是 commit 对象。对象里装着父提交、作者、时间戳等元数据,任何字段变动都会导致 SHA 变化,旧签名立刻作废。
所以当你点下不同的合并按钮,其实是在决定这些对象要以什么方式被重写。
Merge 按钮:历史分叉,签名还在
这是最老实的做法。它不重写 PR 分支上的提交,SHA 不变,你本地的签名大概率能被 GitHub 识别成 Verified。代价是主分支里会多出一个 merge 节点,git log 看起来像蜘蛛网。线性历史没了。
Squash 按钮:历史线性,签名是代签的
Squash 把 PR 里的所有提交压成一个新提交,主分支保持一条直线。最终提交也显示 Verified,但那是 GitHub 用自己的密钥帮你签的——它没有你的私钥,只能重新创建一个对象并代签。你原来的签名并没跟着代码进主分支。
如果只看最后那个绿色标记,这似乎能接受;但如果审计时需要追溯到你的个人签名,这条路就断了。
Rebase 按钮:看似两全,实则签名归零
Rebase 会把 PR 的提交在 main 顶端重放一遍。提交的内容没变,但父提交变了,时间戳变了,SHA 全变。而你本地的签名是签在旧 SHA 上的,新对象没你的签名。GitHub 又没有你的私钥,无法自动重签,结果就是整整齐齐一排灰色的 Unverified。
线性历史倒是有了,但签名的意义也丢了。
真正兼顾的办法:Fast-forward
要同时保住线性历史和原始签名,唯一的方式是 不重写对象,只快进指针。你在本地执行:
git merge --ff-only <pr-branch>
这会让 main 直接指向 PR 分支的头部,不创建新 commit,所有提交的 SHA 和签名纹丝不动。再 git push origin main,主分支上就是一条直线,每个提交都是你亲自签过的 Verified。
麻烦在于,GitHub 的 Web UI 里没有'强制 fast-forward'的按钮。只能本地操作后推送。
如果必须在按钮里选
很多时候没法绕开 UI,那就得取舍。
- 签名优先:用 Merge 按钮。历史里有分叉,但每个提交的签名都原汁原味保留下来。
- 线性优先:用 Squash 按钮。主分支干净得像一本教科书,最后的代签也算有个绿色标记。中间提交的历史上下文会被丢掉。
- Rebase 按钮:看着线性,但把签名全刷成了 Unverified,两头不讨好,我基本不用。
GitHub 的合并策略本质上用重写对象换线性,用无私钥换取签名失效。真想既要又要,就只能离开按钮,自己动手。


