mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Elijah Newren](/assets/img/avatar_default.png)
builtin/merge.c says that when we are about to perform a merge: ...the index must be in sync with the head commit. The strategies are responsible to ensure this. merge-recursive has always relied on unpack_trees() to enforce this requirement, except in the case of an "Already up to date!" merge. unpack-trees.c does not actually enforce this requirement, though. It allows for a pair of exceptions, in cases which it refers to as #14(ALT) and #2ALT. Documentation/technical/trivial-merge.txt can be consulted for the precise meanings of the various case numbers and their meanings for unpack-trees.c, but we have a high-level description of the intent behind these two exceptions in a combined and summarized form in Documentation/git-merge.txt: ...[merge will] abort if there are any changes registered in the index relative to the `HEAD` commit. (One exception is when the changed index entries are in the state that would result from the merge already.) While this high-level description does describe conditions under which it would be safe to allow the index to diverge from HEAD, it does not match what is actually implemented. In particular, unpack-trees.c has no knowledge of renames, and these two exceptions were written assuming that no renames take place. Once renames get into the mix, it is no longer safe to allow the index to not match for #2ALT. We could modify unpack-trees to only allow #14(ALT) as an exception, but that would be more strict than required for the resolve strategy (since the resolve strategy doesn't handle renames at all). Therefore, unpack_trees.c seems like the wrong place to fix this. Further, if someone fixes the combination of break and rename detection and modifies merge-recursive to take advantage of the combination, then it will also no longer be safe to allow the index to not match for #14(ALT) when the recursive strategy is in use. Therefore, leaving one of the exceptions in place with the recursive merge strategy feels like we are just leaving a latent bug in the code for folks in the future to stumble across. It may be possible to fix both unpack-trees and merge-recursive in a way that implements the exception as stated in Documentation/git-merge.txt, but it would be somewhat complex, possibly also buggy at first, and ultimately, not all that valuable. Instead, just enforce the requirement stated in builtin/merge.c; error out if the index does not match the HEAD commit, just like the 'ours' and 'octopus' strategies do. Some testcase fixups were in order: t7611: had many tests designed to show that `git merge --abort` could not always restore the index and working tree to the state they were in before the merge started. The tests that were associated with having changes in the index before the merge started are no longer applicable, so they have been removed. t7504: had a few tests that had stray staged changes that were not actually part of the test under consideration t6044: We no longer expect stray staged changes to sometimes result in the merge continuing. Also, fix a case where a merge didn't abort but should have. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
217 lines
4.5 KiB
Bash
Executable file
217 lines
4.5 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
test_description="merges with unrelated index changes"
|
|
|
|
. ./test-lib.sh
|
|
|
|
# Testcase for some simple merges
|
|
# A
|
|
# o-------o B
|
|
# \
|
|
# \-----o C
|
|
# \
|
|
# \---o D
|
|
# \
|
|
# \-o E
|
|
# \
|
|
# o F
|
|
# Commit A: some file a
|
|
# Commit B: adds file b, modifies end of a
|
|
# Commit C: adds file c
|
|
# Commit D: adds file d, modifies beginning of a
|
|
# Commit E: renames a->subdir/a, adds subdir/e
|
|
# Commit F: empty commit
|
|
|
|
test_expect_success 'setup trivial merges' '
|
|
test_seq 1 10 >a &&
|
|
git add a &&
|
|
test_tick && git commit -m A &&
|
|
|
|
git branch A &&
|
|
git branch B &&
|
|
git branch C &&
|
|
git branch D &&
|
|
git branch E &&
|
|
git branch F &&
|
|
|
|
git checkout B &&
|
|
echo b >b &&
|
|
echo 11 >>a &&
|
|
git add a b &&
|
|
test_tick && git commit -m B &&
|
|
|
|
git checkout C &&
|
|
echo c >c &&
|
|
git add c &&
|
|
test_tick && git commit -m C &&
|
|
|
|
git checkout D &&
|
|
test_seq 2 10 >a &&
|
|
echo d >d &&
|
|
git add a d &&
|
|
test_tick && git commit -m D &&
|
|
|
|
git checkout E &&
|
|
mkdir subdir &&
|
|
git mv a subdir/a &&
|
|
echo e >subdir/e &&
|
|
git add subdir &&
|
|
test_tick && git commit -m E &&
|
|
|
|
git checkout F &&
|
|
test_tick && git commit --allow-empty -m F
|
|
'
|
|
|
|
test_expect_success 'ff update' '
|
|
git reset --hard &&
|
|
git checkout A^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
git merge E^0 &&
|
|
|
|
test_must_fail git rev-parse HEAD:random_file &&
|
|
test "$(git diff --name-only --cached E)" = "random_file"
|
|
'
|
|
|
|
test_expect_success 'ff update, important file modified' '
|
|
git reset --hard &&
|
|
git checkout A^0 &&
|
|
|
|
mkdir subdir &&
|
|
touch subdir/e &&
|
|
git add subdir/e &&
|
|
|
|
test_must_fail git merge E^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'resolve, trivial' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s resolve C^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'resolve, non-trivial' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s resolve D^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'recursive' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s recursive C^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'recursive, when merge branch matches merge base' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s recursive F^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'merge-recursive, when index==head but head!=HEAD' '
|
|
git reset --hard &&
|
|
git checkout C^0 &&
|
|
|
|
# Make index match B
|
|
git diff C B -- | git apply --cached &&
|
|
# Merge B & F, with B as "head"
|
|
git merge-recursive A -- B F > out &&
|
|
test_i18ngrep "Already up to date" out
|
|
'
|
|
|
|
test_expect_success 'recursive, when file has staged changes not matching HEAD nor what a merge would give' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
mkdir subdir &&
|
|
test_seq 1 10 >subdir/a &&
|
|
git add subdir/a &&
|
|
|
|
# We have staged changes; merge should error out
|
|
test_must_fail git merge -s recursive E^0 2>err &&
|
|
test_i18ngrep "changes to the following files would be overwritten" err
|
|
'
|
|
|
|
test_expect_success 'recursive, when file has staged changes matching what a merge would give' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
mkdir subdir &&
|
|
test_seq 1 11 >subdir/a &&
|
|
git add subdir/a &&
|
|
|
|
# We have staged changes; merge should error out
|
|
test_must_fail git merge -s recursive E^0 2>err &&
|
|
test_i18ngrep "changes to the following files would be overwritten" err
|
|
'
|
|
|
|
test_expect_success 'octopus, unrelated file touched' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge C^0 D^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'octopus, related file removed' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
git rm b &&
|
|
|
|
test_must_fail git merge C^0 D^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'octopus, related file modified' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
echo 12 >>a && git add a &&
|
|
|
|
test_must_fail git merge C^0 D^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'ours' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s ours C^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_expect_success 'subtree' '
|
|
git reset --hard &&
|
|
git checkout B^0 &&
|
|
|
|
touch random_file && git add random_file &&
|
|
|
|
test_must_fail git merge -s subtree E^0 &&
|
|
test_path_is_missing .git/MERGE_HEAD
|
|
'
|
|
|
|
test_done
|