diff --git a/include/git2/errors.h b/include/git2/errors.h index 8ca900969..4eb9e94ef 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -64,6 +64,7 @@ typedef enum { GITERR_STASH, GITERR_CHECKOUT, GITERR_FETCHHEAD, + GITERR_MERGE, } git_error_t; /** diff --git a/include/git2/repository.h b/include/git2/repository.h index 02e689111..1371d5409 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -517,6 +517,22 @@ GIT_EXTERN(int) git_repository_fetchhead_foreach(git_repository *repo, git_repository_fetchhead_foreach_cb callback, void *payload); +typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid, + void *payload); + +/** + * If a merge is in progress, call callback 'cb' for each commit ID in the + * MERGE_HEAD file. + * + * @param repo A repository object + * @param callback Callback function + * @param apyload Pointer to callback data (optional) + * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error + */ +GIT_EXTERN(int) git_repository_mergehead_foreach(git_repository *repo, + git_repository_mergehead_foreach_cb callback, + void *payload); + /** * Calculate hash of file using repository filtering rules. * diff --git a/src/merge.c b/src/merge.c index dfdadca81..f52c112c9 100644 --- a/src/merge.c +++ b/src/merge.c @@ -241,3 +241,56 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l *out = result; return 0; } + +int git_repository_mergehead_foreach(git_repository *repo, + git_repository_mergehead_foreach_cb cb, + void *payload) +{ + git_buf merge_head_path = GIT_BUF_INIT, merge_head_file = GIT_BUF_INIT; + char *buffer, *line; + size_t line_num = 1; + git_oid oid; + int error = 0; + + assert(repo && cb); + + if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository, + GIT_MERGE_HEAD_FILE)) < 0) + return error; + + if ((error = git_futils_readbuffer(&merge_head_file, + git_buf_cstr(&merge_head_path))) < 0) + goto cleanup; + + buffer = merge_head_file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + if (strlen(line) != GIT_OID_HEXSZ) { + giterr_set(GITERR_INVALID, "Unable to parse OID - invalid length"); + error = -1; + goto cleanup; + } + + if ((error = git_oid_fromstr(&oid, line)) < 0) + goto cleanup; + + if (cb(&oid, payload) < 0) { + error = GIT_EUSER; + goto cleanup; + } + + ++line_num; + } + + if (*buffer) { + giterr_set(GITERR_MERGE, "No EOL at line %d", line_num); + error = -1; + goto cleanup; + } + +cleanup: + git_buf_free(&merge_head_path); + git_buf_free(&merge_head_file); + + return error; +} diff --git a/src/merge.h b/src/merge.h index af24de474..03b41e388 100644 --- a/src/merge.h +++ b/src/merge.h @@ -10,6 +10,7 @@ #include "git2/types.h" #include "git2/merge.h" #include "commit_list.h" +#include "vector.h" #define GIT_MERGE_MSG_FILE "MERGE_MSG" #define GIT_MERGE_MODE_FILE "MERGE_MODE" diff --git a/tests-clar/merge/setup.c b/tests-clar/merge/setup.c new file mode 100644 index 000000000..d88b2d94c --- /dev/null +++ b/tests-clar/merge/setup.c @@ -0,0 +1,159 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "refs.h" +#include "fileops.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "testrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452" + +#define THEIRS_SIMPLE_BRANCH "branch" +#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define OCTO1_BRANCH "octo1" +#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061" + +#define OCTO2_BRANCH "octo2" +#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c" + +#define OCTO3_BRANCH "octo3" +#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272" + +#define OCTO4_BRANCH "octo4" +#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae" + +#define OCTO5_BRANCH "octo5" +#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f" + +// Fixture setup and teardown +void test_merge_setup__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_setup__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static bool test_file_contents(const char *filename, const char *expected) +{ + git_buf file_path_buf = GIT_BUF_INIT, file_buf = GIT_BUF_INIT; + bool equals; + + git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo), filename); + + cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr)); + equals = (strcmp(file_buf.ptr, expected) == 0); + + git_buf_free(&file_path_buf); + git_buf_free(&file_buf); + + return equals; +} + +static void write_file_contents(const char *filename, const char *output) +{ + git_buf file_path_buf = GIT_BUF_INIT; + + git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo), filename); + cl_git_rewritefile(file_path_buf.ptr, output); + + git_buf_free(&file_path_buf); +} + +struct merge_head_cb_data { + const char **oid_str; + unsigned int len; + + unsigned int i; +}; + +int merge_head_foreach_cb(git_oid *oid, void *payload) +{ + git_oid expected_oid; + + struct merge_head_cb_data *cb_data = payload; + + git_oid_fromstr(&expected_oid, cb_data->oid_str[cb_data->i]); + + cl_assert(git_oid_cmp(&expected_oid, oid) == 0); + + cb_data->i++; + + return 0; +} + +void test_merge_setup__head_notfound(void) +{ + int error; + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + merge_head_foreach_cb, NULL))); + cl_assert(error == GIT_ENOTFOUND); +} + +void test_merge_setup__head_invalid_oid(void) +{ + int error; + + write_file_contents(GIT_MERGE_HEAD_FILE, "invalid-oid\n"); + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + merge_head_foreach_cb, NULL))); + cl_assert(error == -1); +} + +void test_merge_setup__head_foreach_nonewline(void) +{ + int error; + + write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID); + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + merge_head_foreach_cb, NULL))); + cl_assert(error == -1); +} + +void test_merge_setup__head_foreach_one(void) +{ + const char *expected = THEIRS_SIMPLE_OID; + + struct merge_head_cb_data cb_data = { &expected, 1 }; + + write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID "\n"); + + cl_git_pass(git_repository_mergehead_foreach(repo, + merge_head_foreach_cb, &cb_data)); + + cl_assert(cb_data.i == cb_data.len); +} + +void test_merge_setup__head_foreach_octopus(void) +{ + const char *expected[] = { THEIRS_SIMPLE_OID, + OCTO1_OID, OCTO2_OID, OCTO3_OID, OCTO4_OID, OCTO5_OID }; + + struct merge_head_cb_data cb_data = { expected, 6 }; + + write_file_contents(GIT_MERGE_HEAD_FILE, + THEIRS_SIMPLE_OID "\n" + OCTO1_OID "\n" + OCTO2_OID "\n" + OCTO3_OID "\n" + OCTO4_OID "\n" + OCTO5_OID "\n"); + + cl_git_pass(git_repository_mergehead_foreach(repo, + merge_head_foreach_cb, &cb_data)); + + cl_assert(cb_data.i == cb_data.len); +}