有没有一种比较两个差异或补丁的方法?

50

有没有办法测试两个差异或补丁是否相等?

假设你有以下git提交历史记录,其中特性F和G可以干净地rebase到E:

     G
    /
A--B--C--D--E
 \
  F

由于我们当前部署过程的局限性,我们有以下的、有点相关的图表(它没有版本控制)

              G'
             /
------------E'
             \
              F'

最终会以某种待确定的顺序将 F' 和 G' 应用于头部 E',因此它最终会变成

------------E'--G'--F'
有没有一种方法可以测试从 E' 到 G' 的差异是否与由 G 从 B 提交的补丁相同?
我完全意识到在理想的情况下,版本控制会解决这个问题,而我们正在朝着那个方向发展,但现在还不是那个阶段。
您可以在独立的检出上播放两个补丁并比较输出,但这似乎有些笨重。而且假设比较差异本身是行不通的,因为行号可能会更改。即使 G' 和 F' 被变基到 E',F' 的补丁最终也会应用到 G' 上,从而使补丁的差异上下文不同。

3
你尝试对差异进行差异比较了吗? - Geoffroy
Gnu diff有一个命令行开关,用于指定在生成差异时要忽略的行的正则表达式。 - holygeek
对于批处理,可以使用 git patch-idgit cherry(还可以查看 git log 的 --cherry* 选项)作为快速答案,但它太严格了,可能会将一些微小的更改视为重要。当您需要确定的答案时,差异比较是正确的方法。 - max630
4个回答

58
diff <(git show COMMIT1SHA) <(git show COMMIT2SHA)

3
你能否为这个回答增加一些背景信息? - Chrismas007
2
它比较(diffs)两个差异(在这种情况下是两个不同提交的差异)。 - mrbrdo
1
它会生成实际更改的差异,以及提交的元数据(sha、作者、日期、提交信息等)。我用它来检查两个分支中的提交是否引入了相同的更改,以确保我可以使用其中任何一个进行 cherry-pick。 - Amedee Van Gasse
25
对于任何想知道 <(...) 标记是什么的人,谷歌关键词是“进程替换”。请参阅 http://www.tldp.org/LDP/abs/html/process-sub.html。 - Linus Arver
4
我曾经尝试使用过这种方法,发现效果不太好。但是今天我受到启发,添加了“--ignore-matching-lines”选项,可以跳过那些只显示行号差异的无用变更(当比较非常相似的提交时,行号是不可避免的干扰因素)。所以可以尝试像这样使用变体: %diff -U3 --ignore-matching-lines="^@@ " <(git show b7f9f919) <(git show 8ce6b2af) - Ted
显示剩余2条评论

49
自 Git 2.19 起,有一个额外的工具可以做到这一点:git range-diff
所以你可能想要:
git range-diff E..G E'..G'

这个应该适用于每个范围中不止一个补丁。


这个应该是 git range-diff E..G E'..G' 吗? - mkrieger1
2
@mkrieger1 确实,我没有注意到有一个 E'。如果你使用足够新的 git,你也可以这样做:git range-diff G^! G'^!',那么实际的基础就不重要了。我会更正我的答案。感谢提示。 - Uwe Kleine-König
这里的�引� E'..G' 是什么��?🤔 - Lajos
1
@Lajos 与原始问题中相同:E'E的一个变体(例如,重新定位)。因此,它只是一个修订名称,在实际存储库上工作时需要进行调整。 - Uwe Kleine-König
有人建议使用 git range-diff B..G E'..G'。请注意,这两种变体是等价的。虽然 E 不是 G 的祖先,但语法仍然正确,该范围仅包含提交 G - Uwe Kleine-König

4

为了读者的方便,这里更新了@mrbrdo答案并进行了一些微调:

  • 添加git sdiff别名以便容易访问。 sdiff代表show diff
  • 使用^{}后缀忽略注释标签头。
  • 允许使用-udiff选项。

在每个使用git的帐户中运行一次:

git config --global alias.sdiff '!'"bash -c 'O=(); A=(); while x=\"\$1\"; shift; do case \$x in -*) O+=(\"\$x\");; *) A+=(\"\$x^{}\");; esac; done; g(){ git show \"\${A[\$1]}\" && return; echo FAIL \${A[\$1]}; git show \"\${A[\$2]}\"; }; diff \"\${O[@]}\" <(g 0 1) <(g 1 0)' --"

之后您可以使用这个:

git sdiff F G

请注意,这需要bash版本3或以上。

解释:

  • git config --global alias.sdiff 将别名 git sdiff 添加到全局的 ~/.gitconfig 中。

  • ! 作为 shell 命令运行别名。

  • bash -c 我们需要使用 bash(或 ksh),因为 <(..)dash(即 /bin/sh)中无法工作。

  • O=(); A=(); while x="$1"; shift; do case $x in -*) O+=("$x");; *) A+=("$x^{}");; esac; done; 分离选项(-something)和参数(其他所有内容)。选项位于数组 O 中,而参数位于数组 A 中。请注意,所有参数也都会附加上 ^{},这样可以跳过注释(以便您可以使用带注释的标签)。

  • g(){ git show "${A[$1]}" && return; echo FAIL ${A[$1]}; git show "${A[$2]}"; }; 创建一个辅助函数,它对第一个参数执行 git show。如果失败,则输出 "FAIL first-argument",然后输出第二个参数。这是一种减少开销的技巧,以防出现错误。 (适当的错误管理会太过繁琐。)

  • diff "${O[@]}" <(g 0 1) <(g 1 0) 使用给定的选项运行 diff 对第一个参数和第二个参数进行比较(使用上述 FAIL 错误回退到另一个参数以减少 diff 的负担)。

  • -- 允许将 diff 选项(-something)传递给此别名/脚本。

问题:

  • 未检查参数数量。第二个参数后面的所有内容都会被忽略,如果给出的参数太少,你只能看到FAIL或什么也没有。

  • 错误信息有点奇怪并且会混杂在输出中。如果你不喜欢这样,可以使用 2>/dev/null 运行它(或相应地更改脚本)。

  • 如果某些东西出现了问题,它不会返回错误。

  • 你可能希望默认将其输入到分页程序中。

请注意,很容易定义一些其他的别名,例如:

git config --global alias.udiff '!git sdiff -u'
git config --global alias.bdiff '!git sdiff -b'

git config --global alias.pager '!pager() { cd "$GIT_PREFIX" && git -c color.status=always -c color.ui=always "$@" 2>&1 | less -XFR; }; pager'
git config --global alias.ddiff '!git pager udiff'

我希望这不需要进一步解释。
如果需要更多类似的别名,可以查看我的GitHub仓库

0

我正在寻找一个答案,虽然 diff <(git show XYZ) <(git show ABC) 可以工作,但它仍然在 diff 中显示提交 ID,你需要考虑结果。使用 git diff-tree 可以解决这个问题:

diff -q \
   <(git diff-tree XYZ --no-commit-id --cc) \
   <(git diff-tree ABC --no-commit-id --cc)

这只比较两个差异,您不必考虑提交 ID 被 diff 的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接