Commit 7db0e6ee48fd68009a7e78cbcbca125d6ce9008d

Carlos Martín Nieto 2014-07-18T16:00:21

merge: expose multiple merge bases We always calculate multiple merge bases, but up to now we had only exposed the "best" merge base. Introduce git_oidarray which analogously to git_strarray lets us return multiple ids.

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d389b2c..dc453f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,3 +39,6 @@ v0.21 + 1
 
 * Add support for refspecs with the asterisk in the middle of a
   pattern.
+
+* Introduce git_merge_bases() and the git_oidarray type to expose all
+  merge bases between two commits.
diff --git a/include/git2/merge.h b/include/git2/merge.h
index 9eb14cc..bd5ebc1 100644
--- a/include/git2/merge.h
+++ b/include/git2/merge.h
@@ -10,6 +10,7 @@
 #include "common.h"
 #include "types.h"
 #include "oid.h"
+#include "oidarray.h"
 #include "checkout.h"
 #include "index.h"
 
@@ -321,6 +322,21 @@ GIT_EXTERN(int) git_merge_base(
 	const git_oid *two);
 
 /**
+ * Find merge bases between two commits
+ *
+ * @param out array in which to store the resulting ids
+ * @param repo the repository where the commits exist
+ * @param one one of the commits
+ * @param two the other commit
+ * @return 0 on success, GIT_ENOTFOUND if not found or error code
+ */
+GIT_EXTERN(int) git_merge_bases(
+	git_oidarray *out,
+	git_repository *repo,
+	const git_oid *one,
+	const git_oid *two);
+
+/**
  * Find a merge base given a list of commits
  *
  * @param out the OID of a merge base considering all the commits
diff --git a/include/git2/oidarray.h b/include/git2/oidarray.h
new file mode 100644
index 0000000..0b32045
--- /dev/null
+++ b/include/git2/oidarray.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_oidarray_h__
+#define INCLUDE_git_oidarray_h__
+
+#include "common.h"
+#include "oid.h"
+
+GIT_BEGIN_DECL
+
+/** Array of object ids */
+typedef struct git_oidarray {
+	git_oid *ids;
+	size_t count;
+} git_oidarray;
+
+/**
+ * Free the OID array
+ *
+ * This method must (and must only) be called on `git_oidarray`
+ * objects where the array is allocated by the library. Not doing so,
+ * will result in a memory leak.
+ *
+ * This does not free the `git_oidarray` itself, since the library will
+ * never allocate that object directly itself (it is more commonly embedded
+ * inside another struct or created on the stack).
+ *
+ * @param array git_oidarray from which to free oid data
+ */
+GIT_EXTERN(void) git_oidarray_free(git_oidarray *array);
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
diff --git a/src/merge.c b/src/merge.c
index f8d008a..14b5b5c 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -27,6 +27,7 @@
 #include "index.h"
 #include "filebuf.h"
 #include "config.h"
+#include "oidarray.h"
 
 #include "git2/types.h"
 #include "git2/repository.h"
@@ -39,6 +40,7 @@
 #include "git2/signature.h"
 #include "git2/config.h"
 #include "git2/tree.h"
+#include "git2/oidarray.h"
 #include "git2/sys/index.h"
 
 #define GIT_MERGE_INDEX_ENTRY_EXISTS(X)	((X).mode != 0)
@@ -139,7 +141,7 @@ int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, co
 	return 0;
 }
 
-int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two)
 {
 	git_revwalk *walk;
 	git_vector list;
@@ -173,13 +175,63 @@ int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const
 		return GIT_ENOTFOUND;
 	}
 
+	*out = result;
+	*walk_out = walk;
+
+	return 0;
+
+on_error:
+	git_revwalk_free(walk);
+	return -1;
+
+}
+
+int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+	int error;
+	git_revwalk *walk;
+	git_commit_list *result;
+
+	if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+		return error;
+
 	git_oid_cpy(out, &result->item->oid);
 	git_commit_list_free(&result);
 	git_revwalk_free(walk);
 
 	return 0;
+}
+
+int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+	int error;
+        git_revwalk *walk;
+	git_commit_list *result, *list;
+	git_array_oid_t array;
+
+	git_array_init(array);
+
+	if ((error = merge_bases(&result, &walk, repo, one, two)) < 0)
+		return error;
+
+	list = result;
+	while (list) {
+		git_oid *id = git_array_alloc(array);
+		if (id == NULL)
+			goto on_error;
+
+		git_oid_cpy(id, &list->item->oid);
+		list = list->next;
+	}
+
+	git_oidarray__from_array(out, &array);
+	git_commit_list_free(&result);
+	git_revwalk_free(walk);
+
+	return 0;
 
 on_error:
+	git_commit_list_free(&result);
 	git_revwalk_free(walk);
 	return -1;
 }
diff --git a/src/oidarray.c b/src/oidarray.c
new file mode 100644
index 0000000..1d51a29
--- /dev/null
+++ b/src/oidarray.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/oidarray.h"
+#include "oidarray.h"
+#include "array.h"
+
+void git_oidarray_free(git_oidarray *arr)
+{
+	git__free(arr->ids);
+}
+
+void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array)
+{
+	arr->count = array->size;
+	arr->ids = array->ptr;
+}
diff --git a/src/oidarray.h b/src/oidarray.h
new file mode 100644
index 0000000..a7215ae
--- /dev/null
+++ b/src/oidarray.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_oidarray_h__
+#define INCLUDE_oidarray_h__
+
+#include "common.h"
+#include "git2/oidarray.h"
+#include "array.h"
+
+typedef git_array_t(git_oid) git_array_oid_t;
+
+extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array);
+
+#endif
diff --git a/tests/revwalk/mergebase.c b/tests/revwalk/mergebase.c
index 2c7184f..e12e597 100644
--- a/tests/revwalk/mergebase.c
+++ b/tests/revwalk/mergebase.c
@@ -135,6 +135,24 @@ void test_revwalk_mergebase__prefer_youngest_merge_base(void)
 	cl_assert_equal_oid(&expected, &result);
 }
 
+void test_revwalk_mergebase__multiple_merge_bases(void)
+{
+	git_oid one, two, expected1, expected2;
+	git_oidarray result = {NULL, 0};
+
+	cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f "));
+	cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+	cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
+	cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
+
+	cl_git_pass(git_merge_bases(&result, _repo, &one, &two));
+	cl_assert_equal_i(2, result.count);
+	cl_assert_equal_oid(&expected1, &result.ids[0]);
+	cl_assert_equal_oid(&expected2, &result.ids[1]);
+
+	git_oidarray_free(&result);
+}
+
 void test_revwalk_mergebase__no_off_by_one_missing(void)
 {
 	git_oid result, one, two;