#!/bin/sh test_description='Merge-recursive merging renames' . ./test-lib.sh modify () { sed -e "$1" <"$2" >"$2.x" && mv "$2.x" "$2" } test_expect_success setup \ ' cat >A <<\EOF && a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb c cccccccccccccccccccccccccccccccccccccccccccccccc d dddddddddddddddddddddddddddddddddddddddddddddddd e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee f ffffffffffffffffffffffffffffffffffffffffffffffff g gggggggggggggggggggggggggggggggggggggggggggggggg h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk l llllllllllllllllllllllllllllllllllllllllllllllll m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn o oooooooooooooooooooooooooooooooooooooooooooooooo EOF cat >M <<\EOF && A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO EOF git add A M && git commit -m "initial has A and M" && git branch white && git branch red && git branch blue && git branch yellow && git branch change && git branch change+rename && sed -e "/^g /s/.*/g : master changes a line/" A+ && mv A+ A && git commit -a -m "master updates A" && git checkout yellow && rm -f M && git commit -a -m "yellow removes M" && git checkout white && sed -e "/^g /s/.*/g : white changes a line/" B && sed -e "/^G /s/.*/G : colored branch changes a line/" N && rm -f A M && git update-index --add --remove A B M N && git commit -m "white renames A->B, M->N" && git checkout red && sed -e "/^g /s/.*/g : red changes a line/" B && sed -e "/^G /s/.*/G : colored branch changes a line/" N && rm -f A M && git update-index --add --remove A B M N && git commit -m "red renames A->B, M->N" && git checkout blue && sed -e "/^g /s/.*/g : blue changes a line/" C && sed -e "/^G /s/.*/G : colored branch changes a line/" N && rm -f A M && git update-index --add --remove A C M N && git commit -m "blue renames A->C, M->N" && git checkout change && sed -e "/^g /s/.*/g : changed line/" A+ && mv A+ A && git commit -q -a -m "changed" && git checkout change+rename && sed -e "/^g /s/.*/g : changed line/" B && rm A && git update-index --add B && git commit -q -a -m "changed and renamed" && git checkout master' test_expect_success 'pull renaming branch into unrenaming one' \ ' git show-branch git pull . white && { echo "BAD: should have conflicted" return 1 } git ls-files -s test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages for B" return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" return 1 } sed -ne "/^g/{ p q }" B | grep master || { echo "BAD: should have listed our change first" return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" return 1 } ' test_expect_success 'pull renaming branch into another renaming one' \ ' rm -f B git reset --hard git checkout red git pull . white && { echo "BAD: should have conflicted" return 1 } test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages" return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" return 1 } ' test_expect_success 'pull unrenaming branch into renaming one' \ ' git reset --hard git show-branch git pull . master && { echo "BAD: should have conflicted" return 1 } test "$(git ls-files -u B | wc -l)" -eq 3 || { echo "BAD: should have left stages" return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" return 1 } ' test_expect_success 'pull conflicting renames' \ ' git reset --hard git show-branch git pull . blue && { echo "BAD: should have conflicted" return 1 } test "$(git ls-files -u A | wc -l)" -eq 1 || { echo "BAD: should have left a stage" return 1 } test "$(git ls-files -u B | wc -l)" -eq 1 || { echo "BAD: should have left a stage" return 1 } test "$(git ls-files -u C | wc -l)" -eq 1 || { echo "BAD: should have left a stage" return 1 } test "$(git ls-files -s N | wc -l)" -eq 1 || { echo "BAD: should have merged N" return 1 } sed -ne "/^g/{ p q }" B | grep red || { echo "BAD: should have listed our change first" return 1 } test "$(git diff white N | wc -l)" -eq 0 || { echo "BAD: should have taken colored branch" return 1 } ' test_expect_success 'interference with untracked working tree file' ' git reset --hard git show-branch echo >A this file should not matter git pull . white && { echo "BAD: should have conflicted" return 1 } test -f A || { echo "BAD: should have left A intact" return 1 } ' test_expect_success 'interference with untracked working tree file' ' git reset --hard git checkout white git show-branch rm -f A echo >A this file should not matter git pull . red && { echo "BAD: should have conflicted" return 1 } test -f A || { echo "BAD: should have left A intact" return 1 } ' test_expect_success 'interference with untracked working tree file' ' git reset --hard rm -f A M git checkout -f master git tag -f anchor git show-branch git pull . yellow || { echo "BAD: should have cleanly merged" return 1 } test -f M && { echo "BAD: should have removed M" return 1 } git reset --hard anchor ' test_expect_success 'updated working tree file should prevent the merge' ' git reset --hard rm -f A M git checkout -f master git tag -f anchor git show-branch echo >>M one line addition cat M >M.saved git pull . yellow && { echo "BAD: should have complained" return 1 } test_cmp M M.saved || { echo "BAD: should have left M intact" return 1 } rm -f M.saved ' test_expect_success 'updated working tree file should prevent the merge' ' git reset --hard rm -f A M git checkout -f master git tag -f anchor git show-branch echo >>M one line addition cat M >M.saved git update-index M git pull . yellow && { echo "BAD: should have complained" return 1 } test_cmp M M.saved || { echo "BAD: should have left M intact" return 1 } rm -f M.saved ' test_expect_success 'interference with untracked working tree file' ' git reset --hard rm -f A M git checkout -f yellow git tag -f anchor git show-branch echo >M this file should not matter git pull . master || { echo "BAD: should have cleanly merged" return 1 } test -f M || { echo "BAD: should have left M intact" return 1 } git ls-files -s | grep M && { echo "BAD: M must be untracked in the result" return 1 } git reset --hard anchor ' test_expect_success 'merge of identical changes in a renamed file' ' rm -f A M N git reset --hard && git checkout change+rename && GIT_MERGE_VERBOSITY=3 git merge change | grep "^Skipped B" && git reset --hard HEAD^ && git checkout change && GIT_MERGE_VERBOSITY=3 git merge change+rename | grep "^Skipped B" ' test_expect_success 'setup for rename + d/f conflicts' ' git reset --hard && git checkout --orphan dir-in-way && git rm -rf . && mkdir sub && mkdir dir && printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file && echo foo >dir/file-in-the-way && git add -A && git commit -m "Common commmit" && echo 11 >>sub/file && echo more >>dir/file-in-the-way && git add -u && git commit -m "Commit to merge, with dir in the way" && git checkout -b dir-not-in-way && git reset --soft HEAD^ && git rm -rf dir && git commit -m "Commit to merge, with dir removed" -- dir sub/file && git checkout -b renamed-file-has-no-conflicts dir-in-way~1 && git rm -rf dir && git rm sub/file && printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir && git add dir && git commit -m "Independent change" && git checkout -b renamed-file-has-conflicts dir-in-way~1 && git rm -rf dir && git mv sub/file dir && echo 12 >>dir && git add dir && git commit -m "Conflicting change" ' printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n11\n" >expected test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' ' git reset --hard && git checkout -q renamed-file-has-no-conflicts^0 && git merge --strategy=recursive dir-not-in-way && git diff --quiet && test -f dir && test_cmp expected dir ' test_expect_failure 'Rename+D/F conflict; renamed file merges but dir in way' ' git reset --hard && rm -rf dir~* && git checkout -q renamed-file-has-no-conflicts^0 && test_must_fail git merge --strategy=recursive dir-in-way >output && grep "CONFLICT (delete/modify): dir/file-in-the-way" output && grep "Auto-merging dir" output && grep "Adding as dir~HEAD instead" output && test 2 = "$(git ls-files -u | wc -l)" && test 2 = "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && test -f dir/file-in-the-way && test -f dir~HEAD && test_cmp expected dir~HEAD ' test_expect_failure 'Same as previous, but merged other way' ' git reset --hard && rm -rf dir~* && git checkout -q dir-in-way^0 && test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors && ! grep "error: refusing to lose untracked file at" errors && grep "CONFLICT (delete/modify): dir/file-in-the-way" output && grep "Auto-merging dir" output && grep "Adding as dir~renamed-file-has-no-conflicts instead" output && test 2 = "$(git ls-files -u | wc -l)" && test 2 = "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && test -f dir/file-in-the-way && test -f dir~renamed-file-has-no-conflicts && test_cmp expected dir~renamed-file-has-no-conflicts ' cat >expected <<\EOF && 1 2 3 4 5 6 7 8 9 10 <<<<<<< HEAD 12 ======= 11 >>>>>>> dir-not-in-way EOF test_expect_failure 'Rename+D/F conflict; renamed file cannot merge, dir not in way' ' git reset --hard && rm -rf dir~* && git checkout -q renamed-file-has-conflicts^0 && test_must_fail git merge --strategy=recursive dir-not-in-way && test 3 = "$(git ls-files -u | wc -l)" && test 3 = "$(git ls-files -u dir | wc -l)" && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && test -f dir && test_cmp expected dir ' test_expect_failure 'Rename+D/F conflict; renamed file cannot merge and dir in the way' ' modify s/dir-not-in-way/dir-in-way/ expected && git reset --hard && rm -rf dir~* && git checkout -q renamed-file-has-conflicts^0 && test_must_fail git merge --strategy=recursive dir-in-way && test 5 = "$(git ls-files -u | wc -l)" && test 3 = "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && test 2 = "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && test -f dir/file-in-the-way && test -f dir~HEAD && test_cmp expected dir~HEAD ' cat >expected <<\EOF && 1 2 3 4 5 6 7 8 9 10 <<<<<<< HEAD 11 ======= 12 >>>>>>> renamed-file-has-conflicts EOF test_expect_failure 'Same as previous, but merged other way' ' git reset --hard && rm -rf dir~* && git checkout -q dir-in-way^0 && test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && test 5 = "$(git ls-files -u | wc -l)" && test 3 = "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && test 2 = "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && test_must_fail git diff --cached --quiet && test -f dir/file-in-the-way && test -f dir~renamed-file-has-conflicts && test_cmp expected dir~renamed-file-has-conflicts ' test_expect_success 'setup both rename source and destination involved in D/F conflict' ' git reset --hard && git checkout --orphan rename-dest && git rm -rf . && git clean -fdqx && mkdir one && echo stuff >one/file && git add -A && git commit -m "Common commmit" && git mv one/file destdir && git commit -m "Renamed to destdir" && git checkout -b source-conflict HEAD~1 && git rm -rf one && mkdir destdir && touch one destdir/foo && git add -A && git commit -m "Conflicts in the way" ' test_expect_failure 'both rename source and destination involved in D/F conflict' ' git reset --hard && rm -rf dir~* && git checkout -q rename-dest^0 && test_must_fail git merge --strategy=recursive source-conflict && test 1 = "$(git ls-files -u | wc -l)" && test_must_fail git diff --quiet && test -f destdir/foo && test -f one && test -f destdir~HEAD && test "stuff" = "$(cat destdir~HEAD)" ' test_done