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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
diff --git a/src/merge.c b/src/merge.c
index d7b23e2..05ca3d0 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);