Skip to content

Commit c23fc07

Browse files
newrengitster
authored andcommitted
merge: do not exit restore_state() prematurely
Previously, if the user: * Had no local changes before starting the merge * A merge strategy makes changes to the working tree/index but returns with exit status 2 Then we'd call restore_state() to clean up the changes and either let the next merge strategy run (if there is one), or exit telling the user that no merge strategy could handle the merge. Unfortunately, restore_state() did not clean up the changes as expected; that function was a no-op if the stash was a null, and the stash would be null if there were no local changes before starting the merge. So, instead of "Rewinding the tree to pristine..." as the code claimed, restore_state() would leave garbage around in the index and working tree (possibly including conflicts) for either the next merge strategy or for the user after aborting the merge. And in the case of aborting the merge, the user would be unable to run "git merge --abort" to get rid of the unintended leftover conflicts, because the merge control files were not written as it was presumed that we had restored to a clean state already. Fix the main problem by making sure that restore_state() only skips the stash application if the stash is null rather than skipping the whole function. However, there is a secondary problem -- since merge.c forks subprocesses to do the cleanup, the in-memory index is left out-of-sync. While there was a refresh_cache(REFRESH_QUIET) call that attempted to correct that, that function would not handle cases where the previous merge strategy added conflicted entries. We need to drop the index and re-read it to handle such cases. (Alternatively, we could stop forking subprocesses and instead call some appropriate function to do the work which would update the in-memory index automatically. For now, just do the simple fix.) Also, add a testcase checking this, one for which the octopus strategy fails on the first commit it attempts to merge, and thus which it cannot handle at all and must completely bail on (as per the "exit 2" code path of commit 98efc8f ("octopus: allow manual resolve on the last round.", 2006-01-13)). Reported-by: ZheNing Hu <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 034195e commit c23fc07

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

builtin/merge.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,11 @@ static void restore_state(const struct object_id *head,
385385
{
386386
struct strvec args = STRVEC_INIT;
387387

388-
if (is_null_oid(stash))
389-
return;
390-
391388
reset_hard(head, 1);
392389

390+
if (is_null_oid(stash))
391+
goto refresh_cache;
392+
393393
strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL);
394394
strvec_push(&args, oid_to_hex(stash));
395395

@@ -400,7 +400,9 @@ static void restore_state(const struct object_id *head,
400400
run_command_v_opt(args.v, RUN_GIT_CMD);
401401
strvec_clear(&args);
402402

403-
refresh_cache(REFRESH_QUIET);
403+
refresh_cache:
404+
if (discard_cache() < 0 || read_cache() < 0)
405+
die(_("could not read index"));
404406
}
405407

406408
/* This is called when no merge was necessary. */

t/t7607-merge-state.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/sh
2+
3+
test_description="Test that merge state is as expected after failed merge"
4+
5+
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6+
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7+
. ./test-lib.sh
8+
9+
test_expect_success 'Ensure we restore original state if no merge strategy handles it' '
10+
test_commit --no-tag "Initial" base base &&
11+
12+
for b in branch1 branch2 branch3
13+
do
14+
git checkout -b $b main &&
15+
test_commit --no-tag "Change on $b" base $b || return 1
16+
done &&
17+
18+
git checkout branch1 &&
19+
# This is a merge that octopus cannot handle. Note, that it does not
20+
# just hit conflicts, it completely fails and says that it cannot
21+
# handle this type of merge.
22+
test_expect_code 2 git merge branch2 branch3 >output 2>&1 &&
23+
grep "fatal: merge program failed" output &&
24+
grep "Should not be doing an octopus" output &&
25+
26+
# Make sure we did not leave stray changes around when no appropriate
27+
# merge strategy was found
28+
git diff --exit-code --name-status &&
29+
test_path_is_missing .git/MERGE_HEAD
30+
'
31+
32+
test_done

0 commit comments

Comments
 (0)