Skip to content

Commit c629d2a

Browse files
committed
merge: support zdiff3 conflict styles
1 parent 1458fb5 commit c629d2a

File tree

7 files changed

+141
-2
lines changed

7 files changed

+141
-2
lines changed

include/git2/checkout.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ typedef enum {
182182
* notifications; don't update the working directory or index.
183183
*/
184184
GIT_CHECKOUT_DRY_RUN = (1u << 24),
185-
185+
186+
/** Include common ancestor data in zdiff3 format for conflicts */
187+
GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25),
188+
186189
/**
187190
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
188191
*/

include/git2/merge.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ typedef enum {
159159
GIT_MERGE_FILE_DIFF_PATIENCE = (1 << 6),
160160

161161
/** Take extra time to find minimal diff */
162-
GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7)
162+
GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7),
163+
164+
/** Create zdiff3 ("zealous diff3")-style files */
165+
GIT_MERGE_FILE_STYLE_ZDIFF3 = (1 << 8)
163166
} git_merge_file_flag_t;
164167

165168
#define GIT_MERGE_CONFLICT_MARKER_SIZE 7

src/checkout.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,9 @@ static int checkout_write_merge(
20702070
if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)
20712071
opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3;
20722072

2073+
if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3)
2074+
opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3;
2075+
20732076
opts.ancestor_label = data->opts.ancestor_label ?
20742077
data->opts.ancestor_label : "ancestor";
20752078
opts.our_label = data->opts.our_label ?
@@ -2493,6 +2496,8 @@ static int checkout_data_init(
24932496
data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE;
24942497
else if (strcmp(conflict_style->value, "diff3") == 0)
24952498
data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3;
2499+
else if (strcmp(conflict_style->value, "zdiff3") == 0)
2500+
data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3;
24962501
else {
24972502
git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'",
24982503
conflict_style->value);

src/merge_file.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ static int merge_file__xdiff(
123123

124124
if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
125125
xmparam.style = XDL_MERGE_DIFF3;
126+
if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3)
127+
xmparam.style = XDL_MERGE_ZEALOUS_DIFF3;
126128

127129
if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
128130
xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;

tests/merge/conflict_data.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@
3636
"this file is changed in branch and master\n" \
3737
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
3838

39+
#define CONFLICTING_ZDIFF3_FILE \
40+
"<<<<<<< HEAD\n" \
41+
"this file is changed in master and branch\n" \
42+
"||||||| initial\n" \
43+
"this file is a conflict\n" \
44+
"=======\n" \
45+
"this file is changed in branch and master\n" \
46+
">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
47+
3948
#define CONFLICTING_UNION_FILE \
4049
"this file is changed in master and branch\n" \
4150
"this file is changed in branch and master\n"

tests/merge/files.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,42 @@ void test_merge_files__crlf_conflict_markers_for_crlf_files(void)
424424
cl_assert(memcmp(expected_diff3, result.ptr, expected_len) == 0);
425425
git_merge_file_result_free(&result);
426426
}
427+
428+
void test_merge_files__conflicts_in_zdiff3(void)
429+
{
430+
git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
431+
ours = GIT_MERGE_FILE_INPUT_INIT,
432+
theirs = GIT_MERGE_FILE_INPUT_INIT;
433+
git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
434+
git_merge_file_result result = {0};
435+
436+
const char *expected_zdiff3 =
437+
"1,\nfoo,\nbar,\n" \
438+
"<<<<<<< file.txt\n" \
439+
"||||||| file.txt\n# add more here\n" \
440+
"=======\nquux,\nwoot,\n" \
441+
">>>>>>> file.txt\nbaz,\n3,\n";
442+
size_t expected_zdiff3_len = strlen(expected_zdiff3);
443+
444+
ancestor.ptr = "1,\n# add more here\n3,\n";
445+
ancestor.size = strlen(ancestor.ptr);
446+
ancestor.path = "file.txt";
447+
ancestor.mode = 0100644;
448+
449+
ours.ptr = "1,\nfoo,\nbar,\nbaz,\n3,\n";
450+
ours.size = strlen(ours.ptr);
451+
ours.path = "file.txt";
452+
ours.mode = 0100644;
453+
454+
theirs.ptr = "1,\nfoo,\nbar,\nquux,\nwoot,\nbaz,\n3,\n";
455+
theirs.size = strlen(theirs.ptr);
456+
theirs.path = "file.txt";
457+
theirs.mode = 0100644;
458+
459+
opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3;
460+
cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts));
461+
cl_assert_equal_i(0, result.automergeable);
462+
cl_assert_equal_i(expected_zdiff3_len, result.len);
463+
cl_assert(memcmp(expected_zdiff3, result.ptr, expected_zdiff3_len) == 0);
464+
git_merge_file_result_free(&result);
465+
}

tests/merge/workdir/simple.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,42 @@ void test_merge_workdir_simple__diff3(void)
323323
cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
324324
}
325325

326+
void test_merge_workdir_simple__zdiff3(void)
327+
{
328+
git_str conflicting_buf = GIT_STR_INIT;
329+
330+
struct merge_index_entry merge_index_entries[] = {
331+
ADDED_IN_MASTER_INDEX_ENTRY,
332+
AUTOMERGEABLE_INDEX_ENTRY,
333+
CHANGED_IN_BRANCH_INDEX_ENTRY,
334+
CHANGED_IN_MASTER_INDEX_ENTRY,
335+
336+
{ 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
337+
{ 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
338+
{ 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
339+
340+
UNCHANGED_INDEX_ENTRY,
341+
};
342+
343+
struct merge_reuc_entry merge_reuc_entries[] = {
344+
AUTOMERGEABLE_REUC_ENTRY,
345+
REMOVED_IN_BRANCH_REUC_ENTRY,
346+
REMOVED_IN_MASTER_REUC_ENTRY
347+
};
348+
349+
set_core_autocrlf_to(repo, false);
350+
351+
merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3);
352+
353+
cl_git_pass(git_futils_readbuffer(&conflicting_buf,
354+
TEST_REPO_PATH "/conflicting.txt"));
355+
cl_assert_equal_s(CONFLICTING_ZDIFF3_FILE, conflicting_buf.ptr);
356+
git_str_dispose(&conflicting_buf);
357+
358+
cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
359+
cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
360+
}
361+
326362
void test_merge_workdir_simple__union(void)
327363
{
328364
git_str conflicting_buf = GIT_STR_INIT;
@@ -436,6 +472,48 @@ void test_merge_workdir_simple__diff3_from_config(void)
436472
git_config_free(config);
437473
}
438474

475+
void test_merge_workdir_simple__zdiff3_from_config(void)
476+
{
477+
git_config *config;
478+
git_str conflicting_buf = GIT_STR_INIT;
479+
480+
struct merge_index_entry merge_index_entries[] = {
481+
ADDED_IN_MASTER_INDEX_ENTRY,
482+
AUTOMERGEABLE_INDEX_ENTRY,
483+
CHANGED_IN_BRANCH_INDEX_ENTRY,
484+
CHANGED_IN_MASTER_INDEX_ENTRY,
485+
486+
{ 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
487+
{ 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
488+
{ 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
489+
490+
UNCHANGED_INDEX_ENTRY,
491+
};
492+
493+
struct merge_reuc_entry merge_reuc_entries[] = {
494+
AUTOMERGEABLE_REUC_ENTRY,
495+
REMOVED_IN_BRANCH_REUC_ENTRY,
496+
REMOVED_IN_MASTER_REUC_ENTRY
497+
};
498+
499+
cl_git_pass(git_repository_config(&config, repo));
500+
cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "zdiff3"));
501+
502+
set_core_autocrlf_to(repo, false);
503+
504+
merge_simple_branch(0, 0);
505+
506+
cl_git_pass(git_futils_readbuffer(&conflicting_buf,
507+
TEST_REPO_PATH "/conflicting.txt"));
508+
cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_ZDIFF3_FILE) == 0);
509+
git_str_dispose(&conflicting_buf);
510+
511+
cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
512+
cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
513+
514+
git_config_free(config);
515+
}
516+
439517
void test_merge_workdir_simple__merge_overrides_config(void)
440518
{
441519
git_config *config;

0 commit comments

Comments
 (0)