]> git.ipfire.org Git - thirdparty/git.git/commitdiff
merge: make restore_state() restore staged state too
authorElijah Newren <newren@gmail.com>
Sat, 23 Jul 2022 01:53:16 +0000 (01:53 +0000)
committerJunio C Hamano <gitster@pobox.com>
Sat, 23 Jul 2022 04:45:23 +0000 (21:45 -0700)
There are multiple issues at play here:

  1) If `git merge` is invoked with staged changes, it should abort
     without doing any merging, and the user's working tree and index
     should be the same as before merge was invoked.
  2) Merge strategies are responsible for enforcing the index == HEAD
     requirement. (See 9822175d2b ("Ensure index matches head before
     invoking merge machinery, round N", 2019-08-17) for some history
     around this.)
  3) Merge strategies can bail saying they are not an appropriate
     handler for the merge in question (possibly allowing other
     strategies to be used instead).
  4) Merge strategies can make changes to the index and working tree,
     and have no expectation to clean up after themselves, *even* if
     they bail out and say they are not an appropriate handler for
     the merge in question.  (The `octopus` merge strategy does this,
     for example.)
  5) Because of (3) and (4), builtin/merge.c stashes state before
     trying merge strategies and restores it afterward.

Unfortunately, if users had staged changes before calling `git merge`,
builtin/merge.c could do the following:

   * stash the changes, in order to clean up after the strategies
   * try all the merge strategies in turn, each of which report they
     cannot function due to the index not matching HEAD
   * restore the changes via "git stash apply"

But that last step would have the net effect of unstaging the user's
changes.  Fix this by adding the "--index" option to "git stash apply".
While at it, also squelch the stash apply output; we already report
"Rewinding the tree to pristine..." and don't need a detailed `git
status` report afterwards.  Also while at it, switch to using strvec
so folks don't have to count the arguments to ensure we avoided an
off-by-one error, and so it's easier to add additional arguments to
the command.

Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/merge.c
t/t6424-merge-unrelated-index-changes.sh

index 780b4b9100aa3575a9e9349818001dbb795f6cfc..e0a3299e92e8e2ef88f4cc26203e718762b2c3b9 100644 (file)
@@ -383,20 +383,22 @@ static void reset_hard(const struct object_id *oid, int verbose)
 static void restore_state(const struct object_id *head,
                          const struct object_id *stash)
 {
-       const char *args[] = { "stash", "apply", NULL, NULL };
+       struct strvec args = STRVEC_INIT;
 
        if (is_null_oid(stash))
                return;
 
        reset_hard(head, 1);
 
-       args[2] = oid_to_hex(stash);
+       strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL);
+       strvec_push(&args, oid_to_hex(stash));
 
        /*
         * It is OK to ignore error here, for example when there was
         * nothing to restore.
         */
-       run_command_v_opt(args, RUN_GIT_CMD);
+       run_command_v_opt(args.v, RUN_GIT_CMD);
+       strvec_clear(&args);
 
        refresh_cache(REFRESH_QUIET);
 }
index 2c83210f9fd575469c366368b7a5435a45dd709b..a61f20c22fe62031da12af45bd8ca427782043bf 100755 (executable)
@@ -292,6 +292,7 @@ test_expect_success 'with multiple strategies, recursive or ort failure do not e
 
        test_seq 0 10 >a &&
        git add a &&
+       git rev-parse :a >expect &&
 
        sane_unset GIT_TEST_MERGE_ALGORITHM &&
        test_must_fail git merge -s recursive -s ort -s octopus C^0 >output 2>&1 &&
@@ -299,7 +300,11 @@ test_expect_success 'with multiple strategies, recursive or ort failure do not e
        grep "Trying merge strategy recursive..." output &&
        grep "Trying merge strategy ort..." output &&
        grep "Trying merge strategy octopus..." output &&
-       grep "No merge strategy handled the merge." output
+       grep "No merge strategy handled the merge." output &&
+
+       # Changes to "a" should remain staged
+       git rev-parse :a >actual &&
+       test_cmp expect actual
 '
 
 test_done