Browse Source
Teach 'git merge' the --abort option, which verifies the existence of MERGE_HEAD and then invokes 'git reset --merge' to abort the current in-progress merge and attempt to reconstruct the pre-merge state. The reason for adding this option is to provide a user interface for aborting an in-progress merge that is consistent with the interface for aborting a rebase ('git rebase --abort'), aborting the application of a patch series ('git am --abort'), and aborting an in-progress notes merge ('git notes merge --abort'). The patch includes documentation and testcases that explain and verify the various scenarios in which 'git merge --abort' can run. The testcases also document the cases in which 'git merge --abort' is unable to correctly restore the pre-merge state (look for the '###' comments towards the bottom of t/t7609-merge-abort.sh). This patch has been improved by the following contributions: - Jonathan Nieder: Move test documentation into test_description Thanks-to: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
Johan Herland
14 years ago
committed by
Junio C Hamano
3 changed files with 351 additions and 3 deletions
@ -0,0 +1,313 @@
@@ -0,0 +1,313 @@
|
||||
#!/bin/sh |
||||
|
||||
test_description='test aborting in-progress merges |
||||
|
||||
Set up repo with conflicting and non-conflicting branches: |
||||
|
||||
There are three files foo/bar/baz, and the following graph illustrates the |
||||
content of these files in each commit: |
||||
|
||||
# foo/bar/baz --- foo/bar/bazz <-- master |
||||
# \ |
||||
# --- foo/barf/bazf <-- conflict_branch |
||||
# \ |
||||
# --- foo/bart/baz <-- clean_branch |
||||
|
||||
Next, test git merge --abort with the following variables: |
||||
- before/after successful merge (should fail when not in merge context) |
||||
- with/without conflicts |
||||
- clean/dirty index before merge |
||||
- clean/dirty worktree before merge |
||||
- dirty index before merge matches contents on remote branch |
||||
- changed/unchanged worktree after merge |
||||
- changed/unchanged index after merge |
||||
' |
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success 'setup' ' |
||||
# Create the above repo |
||||
echo foo > foo && |
||||
echo bar > bar && |
||||
echo baz > baz && |
||||
git add foo bar baz && |
||||
git commit -m initial && |
||||
echo bazz > baz && |
||||
git commit -a -m "second" && |
||||
git checkout -b conflict_branch HEAD^ && |
||||
echo barf > bar && |
||||
echo bazf > baz && |
||||
git commit -a -m "conflict" && |
||||
git checkout -b clean_branch HEAD^ && |
||||
echo bart > bar && |
||||
git commit -a -m "clean" && |
||||
git checkout master |
||||
' |
||||
|
||||
pre_merge_head="$(git rev-parse HEAD)" |
||||
|
||||
test_expect_success 'fails without MERGE_HEAD (unstarted merge)' ' |
||||
test_must_fail git merge --abort 2>output && |
||||
grep -q MERGE_HEAD output && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" |
||||
' |
||||
|
||||
test_expect_success 'fails without MERGE_HEAD (completed merge)' ' |
||||
git merge clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
# Merge successfully completed |
||||
post_merge_head="$(git rev-parse HEAD)" && |
||||
test_must_fail git merge --abort 2>output && |
||||
grep -q MERGE_HEAD output && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$post_merge_head" = "$(git rev-parse HEAD)" |
||||
' |
||||
|
||||
test_expect_success 'Forget previous merge' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort after --no-commit' ' |
||||
# Redo merge, but stop before creating merge commit |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort non-conflicting merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
test -z "$(git diff --staged)" |
||||
' |
||||
|
||||
test_expect_success 'Abort after conflicts' ' |
||||
# Create conflicting merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort conflicting merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
test -z "$(git diff --staged)" |
||||
' |
||||
|
||||
test_expect_success 'Clean merge with dirty index fails' ' |
||||
echo xyzzy >> foo && |
||||
git add foo && |
||||
git diff --staged > expect && |
||||
test_must_fail git merge clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Conflicting merge with dirty index fails' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset index (but preserve worktree changes)' ' |
||||
git reset "$pre_merge_head" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort clean merge with non-conflicting dirty worktree' ' |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail clean merge with conflicting dirty worktree' ' |
||||
echo xyzzy >> bar && |
||||
git diff > expect && |
||||
test_must_fail git merge --no-commit clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Fail conflicting merge with conflicting dirty worktree' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail clean merge with matching dirty worktree' ' |
||||
echo bart > bar && |
||||
git diff > expect && |
||||
test_must_fail git merge --no-commit clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort clean merge with matching dirty index' ' |
||||
git add bar && |
||||
git diff --staged > expect && |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
### When aborting the merge, git will discard all staged changes, |
||||
### including those that were staged pre-merge. In other words, |
||||
### --abort will LOSE any staged changes (the staged changes that |
||||
### are lost must match the merge result, or the merge would not |
||||
### have been allowed to start). Change expectations accordingly: |
||||
rm expect && |
||||
touch expect && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual && |
||||
test -z "$(git diff)" |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail conflicting merge with matching dirty worktree' ' |
||||
echo barf > bar && |
||||
git diff > expect && |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort conflicting merge with matching dirty index' ' |
||||
git add bar && |
||||
git diff --staged > expect && |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
### When aborting the merge, git will discard all staged changes, |
||||
### including those that were staged pre-merge. In other words, |
||||
### --abort will LOSE any staged changes (the staged changes that |
||||
### are lost must match the merge result, or the merge would not |
||||
### have been allowed to start). Change expectations accordingly: |
||||
rm expect && |
||||
touch expect && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual && |
||||
test -z "$(git diff)" |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort merge with pre- and post-merge worktree changes' ' |
||||
# Pre-merge worktree changes |
||||
echo xyzzy > foo && |
||||
echo barf > bar && |
||||
git add bar && |
||||
git diff > expect && |
||||
git diff --staged > expect-staged && |
||||
# Perform merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Post-merge worktree changes |
||||
echo yzxxz > foo && |
||||
echo blech > baz && |
||||
### When aborting the merge, git will discard staged changes (bar) |
||||
### and unmerged changes (baz). Other changes that are neither |
||||
### staged nor marked as unmerged (foo), will be preserved. For |
||||
### these changed, git cannot tell pre-merge changes apart from |
||||
### post-merge changes, so the post-merge changes will be |
||||
### preserved. Change expectations accordingly: |
||||
git diff -- foo > expect && |
||||
rm expect-staged && |
||||
touch expect-staged && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff > actual && |
||||
test_cmp expect actual && |
||||
git diff --staged > actual-staged && |
||||
test_cmp expect-staged actual-staged |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort merge with pre- and post-merge index changes' ' |
||||
# Pre-merge worktree changes |
||||
echo xyzzy > foo && |
||||
echo barf > bar && |
||||
git add bar && |
||||
git diff > expect && |
||||
git diff --staged > expect-staged && |
||||
# Perform merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Post-merge worktree changes |
||||
echo yzxxz > foo && |
||||
echo blech > baz && |
||||
git add foo bar && |
||||
### When aborting the merge, git will discard all staged changes |
||||
### (foo, bar and baz), and no changes will be preserved. Whether |
||||
### the changes were staged pre- or post-merge does not matter |
||||
### (except for not preventing starting the merge). |
||||
### Change expectations accordingly: |
||||
rm expect expect-staged && |
||||
touch expect && |
||||
touch expect-staged && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff > actual && |
||||
test_cmp expect actual && |
||||
git diff --staged > actual-staged && |
||||
test_cmp expect-staged actual-staged |
||||
' |
||||
|
||||
test_done |
Loading…
Reference in new issue