merge-ort: propagate callback errors from traverse_trees_wrapper()

traverse_trees_wrapper() saves entries from a first pass through traverse_trees() and then replays them through the real callback (collect_merge_info_callback). However, the replay loop silently discards the callback return value. This is not a deferred error; it is an ignored error. Today the only originator of a negative return in this entire call graph is traverse_trees()'s "exceeded maximum allowed tree depth" check; everything else (collect_merge_info_callback, traverse_trees_wrapper, the inner traverse_trees recursion) only relays that. So in current Git, the visible effect of dropping the replay callback's return value is narrow but bad: a tree nested past core.maxTreeDepth has its -1 swallowed, the subtree below the limit is silently pruned, and the merge completes as if that were the correct result. A later patch in this series will teach collect_merge_info_callback() to return -1 on an additional path -- detecting duplicate entries in malformed trees -- which is similarly handled today by just ignoring the problem (resulting in mostly a "last one wins" rule, though the non-last entry can mutate various state flags). Capture the return value, stop the loop on negative returns, and propagate the error to the caller. The callback returns a positive mask value on success, so normalize non-negative returns to 0 for the caller. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Jun 14, 2026 at 06:37 UTC da80feb5be9076bb56af0681d684c36028f92ae6
1 file changed +8 -6
merge-ort.c
+8 -6
@@ -1008,18 +1008,20 @@ static int traverse_trees_wrapper(struct index_state *istate,
1008 info->traverse_path = renames->callback_data_traverse_path;
1009 info->fn = old_fn;
1010 for (i = old_offset; i < renames->callback_data_nr; ++i) {
1011 - info->fn(n,
1012 - renames->callback_data[i].mask,
1013 - renames->callback_data[i].dirmask,
1014 - renames->callback_data[i].names,
1015 - info);
1011 + ret = info->fn(n,
1012 + renames->callback_data[i].mask,
1013 + renames->callback_data[i].dirmask,
1014 + renames->callback_data[i].names,
1015 + info);
1016 + if (ret < 0)
1017 + break;
1018 }
1019
1020 renames->callback_data_nr = old_offset;
1021 free(renames->callback_data_traverse_path);
1022 renames->callback_data_traverse_path = old_callback_data_traverse_path;
1023 info->traverse_path = NULL;
1022 - return 0;
1024 + return ret < 0 ? ret : 0;
1025 }
1026
1027 static void setup_path_info(struct merge_options *opt,