Commit e651e8e2b5c5eb021448fb0f0a36cb3f10fa9326

Edward Thomson 2014-01-19T15:05:08

Introduce diff3 mode for checking out conflicts

diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index b94a5e2..0faf4ab 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 f64aa9a..fcc0f87 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 124befc..8554bf0 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 d131902..fb0e299 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 5d7ea97..332be49 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 d4f387a..40a07d5 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);