Matt McCutchen <[email protected]> writes:
> The current message printed by "git merge-recursive" for a rename/delete
> conflict is like this:
>
> CONFLICT (rename/delete): new-path deleted in HEAD and renamed in
> other-branch. Version other-branch of new-path left in tree.
>
> To be more helpful, the message should show both paths of the rename and
> state that the deletion occurred at the old path, not the new path. So
> change the message to the following format:
>
> CONFLICT (rename/delete): old-path deleted in HEAD and renamed to
> new-path in other-branch. Version other-branch of new-path left in tree.
Sounds like a sensible goal.
> Please check that my refactoring is indeed correct! All the existing tests
> pass
> for me, but the existing test coverage of these conflict messages looks poor.
This unfortunately is doing a bit too many things at once from that
point of view. I need to reserve a solid quiet 20-minutes without
distraction to check it, which I am hoping to do tonight.
Thanks.
>
> merge-recursive.c | 117
> ++++++++++++++++++++++-------------------
> t/t6045-merge-rename-delete.sh | 23 ++++++++
> 2 files changed, 86 insertions(+), 54 deletions(-)
> create mode 100755 t/t6045-merge-rename-delete.sh
>
> diff --git a/merge-recursive.c b/merge-recursive.c
> index d327209..e8fce10 100644
> --- a/merge-recursive.c
> +++ b/merge-recursive.c
> @@ -1061,16 +1061,20 @@ static int merge_file_one(struct merge_options *o,
> }
>
> static int handle_change_delete(struct merge_options *o,
> - const char *path,
> + const char *path, const char *old_path,
> const struct object_id *o_oid, int o_mode,
> - const struct object_id *a_oid, int a_mode,
> - const struct object_id *b_oid, int b_mode,
> + const struct object_id *changed_oid,
> + int changed_mode,
> + const char *change_branch,
> + const char *delete_branch,
> const char *change, const char *change_past)
> {
> - char *renamed = NULL;
> + char *alt_path = NULL;
> + const char *update_path = path;
> int ret = 0;
> +
> if (dir_in_way(path, !o->call_depth, 0)) {
> - renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2);
> + update_path = alt_path = unique_path(o, path, change_branch);
> }
>
> if (o->call_depth) {
> @@ -1081,43 +1085,43 @@ static int handle_change_delete(struct merge_options
> *o,
> */
> ret = remove_file_from_cache(path);
> if (!ret)
> - ret = update_file(o, 0, o_oid, o_mode,
> - renamed ? renamed : path);
> - } else if (!a_oid) {
> - if (!renamed) {
> - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
> - "and %s in %s. Version %s of %s left in tree."),
> - change, path, o->branch1, change_past,
> - o->branch2, o->branch2, path);
> - ret = update_file(o, 0, b_oid, b_mode, path);
> - } else {
> - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
> - "and %s in %s. Version %s of %s left in tree at
> %s."),
> - change, path, o->branch1, change_past,
> - o->branch2, o->branch2, path, renamed);
> - ret = update_file(o, 0, b_oid, b_mode, renamed);
> - }
> + ret = update_file(o, 0, o_oid, o_mode, update_path);
> } else {
> - if (!renamed) {
> - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
> - "and %s in %s. Version %s of %s left in tree."),
> - change, path, o->branch2, change_past,
> - o->branch1, o->branch1, path);
> + if (!alt_path) {
> + if (!old_path) {
> + output(o, 1, _("CONFLICT (%s/delete): %s
> deleted in %s "
> + "and %s in %s. Version %s of %s left in
> tree."),
> + change, path, delete_branch, change_past,
> + change_branch, change_branch, path);
> + } else {
> + output(o, 1, _("CONFLICT (%s/delete): %s
> deleted in %s "
> + "and %s to %s in %s. Version %s of %s
> left in tree."),
> + change, old_path, delete_branch,
> change_past, path,
> + change_branch, change_branch, path);
> + }
> } else {
> - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s "
> - "and %s in %s. Version %s of %s left in tree at
> %s."),
> - change, path, o->branch2, change_past,
> - o->branch1, o->branch1, path, renamed);
> - ret = update_file(o, 0, a_oid, a_mode, renamed);
> + if (!old_path) {
> + output(o, 1, _("CONFLICT (%s/delete): %s
> deleted in %s "
> + "and %s in %s. Version %s of %s left in
> tree at %s."),
> + change, path, delete_branch, change_past,
> + change_branch, change_branch, path,
> alt_path);
> + } else {
> + output(o, 1, _("CONFLICT (%s/delete): %s
> deleted in %s "
> + "and %s to %s in %s. Version %s of %s
> left in tree at %s."),
> + change, old_path, delete_branch,
> change_past, path,
> + change_branch, change_branch, path,
> alt_path);
> + }
> }
> /*
> - * No need to call update_file() on path when !renamed, since
> - * that would needlessly touch path. We could call
> - * update_file_flags() with update_cache=0 and update_wd=0,
> - * but that's a no-op.
> + * No need to call update_file() on path when change_branch ==
> + * o->branch1 && !alt_path, since that would needlessly touch
> + * path. We could call update_file_flags() with update_cache=0
> + * and update_wd=0, but that's a no-op.
> */
> + if (change_branch != o->branch1 || alt_path)
> + ret = update_file(o, 0, changed_oid, changed_mode,
> update_path);
> }
> - free(renamed);
> + free(alt_path);
>
> return ret;
> }
> @@ -1125,28 +1129,17 @@ static int handle_change_delete(struct merge_options
> *o,
> static int conflict_rename_delete(struct merge_options *o,
> struct diff_filepair *pair,
> const char *rename_branch,
> - const char *other_branch)
> + const char *delete_branch)
> {
> const struct diff_filespec *orig = pair->one;
> const struct diff_filespec *dest = pair->two;
> - const struct object_id *a_oid = NULL;
> - const struct object_id *b_oid = NULL;
> - int a_mode = 0;
> - int b_mode = 0;
> -
> - if (rename_branch == o->branch1) {
> - a_oid = &dest->oid;
> - a_mode = dest->mode;
> - } else {
> - b_oid = &dest->oid;
> - b_mode = dest->mode;
> - }
>
> if (handle_change_delete(o,
> o->call_depth ? orig->path : dest->path,
> + o->call_depth ? NULL : orig->path,
> &orig->oid, orig->mode,
> - a_oid, a_mode,
> - b_oid, b_mode,
> + &dest->oid, dest->mode,
> + rename_branch, delete_branch,
> _("rename"), _("renamed")))
> return -1;
>
> @@ -1665,11 +1658,27 @@ static int handle_modify_delete(struct merge_options
> *o,
> struct object_id *a_oid, int a_mode,
> struct object_id *b_oid, int b_mode)
> {
> + const char *modify_branch, *delete_branch;
> + struct object_id *changed_oid;
> + int changed_mode;
> +
> + if (a_oid) {
> + modify_branch = o->branch1;
> + delete_branch = o->branch2;
> + changed_oid = a_oid;
> + changed_mode = a_mode;
> + } else {
> + modify_branch = o->branch2;
> + delete_branch = o->branch1;
> + changed_oid = b_oid;
> + changed_mode = b_mode;
> + }
> +
> return handle_change_delete(o,
> - path,
> + path, NULL,
> o_oid, o_mode,
> - a_oid, a_mode,
> - b_oid, b_mode,
> + changed_oid, changed_mode,
> + modify_branch, delete_branch,
> _("modify"), _("modified"));
> }
>
> diff --git a/t/t6045-merge-rename-delete.sh b/t/t6045-merge-rename-delete.sh
> new file mode 100755
> index 0000000..8f33244
> --- /dev/null
> +++ b/t/t6045-merge-rename-delete.sh
> @@ -0,0 +1,23 @@
> +#!/bin/sh
> +
> +test_description='Merge-recursive rename/delete conflict message'
> +. ./test-lib.sh
> +
> +test_expect_success 'rename/delete' '
> +echo foo >A &&
> +git add A &&
> +git commit -m "initial" &&
> +
> +git checkout -b rename &&
> +git mv A B &&
> +git commit -m "rename" &&
> +
> +git checkout master &&
> +git rm A &&
> +git commit -m "delete" &&
> +
> +test_must_fail git merge --strategy=recursive rename >output &&
> +test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B
> in rename. Version rename of B left in tree." output
> +'
> +
> +test_done