Merge branch 'en/directory-renames-nothanks'
authorJunio C Hamano <gitster@pobox.com>
Tue, 4 Sep 2018 21:31:38 +0000 (14:31 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 4 Sep 2018 21:31:38 +0000 (14:31 -0700)
Recent addition of "directory rename" heuristics to the
merge-recursive backend makes the command susceptible to false
positives and false negatives.  In the context of "git am -3",
which does not know about surrounding unmodified paths and thus
cannot inform the merge machinery about the full trees involved,
this risk is particularly severe.  As such, the heuristic is
disabled for "git am -3" to keep the machinery "more stupid but
predictable".

* en/directory-renames-nothanks:
  am: avoid directory rename detection when calling recursive merge machinery
  merge-recursive: add ability to turn off directory rename detection
  t3401: add another directory rename testcase for rebase and am

builtin/am.c
merge-recursive.c
merge-recursive.h
t/t3401-rebase-and-am-rename.sh

index 9f7ecf6..5e866d1 100644 (file)
@@ -1598,6 +1598,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
        o.branch1 = "HEAD";
        their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
        o.branch2 = their_tree_name;
+       o.detect_directory_renames = 0;
 
        if (state->quiet)
                o.verbosity = 0;
index dcdc930..e5243db 100644 (file)
@@ -2869,12 +2869,19 @@ static int detect_and_process_renames(struct merge_options *o,
        head_pairs = get_diffpairs(o, common, head);
        merge_pairs = get_diffpairs(o, common, merge);
 
-       dir_re_head = get_directory_renames(head_pairs, head);
-       dir_re_merge = get_directory_renames(merge_pairs, merge);
+       if (o->detect_directory_renames) {
+               dir_re_head = get_directory_renames(head_pairs, head);
+               dir_re_merge = get_directory_renames(merge_pairs, merge);
 
-       handle_directory_level_conflicts(o,
-                                        dir_re_head, head,
-                                        dir_re_merge, merge);
+               handle_directory_level_conflicts(o,
+                                                dir_re_head, head,
+                                                dir_re_merge, merge);
+       } else {
+               dir_re_head  = xmalloc(sizeof(*dir_re_head));
+               dir_re_merge = xmalloc(sizeof(*dir_re_merge));
+               dir_rename_init(dir_re_head);
+               dir_rename_init(dir_re_merge);
+       }
 
        ri->head_renames  = get_renames(o, head_pairs,
                                        dir_re_merge, dir_re_head, head,
@@ -3586,6 +3593,7 @@ void init_merge_options(struct merge_options *o)
        o->renormalize = 0;
        o->diff_detect_rename = -1;
        o->merge_detect_rename = -1;
+       o->detect_directory_renames = 1;
        merge_recursive_config(o);
        merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
        if (merge_verbosity)
index 0c46a5a..e6a0828 100644 (file)
@@ -20,6 +20,7 @@ struct merge_options {
        unsigned renormalize : 1;
        long xdl_opts;
        int verbosity;
+       int detect_directory_renames;
        int diff_detect_rename;
        int merge_detect_rename;
        int diff_rename_limit;
index 8f83295..e0b5111 100755 (executable)
@@ -5,7 +5,7 @@ test_description='git rebase + directory rename tests'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-rebase.sh
 
-test_expect_success 'setup testcase' '
+test_expect_success 'setup testcase where directory rename should be detected' '
        test_create_repo dir-rename &&
        (
                cd dir-rename &&
@@ -102,4 +102,112 @@ test_expect_failure 'am: directory rename detected' '
        )
 '
 
+test_expect_success 'setup testcase where directory rename should NOT be detected' '
+       test_create_repo no-dir-rename &&
+       (
+               cd no-dir-rename &&
+
+               mkdir x &&
+               test_seq  1 10 >x/a &&
+               test_seq 11 20 >x/b &&
+               test_seq 21 30 >x/c &&
+               echo original >project_info &&
+               git add x project_info &&
+               git commit -m "Initial" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               echo v2 >project_info &&
+               git add project_info &&
+               git commit -m "Modify project_info" &&
+
+               git checkout B &&
+               mkdir y &&
+               git mv x/c y/c &&
+               echo v1 >project_info &&
+               git add project_info &&
+               git commit -m "Rename x/c to y/c, modify project_info"
+       )
+'
+
+test_expect_success 'rebase --interactive: NO directory rename' '
+       test_when_finished "git -C no-dir-rename rebase --abort" &&
+       (
+               cd no-dir-rename &&
+
+               git checkout B^0 &&
+
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="1" git rebase --interactive A &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               test_path_is_file x/a &&
+               test_path_is_file x/b &&
+               test_path_is_missing x/c
+       )
+'
+
+test_expect_success 'rebase (am): NO directory rename' '
+       test_when_finished "git -C no-dir-rename rebase --abort" &&
+       (
+               cd no-dir-rename &&
+
+               git checkout B^0 &&
+
+               set_fake_editor &&
+               test_must_fail git rebase A &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               test_path_is_file x/a &&
+               test_path_is_file x/b &&
+               test_path_is_missing x/c
+       )
+'
+
+test_expect_success 'rebase --merge: NO directory rename' '
+       test_when_finished "git -C no-dir-rename rebase --abort" &&
+       (
+               cd no-dir-rename &&
+
+               git checkout B^0 &&
+
+               set_fake_editor &&
+               test_must_fail git rebase --merge A &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               test_path_is_file x/a &&
+               test_path_is_file x/b &&
+               test_path_is_missing x/c
+       )
+'
+
+test_expect_success 'am: NO directory rename' '
+       test_when_finished "git -C no-dir-rename am --abort" &&
+       (
+               cd no-dir-rename &&
+
+               git checkout A^0 &&
+
+               git format-patch -1 B &&
+
+               test_must_fail git am --3way 0001*.patch &&
+
+               git ls-files -s >out &&
+               test_line_count = 6 out &&
+
+               test_path_is_file x/a &&
+               test_path_is_file x/b &&
+               test_path_is_missing x/c
+       )
+'
+
 test_done