Message ID | 17c97301baa829a993cf8838deb9271add5bd1cd.1718310307.git.gitgitgadget@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Fix and improve some error codepaths in merge-ort | expand |
On Thu, Jun 13, 2024 at 08:25:02PM +0000, Elijah Newren via GitGitGadget wrote: > From: Elijah Newren <newren@gmail.com> > > The calling convention for the merge machinery is > One call to init_merge_options() > One or more calls to merge_incore_[non]recursive() > One call to merge_finalize() > (possibly indirectly via merge_switch_to_result()) > Both merge_switch_to_result() and merge_finalize() expect > opt->priv == NULL && result->priv != NULL > which is supposed to be set up by our move_opt_priv_to_result_priv() > function. However, two codepath dealing with error cases did not s/codepath/&s/ ? > execute this necessary logic, which could result in assertion failures > (or, if assertions were compiled out, could result in segfaults). Fix > the oversight and add a test that would have caught one of these > problems. > > While at it, also tighten an existing test for a non-recursive merge > to verify that it fails correctly, i.e. with the expected exit code > rather than with an assertion failure. I suspect that this test was flaky, too, since if the assertion errors were compiled out and it died via a segfault, the test would have failed outright as test_must_fail does not allow for segfaults typically. So I'm glad to see us tightening up that area of the test suite. > Reported-by: Matt Cree <matt.cree@gearset.com> > Signed-off-by: Elijah Newren <newren@gmail.com> > --- > merge-ort.c | 3 ++- > t/t6406-merge-attr.sh | 42 +++++++++++++++++++++++++++++++++++++++++- > 2 files changed, 43 insertions(+), 2 deletions(-) > > diff --git a/merge-ort.c b/merge-ort.c > index 10f5a655f29..6ca7b0f9be4 100644 > --- a/merge-ort.c > +++ b/merge-ort.c > @@ -5015,7 +5015,7 @@ static void move_opt_priv_to_result_priv(struct merge_options *opt, > * to move it. > */ > assert(opt->priv && !result->priv); > - if (!opt->priv->call_depth) { > + if (!opt->priv->call_depth || result->clean < 0) { > result->priv = opt->priv; > result->_properly_initialized = RESULT_INITIALIZED; > opt->priv = NULL; > @@ -5052,6 +5052,7 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt, > oid_to_hex(&side1->object.oid), > oid_to_hex(&side2->object.oid)); > result->clean = -1; > + move_opt_priv_to_result_priv(opt, result); > return; > } > trace2_region_leave("merge", "collect_merge_info", opt->repo); > diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh > index 156a1efacfe..b6db5c2cc36 100755 > --- a/t/t6406-merge-attr.sh > +++ b/t/t6406-merge-attr.sh > @@ -185,7 +185,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal' > > >./please-abort && > echo "* merge=custom" >.gitattributes && > - test_must_fail git merge main 2>err && > + test_expect_code 2 git merge main 2>err && > grep "^error: failed to execute internal merge" err && > git ls-files -u >output && > git diff --name-only HEAD >>output && > @@ -261,4 +261,44 @@ test_expect_success 'binary files with union attribute' ' > grep -i "warning.*cannot merge.*HEAD vs. bin-main" output > ' > > +test_expect_success !WINDOWS 'custom merge driver that is killed with a signal on recursive merge' ' > + test_when_finished "rm -f output please-abort" && > + test_when_finished "git checkout side" && > + > + git reset --hard anchor && > + > + git checkout -b base-a main^ && > + echo base-a >text && > + git commit -m base-a text && > + > + git checkout -b base-b main^ && > + echo base-b >text && > + git commit -m base-b text && > + > + git checkout -b recursive-a base-a && > + test_must_fail git merge base-b && Here and below, do you care about the particular exit code of merging as above? IOW, should these be `test_expect_code`'s as well? Thanks, Taylor
On Thu, Jun 13, 2024 at 10:59 PM Taylor Blau <me@ttaylorr.com> wrote: > > On Thu, Jun 13, 2024 at 08:25:02PM +0000, Elijah Newren via GitGitGadget wrote: > > From: Elijah Newren <newren@gmail.com> > > > > The calling convention for the merge machinery is > > One call to init_merge_options() > > One or more calls to merge_incore_[non]recursive() > > One call to merge_finalize() > > (possibly indirectly via merge_switch_to_result()) > > Both merge_switch_to_result() and merge_finalize() expect > > opt->priv == NULL && result->priv != NULL > > which is supposed to be set up by our move_opt_priv_to_result_priv() > > function. However, two codepath dealing with error cases did not > > s/codepath/&s/ ? Indeed. > > execute this necessary logic, which could result in assertion failures > > (or, if assertions were compiled out, could result in segfaults). Fix > > the oversight and add a test that would have caught one of these > > problems. > > > > While at it, also tighten an existing test for a non-recursive merge > > to verify that it fails correctly, i.e. with the expected exit code > > rather than with an assertion failure. It turns out my logic here was faulty; if a test_must_fail git ARGS... command is run and git hits an assertion failure, the test_must_fail will fail with test_must_fail: died by signal 6: git ARGS... similar to how it would fail if git were to segfault. However, I still like the idea of testing the exit status here because of this comment in builtin/merge.c: /* * The backend exits with 1 when conflicts are * left to be resolved, with 2 when it does not * handle the given merge at all. */ and this is one of the few tests in the testsuite where we are explicitly testing a case where the merge is neither success nor conflicts, but failed-to-handle. > I suspect that this test was flaky, too, since if the assertion errors > were compiled out and it died via a segfault, the test would have failed > outright as test_must_fail does not allow for segfaults typically. > > So I'm glad to see us tightening up that area of the test suite. [...] > > + test_must_fail git merge base-b && > > Here and below, do you care about the particular exit code of merging as > above? IOW, should these be `test_expect_code`'s as well? Both of these comments led me on the path to check something out and discover my error above, which will lead to slightly different changes to the patch.
diff --git a/merge-ort.c b/merge-ort.c index 10f5a655f29..6ca7b0f9be4 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -5015,7 +5015,7 @@ static void move_opt_priv_to_result_priv(struct merge_options *opt, * to move it. */ assert(opt->priv && !result->priv); - if (!opt->priv->call_depth) { + if (!opt->priv->call_depth || result->clean < 0) { result->priv = opt->priv; result->_properly_initialized = RESULT_INITIALIZED; opt->priv = NULL; @@ -5052,6 +5052,7 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt, oid_to_hex(&side1->object.oid), oid_to_hex(&side2->object.oid)); result->clean = -1; + move_opt_priv_to_result_priv(opt, result); return; } trace2_region_leave("merge", "collect_merge_info", opt->repo); diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 156a1efacfe..b6db5c2cc36 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -185,7 +185,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal' >./please-abort && echo "* merge=custom" >.gitattributes && - test_must_fail git merge main 2>err && + test_expect_code 2 git merge main 2>err && grep "^error: failed to execute internal merge" err && git ls-files -u >output && git diff --name-only HEAD >>output && @@ -261,4 +261,44 @@ test_expect_success 'binary files with union attribute' ' grep -i "warning.*cannot merge.*HEAD vs. bin-main" output ' +test_expect_success !WINDOWS 'custom merge driver that is killed with a signal on recursive merge' ' + test_when_finished "rm -f output please-abort" && + test_when_finished "git checkout side" && + + git reset --hard anchor && + + git checkout -b base-a main^ && + echo base-a >text && + git commit -m base-a text && + + git checkout -b base-b main^ && + echo base-b >text && + git commit -m base-b text && + + git checkout -b recursive-a base-a && + test_must_fail git merge base-b && + echo recursive-a >text && + git add text && + git commit -m recursive-a && + + git checkout -b recursive-b base-b && + test_must_fail git merge base-a && + echo recursive-b >text && + git add text && + git commit -m recursive-b && + + git config --replace-all \ + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" && + git config --replace-all \ + merge.custom.name "custom merge driver for testing" && + + >./please-abort && + echo "* merge=custom" >.gitattributes && + test_expect_code 2 git merge recursive-a 2>err && + grep "^error: failed to execute internal merge" err && + git ls-files -u >output && + git diff --name-only HEAD >>output && + test_must_be_empty output +' + test_done