From 75dee59c94d39b308529d45dfb993b25d2e9a5f0 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 26 Oct 2015 10:37:58 -0400 Subject: merge: build virtual base of multiple merge bases When the commits to merge have multiple common ancestors, build a "virtual" base tree by merging the common ancestors. --- src/merge.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 10 deletions(-) diff --git a/src/merge.c b/src/merge.c index d7b23e2c8..05ca3d0ec 100644 --- a/src/merge.c +++ b/src/merge.c @@ -27,6 +27,7 @@ #include "config.h" #include "oidarray.h" #include "annotated_commit.h" +#include "commit.h" #include "git2/types.h" #include "git2/repository.h" @@ -1922,6 +1923,117 @@ done: return error; } +#define INSERT_VIRTUAL_BASE_PARENT(commit, parent_id) \ + do { \ + id = git_array_alloc(commit->parent_ids); \ + GITERR_CHECK_ALLOC(id); \ + git_oid_cpy(id, parent_id); \ + } while(0) + +static int build_virtual_base( + git_commit **out, + git_repository *repo, + const git_commit *one, + bool one_is_virtual, + const git_oid *two_id) +{ + git_commit *two = NULL, *result; + git_index *index = NULL; + git_oid tree_id, *id; + int error; + + /* TODO: propagate merge options */ + if ((error = git_commit_lookup(&two, repo, two_id)) < 0 || + (error = git_merge_commits(&index, repo, one, two, NULL)) < 0) + goto done; + + if ((error = git_index_write_tree_to(&tree_id, index, repo)) < 0) + goto done; + + if ((result = git__calloc(1, sizeof(git_commit))) == NULL) + goto done; + + result->object.repo = repo; + + /* if the initial commit we were given is virtual, we are octopus + * merging - that virtual base's parents should actually be the + * parents that we use for our new virtual commit. otherwise, it + * is an actual parent. + */ + if (one_is_virtual) { + size_t i, cnt = git_commit_parentcount(one); + + for (i = 0; i < cnt; i++) + INSERT_VIRTUAL_BASE_PARENT(result, git_commit_parent_id(one, i)); + } else { + INSERT_VIRTUAL_BASE_PARENT(result, git_commit_id(one)); + } + + INSERT_VIRTUAL_BASE_PARENT(result, two_id); + + git_oid_cpy(&result->tree_id, &tree_id); + + *out = result; + +done: + git_index_free(index); + git_commit_free(two); + return error; +} + +#undef INSERT_VIRTUAL_BASE_PARENT + +static int compute_base_tree( + git_tree **out, + git_repository *repo, + const git_commit *our_commit, + const git_commit *their_commit, + bool recursive) +{ + git_commit_list *base_list; + git_revwalk *walk; + git_commit *base = NULL; + bool base_virtual = false; + int error = 0; + + *out = NULL; + + if ((error = merge_bases(&base_list, &walk, repo, + git_commit_id(our_commit), git_commit_id(their_commit))) < 0) + return error; + + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + goto done; + } + + if ((error = git_commit_lookup(&base, repo, &base_list->item->oid)) < 0) + goto done; + + while (recursive && base_list->next) { + git_commit *new_base; + + base_list = base_list->next; + + if ((error = build_virtual_base(&new_base, repo, base, base_virtual, + &base_list->item->oid)) < 0) + goto done; + + git_commit_free(base); + base = new_base; + base_virtual = true; + } + + error = git_commit_tree(out, base); + +done: + git_commit_free(base); + git_commit_list_free(&base_list); + git_revwalk_free(walk); + + return error; +} int git_merge_commits( git_index **out, @@ -1930,18 +2042,20 @@ int git_merge_commits( const git_commit *their_commit, const git_merge_options *opts) { - git_oid ancestor_oid; - git_commit *ancestor_commit = NULL; git_tree *our_tree = NULL, *their_tree = NULL, *ancestor_tree = NULL; + bool recursive; int error = 0; - if ((error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))) < 0 && - error == GIT_ENOTFOUND) - giterr_clear(); - else if (error < 0 || - (error = git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)) < 0 || - (error = git_commit_tree(&ancestor_tree, ancestor_commit)) < 0) - goto done; + recursive = !opts || (opts->flags & GIT_MERGE_NO_RECURSIVE) == 0; + + if ((error = compute_base_tree(&ancestor_tree, repo, + our_commit, their_commit, recursive)) < 0) { + + if (error == GIT_ENOTFOUND) + giterr_clear(); + else + goto done; + } if ((error = git_commit_tree(&our_tree, our_commit)) < 0 || (error = git_commit_tree(&their_tree, their_commit)) < 0 || @@ -1949,7 +2063,6 @@ int git_merge_commits( goto done; done: - git_commit_free(ancestor_commit); git_tree_free(our_tree); git_tree_free(their_tree); git_tree_free(ancestor_tree); -- cgit v1.2.1