Skip to content

Instantly share code, notes, and snippets.

@tvogel
Created March 30, 2011 13:23
  • Star 26 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save tvogel/894374 to your computer and use it in GitHub Desktop.
git script to manually associate files in a conflicting merge when rename detection fails
#!/bin/bash
#
# Purpose: manually associate missed renames in merge conflicts
#
# Usage: git merge-associate <our-target> <base> <theirs>
#
# Example: After a failed rename detection A/a -> B/b which results
# in CONFLICT (delete/modify) for A/a and corresponding "deleted by us"
# messages in git status, the following invocation can be used to manually
# establish the link:
#
# git merge-associate B/b :1:A/a :3:A/a
#
# or using a shell with brace expansion:
#
# git merge-associate B/b :{1,3}:A/a
#
# This registers
# - :1:A/a as :1:B/b
# - HEAD:B/b as :2:B/b
# - :3:A/a as :3:B/b
# and replaces B/b with a merge-conflict version using "git checkout -m -- B/b".
#
# After manual resolution of B/b and "git add B/b", A/a can be resolved by
# "git rm A/a"
#
set -e
#set -x
get_tree()
{
SPEC="${!1}"
TREE=""
if [[ -z "$SPEC" ]]; then
TREE="EMPTY"
elif [[ "$SPEC" =~ ^:(.): ]]; then
TREE="INDEX:${BASH_REMATCH[1]}"
elif [[ "$SPEC" =~ ^: ]]; then
TREE="INDEX:0"
elif [[ "$SPEC" =~ ^([^:]+): ]]; then
TREE=${BASH_REMATCH[1]}
else # no colon, considered as HEAD
eval ${1}="HEAD:${!1}"
TREE="HEAD"
fi
eval ${1}_TREE="$TREE"
eval ${1}_BASENAME="${SPEC##*:}"
}
get_mode_and_sha()
{
eval SPEC="\${$1}"
eval TREE="\${$1_TREE}"
eval BASENAME="\${$1_BASENAME}"
RESULT_MODE=""
RESULT_SHA=""
case "$TREE" in
EMPTY)
RESULT_MODE="100644"
RESULT_SHA="e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
;;
INDEX:*)
[[ "$TREE" =~ :(.) ]]
REQ_STAGE=${BASH_REMATCH[1]}
while read MODE SHA STAGE FILEPATH; do
if ! [ "$STAGE" = "$REQ_STAGE" ]; then
continue
fi
RESULT_MODE="$MODE"
RESULT_SHA="$SHA"
break
done < <(git ls-files --stage "$BASENAME")
;;
*)
while read MODE TYPE SHA FILEPATH; do
RESULT_MODE="$MODE"
RESULT_SHA="$SHA"
done < <(git ls-tree "$TREE" "$BASENAME")
;;
esac
if [ -z "$RESULT_MODE" ] ; then
echo "Could not determine mode for $SPEC"
exit 1
fi
if [ -z "$RESULT_SHA" ] ; then
echo "Could not determine SHA1 for $SPEC"
exit 1
fi
eval $1_SHA="$RESULT_SHA"
eval $1_MODE="$RESULT_MODE"
}
if [ "$#" -ne 3 ]; then
echo "Usage: git merge-associate <our-target> <base> <theirs>"
exit 1
fi
TARGET="$1"
BASE="$2"
THEIRS="$3"
for VAR in TARGET BASE THEIRS; do
get_tree $VAR
get_mode_and_sha $VAR
done
git update-index --index-info <<EOI
000000 0000000000000000000000000000000000000000 0 $TARGET_BASENAME
$BASE_MODE $BASE_SHA 1 $TARGET_BASENAME
$TARGET_MODE $TARGET_SHA 2 $TARGET_BASENAME
$THEIRS_MODE $THEIRS_SHA 3 $TARGET_BASENAME
EOI
git checkout -m -- $TARGET_BASENAME
@ggascoigne
Copy link

It wasn’t awfully obvious to me as I was working out how this script worked, that you can use commit ids as well as branch names. In my case, the file similarity was low enough that there were no useful :1: or :2: references but I was able to patch things up using:

$ git merge-assist target_file_name `git merge-base HEAD other_branch_name`:original_file_name other_branch_name:other_file_name

A little hard to automate since I generally had to manually look up the file names in all of these branches, but still a very workable solution.

This script has saved me a ton of time. Thank you.

@berkus
Copy link

berkus commented Jul 11, 2016

Does not work with modern git citing:
fatal: malformed index info 000000 0000000000000000000000000000000000000000 0 XXXX/xxx.file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment