summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@microsoft.com>2014-01-19 15:05:08 -0800
committerEdward Thomson <ethomson@microsoft.com>2014-01-20 17:15:13 -0500
commite651e8e2b5c5eb021448fb0f0a36cb3f10fa9326 (patch)
treec987d53e643065e1983bfd75e1549deb2c3edbb3
parent6b92c99bcbf32de131754a4f750278f84bf5b766 (diff)
downloadlibgit2-e651e8e2b5c5eb021448fb0f0a36cb3f10fa9326.tar.gz
Introduce diff3 mode for checking out conflicts
-rw-r--r--include/git2/checkout.h7
-rw-r--r--src/checkout.c5
-rw-r--r--src/merge.c24
-rw-r--r--src/merge_file.c3
-rw-r--r--src/merge_file.h9
-rw-r--r--tests/merge/workdir/simple.c49
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;
@@ -273,6 +282,44 @@ void test_merge_workdir_simple__diff3(void)
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);
git_buf_free(&conflicting_buf);