From e651e8e2b5c5eb021448fb0f0a36cb3f10fa9326 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 19 Jan 2014 15:05:08 -0800 Subject: [PATCH] Introduce diff3 mode for checking out conflicts --- include/git2/checkout.h | 7 ++++++ src/checkout.c | 5 +++- src/merge.c | 24 ++++++++++++++---- src/merge_file.c | 3 +++ src/merge_file.h | 9 +++++++ tests/merge/workdir/simple.c | 49 +++++++++++++++++++++++++++++++++++- 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index b94a5e2ff..0faf4ab14 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -152,6 +152,12 @@ typedef enum { /** Don't overwrite ignored files that exist in the checkout target */ GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = (1u << 19), + /** Write normal merge files for conflicts */ + GIT_CHECKOUT_CONFLICT_STYLE_MERGE = (1u << 20), + + /** Include common ancestor data in diff3 format files for conflicts */ + GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = (1u << 21), + /** * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ @@ -252,6 +258,7 @@ typedef struct git_checkout_opts { const char *target_directory; /** alternative checkout path to workdir */ + const char *ancestor_label; /** the name of the common ancestor side of conflicts */ const char *our_label; /** the name of the "our" side of conflicts */ const char *their_label; /** the name of the "their" side of conflicts */ } git_checkout_opts; diff --git a/src/checkout.c b/src/checkout.c index f64aa9a77..fcc0f872a 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1672,6 +1672,9 @@ static int checkout_write_merge( git_filebuf output = GIT_FILEBUF_INIT; int error = 0; + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) + merge_file_opts.style = GIT_MERGE_FILE_STYLE_DIFF3; + if ((conflict->ancestor && (error = git_merge_file_input_from_index_entry( &ancestor, data->repo, conflict->ancestor)) < 0) || @@ -1681,7 +1684,7 @@ static int checkout_write_merge( &theirs, data->repo, conflict->theirs)) < 0) goto done; - ancestor.label = NULL; + ancestor.label = data->opts.ancestor_label ? data->opts.ancestor_label : "ancestor"; ours.label = data->opts.our_label ? data->opts.our_label : "ours"; theirs.label = data->opts.their_label ? data->opts.their_label : "theirs"; diff --git a/src/merge.c b/src/merge.c index 124befc14..8554bf02b 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2165,6 +2165,8 @@ static int merge_normalize_opts( git_repository *repo, git_merge_opts *opts, const git_merge_opts *given, + const git_merge_head *ancestor_head, + const git_merge_head *our_head, size_t their_heads_len, const git_merge_head **their_heads) { @@ -2184,8 +2186,20 @@ static int merge_normalize_opts( if (!opts->checkout_opts.checkout_strategy) opts->checkout_opts.checkout_strategy = default_checkout_strategy; - if (!opts->checkout_opts.our_label) - opts->checkout_opts.our_label = "HEAD"; + /* TODO: for multiple ancestors in merge-recursive, this is "merged common ancestors" */ + if (!opts->checkout_opts.ancestor_label) { + if (ancestor_head && ancestor_head->commit) + opts->checkout_opts.ancestor_label = git_commit_summary(ancestor_head->commit); + else + opts->checkout_opts.ancestor_label = "ancestor"; + } + + if (!opts->checkout_opts.our_label) { + if (our_head && our_head->ref_name) + opts->checkout_opts.our_label = our_head->ref_name; + else + opts->checkout_opts.our_label = "ours"; + } if (!opts->checkout_opts.their_label) { if (their_heads_len == 1 && their_heads[0]->ref_name) @@ -2480,9 +2494,6 @@ int git_merge( their_trees = git__calloc(their_heads_len, sizeof(git_tree *)); GITERR_CHECK_ALLOC(their_trees); - if ((error = merge_normalize_opts(repo, &opts, given_opts, their_heads_len, their_heads)) < 0) - goto on_error; - if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) goto on_error; @@ -2494,6 +2505,9 @@ int git_merge( error != GIT_ENOTFOUND) goto on_error; + if ((error = merge_normalize_opts(repo, &opts, given_opts, ancestor_head, our_head, their_heads_len, their_heads)) < 0) + goto on_error; + if (their_heads_len == 1 && ancestor_head != NULL && (merge_check_uptodate(result, ancestor_head, their_heads[0]) || diff --git a/src/merge_file.c b/src/merge_file.c index d13190276..fb0e29960 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -161,6 +161,9 @@ int git_merge_files( (opts && (opts->flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM)) ? XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; + if (opts && opts->style == GIT_MERGE_FILE_STYLE_DIFF3) + xmparam.style = XDL_MERGE_DIFF3; + if ((xdl_result = xdl_merge(&ancestor->mmfile, &ours->mmfile, &theirs->mmfile, &xmparam, &mmbuffer)) < 0) { giterr_set(GITERR_MERGE, "Failed to merge files."); diff --git a/src/merge_file.h b/src/merge_file.h index 5d7ea9752..332be490b 100644 --- a/src/merge_file.h +++ b/src/merge_file.h @@ -39,9 +39,18 @@ typedef enum { GIT_MERGE_FILE_SIMPLIFY_ALNUM = (1 << 0), } git_merge_file_flags_t; +typedef enum { + /* Create standard conflicted merge files */ + GIT_MERGE_FILE_STYLE_MERGE = 0, + + /* Create diff3-style files */ + GIT_MERGE_FILE_STYLE_DIFF3 = 1, +} git_merge_file_style_t; + typedef struct { git_merge_file_favor_t favor; git_merge_file_flags_t flags; + git_merge_file_style_t style; } git_merge_file_options; #define GIT_MERGE_FILE_OPTIONS_INIT {0} diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c index d4f387a26..40a07d5e7 100644 --- a/tests/merge/workdir/simple.c +++ b/tests/merge/workdir/simple.c @@ -93,9 +93,18 @@ static git_index *repo_index; "this file is automergeable\r\n" \ "this file is changed in branch\r\n" +#define CONFLICTING_MERGE_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + #define CONFLICTING_DIFF3_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" @@ -244,7 +253,7 @@ void test_merge_workdir_simple__automerge_crlf(void) #endif /* GIT_WIN32 */ } -void test_merge_workdir_simple__diff3(void) +void test_merge_workdir_simple__mergefile(void) { git_merge_result *result; git_buf conflicting_buf = GIT_BUF_INIT; @@ -271,6 +280,44 @@ void test_merge_workdir_simple__diff3(void) cl_assert(result = merge_simple_branch(0, 0)); cl_assert(!git_merge_result_is_fastforward(result)); + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0); + git_buf_free(&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_merge_result_free(result); +} + +void test_merge_workdir_simple__diff3(void) +{ + git_merge_result *result; + git_buf conflicting_buf = GIT_BUF_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_assert(result = merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)); + cl_assert(!git_merge_result_is_fastforward(result)); + cl_git_pass(git_futils_readbuffer(&conflicting_buf, TEST_REPO_PATH "/conflicting.txt")); cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0);