You can help Git by simulating a merge with everything already renamed. Using Bash for example,
# Mark this as the branch to be merged.
git update-ref --stdin <<<'create MERGE_HEAD stable'
printf '%s\t\t%s\n' $(git rev-parse MERGE_HEAD) "branch 'stable'" |
>$(git rev-parse --git-dir)/MERGE_MSG git fmt-merge-msg \
$([[ $(git config --bool merge.log) = true ]] && echo --log)
# This outputs a tree object with some paths transformed.
refactor-tree() {
(
index1= index2=
trap 'rm -f "${index1}" "${index2}"' 0
index1=$(mktemp) index2=$(mktemp)
GIT_INDEX_FILE=${index1} git read-tree "$@"
GIT_INDEX_FILE=${index2} git read-tree --empty
GIT_INDEX_FILE=${index1} git ls-files -s -z |
while IFS=$'\t' read -d '' -r info file; do
[[ ${file} = app/scripts/site/* ]] && file=app/modules/${file#*/*/*/}
printf '%s\t%s\0' "${info}" "${file}"
done |
GIT_INDEX_FILE=${index2} git update-index --index-info
GIT_INDEX_FILE=${index2} git write-tree
)
}
# Update the index for the 3-way merge.
git read-tree -m --aggressive -u \
$(refactor-tree $(git merge-base MERGE_HEAD HEAD)) \
$(git write-tree) \
$(refactor-tree MERGE_HEAD)
# Resolve the merge.
git merge-index -o git-merge-one-file -a
# Commit the results.
git commit
The above was tested on a demo repository created like this:
git init
mkdir -p app/scripts/site
cat >app/scripts/site/README.md <<EOF
Hello, world!
=============
EOF
git add app/scripts/site/README.md
git commit -m 'base'
git checkout -b stable
sed -i -e '1s/w/W/' app/scripts/site/README.md
git add app/scripts/site/README.md
git commit -m 'hotfix'
git checkout master -b develop
git mv app/scripts/site app/modules
rm -rf app/scripts
git commit -m 'refactor'
cat >>app/modules/README.md <<EOF
blah blah blah
EOF
git add app/modules/README.md
git commit -m 'blah'
Notice that if you remove the =============
line, the merge will fail due to a conflict in that file, but it gets context markers like usual. You can resolve it by hand and try to commit again.