你如何检测git中的邪恶合并?

smi*_*org 28 git git-merge

我已经创建了一个简单的git repo来说明我的问题,可以在GitHub上找到:https://github.com/smileyborg/EvilMerge

这是回购历史的例证:

master          A---B---D---E-----G-----I
                 \     /     \         /
another_branch    ----C       \       /
                               \     /
another_branch2                 F---H
Run Code Online (Sandbox Code Playgroud)

(在GitHub上的实际回购中,D4a48c9,I48349d.)

D是一个"简单"的邪恶合并,其中合并提交"正确"解决了合并冲突,但也产生了一个不相关的"邪恶"变化,这两个变化在父系统中都不存在.通过git show -c在此提交上使用,可以发现此合并的"邪恶"部分,因为输出包括++--(而不是单个+-)以指示父级中不存在的更改(请参阅上下文的答案).

I一种别样邪恶的合并,其中合并提交"正确"的解决合并冲突(从变化所引起F,以file.txt与变化相冲突G),也有"邪恶"丢弃到一个完全不同的文件中所做的更改file2.txt(有效撤消变化H).

你怎么知道这I是一个邪恶的合并?换句话说,您可以使用哪些命令来发现I不仅手动解决冲突,而且还无法合并应该具有的更改?

编辑/更新:什么是邪恶合并?

正如下面的RenéLink所指出的那样,很难(或许不可能)定义一套通用标准来识别"邪恶合并".然而,就像最高法院大法官斯图尔特所说的关于色情内容一样,当你看到邪恶的合并是你所知道的.

因此,或许更好的问题是:在合并提交中可以使用什么git命令来获得仅在合并提交本身中引入的所有新颖更改的diff输出.这种差异应包括:

  • 所有合并冲突解决方案(至少,如果决议涉及的事情比选择一个父母的变化比另一个更复杂)
  • 父母之间不存在的所有添加或删除(如中所示D)
  • 其中一个父项中存在的所有更改但合并提交丢弃(如中所示I)

这里的目标是能够让人类看看这个输出并知道合并是否成功或(意外或恶意)"邪恶",不必重新审查所有先前审查的变化(例如FH)整合在合并中.

Jos*_*uss 7

最简单的方法是使用合并自动解决冲突而不需要人为干预来解决冲突解决的结果.任何自动分辨率都将被忽略,因为它们将以完全相同的方式解析.

我看到两种可视化可能的"邪恶"决议的方法.如果要将其添加&> /dev/null到脚本中,请添加到您不关心输出的所有行的末尾.

1)使用两个单独的差异,一个有利于第一个父母,另一个有利于第二个父母.

MERGE_COMMIT=<Merge Commit>
git checkout $MERGE_COMMIT~
git merge --no-ff --no-edit -s recursive -Xours $MERGE_COMMIT^2
echo "Favor ours"
git diff HEAD..$MERGE_COMMIT
git checkout $MERGE_COMMIT~
git merge --no-ff --no-edit -s recursive -Xtheirs $MERGE_COMMIT^2
echo "Favor theirs"
git diff HEAD..$MERGE_COMMIT
Run Code Online (Sandbox Code Playgroud)

2)对冲突合并的结果与仍存在的冲突的差异.

MERGE_COMMIT=<Merge Commit>
git checkout $MERGE_COMMIT~
git -c merge.conflictstyle=diff3 merge --no-ff $MERGE_COMMIT^2 --no-commit
git add $(git status -s | cut -c 3-)
git commit --no-edit
git diff HEAD..$MERGE_COMMIT
Run Code Online (Sandbox Code Playgroud)


Jos*_*uss 6

免责声明:正如 @smileyborg 所指出的,此解决方案不会检测到邪恶合并完全恢复了其中一位父母引入的更改的情况。发生此缺陷的原因是根据 Git 文档的-c选项

此外,它仅列出所有父级修改的文件。

我最近发现了一个比当前任何答案都简单得多的解决方案。

git show基本上,合并提交的默认行为应该可以解决您的问题。如果合并双方的修改不接触,并且没有进行“邪恶”更改,则不会有 diff 输出。我之前认为git show永远不会显示合并提交的差异。但是,如果合并提交涉及混乱的冲突或邪恶的合并,则差异将以组合格式显示。

要在使用 查看多个提交补丁时查看组合格式log -p,只需添加参数即可--cc

在问题中 GitHub 给出的示例中,显示以下内容(其中散布着我的评论):

$ git show 4a48c9
Run Code Online (Sandbox Code Playgroud)

(示例中的D )

commit 4a48c9d0bbb4da5fb30e1d24ae4e0a4934eabb8d
Merge: 0fbc6bb 086c3e8
Author: Tyler Fox <Tyler_Fox@intuit.com>
Date:   Sun Dec 28 18:46:08 2014 -0800

    Merge branch 'another_branch'

    Conflicts:
        file.txt

diff --cc file.txt
index 8be441d,f700ccd..fe5c38a
--- a/file.txt
+++ b/file.txt
@@@ -1,9 -1,7 +1,9 @@@
  This is a file in a git repo used to demonstrate an 'evil merge'.
Run Code Online (Sandbox Code Playgroud)

下面的几行并不是邪恶的。第一个父级所做的更改由最左列中的+/表示;第二个父级所做的更改由第二列中的 /-表示。+-

- int a = 0;
- int b = 1;
+ int a = 1;
+ int b = 0;
 +int c = 2;
- a = b;
+ b = a;
  a++;
Run Code Online (Sandbox Code Playgroud)

这是邪恶的部分:是来自父母双方++的改变。请注意前导并指示这些更改均来自父母双方,这意味着有人在此提交中引入了尚未反映在父母之一中的新更改。不要将差异生成的前导/与作为文件内容一部分的尾随/混淆。----++++--++--

--b++;
++b-- ;
Run Code Online (Sandbox Code Playgroud)

恶事尽尽。

 +c++;
Run Code Online (Sandbox Code Playgroud)

要快速查看可能有问题的所有合并提交:

git log --oneline --min-parents=2 --cc -p --unified=0
Run Code Online (Sandbox Code Playgroud)

所有无趣的合并都将显示在一行上,而混乱的合并(邪恶的或其他的)将显示组合的差异。

解释:

  • -p- 显示补丁
  • --oneline- 在一行上显示每个提交标头
  • --min-parents=2- 只显示合并。
  • --cc- 显示组合差异,但仅适用于父母双方的更改重叠的地方
  • --unified=0- 显示0行上下文;修改数字以更积极地发现邪恶合并。

或者,添加以下内容以消除所有无趣的提交:

-z --color=always | perl -pe 's/^([^\0]*\0\0)*([^\0]*\0\0)(.*)$/\n$2\n$3/'
Run Code Online (Sandbox Code Playgroud)
  • -z- 在提交日志末尾显示 NUL 而不是换行符
  • --color=always- 通过管道传输到 Perl 时不要关闭颜色
  • perl -pe 's/^([^\0]*\0\0)*([^\0]*\0\0)- 按摩输出以隐藏具有空差异的日志条目


Ren*_*ink 5

在我们检测到邪恶合并之前,我们必须定义什么是邪恶合并。

每个有冲突的合并都必须手动解决。为了解决冲突,我们可以

  1. 采取其中一个更改并省略另一个。
  2. 最终进行两个更改(在这种情况下,结果中的顺序可能很重要)
  3. 不采取任何措施并创建一个新的变化,即两者的整合。
  4. 不带任何一个并省略两者。

那么现在什么是邪恶合并?

根据这个博客

如果合并没有忠实地整合所有父级的所有更改,则它被认为是邪恶的。

那么什么是“忠实整合”呢?我认为没有人可以给出一般性的答案,因为这取决于代码或文本的语义或合并的任何内容。

其他说

邪恶的合并是一种合并,它引入了没有出现在任何父级中的更改。

有了这个定义,解决的所有冲突

  1. 采取其中一个更改并省略另一个。
  2. 不采取任何措施并创建一个新的变化,即两者的整合。
  3. 不带任何一个并省略两者。

是邪恶的合并。

所以我们终于来回答问题了。

是否合法

  • 只采用其中一个更改而忽略另一个?
  • 采取两种改变?
  • 不采取任何措施并创建一个新的变化,即合并两者?
  • 一个都不拿,两个都省略?

如果我们考虑章鱼合并,事情会变得更加复杂。

我的结论

我们可以检测到的唯一邪恶合并是在没有冲突的情况下完成的合并。在这种情况下,我们可以重做合并并将其与已经完成的合并进行比较。如果存在的差异比某人介绍的要多,我们可以确定这次合并是一个邪恶的合并。

至少我认为我们必须手动检测邪恶合并,因为这取决于更改的语义,我无法对什么是邪恶合并制定正式的定义。


smi*_*org 4

我扩展了Joseph K. Strauss 的答案,创建了两个完整的 shell 脚本,可以轻松地使用它们为给定的合并提交获取有意义的差异输出。

这些脚本可在此 GitHub Gist 中找到:https://gist.github.com/smileyborg/913fe3221edfad996f06

第一个脚本,detect_evil_merge.sh使用自动重做合并而不解决任何冲突的策略,然后将其与实际合并进行比较。

第二个脚本,detect_evil_merge2.sh使用自动重做两次合并的策略,一次解决与第一个父版本的冲突,第二次使用第二个父版本解决冲突,然后将每个版本与实际合并进行比较。

任何一个脚本都可以完成这项工作,这只是个人喜好,您会发现哪种方式更容易理解冲突是如何解决的。