Last active
August 16, 2023 04:00
-
-
Save smileyborg/913fe3221edfad996f06 to your computer and use it in GitHub Desktop.
Two scripts that can be used to detect evil merges in Git. See http://stackoverflow.com/questions/27683077
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# A shell script to provide a meaningful diff output for a merge commit that can be used to determine whether the merge was evil. | |
# The script should be run from outside the git repository, with two arguments: | |
# 1 - the directory of the git repository | |
# 2 - the SHA for the merge commit to inspect | |
# The script will output one file: | |
# - the merge redone fresh without any conflicts resolved, diff'ed to the actual merge | |
output_file="diff.txt" | |
if [ "$#" -ne 2 ] | |
then | |
echo "ERROR: This script must be run with exactly two arguments: the directory for the Git repo, and the SHA for the merge commit to inspect." >&2 | |
echo "Usage: $0 GIT_REPO_DIR MERGE_COMMIT_SHA" >&2 | |
exit 1 | |
fi | |
# Store the current HEAD so we can put the repository back into the original state when finished | |
if ! original_head=$(git -C $1 symbolic-ref --short -q HEAD) # get the branch name, if possible | |
then | |
original_head=$(git -C $1 rev-parse HEAD) # detached HEAD, get the commit SHA | |
fi | |
# Perform the merge again, without resolving conflicts. Then diff the result with the actual merge commit we're inspecting. | |
git -C $1 checkout $2~ &>/dev/null | |
git -C $1 -c merge.conflictstyle=diff3 merge --no-ff $2^2 --no-commit &>/dev/null | |
git -C $1 add $(git -C $1 status -s | cut -c 3-) &>/dev/null | |
git -C $1 commit --no-edit &>/dev/null | |
git -C $1 diff HEAD..$2 > $output_file | |
# Put the repository back in the original state | |
git -C $1 checkout $original_head &>/dev/null |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# A shell script to provide a meaningful diff output for a merge commit that can be used to determine whether the merge was evil. | |
# The script should be run from outside the git repository, with two arguments: | |
# 1 - the directory of the git repository | |
# 2 - the SHA for the merge commit to inspect | |
# The script will output two files: | |
# - the merge redone fresh with conflicts resolved using the first parent, diff'ed to the actual merge | |
# - the merge redone fresh with conflicts resolved using the second parent, diff'ed to the actual merge | |
output_fileA="diffA.txt" | |
output_fileB="diffB.txt" | |
if [ "$#" -ne 2 ] | |
then | |
echo "ERROR: This script must be run with exactly two arguments: the directory for the Git repo, and the SHA for the merge commit to inspect." >&2 | |
echo "Usage: $0 GIT_REPO_DIR MERGE_COMMIT_SHA" >&2 | |
exit 1 | |
fi | |
# Store the current HEAD so we can put the repository back into the original state when finished | |
if ! original_head=$(git -C $1 symbolic-ref --short -q HEAD) # get the branch name, if possible | |
then | |
original_head=$(git -C $1 rev-parse HEAD) # detached HEAD, get the commit SHA | |
fi | |
# Perform the merge again, resolving conflicts using the version from the first parent. | |
# Then diff the result with the actual merge commit we're inspecting. | |
git -C $1 checkout $2~ &>/dev/null | |
git -C $1 merge --no-ff --no-edit -s recursive -Xours $2^2 &>/dev/null | |
git -C $1 diff HEAD..$2 > $output_fileA | |
# Perform the merge again, resolving conflicts using the version from the second parent. | |
# Then diff the result with the actual merge commit we're inspecting. | |
git -C $1 checkout $2~ &>/dev/null | |
git -C $1 merge --no-ff --no-edit -s recursive -Xtheirs $2^2 &>/dev/null | |
git -C $1 diff HEAD..$2 > $output_fileB | |
# Put the repository back in the original state | |
git -C $1 checkout $original_head &>/dev/null |
What do you mean by "evil" here? Possibly what I call "dirty merges"? That is to say, merges where changesets are snuck-in amidst the parents' changesets?
What do you mean by "evil" here? Possibly what I call "dirty merges"? That is to say, merges where changesets are snuck-in amidst the parents' changesets?
$ man gitglossary | grep "evil merge"
evil merge
An evil merge is a merge that introduces changes that do not appear in any parent.
@smileyborg, I'd like to suggest a few changes:
- combine the two scripts into one, so that functionality of the second script is engaged by passing an argument
- Rename this new script to something like
git-is-evil-merge
, so that it can be engaged asgit is-evil-merge
when placed into a dir in PATH - Add a new
git-detect-evil-merges
script that would iterate over commits in a branch or a range of commits (commitA..commitB) detecting evil merge commits. I think a lot of folks would benefit from such a repo
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Difference between the two scripts:
detect_evil_merge.sh
outputs a single diff, the merge redone fresh without any conflicts resolved, diff'ed to the actual mergedetect_evil_merge2.sh
outputs two diffs, the merge redone fresh twice, once resolving conflicts using each parent, diff'ed to the actual mergeInterpreting the output:
+
in the diff are lines that the actual merge commit added, as compared to the fresh (automatic) merge(s)-
in the diff are lines that the actual merge commit removed, as compared to the fresh (automatic) merge(s)Some tips: