diff --git a/include/git2/merge.h b/include/git2/merge.h index 5a0b2e7f2..c80803d36 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -30,6 +30,16 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two); +/** + * Find a merge base given a list of commits + * + * @param out the OID of a merge base considering all the commits + * @param repo the repository where the commits exist + * @param input_array oids of the commits + * @param length The number of commits in the provided `input_array` + */ +GIT_EXTERN(int) git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length); + /** @} */ GIT_END_DECL #endif diff --git a/src/revwalk.c b/src/revwalk.c index e64d93f20..719361c55 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -104,6 +104,9 @@ static void commit_list_free(commit_list **list_p) { commit_list *list = *list_p; + if (list == NULL) + return; + while (list) { commit_list *temp = list; list = temp->next; @@ -345,6 +348,59 @@ static int merge_bases_many(commit_list **out, git_revwalk *walk, commit_object return 0; } +int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length) +{ + git_revwalk *walk; + git_vector list; + commit_list *result = NULL; + int error = -1; + unsigned int i; + commit_object *commit; + + assert(out && repo && input_array); + + if (length < 2) { + giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length); + return -1; + } + + if (git_vector_init(&list, length - 1, NULL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + goto cleanup; + + for (i = 1; i < length; i++) { + commit = commit_lookup(walk, &input_array[i]); + if (commit == NULL) + goto cleanup; + + git_vector_insert(&list, commit); + } + + commit = commit_lookup(walk, &input_array[0]); + if (commit == NULL) + goto cleanup; + + if (merge_bases_many(&result, walk, commit, &list) < 0) + goto cleanup; + + if (!result) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + git_oid_cpy(out, &result->item->oid); + + error = 0; + +cleanup: + commit_list_free(&result); + git_revwalk_free(walk); + git_vector_free(&list); + return error; +} + int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two) { git_revwalk *walk; diff --git a/tests-clar/revwalk/mergebase.c b/tests-clar/revwalk/mergebase.c index 54ddeaf72..5339171d7 100644 --- a/tests-clar/revwalk/mergebase.c +++ b/tests-clar/revwalk/mergebase.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "vector.h" static git_repository *_repo; @@ -65,6 +66,71 @@ void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) cl_assert_equal_i(GIT_ENOTFOUND, error); } +static void assert_mergebase_many(const char *expected_sha, int count, ...) +{ + va_list ap; + int i; + git_oid *oids; + git_oid oid, expected; + char *partial_oid; + git_object *object; + + oids = git__malloc(count * sizeof(git_oid)); + cl_assert(oids != NULL); + + memset(oids, 0x0, count * sizeof(git_oid)); + + va_start(ap, count); + + for (i = 0; i < count; ++i) { + partial_oid = va_arg(ap, char *); + cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid))); + + cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJ_COMMIT)); + git_oid_cpy(&oids[i], git_object_id(object)); + git_object_free(object); + } + + va_end(ap); + + if (expected_sha == NULL) + cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, oids, count)); + else { + cl_git_pass(git_merge_base_many(&oid, _repo, oids, count)); + cl_git_pass(git_oid_fromstr(&expected, expected_sha)); + + cl_assert(git_oid_cmp(&expected, &oid) == 0); + } + + git__free(oids); +} + +void test_revwalk_mergebase__many_no_common_ancestor_returns_ENOTFOUND(void) +{ + assert_mergebase_many(NULL, 3, "41bc8c", "e90810", "a65fed"); + assert_mergebase_many(NULL, 3, "e90810", "41bc8c", "a65fed"); + assert_mergebase_many(NULL, 3, "e90810", "a65fed", "41bc8c"); + assert_mergebase_many(NULL, 3, "a65fed", "e90810", "41bc8c"); + assert_mergebase_many(NULL, 3, "a65fed", "e90810", "41bc8c"); + assert_mergebase_many(NULL, 3, "a65fed", "41bc8c", "e90810"); + + assert_mergebase_many(NULL, 3, "e90810", "763d71", "a65fed"); +} + +void test_revwalk_mergebase__many_merge_branch(void) +{ + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); + + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "e90810", "a65fed"); + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "a65fed", "e90810"); + + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "849607", "763d71"); + assert_mergebase_many("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71"); + + assert_mergebase_many("5b5b025afb0b4c913b4c338a42934a3863bf3644", 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c"); +} + /* * $ git log --graph --all * * commit 763d71aadf09a7951596c9746c024e7eece7c7af