Commit e651e8e2 by Edward Thomson

Introduce diff3 mode for checking out conflicts

parent 6b92c99b
......@@ -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;
......
......@@ -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";
......
......@@ -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]) ||
......
......@@ -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.");
......
......@@ -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}
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment