Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you detect an evil merge in git?

Tags:

git

git-merge

I've created a simple git repo to illustrate my question, available on GitHub here: https://github.com/smileyborg/EvilMerge

Here's an illustration of the repo history:

master          A---B---D---E-----G-----I                  \     /     \         / another_branch    ----C       \       /                                \     / another_branch2                 F---H 

(In the actual repo on GitHub, D is 4a48c9, and I is 48349d.)

D is a "simple" evil merge, where the merge commit "correctly" resolves a merge conflict, but also makes an unrelated "evil" change that did not exist in either parent. It is possible to discover the "evil" part of this merge by using git show -c on this commit, as the output includes ++ and -- (as opposed to single + and -) to indicate the changes that did not exist in either parent (see this answer for context).

I is a different kind of evil merge, where the merge commit "correctly" resolves a merge conflict (caused by changes from F to file.txt that conflict with changes from G), but also "evilly" discards the changes made to a completely different file file2.txt (effectively undoing the changes from H).

How can you know that I is an evil merge? In other words, what command(s) can you use to discover that I not only manually resolves a conflict, but also fails to merge changes that it should have?

Edit/Update: What is an evil merge?

As pointed out by René Link below, it is hard (perhaps impossible) to define a generic set of criteria to identify an "evil merge". However, much like Supreme Court Justice Stewart said about pornography, evil merges are something you know when you see.

So perhaps a better question to ask is this: what git command(s) can you use on a merge commit to get a diff output of all novel changes introduced solely in the merge commit itself. This diff should include:

  • all merge conflict resolutions (at least, if the resolution involved anything more complex than choosing one parent's changes over the other's)
  • all additions or removals that did not exist in either parent (as seen in D)
  • all changes that did exist in one of the parents but that the merge commit discards (as seen in I)

The goal here is to be able to have a human look at this output and know whether the merge was successful or (accidentally or maliciously) "evil" without having to re-review all the previously-reviewed changes (e.g. F and H) that are being integrated in the merge.

like image 727
smileyborg Avatar asked Dec 29 '14 03:12

smileyborg


People also ask

How does Git detect merge conflicts?

For simple text files, Git uses an approach known as the longest common subsequence algorithm to perform merges and to detect merge conflicts. In its simplest form, Git find the longest set of lines in common between your changed file and the common ancestor.

What is an evil merge?

An evil merge is a merge that introduces changes that do not appear in any parent.

How do you check if there is merge conflict?

To see the beginning of the merge conflict in your file, search the file for the conflict marker <<<<<<< . When you open the file in your text editor, you'll see the changes from the HEAD or base branch after the line <<<<<<< HEAD .


1 Answers

The easiest thing to do would be to diff the results of your conflict resolution with a merge that auto-resolves conflicts without human intervention. Any automatic resolutions will be ignored, since they will be resolved in exactly the same way.

I see two ways of visualizing the possible "evil" resolutions. If you are making this into a script add &> /dev/null to the end of all lines that you do not care to see output.

1) Use two separate diffs, one that favors the first parent, and a second that favors the second parent.

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 

2) Diff against the results of the conflicted merge with the conflicts still in.

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 
like image 113
Joseph K. Strauss Avatar answered Sep 24 '22 20:09

Joseph K. Strauss