ll-merge: killing the external merge driver aborts the merge

When an external merge driver dies with a signal, we should not expect that the result left on the filesystem is in any useful state. However, because the current code uses the return value from run_command() and declares any positive value as a sign that the driver successfully left conflicts in the result, and because the return value from run_command() for a subprocess that died upon a signal is positive, we end up treating whatever garbage left on the filesystem as the result the merge driver wanted to leave us. run_command() returns larger than 128 (WTERMSIG(status) + 128, to be exact) when it notices that the subprocess died with a signal, so detect such a case and return LL_MERGE_ERROR from ll_ext_merge(). Signed-off-by: Junio C Hamano <gitster@pobox.com> Reviewed-by: Elijah Newren <newren@gmail.com>

Junio C Hamano committed Jun 22, 2023 at 17:33 UTC 2b7b788fb31a74bcbff4e4c6efc6f3db6c3a49b7
3 files changed +35 -2
Documentation/gitattributes.txt
+4 -1
@@ -1132,7 +1132,10 @@ size (see below).
1132 The merge driver is expected to leave the result of the merge in
1133 the file named with `%A` by overwriting it, and exit with zero
1134 status if it managed to merge them cleanly, or non-zero if there
1135 -were conflicts.
1135 +were conflicts. When the driver crashes (e.g. killed by SEGV),
1136 +it is expected to exit with non-zero status that are higher than
1137 +128, and in such a case, the merge results in a failure (which is
1138 +different from producing a conflict).
1139
1140 The `merge.*.recursive` variable specifies what other merge
1141 driver to use when the merge driver is called for an internal
ll-merge.c
+8 -1
@@ -243,7 +243,14 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
243 unlink_or_warn(temp[i]);
244 strbuf_release(&cmd);
245 strbuf_release(&path_sq);
246 - ret = (status > 0) ? LL_MERGE_CONFLICT : status;
246 +
247 + if (!status)
248 + ret = LL_MERGE_OK;
249 + else if (status <= 128)
250 + ret = LL_MERGE_CONFLICT;
251 + else
252 + /* died due to a signal: WTERMSIG(status) + 128 */
253 + ret = LL_MERGE_ERROR;
254 return ret;
255 }
256
t/t6406-merge-attr.sh
+23
@@ -56,6 +56,12 @@ test_expect_success setup '
56 ) >"$ours+"
57 cat "$ours+" >"$ours"
58 rm -f "$ours+"
59 +
60 + if test -f ./please-abort
61 + then
62 + echo >>./please-abort killing myself
63 + kill -9 $$
64 + fi
65 exit "$exit"
66 EOF
67 chmod +x ./custom-merge
@@ -162,6 +168,23 @@ test_expect_success 'custom merge backend' '
168 rm -f $o $a $b
169 '
170
171 +test_expect_success 'custom merge driver that is killed with a signal' '
172 + test_when_finished "rm -f output please-abort" &&
173 +
174 + git reset --hard anchor &&
175 + git config --replace-all \
176 + merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
177 + git config --replace-all \
178 + merge.custom.name "custom merge driver for testing" &&
179 +
180 + >./please-abort &&
181 + echo "* merge=custom" >.gitattributes &&
182 + test_must_fail git merge main &&
183 + git ls-files -u >output &&
184 + git diff --name-only HEAD >>output &&
185 + test_must_be_empty output
186 +'
187 +
188 test_expect_success 'up-to-date merge without common ancestor' '
189 git init repo1 &&
190 git init repo2 &&