diff options
author | Edward Thomson <ethomson@microsoft.com> | 2015-11-20 17:33:49 -0500 |
---|---|---|
committer | Edward Thomson <ethomson@microsoft.com> | 2015-11-25 15:38:39 -0500 |
commit | 78859c63442bb367a4d426ec8ee31c82a28a93d7 (patch) | |
tree | 75a5bc0e2ccfd8d0dda419afdb906db3e866ee0a /src | |
parent | 34a51428a121800509c2bea94137a17802e37982 (diff) | |
download | libgit2-78859c63442bb367a4d426ec8ee31c82a28a93d7.tar.gz |
merge: handle conflicts in recursive base building
When building a recursive merge base, allow conflicts to occur.
Use the file (with conflict markers) as the common ancestor.
The user has already seen and dealt with this conflict by virtue
of having a criss-cross merge. If they resolved this conflict
identically in both branches, then there will be no conflict in the
result. This is the best case scenario.
If they did not resolve the conflict identically in the two branches,
then we will generate a new conflict. If the user is simply using
standard conflict output then the results will be fairly sensible.
But if the user is using a mergetool or using diff3 output, then the
common ancestor will be a conflict file (itself with diff3 output,
haha!). This is quite terrible, but it matches git's behavior.
Diffstat (limited to 'src')
-rw-r--r-- | src/merge.c | 67 |
1 files changed, 48 insertions, 19 deletions
diff --git a/src/merge.c b/src/merge.c index 64c8f1116..f05e45c9f 100644 --- a/src/merge.c +++ b/src/merge.c @@ -49,6 +49,19 @@ #define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) + +/** Internal merge flags. */ +enum { + /** The merge is for a virtual base in a recursive merge. */ + GIT_MERGE__VIRTUAL_BASE = (1 << 31), +}; + +enum { + /** Accept the conflict file, staging it as the merge result. */ + GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, +}; + + typedef enum { TREE_IDX_ANCESTOR = 0, TREE_IDX_OURS = 1, @@ -801,11 +814,9 @@ static int merge_conflict_resolve_automerge( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict, - unsigned int merge_file_favor, - unsigned int file_flags) + const git_merge_file_options *file_opts) { const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_file_result result = {0}; git_index_entry *index_entry; git_odb *odb = NULL; @@ -852,12 +863,9 @@ static int merge_conflict_resolve_automerge( theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; - opts.favor = merge_file_favor; - opts.flags = file_flags; - if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 || - (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, &opts)) < 0 || - !result.automergeable || + (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, file_opts)) < 0 || + (!result.automergeable && !(file_opts->flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) || (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0) goto done; @@ -887,8 +895,7 @@ static int merge_conflict_resolve( int *out, git_merge_diff_list *diff_list, const git_merge_diff *conflict, - unsigned int merge_file_favor, - unsigned int file_flags) + const git_merge_file_options *file_opts) { int resolved = 0; int error = 0; @@ -904,8 +911,7 @@ static int merge_conflict_resolve( if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, - merge_file_favor, file_flags)) < 0) + if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, file_opts)) < 0) goto done; *out = resolved; @@ -1829,6 +1835,7 @@ int git_merge__iterators( *empty_theirs = NULL; git_merge_diff_list *diff_list; git_merge_options opts; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_diff *conflict; git_vector changes; size_t i; @@ -1844,6 +1851,17 @@ int git_merge__iterators( if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) return error; + file_opts.favor = opts.file_favor; + file_opts.flags = opts.file_flags; + + /* use the git-inspired labels when virtual base building */ + if (opts.flags & GIT_MERGE__VIRTUAL_BASE) { + file_opts.ancestor_label = "merged common ancestors"; + file_opts.our_label = "Temporary merge branch 1"; + file_opts.their_label = "Temporary merge branch 2"; + file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED; + } + diff_list = git_merge_diff_list__alloc(repo); GITERR_CHECK_ALLOC(diff_list); @@ -1862,7 +1880,8 @@ int git_merge__iterators( git_vector_foreach(&changes, i, conflict) { int resolved = 0; - if ((error = merge_conflict_resolve(&resolved, diff_list, conflict, opts.file_favor, opts.file_flags)) < 0) + if ((error = merge_conflict_resolve( + &resolved, diff_list, conflict, &file_opts)) < 0) goto done; if (!resolved) { @@ -1962,16 +1981,27 @@ static int create_virtual_base( git_repository *repo, git_annotated_commit *one, git_annotated_commit *two, + const git_merge_options *opts, size_t recursion_level) { git_annotated_commit *result = NULL; git_index *index = NULL; + git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; result = git__calloc(1, sizeof(git_annotated_commit)); GITERR_CHECK_ALLOC(result); + /* Conflicts in the merge base creation do not propagate to conflicts + * in the result; the conflicted base will act as the common ancestor. + */ + if (opts) + memcpy(&virtual_opts, opts, sizeof(git_merge_options)); + + virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; + virtual_opts.flags |= GIT_MERGE__VIRTUAL_BASE; + if ((merge_annotated_commits(&index, NULL, repo, one, two, - recursion_level + 1, NULL)) < 0) + recursion_level + 1, &virtual_opts)) < 0) return -1; result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; @@ -1989,7 +2019,7 @@ static int compute_base( git_repository *repo, const git_annotated_commit *one, const git_annotated_commit *two, - bool recurse, + const git_merge_options *opts, size_t recursion_level) { git_array_oid_t head_ids = GIT_ARRAY_INIT; @@ -2007,7 +2037,7 @@ static int compute_base( if ((error = git_merge_bases_many(&bases, repo, head_ids.size, head_ids.ptr)) < 0 || (error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0 || - !recurse) + (opts && (opts->flags & GIT_MERGE_NO_RECURSIVE))) goto done; for (i = 1; i < bases.count; i++) { @@ -2015,7 +2045,7 @@ static int compute_base( if ((error = git_annotated_commit_lookup(&other, repo, &bases.ids[i])) < 0 || - (error = create_virtual_base(&new_base, repo, base, other, + (error = create_virtual_base(&new_base, repo, base, other, opts, recursion_level)) < 0) goto done; @@ -2076,10 +2106,9 @@ static int merge_annotated_commits( { git_annotated_commit *base = NULL; git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; - bool recurse = !opts || !(opts->flags & GIT_MERGE_NO_RECURSIVE); int error; - if ((error = compute_base(&base, repo, ours, theirs, recurse, + if ((error = compute_base(&base, repo, ours, theirs, opts, recursion_level)) < 0) { if (error != GIT_ENOTFOUND) |