merge-ort: let renormalization change modify/delete into clean delete

When we have a modify/delete conflict, but the only change to the modification is e.g. change of line endings, then if renormalization is requested then we should be able to recognize such a case as a not-modified/delete and resolve the conflict automatically. This fixes t6418.10 under GIT_TEST_MERGE_ALGORITHM=ort. Signed-off-by: Elijah Newren <newren@gmail.com> Reviewed-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Mar 20, 2021 at 00:03 UTC 3860220bfa594a56daa801aaedbdf8ac75b62bb6
1 file changed +62 -2
merge-ort.c
+62 -2
@@ -2549,6 +2549,61 @@ static int string_list_df_name_compare(const char *one, const char *two)
2549 return onelen - twolen;
2550 }
2551
2552 +static int read_oid_strbuf(struct merge_options *opt,
2553 + const struct object_id *oid,
2554 + struct strbuf *dst)
2555 +{
2556 + void *buf;
2557 + enum object_type type;
2558 + unsigned long size;
2559 + buf = read_object_file(oid, &type, &size);
2560 + if (!buf)
2561 + return err(opt, _("cannot read object %s"), oid_to_hex(oid));
2562 + if (type != OBJ_BLOB) {
2563 + free(buf);
2564 + return err(opt, _("object %s is not a blob"), oid_to_hex(oid));
2565 + }
2566 + strbuf_attach(dst, buf, size, size + 1);
2567 + return 0;
2568 +}
2569 +
2570 +static int blob_unchanged(struct merge_options *opt,
2571 + const struct version_info *base,
2572 + const struct version_info *side,
2573 + const char *path)
2574 +{
2575 + struct strbuf basebuf = STRBUF_INIT;
2576 + struct strbuf sidebuf = STRBUF_INIT;
2577 + int ret = 0; /* assume changed for safety */
2578 + const struct index_state *idx = &opt->priv->attr_index;
2579 +
2580 + if (!idx->initialized)
2581 + initialize_attr_index(opt);
2582 +
2583 + if (base->mode != side->mode)
2584 + return 0;
2585 + if (oideq(&base->oid, &side->oid))
2586 + return 1;
2587 +
2588 + if (read_oid_strbuf(opt, &base->oid, &basebuf) ||
2589 + read_oid_strbuf(opt, &side->oid, &sidebuf))
2590 + goto error_return;
2591 + /*
2592 + * Note: binary | is used so that both renormalizations are
2593 + * performed. Comparison can be skipped if both files are
2594 + * unchanged since their sha1s have already been compared.
2595 + */
2596 + if (renormalize_buffer(idx, path, basebuf.buf, basebuf.len, &basebuf) |
2597 + renormalize_buffer(idx, path, sidebuf.buf, sidebuf.len, &sidebuf))
2598 + ret = (basebuf.len == sidebuf.len &&
2599 + !memcmp(basebuf.buf, sidebuf.buf, basebuf.len));
2600 +
2601 +error_return:
2602 + strbuf_release(&basebuf);
2603 + strbuf_release(&sidebuf);
2604 + return ret;
2605 +}
2606 +
2607 struct directory_versions {
2608 /*
2609 * versions: list of (basename -> version_info)
@@ -3136,8 +3191,13 @@ static void process_entry(struct merge_options *opt,
3191 modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
3192 delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
3193
3139 - if (ci->path_conflict &&
3140 - oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
3194 + if (opt->renormalize &&
3195 + blob_unchanged(opt, &ci->stages[0], &ci->stages[side],
3196 + path)) {
3197 + ci->merged.is_null = 1;
3198 + ci->merged.clean = 1;
3199 + } else if (ci->path_conflict &&
3200 + oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
3201 /*
3202 * This came from a rename/delete; no action to take,
3203 * but avoid printing "modify/delete" conflict notice