From 9c06b25054dd43802cbf6620c1082fa5d4498bf5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 16 May 2013 13:04:37 -0500 Subject: [PATCH] merge setup --- include/git2/merge.h | 59 ++- include/git2/types.h | 3 + src/merge.c | 595 +++++++++++++++++++-- src/merge.h | 22 + tests-clar/merge/workdir/setup.c | 856 ++++++++++++++++++++++++++++++- 5 files changed, 1476 insertions(+), 59 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index 955840569..af8f36063 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -23,13 +23,13 @@ GIT_BEGIN_DECL /** - * Flags for tree_many diff options. A combination of these flags can be - * passed in via the `flags` value in the `git_diff_tree_many_options`. + * Flags for `git_mrege_tree` options. A combination of these flags can be + * passed in via the `flags` vlaue in the `git_merge_tree_opts`. */ typedef enum { /** Detect renames */ GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), -} git_merge_tree_flags; +} git_merge_tree_flag_t; /** * Automerge options for `git_merge_trees_opts`. @@ -44,7 +44,7 @@ typedef enum { typedef struct { unsigned int version; - git_merge_tree_flags flags; + git_merge_tree_flag_t flags; /** Similarity to consider a file renamed (default 50) */ unsigned int rename_threshold; @@ -95,6 +95,57 @@ GIT_EXTERN(int) git_merge_base_many( const git_oid input_array[], size_t length); +/** + * Creates a `git_merge_head` from the given reference + * + * @param out pointer to store the git_merge_head result in + * @param repo repository that contains the given reference + * @param ref reference to use as a merge input + * @return zero on success, -1 on failure. + */ +GIT_EXTERN(int) git_merge_head_from_ref( + git_merge_head **out, + git_repository *repo, + git_reference *ref); + +/** + * Creates a `git_merge_head` from the given fetch head data + * + * @param out pointer to store the git_merge_head result in + * @param repo repository that contains the given commit + * @param branch_name name of the (remote) branch + * @param remote_url url of the remote + * @param oid the commit object id to use as a merge input + * @return zero on success, -1 on failure. + */ +GIT_EXTERN(int) git_merge_head_from_fetchhead( + git_merge_head **out, + git_repository *repo, + const char *branch_name, + const char *remote_url, + const git_oid *oid); + +/** + * Creates a `git_merge_head` from the given commit id + * + * @param out pointer to store the git_merge_head result in + * @param repo repository that contains the given commit + * @param oid the commit object id to use as a merge input + * @return zero on success, -1 on failure. + */ +GIT_EXTERN(int) git_merge_head_from_oid( + git_merge_head **out, + git_repository *repo, + const git_oid *oid); + +/** + * Frees a `git_merge_head` + * + * @param the merge head to free + */ +GIT_EXTERN(void) git_merge_head_free( + git_merge_head *head); + /** * Merge two trees, producing a `git_index` that reflects the result of * the merge. diff --git a/include/git2/types.h b/include/git2/types.h index 43751d3b0..d97bbcb30 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -168,6 +168,9 @@ typedef struct git_reference git_reference; /** Iterator for references */ typedef struct git_reference_iterator git_reference_iterator; +/** Merge heads, the input to merge */ +typedef struct git_merge_head git_merge_head; + /** Basic type of any Git reference. */ typedef enum { diff --git a/src/merge.c b/src/merge.c index de5d65ac0..11345587c 100644 --- a/src/merge.c +++ b/src/merge.c @@ -54,39 +54,6 @@ struct merge_diff_df_data { }; -int git_repository_merge_cleanup(git_repository *repo) -{ - int error = 0; - git_buf merge_head_path = GIT_BUF_INIT, - merge_mode_path = GIT_BUF_INIT, - merge_msg_path = GIT_BUF_INIT; - - assert(repo); - - if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 || - git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 || - git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) - return -1; - - if (git_path_isfile(merge_head_path.ptr)) { - if ((error = p_unlink(merge_head_path.ptr)) < 0) - goto cleanup; - } - - if (git_path_isfile(merge_mode_path.ptr)) - (void)p_unlink(merge_mode_path.ptr); - - if (git_path_isfile(merge_msg_path.ptr)) - (void)p_unlink(merge_msg_path.ptr); - -cleanup: - git_buf_free(&merge_msg_path); - git_buf_free(&merge_mode_path); - git_buf_free(&merge_head_path); - - return error; -} - /* Merge base computation */ int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length) @@ -1380,6 +1347,18 @@ git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) return diff_list; } +void git_merge_diff_list__free(git_merge_diff_list *diff_list) +{ + if (!diff_list) + return; + + git_vector_free(&diff_list->staged); + git_vector_free(&diff_list->conflicts); + git_vector_free(&diff_list->resolved); + git_pool_clear(&diff_list->pool); + git__free(diff_list); +} + static int merge_tree_normalize_opts( git_repository *repo, git_merge_tree_opts *opts, @@ -1617,14 +1596,550 @@ done: return error; } -void git_merge_diff_list__free(git_merge_diff_list *diff_list) +/* Merge setup / cleanup */ + +static int write_orig_head( + git_repository *repo, + const git_merge_head *our_head) { - if (!diff_list) + git_filebuf file = GIT_FILEBUF_INIT; + git_buf file_path = GIT_BUF_INIT; + char orig_oid_str[GIT_OID_HEXSZ + 1]; + int error = 0; + + assert(repo && our_head); + + git_oid_tostr(orig_oid_str, GIT_OID_HEXSZ+1, &our_head->oid); + + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) == 0 && + (error = git_filebuf_printf(&file, "%s\n", orig_oid_str)) == 0) + error = git_filebuf_commit(&file, 0666); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_buf_free(&file_path); + + return error; +} + +static int write_merge_head( + git_repository *repo, + const git_merge_head *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_buf file_path = GIT_BUF_INIT; + char merge_oid_str[GIT_OID_HEXSZ + 1]; + size_t i; + int error = 0; + + assert(repo && heads); + + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + git_oid_tostr(merge_oid_str, GIT_OID_HEXSZ+1, &heads[i]->oid); + + if ((error = git_filebuf_printf(&file, "%s\n", merge_oid_str)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&file, 0666); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_buf_free(&file_path); + + return error; +} + +static int write_merge_mode(git_repository *repo, unsigned int flags) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_buf file_path = GIT_BUF_INIT; + int error = 0; + + /* For future expansion */ + GIT_UNUSED(flags); + + assert(repo); + + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0) + goto cleanup; + + error = git_filebuf_commit(&file, 0666); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_buf_free(&file_path); + + return error; +} + +struct merge_msg_entry { + const git_merge_head *merge_head; + bool written; +}; + +static int msg_entry_is_branch( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0); +} + +static int msg_entry_is_tracking( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0); +} + +static int msg_entry_is_tag( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0); +} + +static int msg_entry_is_remote( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + if (entry->written == 0 && + entry->merge_head->remote_url != NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0) + { + struct merge_msg_entry *existing; + + /* Match only branches from the same remote */ + if (entries->length == 0) + return 1; + + existing = git_vector_get(entries, 0); + + return (git__strcmp(existing->merge_head->remote_url, + entry->merge_head->remote_url) == 0); + } + + return 0; +} + +static int msg_entry_is_oid( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 0 && + merge_msg_entry->merge_head->ref_name == NULL && + merge_msg_entry->merge_head->remote_url == NULL); +} + +static int merge_msg_entry_written( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 1); +} + +static int merge_msg_entries( + git_vector *v, + const struct merge_msg_entry *entries, + size_t len, + int (*match)(const struct merge_msg_entry *entry, git_vector *entries)) +{ + size_t i; + int matches, total = 0; + + git_vector_clear(v); + + for (i = 0; i < len; i++) { + if ((matches = match(&entries[i], v)) < 0) + return matches; + else if (!matches) + continue; + + git_vector_insert(v, (struct merge_msg_entry *)&entries[i]); + total++; + } + + return total; +} + +static int merge_msg_write_entries( + git_filebuf *file, + git_vector *entries, + const char *item_name, + const char *item_plural_name, + size_t ref_name_skip, + const char *source, + char sep) +{ + struct merge_msg_entry *entry; + size_t i; + int error = 0; + + if (entries->length == 0) + return 0; + + if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "%s ", + (entries->length == 1) ? item_name : item_plural_name)) < 0) + goto done; + + git_vector_foreach(entries, i, entry) { + if (i > 0 && + (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0) + goto done; + + entry->written = 1; + } + + if (source) + error = git_filebuf_printf(file, " of %s", source); + +done: + return error; +} + +static int merge_msg_write_branches( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep); +} + +static int merge_msg_write_tracking( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "remote-tracking branch", "remote-tracking branches", 0, NULL, sep); +} + +static int merge_msg_write_tags( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep); +} + +static int merge_msg_write_remotes( + git_filebuf *file, + git_vector *entries, + char sep) +{ + const char *source; + + if (entries->length == 0) + return 0; + + source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url; + + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep); +} + +static int write_merge_msg( + git_repository *repo, + const git_merge_head *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_buf file_path = GIT_BUF_INIT; + char oid_str[GIT_OID_HEXSZ + 1]; + struct merge_msg_entry *entries; + git_vector matching = GIT_VECTOR_INIT; + size_t i; + char sep = 0; + int error = 0; + + assert(repo && heads); + + entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); + GITERR_CHECK_ALLOC(entries); + + if (git_vector_init(&matching, heads_len, NULL) < 0) + return -1; + + for (i = 0; i < heads_len; i++) + entries[i].merge_head = heads[i]; + + if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE)) < 0 || + (error = git_filebuf_write(&file, "Merge ", 6)) < 0) + goto cleanup; + + /* + * This is to emulate the format of MERGE_MSG by core git. + * + * Core git will write all the commits specified by OID, in the order + * provided, until the first named branch or tag is reached, at which + * point all branches will be written in the order provided, then all + * tags, then all remote tracking branches and finally all commits that + * were specified by OID that were not already written. + * + * Yes. Really. + */ + for (i = 0; i < heads_len; i++) { + if (!msg_entry_is_oid(&entries[i])) + break; + + git_oid_fmt(oid_str, &entries[i].merge_head->oid); + oid_str[GIT_OID_HEXSZ] = '\0'; + + if ((error = git_filebuf_printf(&file, "%scommit '%s'", (i > 0) ? "; " : "", oid_str)) < 0) + goto cleanup; + + entries[i].written = 1; + } + + if (i) + sep = ';'; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 || + (error = merge_msg_write_branches(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 || + (error = merge_msg_write_tracking(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || + (error = merge_msg_write_tags(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + /* We should never be called with multiple remote branches, but handle + * it in case we are... */ + while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) { + if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + } + + if (error < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + if (merge_msg_entry_written(&entries[i])) + continue; + + git_oid_fmt(oid_str, &entries[i].merge_head->oid); + oid_str[GIT_OID_HEXSZ] = '\0'; + + if ((error = git_filebuf_printf(&file, "; commit '%s'", oid_str)) < 0) + goto cleanup; + } + + if ((error = git_filebuf_printf(&file, "\n")) < 0 || + (error = git_filebuf_commit(&file, 0666)) < 0) + goto cleanup; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_buf_free(&file_path); + + git_vector_free(&matching); + git__free(entries); + + return error; +} + +int git_merge__setup( + git_repository *repo, + const git_merge_head *our_head, + const git_merge_head *heads[], + size_t heads_len, + unsigned int flags) +{ + int error = 0; + + assert (repo && our_head && heads); + + if ((error = write_orig_head(repo, our_head)) == 0 && + (error = write_merge_head(repo, heads, heads_len)) == 0 && + (error = write_merge_mode(repo, flags)) == 0) { + error = write_merge_msg(repo, heads, heads_len); + } + + return error; +} + +int git_repository_merge_cleanup(git_repository *repo) +{ + int error = 0; + git_buf merge_head_path = GIT_BUF_INIT, + merge_mode_path = GIT_BUF_INIT, + merge_msg_path = GIT_BUF_INIT; + + assert(repo); + + if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 || + git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 || + git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) + return -1; + + if (git_path_isfile(merge_head_path.ptr)) { + if ((error = p_unlink(merge_head_path.ptr)) < 0) + goto cleanup; + } + + if (git_path_isfile(merge_mode_path.ptr)) + (void)p_unlink(merge_mode_path.ptr); + + if (git_path_isfile(merge_msg_path.ptr)) + (void)p_unlink(merge_msg_path.ptr); + +cleanup: + git_buf_free(&merge_msg_path); + git_buf_free(&merge_mode_path); + git_buf_free(&merge_head_path); + + return error; +} + +/* Merge heads are the input to merge */ + +static int merge_head_init( + git_merge_head **out, + git_repository *repo, + const char *ref_name, + const char *remote_url, + const git_oid *oid) +{ + git_merge_head *head; + int error = 0; + + assert(out && oid); + + *out = NULL; + + head = git__calloc(1, sizeof(git_merge_head)); + GITERR_CHECK_ALLOC(head); + + if (ref_name) { + head->ref_name = git__strdup(ref_name); + GITERR_CHECK_ALLOC(head->ref_name); + } + + if (remote_url) { + head->remote_url = git__strdup(remote_url); + GITERR_CHECK_ALLOC(head->remote_url); + } + + git_oid_cpy(&head->oid, oid); + + if ((error = git_commit_lookup(&head->commit, repo, &head->oid)) < 0) { + git_merge_head_free(head); + return error; + } + + *out = head; + return error; +} + +int git_merge_head_from_ref( + git_merge_head **out, + git_repository *repo, + git_reference *ref) +{ + git_reference *resolved; + int error = 0; + + assert(out && repo && ref); + + *out = NULL; + + if ((error = git_reference_resolve(&resolved, ref)) < 0) + return error; + + error = merge_head_init(out, repo, git_reference_name(ref), NULL, + git_reference_target(resolved)); + + git_reference_free(resolved); + return error; +} + +int git_merge_head_from_oid( + git_merge_head **out, + git_repository *repo, + const git_oid *oid) +{ + assert(out && repo && oid); + + return merge_head_init(out, repo, NULL, NULL, oid); +} + +int git_merge_head_from_fetchhead( + git_merge_head **out, + git_repository *repo, + const char *branch_name, + const char *remote_url, + const git_oid *oid) +{ + assert(repo && branch_name && remote_url && oid); + + return merge_head_init(out, repo, branch_name, remote_url, oid); +} + +void git_merge_head_free(git_merge_head *head) +{ + if (head == NULL) return; - git_vector_free(&diff_list->staged); - git_vector_free(&diff_list->conflicts); - git_vector_free(&diff_list->resolved); - git_pool_clear(&diff_list->pool); - git__free(diff_list); + if (head->commit != NULL) + git_object_free((git_object *)head->commit); + + if (head->ref_name != NULL) + git__free(head->ref_name); + + if (head->remote_url != NULL) + git__free(head->remote_url); + + git__free(head); } diff --git a/src/merge.h b/src/merge.h index 50538b12b..ba6725de9 100644 --- a/src/merge.h +++ b/src/merge.h @@ -107,12 +107,25 @@ typedef struct { git_delta_t their_status; } git_merge_diff; +/** Internal structure for merge inputs */ +struct git_merge_head { + char *ref_name; + char *remote_url; + + git_oid oid; + git_commit *commit; +}; + int git_merge__bases_many( git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos); +/* + * Three-way tree differencing + */ + git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo); int git_merge_diff_list__find_differences(git_merge_diff_list *merge_diff_list, @@ -124,4 +137,13 @@ int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list void git_merge_diff_list__free(git_merge_diff_list *diff_list); +/* Merge metadata setup */ + +int git_merge__setup( + git_repository *repo, + const git_merge_head *our_head, + const git_merge_head *their_heads[], + size_t their_heads_len, + unsigned int flags); + #endif diff --git a/tests-clar/merge/workdir/setup.c b/tests-clar/merge/workdir/setup.c index 6ae9dbb14..1c8403221 100644 --- a/tests-clar/merge/workdir/setup.c +++ b/tests-clar/merge/workdir/setup.c @@ -8,28 +8,28 @@ static git_repository *repo; static git_index *repo_index; -#define TEST_REPO_PATH "testrepo" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" -#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452" +#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452" -#define THEIRS_SIMPLE_BRANCH "branch" -#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" +#define THEIRS_SIMPLE_BRANCH "branch" +#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" -#define OCTO1_BRANCH "octo1" -#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061" +#define OCTO1_BRANCH "octo1" +#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061" -#define OCTO2_BRANCH "octo2" -#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c" +#define OCTO2_BRANCH "octo2" +#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c" -#define OCTO3_BRANCH "octo3" -#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272" +#define OCTO3_BRANCH "octo3" +#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272" -#define OCTO4_BRANCH "octo4" -#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae" +#define OCTO4_BRANCH "octo4" +#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae" -#define OCTO5_BRANCH "octo5" -#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f" +#define OCTO5_BRANCH "octo5" +#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f" // Fixture setup and teardown void test_merge_workdir_setup__initialize(void) @@ -44,6 +44,22 @@ void test_merge_workdir_setup__cleanup(void) 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; @@ -55,6 +71,816 @@ static void write_file_contents(const char *filename, const char *output) git_buf_free(&file_path_buf); } +/* git merge --no-ff octo1 */ +void test_merge_workdir_setup__one_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_merge_head *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); +} + +/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */ +void test_merge_workdir_setup__one_oid(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_merge_head *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); +} + +/* git merge octo1 octo2 */ +void test_merge_workdir_setup__two_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git merge octo1 octo2 octo3 */ +void test_merge_workdir_setup__three_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 158dc7bedb202f5b26502bf3574faa7f4238d56c 50ce7d7d01217679e26c55939eef119e0c93e272 */ +void test_merge_workdir_setup__three_oids(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO2_OID "'; commit '" OCTO3_OID "'\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c */ +void test_merge_workdir_setup__branches_and_oids_1(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_oid octo2_oid; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'; commit '" OCTO2_OID "'\n")); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c octo3 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae */ +void test_merge_workdir_setup__branches_and_oids_2(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_oid octo2_oid; + git_reference *octo3_ref; + git_oid octo4_oid; + git_merge_head *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[3], repo, &octo4_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "'; commit '" OCTO2_OID "'; commit '" OCTO4_OID "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo3_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); + git_merge_head_free(their_heads[3]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 */ +void test_merge_workdir_setup__branches_and_oids_3(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_reference *octo2_ref; + git_oid octo3_oid; + git_reference *octo4_ref; + git_merge_head *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "'; commit '" OCTO3_OID "'\n")); + + git_reference_free(octo2_ref); + git_reference_free(octo4_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); + git_merge_head_free(their_heads[3]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 octo5 */ +void test_merge_workdir_setup__branches_and_oids_4(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_reference *octo2_ref; + git_oid octo3_oid; + git_reference *octo4_ref; + git_reference *octo5_ref; + git_merge_head *our_head, *their_heads[5]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_reference_lookup(&octo5_ref, repo, GIT_REFS_HEADS_DIR OCTO5_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[4], repo, octo5_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 5, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n" OCTO5_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "', '" OCTO4_BRANCH "' and '" OCTO5_BRANCH "'; commit '" OCTO3_OID "'\n")); + + git_reference_free(octo2_ref); + git_reference_free(octo4_ref); + git_reference_free(octo5_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); + git_merge_head_free(their_heads[3]); + git_merge_head_free(their_heads[4]); +} + +/* git merge octo1 octo1 octo1 */ +void test_merge_workdir_setup__three_same_branches(void) +{ + git_oid our_oid; + git_reference *octo1_1_ref; + git_reference *octo1_2_ref; + git_reference *octo1_3_ref; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_1_ref)); + + cl_git_pass(git_reference_lookup(&octo1_2_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo1_2_ref)); + + cl_git_pass(git_reference_lookup(&octo1_3_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo1_3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO1_BRANCH "' and '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_1_ref); + git_reference_free(octo1_2_ref); + git_reference_free(octo1_3_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 */ +void test_merge_workdir_setup__three_same_oids(void) +{ + git_oid our_oid; + git_oid octo1_1_oid; + git_oid octo1_2_oid; + git_oid octo1_3_oid; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[0], repo, &octo1_1_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_2_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[1], repo, &octo1_2_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_3_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_oid(&their_heads[2], repo, &octo1_3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO1_OID "'; commit '" OCTO1_OID "'\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +static int create_remote_tracking_branch(const char *branch_name, const char *oid_str) +{ + int error = 0; + + git_buf remotes_path = GIT_BUF_INIT, + origin_path = GIT_BUF_INIT, + filename = GIT_BUF_INIT, + data = GIT_BUF_INIT; + + if ((error = git_buf_puts(&remotes_path, git_repository_path(repo))) < 0 || + (error = git_buf_puts(&remotes_path, GIT_REFS_REMOTES_DIR)) < 0) + goto done; + + if (!git_path_exists(git_buf_cstr(&remotes_path)) && + (error = p_mkdir(git_buf_cstr(&remotes_path), 0777)) < 0) + goto done; + + if ((error = git_buf_puts(&origin_path, git_buf_cstr(&remotes_path))) < 0 || + (error = git_buf_puts(&origin_path, "origin")) < 0) + goto done; + + if (!git_path_exists(git_buf_cstr(&origin_path)) && + (error = p_mkdir(git_buf_cstr(&origin_path), 0777)) < 0) + goto done; + + if ((error = git_buf_puts(&filename, git_buf_cstr(&origin_path))) < 0 || + (error = git_buf_puts(&filename, "/")) < 0 || + (error = git_buf_puts(&filename, branch_name)) < 0 || + (error = git_buf_puts(&data, oid_str)) < 0 || + (error = git_buf_puts(&data, "\n")) < 0) + goto done; + + cl_git_rewritefile(git_buf_cstr(&filename), git_buf_cstr(&data)); + +done: + git_buf_free(&remotes_path); + git_buf_free(&origin_path); + git_buf_free(&filename); + git_buf_free(&data); + + return error; +} + +/* git merge refs/remotes/origin/octo1 */ +void test_merge_workdir_setup__remote_tracking_one_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_merge_head *our_head, *their_heads[1]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); +} + +/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 */ +void test_merge_workdir_setup__remote_tracking_two_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "' and 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 refs/remotes/origin/octo3 */ +void test_merge_workdir_setup__remote_tracking_three_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO3_BRANCH, OCTO3_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO3_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "', 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO3_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +/* git merge octo1 refs/remotes/origin/octo2 */ +void test_merge_workdir_setup__normal_branch_and_remote_tracking_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git merge refs/remotes/origin/octo1 octo2 */ +void test_merge_workdir_setup__remote_tracking_branch_and_normal_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO2_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git merge octo1 refs/remotes/origin/octo2 octo3 refs/remotes/origin/octo4 */ +void test_merge_workdir_setup__two_remote_tracking_branch_and_two_normal_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_reference *octo4_ref; + git_merge_head *our_head, *their_heads[4]; + + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO4_BRANCH, OCTO4_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO4_BRANCH)); + cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "', remote-tracking branches 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO4_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + git_reference_free(octo4_ref); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); + git_merge_head_free(their_heads[3]); +} + +/* git pull origin branch octo1 */ +void test_merge_workdir_setup__pull_one(void) +{ + git_oid our_oid; + git_oid octo1_1_oid; + git_merge_head *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_1_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch 'octo1' of http://remote.url/repo.git\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); +} + +/* git pull origin octo1 octo2 */ +void test_merge_workdir_setup__pull_two(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_merge_head *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "' of http://remote.url/repo.git\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); +} + +/* git pull origin octo1 octo2 octo3 */ +void test_merge_workdir_setup__pull_three(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.url/repo.git", &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.url/repo.git\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +void test_merge_workdir_setup__three_remotes(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_merge_head *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.third/repo.git", &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "' of http://remote.first/repo.git, branch '" OCTO2_BRANCH "' of http://remote.second/repo.git, branch '" OCTO3_BRANCH "' of http://remote.third/repo.git\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); +} + +void test_merge_workdir_setup__two_remotes(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_oid octo4_oid; + git_merge_head *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_merge_head_from_oid(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.first/repo.git", &octo3_oid)); + + cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); + cl_git_pass(git_merge_head_from_fetchhead(&their_heads[3], repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH, "http://remote.second/repo.git", &octo4_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4, 0)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.first/repo.git, branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "' of http://remote.second/repo.git\n")); + + git_merge_head_free(our_head); + git_merge_head_free(their_heads[0]); + git_merge_head_free(their_heads[1]); + git_merge_head_free(their_heads[2]); + git_merge_head_free(their_heads[3]); +} + struct merge_head_cb_data { const char **oid_str; unsigned int len;