From c629d2a11cbc5863c4a68eaafa8bf6a99b263f26 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 29 Jan 2022 21:02:15 -0500 Subject: merge: support zdiff3 conflict styles --- include/git2/checkout.h | 5 ++- include/git2/merge.h | 5 ++- src/checkout.c | 5 +++ src/merge_file.c | 2 ++ tests/merge/conflict_data.h | 9 +++++ tests/merge/files.c | 39 ++++++++++++++++++++++ tests/merge/workdir/simple.c | 78 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 141 insertions(+), 2 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index f026d5bc2..9f834111a 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -182,7 +182,10 @@ typedef enum { * notifications; don't update the working directory or index. */ GIT_CHECKOUT_DRY_RUN = (1u << 24), - + + /** Include common ancestor data in zdiff3 format for conflicts */ + GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25), + /** * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ diff --git a/include/git2/merge.h b/include/git2/merge.h index 3b3132f26..edd090cff 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -159,7 +159,10 @@ typedef enum { GIT_MERGE_FILE_DIFF_PATIENCE = (1 << 6), /** Take extra time to find minimal diff */ - GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7) + GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), + + /** Create zdiff3 ("zealous diff3")-style files */ + GIT_MERGE_FILE_STYLE_ZDIFF3 = (1 << 8) } git_merge_file_flag_t; #define GIT_MERGE_CONFLICT_MARKER_SIZE 7 diff --git a/src/checkout.c b/src/checkout.c index bc3048cc8..6a4643196 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2070,6 +2070,9 @@ static int checkout_write_merge( if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) + opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; + opts.ancestor_label = data->opts.ancestor_label ? data->opts.ancestor_label : "ancestor"; opts.our_label = data->opts.our_label ? @@ -2493,6 +2496,8 @@ static int checkout_data_init( data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE; else if (strcmp(conflict_style->value, "diff3") == 0) data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; + else if (strcmp(conflict_style->value, "zdiff3") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3; else { git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", conflict_style->value); diff --git a/src/merge_file.c b/src/merge_file.c index 7285884b4..732a047b1 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -123,6 +123,8 @@ static int merge_file__xdiff( if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) xmparam.style = XDL_MERGE_DIFF3; + if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3) + xmparam.style = XDL_MERGE_ZEALOUS_DIFF3; if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; diff --git a/tests/merge/conflict_data.h b/tests/merge/conflict_data.h index 27f19c1b0..0b1e7ee03 100644 --- a/tests/merge/conflict_data.h +++ b/tests/merge/conflict_data.h @@ -36,6 +36,15 @@ "this file is changed in branch and master\n" \ ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" +#define CONFLICTING_ZDIFF3_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "||||||| initial\n" \ + "this file is a conflict\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + #define CONFLICTING_UNION_FILE \ "this file is changed in master and branch\n" \ "this file is changed in branch and master\n" diff --git a/tests/merge/files.c b/tests/merge/files.c index fbc54e11e..6296f3b7b 100644 --- a/tests/merge/files.c +++ b/tests/merge/files.c @@ -424,3 +424,42 @@ void test_merge_files__crlf_conflict_markers_for_crlf_files(void) cl_assert(memcmp(expected_diff3, result.ptr, expected_len) == 0); git_merge_file_result_free(&result); } + +void test_merge_files__conflicts_in_zdiff3(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + const char *expected_zdiff3 = + "1,\nfoo,\nbar,\n" \ + "<<<<<<< file.txt\n" \ + "||||||| file.txt\n# add more here\n" \ + "=======\nquux,\nwoot,\n" \ + ">>>>>>> file.txt\nbaz,\n3,\n"; + size_t expected_zdiff3_len = strlen(expected_zdiff3); + + ancestor.ptr = "1,\n# add more here\n3,\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "file.txt"; + ancestor.mode = 0100644; + + ours.ptr = "1,\nfoo,\nbar,\nbaz,\n3,\n"; + ours.size = strlen(ours.ptr); + ours.path = "file.txt"; + ours.mode = 0100644; + + theirs.ptr = "1,\nfoo,\nbar,\nquux,\nwoot,\nbaz,\n3,\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "file.txt"; + theirs.mode = 0100644; + + opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_zdiff3_len, result.len); + cl_assert(memcmp(expected_zdiff3, result.ptr, expected_zdiff3_len) == 0); + git_merge_file_result_free(&result); +} diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c index f51ff09a7..0b672d6a0 100644 --- a/tests/merge/workdir/simple.c +++ b/tests/merge/workdir/simple.c @@ -323,6 +323,42 @@ void test_merge_workdir_simple__diff3(void) cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); } +void test_merge_workdir_simple__zdiff3(void) +{ + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert_equal_s(CONFLICTING_ZDIFF3_FILE, conflicting_buf.ptr); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); +} + void test_merge_workdir_simple__union(void) { git_str conflicting_buf = GIT_STR_INIT; @@ -436,6 +472,48 @@ void test_merge_workdir_simple__diff3_from_config(void) git_config_free(config); } +void test_merge_workdir_simple__zdiff3_from_config(void) +{ + git_config *config; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "zdiff3")); + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_ZDIFF3_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_config_free(config); +} + void test_merge_workdir_simple__merge_overrides_config(void) { git_config *config; -- cgit v1.2.1