Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.