merge-recursive: don't segfault while handling rename clashes
authorJunio C Hamano <gitster@pobox.com>
Fri, 31 Jul 2009 00:38:15 +0000 (17:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 31 Jul 2009 02:25:05 +0000 (19:25 -0700)
When a branch moves A to B while the other branch created B (or moved C to
B), the code tried to rename one of them to B~something to preserve both
versions, and failed to register temporary resolution for the original
path B at stage#0 during virtual ancestor computation.  This left the
index in unmerged state and caused a segfault.

A better solution is to merge these two versions of B's in place and use
the (potentially conflicting) result as the intermediate merge result in
the virtual ancestor.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
merge-recursive.c
t/t6036-recursive-corner-cases.sh [new file with mode: 0755]

index b97026bd5cc1d2ef1b46a9ef3dcd7562ad52c377..54d23b214c9fe845ce4862fe95916bb791b36134 100644 (file)
@@ -947,9 +947,31 @@ static int process_renames(struct merge_options *o,
                                       "%s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
-                               new_path = unique_path(o, ren1_dst, branch2);
-                               output(o, 1, "Adding as %s instead", new_path);
-                               update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                               if (o->call_depth) {
+                                       struct merge_file_info mfi;
+                                       struct diff_filespec one, a, b;
+
+                                       one.path = a.path = b.path =
+                                               (char *)ren1_dst;
+                                       hashcpy(one.sha1, null_sha1);
+                                       one.mode = 0;
+                                       hashcpy(a.sha1, ren1->pair->two->sha1);
+                                       a.mode = ren1->pair->two->mode;
+                                       hashcpy(b.sha1, dst_other.sha1);
+                                       b.mode = dst_other.mode;
+                                       mfi = merge_file(o, &one, &a, &b,
+                                                        branch1,
+                                                        branch2);
+                                       output(o, 1, "Adding merged %s", ren1_dst);
+                                       update_file(o, 0,
+                                                   mfi.sha,
+                                                   mfi.mode,
+                                                   ren1_dst);
+                               } else {
+                                       new_path = unique_path(o, ren1_dst, branch2);
+                                       output(o, 1, "Adding as %s instead", new_path);
+                                       update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
+                               }
                        } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
new file mode 100755 (executable)
index 0000000..b874141
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='recursive merge corner cases'
+
+. ./test-lib.sh
+
+#
+#  L1  L2
+#   o---o
+#  / \ / \
+# o   X   ?
+#  \ / \ /
+#   o---o
+#  R1  R2
+#
+
+test_expect_success setup '
+       ten="0 1 2 3 4 5 6 7 8 9"
+       for i in $ten
+       do
+               echo line $i in a sample file
+       done >one &&
+       for i in $ten
+       do
+               echo line $i in another sample file
+       done >two &&
+       git add one two &&
+       test_tick && git commit -m initial &&
+
+       git branch L1 &&
+       git checkout -b R1 &&
+       git mv one three &&
+       test_tick && git commit -m R1 &&
+
+       git checkout L1 &&
+       git mv two three &&
+       test_tick && git commit -m L1 &&
+
+       git checkout L1^0 &&
+       test_tick && git merge -s ours R1 &&
+       git tag L2 &&
+
+       git checkout R1^0 &&
+       test_tick && git merge -s ours L1 &&
+       git tag R2
+'
+
+test_expect_success merge '
+       git reset --hard &&
+       git checkout L2^0 &&
+
+       test_must_fail git merge -s recursive R2^0
+'
+
+test_done