处理混乱的 pull-request diffstat¶
子系统维护者通常会使用 git request-pull 作为将工作发送到上游(upstream)流程的一部分。通常,结果会包含一个不错的 diffstat,显示哪些文件会被触动以及每个文件被更改的程度。然而,有时一个具有相对复杂开发历史的存储库会产生一个巨大的 diffstat,其中包含大量不相关的工作。结果看起来很糟糕,并且掩盖了 pull request 实际所做的事情。本文档描述了正在发生的事情以及如何修复它;它源自 Linus Torvalds 的智慧,见于 Linus1 和 Linus2。
Git 开发历史以一系列提交 (commit) 的形式进行。简化来看,主线内核开发看起来是这样的
... vM --- vN-rc1 --- vN-rc2 --- vN-rc3 --- ... --- vN-rc7 --- vN
如果想要查看两点之间发生了什么变化,像这样的命令就可以完成这项工作
$ git diff --stat --summary vN-rc2..vN-rc3
这里,历史中有两个清晰的点;Git 本质上会从结束点“减去”开始点,并显示由此产生的差异。所请求的操作非常明确且易于理解。
当子系统维护者创建一个分支并向其提交更改时,最简单的情况下的结果是看起来像这样的历史
... vM --- vN-rc1 --- vN-rc2 --- vN-rc3 --- ... --- vN-rc7 --- vN
|
+-- c1 --- c2 --- ... --- cN
如果该维护者现在使用 git diff 来查看主线分支(我们称之为“linus”)和 cN 之间发生了什么变化,仍然有两个清晰的端点,结果也是预期的那样。因此,使用 git request-pull 生成的 pull request 也将符合预期。但现在考虑一个稍微复杂的开发历史
... vM --- vN-rc1 --- vN-rc2 --- vN-rc3 --- ... --- vN-rc7 --- vN
| |
| +-- c1 --- c2 --- ... --- cN
| /
+-- x1 --- x2 --- x3
我们的维护者在 vN-rc1 处创建了一个分支,在 vN-rc2 处创建了另一个分支;随后这两个分支被合并到了 c2 中。现在,为 cN 生成的 pull request 最终可能会非常混乱,开发人员通常会纳闷这是为什么。
这里发生的情况是,git diff 操作不再有两个清晰的端点可以使用。导致 cN 的开发起始于两个不同的地方;为了生成 diffstat,git diff 最终不得不选择其中之一并祈求好运。如果 diffstat 从 vN-rc1 开始,它可能最终会包含从那里到第二个原始端点 (vN-rc2) 之间的所有更改,这肯定不是我们维护者想要的。由于 diffstat 中有所有这些额外的垃圾,可能无法判断导致 cN 的更改中实际发生了什么。
维护者经常尝试通过例如变基 (rebase) 分支或执行与 linus 分支的另一次合并,然后重新创建 pull request 来解决这个问题。这种方法往往不会让收到该 pull request 的人感到高兴;在推送到上游之前进行变基和/或合并是导致收到抱怨回复的众所周知的方法。
那么该怎么做呢?面对这种情况,最好的应对方法确实是与你打算将你的工作合并进去的分支进行一次合并,但要私下进行,就好像它是羞耻的源头一样。创建一个新的、一次性的分支并在那里进行合并
... vM --- vN-rc1 --- vN-rc2 --- vN-rc3 --- ... --- vN-rc7 --- vN
| | |
| +-- c1 --- c2 --- ... --- cN |
| / | |
+-- x1 --- x2 --- x3 +------------+-- TEMP
合并操作解决了由多个起点导致的所有复杂问题,产生了一个仅包含与主线分支差异的连贯结果。现在可以生成包含所需信息的 diffstat 了
$ git diff -C --stat --summary linus..TEMP
保存此命令的输出,然后直接删除 TEMP 分支;绝对不要将其暴露给外界。取出保存的 diffstat 输出并将其编辑到混乱的 pull request 中,从而得到一个显示实际情况的结果。然后就可以将该请求发送到上游了。