/* * 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 "common.h" #include "posix.h" #include "buffer.h" #include "repository.h" #include "revwalk.h" #include "commit_list.h" #include "merge.h" #include "path.h" #include "refs.h" #include "object.h" #include "iterator.h" #include "refs.h" #include "diff.h" #include "diff_generate.h" #include "diff_tform.h" #include "checkout.h" #include "tree.h" #include "blob.h" #include "oid.h" #include "index.h" #include "filebuf.h" #include "config.h" #include "oidarray.h" #include "annotated_commit.h" #include "commit.h" #include "oidarray.h" #include "merge_driver.h" #include "git2/types.h" #include "git2/repository.h" #include "git2/object.h" #include "git2/commit.h" #include "git2/merge.h" #include "git2/refs.h" #include "git2/reset.h" #include "git2/checkout.h" #include "git2/signature.h" #include "git2/config.h" #include "git2/tree.h" #include "git2/oidarray.h" #include "git2/annotated_commit.h" #include "git2/sys/index.h" #include "git2/sys/hashsig.h" #define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) typedef enum { TREE_IDX_ANCESTOR = 0, TREE_IDX_OURS = 1, TREE_IDX_THEIRS = 2 } merge_tree_index_t; /* Tracks D/F conflicts */ struct merge_diff_df_data { const char *df_path; const char *prev_path; git_merge_diff *prev_conflict; }; /* Merge base computation */ int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) { git_revwalk *walk = NULL; git_vector list; git_commit_list *result = NULL; git_commit_list_node *commit; int error = -1; unsigned int i; if (length < 2) { giterr_set(GITERR_INVALID, "at least two commits are required to find an ancestor"); return -1; } if (git_vector_init(&list, length - 1, NULL) < 0) return -1; if (git_revwalk_new(&walk, repo) < 0) goto on_error; for (i = 1; i < length; i++) { commit = git_revwalk__commit_lookup(walk, &input_array[i]); if (commit == NULL) goto on_error; git_vector_insert(&list, commit); } commit = git_revwalk__commit_lookup(walk, &input_array[0]); if (commit == NULL) goto on_error; if (git_merge__bases_many(&result, walk, commit, &list) < 0) goto on_error; if (!result) { giterr_set(GITERR_MERGE, "no merge base found"); error = GIT_ENOTFOUND; goto on_error; } *out = result; *walk_out = walk; git_vector_free(&list); return 0; on_error: git_vector_free(&list); git_revwalk_free(walk); return error; } int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) { git_revwalk *walk; git_commit_list *result = NULL; int error = 0; assert(out && repo && input_array); if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 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_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) { git_revwalk *walk; git_commit_list *list, *result = NULL; int error = 0; git_array_oid_t array; assert(out && repo && input_array); if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) return error; git_array_init(array); list = result; while (list) { git_oid *id = git_array_alloc(array); if (id == NULL) { error = -1; goto cleanup; } git_oid_cpy(id, &list->item->oid); list = list->next; } git_oidarray__from_array(out, &array); cleanup: git_commit_list_free(&result); git_revwalk_free(walk); return error; } int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) { git_oid result; unsigned int i; int error = -1; assert(out && repo && input_array); if (length < 2) { giterr_set(GITERR_INVALID, "at least two commits are required to find an ancestor"); return -1; } result = input_array[0]; for (i = 1; i < length; i++) { error = git_merge_base(&result, repo, &result, &input_array[i]); if (error < 0) return error; } *out = result; return 0; } 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; git_commit_list *result = NULL; git_commit_list_node *commit; void *contents[1]; if (git_revwalk_new(&walk, repo) < 0) return -1; commit = git_revwalk__commit_lookup(walk, two); if (commit == NULL) goto on_error; /* This is just one value, so we can do it on the stack */ memset(&list, 0x0, sizeof(git_vector)); contents[0] = commit; list.length = 1; list.contents = contents; commit = git_revwalk__commit_lookup(walk, one); if (commit == NULL) goto on_error; if (git_merge__bases_many(&result, walk, commit, &list) < 0) goto on_error; if (!result) { git_revwalk_free(walk); giterr_set(GITERR_MERGE, "no merge base found"); 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; } static int interesting(git_pqueue *list) { size_t i; for (i = 0; i < git_pqueue_size(list); i++) { git_commit_list_node *commit = git_pqueue_get(list, i); if ((commit->flags & STALE) == 0) return 1; } return 0; } static void clear_commit_marks_1(git_commit_list **plist, git_commit_list_node *commit, unsigned int mark) { while (commit) { unsigned int i; if (!(mark & commit->flags)) return; commit->flags &= ~mark; for (i = 1; i < commit->out_degree; i++) { git_commit_list_node *p = commit->parents[i]; git_commit_list_insert(p, plist); } commit = commit->out_degree ? commit->parents[0] : NULL; } } static void clear_commit_marks_many(git_vector *commits, unsigned int mark) { git_commit_list *list = NULL; git_commit_list_node *c; unsigned int i; git_vector_foreach(commits, i, c) { git_commit_list_insert(c, &list); } while (list) clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); } static void clear_commit_marks(git_commit_list_node *commit, unsigned int mark) { git_commit_list *list = NULL; git_commit_list_insert(commit, &list); while (list) clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); } static int paint_down_to_common( git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos) { git_pqueue list; git_commit_list *result = NULL; git_commit_list_node *two; int error; unsigned int i; if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_time_cmp) < 0) return -1; one->flags |= PARENT1; if (git_pqueue_insert(&list, one) < 0) return -1; git_vector_foreach(twos, i, two) { if (git_commit_list_parse(walk, two) < 0) return -1; two->flags |= PARENT2; if (git_pqueue_insert(&list, two) < 0) return -1; } /* as long as there are non-STALE commits */ while (interesting(&list)) { git_commit_list_node *commit = git_pqueue_pop(&list); int flags; if (commit == NULL) break; flags = commit->flags & (PARENT1 | PARENT2 | STALE); if (flags == (PARENT1 | PARENT2)) { if (!(commit->flags & RESULT)) { commit->flags |= RESULT; if (git_commit_list_insert(commit, &result) == NULL) return -1; } /* we mark the parents of a merge stale */ flags |= STALE; } for (i = 0; i < commit->out_degree; i++) { git_commit_list_node *p = commit->parents[i]; if ((p->flags & flags) == flags) continue; if ((error = git_commit_list_parse(walk, p)) < 0) return error; p->flags |= flags; if (git_pqueue_insert(&list, p) < 0) return -1; } } git_pqueue_free(&list); *out = result; return 0; } static int remove_redundant(git_revwalk *walk, git_vector *commits) { git_vector work = GIT_VECTOR_INIT; unsigned char *redundant; unsigned int *filled_index; unsigned int i, j; int error = 0; redundant = git__calloc(commits->length, 1); GITERR_CHECK_ALLOC(redundant); filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); GITERR_CHECK_ALLOC(filled_index); for (i = 0; i < commits->length; ++i) { if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) goto done; } for (i = 0; i < commits->length; ++i) { git_commit_list *common = NULL; git_commit_list_node *commit = commits->contents[i]; if (redundant[i]) continue; git_vector_clear(&work); for (j = 0; j < commits->length; j++) { if (i == j || redundant[j]) continue; filled_index[work.length] = j; if ((error = git_vector_insert(&work, commits->contents[j])) < 0) goto done; } error = paint_down_to_common(&common, walk, commit, &work); if (error < 0) goto done; if (commit->flags & PARENT2) redundant[i] = 1; for (j = 0; j < work.length; j++) { git_commit_list_node *w = work.contents[j]; if (w->flags & PARENT1) redundant[filled_index[j]] = 1; } clear_commit_marks(commit, ALL_FLAGS); clear_commit_marks_many(&work, ALL_FLAGS); git_commit_list_free(&common); } for (i = 0; i < commits->length; ++i) { if (redundant[i]) commits->contents[i] = NULL; } done: git__free(redundant); git__free(filled_index); git_vector_free(&work); return error; } int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos) { int error; unsigned int i; git_commit_list_node *two; git_commit_list *result = NULL, *tmp = NULL; /* If there's only the one commit, there can be no merge bases */ if (twos->length == 0) { *out = NULL; return 0; } /* if the commit is repeated, we have a our merge base already */ git_vector_foreach(twos, i, two) { if (one == two) return git_commit_list_insert(one, out) ? 0 : -1; } if (git_commit_list_parse(walk, one) < 0) return -1; error = paint_down_to_common(&result, walk, one, twos); if (error < 0) return error; /* filter out any stale commits in the results */ tmp = result; result = NULL; while (tmp) { git_commit_list_node *c = git_commit_list_pop(&tmp); if (!(c->flags & STALE)) if (git_commit_list_insert_by_date(c, &result) == NULL) return -1; } /* * more than one merge base -- see if there are redundant merge * bases and remove them */ if (result && result->next) { git_vector redundant = GIT_VECTOR_INIT; while (result) git_vector_insert(&redundant, git_commit_list_pop(&result)); clear_commit_marks(one, ALL_FLAGS); clear_commit_marks_many(twos, ALL_FLAGS); if ((error = remove_redundant(walk, &redundant)) < 0) { git_vector_free(&redundant); return error; } git_vector_foreach(&redundant, i, two) { if (two != NULL) git_commit_list_insert_by_date(two, &result); } git_vector_free(&redundant); } *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->gitdir, 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 ((error = cb(&oid, payload)) != 0) { giterr_set_after_callback(error); goto cleanup; } ++line_num; } if (*buffer) { giterr_set(GITERR_MERGE, "no EOL at line %"PRIuZ, line_num); error = -1; goto cleanup; } cleanup: git_buf_free(&merge_head_path); git_buf_free(&merge_head_file); return error; } GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b) { int value = 0; if (a->path == NULL) return (b->path == NULL) ? 0 : 1; if ((value = a->mode - b->mode) == 0 && (value = git_oid__cmp(&a->id, &b->id)) == 0) value = strcmp(a->path, b->path); return value; } /* Conflict resolution */ static int merge_conflict_resolve_trivial( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict) { int ours_empty, theirs_empty; int ours_changed, theirs_changed, ours_theirs_differ; git_index_entry const *result = NULL; int error = 0; assert(resolved && diff_list && conflict); *resolved = 0; if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) return 0; if (conflict->our_status == GIT_DELTA_RENAMED || conflict->their_status == GIT_DELTA_RENAMED) return 0; ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); ours_theirs_differ = ours_changed && theirs_changed && index_entry_cmp(&conflict->our_entry, &conflict->their_entry); /* * Note: with only one ancestor, some cases are not distinct: * * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge * * Note that the two cases that take D/F conflicts into account * specifically do not need to be explicitly tested, as D/F conflicts * would fail the *empty* test: * * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote * * Note that many of these cases need not be explicitly tested, as * they simply degrade to "all different" cases (eg, 11): * * 4: ancest:(empty)^, head:head, remote:remote = result:no merge * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge * 11: ancest:ancest+, head:head, remote:remote = result:no merge */ /* 5ALT: ancest:*, head:head, remote:head = result:head */ if (ours_changed && !ours_empty && !ours_theirs_differ) result = &conflict->our_entry; /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ else if (ours_changed && ours_empty && theirs_empty) *resolved = 0; /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ else if (ours_empty && !theirs_changed) *resolved = 0; /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ else if (!ours_changed && theirs_empty) *resolved = 0; /* 13: ancest:ancest+, head:head, remote:ancest = result:head */ else if (ours_changed && !theirs_changed) result = &conflict->our_entry; /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ else if (!ours_changed && theirs_changed) result = &conflict->their_entry; else *resolved = 0; if (result != NULL && GIT_MERGE_INDEX_ENTRY_EXISTS(*result) && (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0) *resolved = 1; /* Note: trivial resolution does not update the REUC. */ return error; } static int merge_conflict_resolve_one_removed( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict) { int ours_empty, theirs_empty; int ours_changed, theirs_changed; int error = 0; assert(resolved && diff_list && conflict); *resolved = 0; if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) return 0; ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); /* Removed in both */ if (ours_changed && ours_empty && theirs_empty) *resolved = 1; /* Removed in ours */ else if (ours_empty && !theirs_changed) *resolved = 1; /* Removed in theirs */ else if (!ours_changed && theirs_empty) *resolved = 1; if (*resolved) git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); return error; } static int merge_conflict_resolve_one_renamed( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict) { int ours_renamed, theirs_renamed; int ours_changed, theirs_changed; git_index_entry *merged; int error = 0; assert(resolved && diff_list && conflict); *resolved = 0; if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) return 0; ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED); theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED); if (!ours_renamed && !theirs_renamed) return 0; /* Reject one file in a 2->1 conflict */ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) return 0; ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0); theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0); /* if both are modified (and not to a common target) require a merge */ if (ours_changed && theirs_changed && git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0) return 0; if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) return -1; if (ours_changed) memcpy(merged, &conflict->our_entry, sizeof(git_index_entry)); else memcpy(merged, &conflict->their_entry, sizeof(git_index_entry)); if (ours_renamed) merged->path = conflict->our_entry.path; else merged->path = conflict->their_entry.path; *resolved = 1; git_vector_insert(&diff_list->staged, merged); git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); return error; } static bool merge_conflict_can_resolve_contents( const git_merge_diff *conflict) { if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) return false; /* Reject D/F conflicts */ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) return false; /* Reject submodules. */ if (S_ISGITLINK(conflict->ancestor_entry.mode) || S_ISGITLINK(conflict->our_entry.mode) || S_ISGITLINK(conflict->their_entry.mode)) return false; /* Reject link/file conflicts. */ if ((S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->our_entry.mode)) || (S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->their_entry.mode))) return false; /* Reject name conflicts */ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) return false; if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) return false; return true; } static int merge_conflict_invoke_driver( git_index_entry **out, const char *name, git_merge_driver *driver, git_merge_diff_list *diff_list, git_merge_driver_source *src) { git_index_entry *result; git_buf buf = GIT_BUF_INIT; const char *path; uint32_t mode; git_odb *odb = NULL; git_oid oid; int error; *out = NULL; if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || (error = git_repository_odb(&odb, src->repo)) < 0 || (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)) < 0) goto done; result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); GITERR_CHECK_ALLOC(result); git_oid_cpy(&result->id, &oid); result->mode = mode; result->file_size = buf.size; result->path = git_pool_strdup(&diff_list->pool, path); GITERR_CHECK_ALLOC(result->path); *out = result; done: git_buf_free(&buf); git_odb_free(odb); return error; } static int merge_conflict_resolve_contents( int *resolved, git_merge_diff_list *diff_list, const git_merge_diff *conflict, const git_merge_options *merge_opts, const git_merge_file_options *file_opts) { git_merge_driver_source source = {0}; git_merge_file_result result = {0}; git_merge_driver *driver; git_merge_driver__builtin builtin = {{0}}; git_index_entry *merge_result; git_odb *odb = NULL; const char *name; bool fallback = false; int error; assert(resolved && diff_list && conflict); *resolved = 0; if (!merge_conflict_can_resolve_contents(conflict)) return 0; source.repo = diff_list->repo; source.default_driver = merge_opts->default_driver; source.file_opts = file_opts; source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? &conflict->ancestor_entry : NULL; source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? &conflict->our_entry : NULL; source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { /* if the user requested a particular type of resolution (via the * favor flag) then let that override the gitattributes and use * the builtin driver. */ name = "text"; builtin.base.apply = git_merge_driver__builtin_apply; builtin.favor = file_opts->favor; driver = &builtin.base; } else { /* find the merge driver for this file */ if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) goto done; if (driver == NULL) fallback = true; } if (driver) { error = merge_conflict_invoke_driver(&merge_result, name, driver, diff_list, &source); if (error == GIT_PASSTHROUGH) fallback = true; } if (fallback) { error = merge_conflict_invoke_driver(&merge_result, "text", &git_merge_driver__text.base, diff_list, &source); } if (error < 0) { if (error == GIT_EMERGECONFLICT) error = 0; goto done; } git_vector_insert(&diff_list->staged, merge_result); git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); *resolved = 1; done: git_merge_file_result_free(&result); git_odb_free(odb); return error; } static int merge_conflict_resolve( int *out, git_merge_diff_list *diff_list, const git_merge_diff *conflict, const git_merge_options *merge_opts, const git_merge_file_options *file_opts) { int resolved = 0; int error = 0; *out = 0; if ((error = merge_conflict_resolve_trivial( &resolved, diff_list, conflict)) < 0) goto done; if (!resolved && (error = merge_conflict_resolve_one_removed( &resolved, diff_list, conflict)) < 0) goto done; if (!resolved && (error = merge_conflict_resolve_one_renamed( &resolved, diff_list, conflict)) < 0) goto done; if (!resolved && (error = merge_conflict_resolve_contents( &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) goto done; *out = resolved; done: return error; } /* Rename detection and coalescing */ struct merge_diff_similarity { unsigned char similarity; size_t other_idx; }; static int index_entry_similarity_exact( git_repository *repo, git_index_entry *a, size_t a_idx, git_index_entry *b, size_t b_idx, void **cache, const git_merge_options *opts) { GIT_UNUSED(repo); GIT_UNUSED(a_idx); GIT_UNUSED(b_idx); GIT_UNUSED(cache); GIT_UNUSED(opts); if (git_oid__cmp(&a->id, &b->id) == 0) return 100; return 0; } static int index_entry_similarity_calc( void **out, git_repository *repo, git_index_entry *entry, const git_merge_options *opts) { git_blob *blob; git_diff_file diff_file = {{{0}}}; git_off_t blobsize; int error; *out = NULL; if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0) return error; git_oid_cpy(&diff_file.id, &entry->id); diff_file.path = entry->path; diff_file.size = entry->file_size; diff_file.mode = entry->mode; diff_file.flags = 0; blobsize = git_blob_rawsize(blob); /* file too big for rename processing */ if (!git__is_sizet(blobsize)) return 0; error = opts->metric->buffer_signature(out, &diff_file, git_blob_rawcontent(blob), (size_t)blobsize, opts->metric->payload); git_blob_free(blob); return error; } static int index_entry_similarity_inexact( git_repository *repo, git_index_entry *a, size_t a_idx, git_index_entry *b, size_t b_idx, void **cache, const git_merge_options *opts) { int score = 0; int error = 0; if (!GIT_MODE_ISBLOB(a->mode) || !GIT_MODE_ISBLOB(b->mode)) return 0; /* update signature cache if needed */ if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0) return error; if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) return error; /* some metrics may not wish to process this file (too big / too small) */ if (!cache[a_idx] || !cache[b_idx]) return 0; /* compare signatures */ if (opts->metric->similarity( &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) return -1; /* clip score */ if (score < 0) score = 0; else if (score > 100) score = 100; return score; } static int merge_diff_mark_similarity( git_repository *repo, git_merge_diff_list *diff_list, struct merge_diff_similarity *similarity_ours, struct merge_diff_similarity *similarity_theirs, int (*similarity_fn)(git_repository *, git_index_entry *, size_t, git_index_entry *, size_t, void **, const git_merge_options *), void **cache, const git_merge_options *opts) { size_t i, j; git_merge_diff *conflict_src, *conflict_tgt; int similarity; git_vector_foreach(&diff_list->conflicts, i, conflict_src) { /* Items can be the source of a rename iff they have an item in the * ancestor slot and lack an item in the ours or theirs slot. */ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) || (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) && GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry))) continue; git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { size_t our_idx = diff_list->conflicts.length + j; size_t their_idx = (diff_list->conflicts.length * 2) + j; if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) continue; if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); if (similarity == GIT_EBUFS) continue; else if (similarity < 0) return similarity; if (similarity > similarity_ours[i].similarity && similarity > similarity_ours[j].similarity) { /* Clear previous best similarity */ if (similarity_ours[i].similarity > 0) similarity_ours[similarity_ours[i].other_idx].similarity = 0; if (similarity_ours[j].similarity > 0) similarity_ours[similarity_ours[j].other_idx].similarity = 0; similarity_ours[i].similarity = similarity; similarity_ours[i].other_idx = j; similarity_ours[j].similarity = similarity; similarity_ours[j].other_idx = i; } } if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); if (similarity > similarity_theirs[i].similarity && similarity > similarity_theirs[j].similarity) { /* Clear previous best similarity */ if (similarity_theirs[i].similarity > 0) similarity_theirs[similarity_theirs[i].other_idx].similarity = 0; if (similarity_theirs[j].similarity > 0) similarity_theirs[similarity_theirs[j].other_idx].similarity = 0; similarity_theirs[i].similarity = similarity; similarity_theirs[i].other_idx = j; similarity_theirs[j].similarity = similarity; similarity_theirs[j].other_idx = i; } } } } return 0; } /* * Rename conflicts: * * Ancestor Ours Theirs * * 0a A A A No rename * b A A* A No rename (ours was rewritten) * c A A A* No rename (theirs rewritten) * 1a A A B[A] Rename or rename/edit * b A B[A] A (automergeable) * 2 A B[A] B[A] Both renamed (automergeable) * 3a A B[A] Rename/delete * b A B[A] (same) * 4a A B[A] B Rename/add [B~ours B~theirs] * b A B B[A] (same) * 5 A B[A] C[A] Both renamed ("1 -> 2") * 6 A C[A] Both renamed ("2 -> 1") * B C[B] [C~ours C~theirs] (automergeable) */ static void merge_diff_mark_rename_conflict( git_merge_diff_list *diff_list, struct merge_diff_similarity *similarity_ours, bool ours_renamed, size_t ours_source_idx, struct merge_diff_similarity *similarity_theirs, bool theirs_renamed, size_t theirs_source_idx, git_merge_diff *target, const git_merge_options *opts) { git_merge_diff *ours_source = NULL, *theirs_source = NULL; if (ours_renamed) ours_source = diff_list->conflicts.contents[ours_source_idx]; if (theirs_renamed) theirs_source = diff_list->conflicts.contents[theirs_source_idx]; /* Detect 2->1 conflicts */ if (ours_renamed && theirs_renamed) { /* Both renamed to the same target name. */ if (ours_source_idx == theirs_source_idx) ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED; else { ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; } } else if (ours_renamed) { /* If our source was also renamed in theirs, this is a 1->2 */ if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold) ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) { ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; target->type = GIT_MERGE_DIFF_RENAMED_ADDED; } else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry)) ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; } else if (theirs_renamed) { /* If their source was also renamed in ours, this is a 1->2 */ if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold) theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) { theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; target->type = GIT_MERGE_DIFF_RENAMED_ADDED; } else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry)) theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; } } GIT_INLINE(void) merge_diff_coalesce_rename( git_index_entry *source_entry, git_delta_t *source_status, git_index_entry *target_entry, git_delta_t *target_status) { /* Coalesce the rename target into the rename source. */ memcpy(source_entry, target_entry, sizeof(git_index_entry)); *source_status = GIT_DELTA_RENAMED; memset(target_entry, 0x0, sizeof(git_index_entry)); *target_status = GIT_DELTA_UNMODIFIED; } static void merge_diff_list_coalesce_renames( git_merge_diff_list *diff_list, struct merge_diff_similarity *similarity_ours, struct merge_diff_similarity *similarity_theirs, const git_merge_options *opts) { size_t i; bool ours_renamed = 0, theirs_renamed = 0; size_t ours_source_idx = 0, theirs_source_idx = 0; git_merge_diff *ours_source, *theirs_source, *target; for (i = 0; i < diff_list->conflicts.length; i++) { target = diff_list->conflicts.contents[i]; ours_renamed = 0; theirs_renamed = 0; if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) && similarity_ours[i].similarity >= opts->rename_threshold) { ours_source_idx = similarity_ours[i].other_idx; ours_source = diff_list->conflicts.contents[ours_source_idx]; merge_diff_coalesce_rename( &ours_source->our_entry, &ours_source->our_status, &target->our_entry, &target->our_status); similarity_ours[ours_source_idx].similarity = 0; similarity_ours[i].similarity = 0; ours_renamed = 1; } /* insufficient to determine direction */ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) && similarity_theirs[i].similarity >= opts->rename_threshold) { theirs_source_idx = similarity_theirs[i].other_idx; theirs_source = diff_list->conflicts.contents[theirs_source_idx]; merge_diff_coalesce_rename( &theirs_source->their_entry, &theirs_source->their_status, &target->their_entry, &target->their_status); similarity_theirs[theirs_source_idx].similarity = 0; similarity_theirs[i].similarity = 0; theirs_renamed = 1; } merge_diff_mark_rename_conflict(diff_list, similarity_ours, ours_renamed, ours_source_idx, similarity_theirs, theirs_renamed, theirs_source_idx, target, opts); } } static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p) { git_merge_diff *conflict = conflicts->contents[idx]; GIT_UNUSED(p); return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) && !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)); } static void merge_diff_list_count_candidates( git_merge_diff_list *diff_list, size_t *src_count, size_t *tgt_count) { git_merge_diff *entry; size_t i; *src_count = 0; *tgt_count = 0; git_vector_foreach(&diff_list->conflicts, i, entry) { if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) && (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) || !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry))) (*src_count)++; else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry)) (*tgt_count)++; } } int git_merge_diff_list__find_renames( git_repository *repo, git_merge_diff_list *diff_list, const git_merge_options *opts) { struct merge_diff_similarity *similarity_ours, *similarity_theirs; void **cache = NULL; size_t cache_size = 0; size_t src_count, tgt_count, i; int error = 0; assert(diff_list && opts); if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0) return 0; similarity_ours = git__calloc(diff_list->conflicts.length, sizeof(struct merge_diff_similarity)); GITERR_CHECK_ALLOC(similarity_ours); similarity_theirs = git__calloc(diff_list->conflicts.length, sizeof(struct merge_diff_similarity)); GITERR_CHECK_ALLOC(similarity_theirs); /* Calculate similarity between items that were deleted from the ancestor * and added in the other branch. */ if ((error = merge_diff_mark_similarity(repo, diff_list, similarity_ours, similarity_theirs, index_entry_similarity_exact, NULL, opts)) < 0) goto done; if (diff_list->conflicts.length <= opts->target_limit) { GITERR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); cache = git__calloc(cache_size, sizeof(void *)); GITERR_CHECK_ALLOC(cache); merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count); if (src_count > opts->target_limit || tgt_count > opts->target_limit) { /* TODO: report! */ } else { if ((error = merge_diff_mark_similarity( repo, diff_list, similarity_ours, similarity_theirs, index_entry_similarity_inexact, cache, opts)) < 0) goto done; } } /* For entries that are appropriately similar, merge the new name's entry * into the old name. */ merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts); /* And remove any entries that were merged and are now empty. */ git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL); done: if (cache != NULL) { for (i = 0; i < cache_size; ++i) { if (cache[i] != NULL) opts->metric->free_signature(cache[i], opts->metric->payload); } git__free(cache); } git__free(similarity_ours); git__free(similarity_theirs); return error; } /* Directory/file conflict handling */ GIT_INLINE(const char *) merge_diff_path( const git_merge_diff *conflict) { if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) return conflict->ancestor_entry.path; else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry)) return conflict->our_entry.path; else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) return conflict->their_entry.path; return NULL; } GIT_INLINE(bool) merge_diff_any_side_added_or_modified( const git_merge_diff *conflict) { if (conflict->our_status == GIT_DELTA_ADDED || conflict->our_status == GIT_DELTA_MODIFIED || conflict->their_status == GIT_DELTA_ADDED || conflict->their_status == GIT_DELTA_MODIFIED) return true; return false; } GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child) { size_t child_len = strlen(child); size_t parent_len = strlen(parent); if (child_len < parent_len || strncmp(parent, child, parent_len) != 0) return 0; return (child[parent_len] == '/'); } GIT_INLINE(int) merge_diff_detect_df_conflict( struct merge_diff_df_data *df_data, git_merge_diff *conflict) { const char *cur_path = merge_diff_path(conflict); /* Determine if this is a D/F conflict or the child of one */ if (df_data->df_path && path_is_prefixed(df_data->df_path, cur_path)) conflict->type = GIT_MERGE_DIFF_DF_CHILD; else if(df_data->df_path) df_data->df_path = NULL; else if (df_data->prev_path && merge_diff_any_side_added_or_modified(df_data->prev_conflict) && merge_diff_any_side_added_or_modified(conflict) && path_is_prefixed(df_data->prev_path, cur_path)) { conflict->type = GIT_MERGE_DIFF_DF_CHILD; df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE; df_data->df_path = df_data->prev_path; } df_data->prev_path = cur_path; df_data->prev_conflict = conflict; return 0; } /* Conflict handling */ GIT_INLINE(int) merge_diff_detect_type( git_merge_diff *conflict) { if (conflict->our_status == GIT_DELTA_ADDED && conflict->their_status == GIT_DELTA_ADDED) conflict->type = GIT_MERGE_DIFF_BOTH_ADDED; else if (conflict->our_status == GIT_DELTA_MODIFIED && conflict->their_status == GIT_DELTA_MODIFIED) conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED; else if (conflict->our_status == GIT_DELTA_DELETED && conflict->their_status == GIT_DELTA_DELETED) conflict->type = GIT_MERGE_DIFF_BOTH_DELETED; else if (conflict->our_status == GIT_DELTA_MODIFIED && conflict->their_status == GIT_DELTA_DELETED) conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; else if (conflict->our_status == GIT_DELTA_DELETED && conflict->their_status == GIT_DELTA_MODIFIED) conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; else conflict->type = GIT_MERGE_DIFF_NONE; return 0; } GIT_INLINE(int) index_entry_dup_pool( git_index_entry *out, git_pool *pool, const git_index_entry *src) { if (src != NULL) { memcpy(out, src, sizeof(git_index_entry)); if ((out->path = git_pool_strdup(pool, src->path)) == NULL) return -1; } return 0; } GIT_INLINE(int) merge_delta_type_from_index_entries( const git_index_entry *ancestor, const git_index_entry *other) { if (ancestor == NULL && other == NULL) return GIT_DELTA_UNMODIFIED; else if (ancestor == NULL && other != NULL) return GIT_DELTA_ADDED; else if (ancestor != NULL && other == NULL) return GIT_DELTA_DELETED; else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode)) return GIT_DELTA_TYPECHANGE; else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode)) return GIT_DELTA_TYPECHANGE; else if (git_oid__cmp(&ancestor->id, &other->id) || ancestor->mode != other->mode) return GIT_DELTA_MODIFIED; return GIT_DELTA_UNMODIFIED; } static git_merge_diff *merge_diff_from_index_entries( git_merge_diff_list *diff_list, const git_index_entry **entries) { git_merge_diff *conflict; git_pool *pool = &diff_list->pool; if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) return NULL; if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 || index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0) return NULL; conflict->our_status = merge_delta_type_from_index_entries( entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]); conflict->their_status = merge_delta_type_from_index_entries( entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]); return conflict; } /* Merge trees */ static int merge_diff_list_insert_conflict( git_merge_diff_list *diff_list, struct merge_diff_df_data *merge_df_data, const git_index_entry *tree_items[3]) { git_merge_diff *conflict; if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL || merge_diff_detect_type(conflict) < 0 || merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 || git_vector_insert(&diff_list->conflicts, conflict) < 0) return -1; return 0; } static int merge_diff_list_insert_unmodified( git_merge_diff_list *diff_list, const git_index_entry *tree_items[3]) { int error = 0; git_index_entry *entry; entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry)); GITERR_CHECK_ALLOC(entry); if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0) error = git_vector_insert(&diff_list->staged, entry); return error; } struct merge_diff_find_data { git_merge_diff_list *diff_list; struct merge_diff_df_data df_data; }; static int queue_difference(const git_index_entry **entries, void *data) { struct merge_diff_find_data *find_data = data; bool item_modified = false; size_t i; if (!entries[0] || !entries[1] || !entries[2]) { item_modified = true; } else { for (i = 1; i < 3; i++) { if (index_entry_cmp(entries[0], entries[i]) != 0) { item_modified = true; break; } } } return item_modified ? merge_diff_list_insert_conflict( find_data->diff_list, &find_data->df_data, entries) : merge_diff_list_insert_unmodified(find_data->diff_list, entries); } int git_merge_diff_list__find_differences( git_merge_diff_list *diff_list, git_iterator *ancestor_iter, git_iterator *our_iter, git_iterator *their_iter) { git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; struct merge_diff_find_data find_data = { diff_list }; return git_iterator_walk(iterators, 3, queue_difference, &find_data); } git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) { git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list)); if (diff_list == NULL) return NULL; diff_list->repo = repo; git_pool_init(&diff_list->pool, 1); if (git_vector_init(&diff_list->staged, 0, NULL) < 0 || git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || git_vector_init(&diff_list->resolved, 0, NULL) < 0) { git_merge_diff_list__free(diff_list); return NULL; } 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_normalize_opts( git_repository *repo, git_merge_options *opts, const git_merge_options *given) { git_config *cfg = NULL; git_config_entry *entry = NULL; int error = 0; assert(repo && opts); if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) return error; if (given != NULL) { memcpy(opts, given, sizeof(git_merge_options)); } else { git_merge_options init = GIT_MERGE_OPTIONS_INIT; memcpy(opts, &init, sizeof(init)); } if ((opts->flags & GIT_MERGE_FIND_RENAMES) && !opts->rename_threshold) opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; if (given && given->default_driver) { opts->default_driver = git__strdup(given->default_driver); GITERR_CHECK_ALLOC(opts->default_driver); } else { error = git_config_get_entry(&entry, cfg, "merge.default"); if (error == 0) { opts->default_driver = git__strdup(entry->value); GITERR_CHECK_ALLOC(opts->default_driver); } else if (error == GIT_ENOTFOUND) { error = 0; } else { goto done; } } if (!opts->target_limit) { int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); if (!limit) limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); opts->target_limit = (limit <= 0) ? GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; } /* assign the internal metric with whitespace flag as payload */ if (!opts->metric) { opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); GITERR_CHECK_ALLOC(opts->metric); opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; opts->metric->free_signature = git_diff_find_similar__hashsig_free; opts->metric->similarity = git_diff_find_similar__calc_similarity; opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; } done: git_config_entry_free(entry); return error; } static int merge_index_insert_reuc( git_index *index, size_t idx, const git_index_entry *entry) { const git_index_reuc_entry *reuc; int mode[3] = { 0, 0, 0 }; git_oid const *oid[3] = { NULL, NULL, NULL }; size_t i; if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry)) return 0; if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) { for (i = 0; i < 3; i++) { mode[i] = reuc->mode[i]; oid[i] = &reuc->oid[i]; } } mode[idx] = entry->mode; oid[idx] = &entry->id; return git_index_reuc_add(index, entry->path, mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); } static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) { int error; size_t i; git_merge_diff *conflict; /* Add each entry in the resolved conflict to the REUC independently, since * the paths may differ due to renames. */ git_vector_foreach(&diff_list->resolved, i, conflict) { const git_index_entry *ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? &conflict->ancestor_entry : NULL; const git_index_entry *ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? &conflict->our_entry : NULL; const git_index_entry *theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; if (ancestor != NULL && (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) return error; if (ours != NULL && (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) return error; if (theirs != NULL && (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) return error; } return 0; } static int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list, bool skip_reuc) { git_index *index; size_t i; git_merge_diff *conflict; int error = 0; *out = NULL; if ((error = git_index_new(&index)) < 0) return error; if ((error = git_index__fill(index, &diff_list->staged)) < 0) goto on_error; git_vector_foreach(&diff_list->conflicts, i, conflict) { const git_index_entry *ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? &conflict->ancestor_entry : NULL; const git_index_entry *ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? &conflict->our_entry : NULL; const git_index_entry *theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? &conflict->their_entry : NULL; if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0) goto on_error; } /* Add each rename entry to the rename portion of the index. */ git_vector_foreach(&diff_list->conflicts, i, conflict) { const char *ancestor_path, *our_path, *their_path; if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) continue; ancestor_path = conflict->ancestor_entry.path; our_path = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? conflict->our_entry.path : NULL; their_path = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? conflict->their_entry.path : NULL; if ((our_path && strcmp(ancestor_path, our_path) != 0) || (their_path && strcmp(ancestor_path, their_path) != 0)) { if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0) goto on_error; } } if (!skip_reuc) { if ((error = index_update_reuc(index, diff_list)) < 0) goto on_error; } *out = index; return 0; on_error: git_index_free(index); return error; } static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) { git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; if (given) return given; opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; if (git_iterator_for_nothing(empty, &opts) < 0) return NULL; return *empty; } int git_merge__iterators( git_index **out, git_repository *repo, git_iterator *ancestor_iter, git_iterator *our_iter, git_iterator *theirs_iter, const git_merge_options *given_opts) { git_iterator *empty_ancestor = NULL, *empty_ours = NULL, *empty_theirs = NULL; git_merge_diff_list *diff_list; git_merge_options opts; git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_diff *conflict; git_vector changes; size_t i; int error = 0; assert(out && repo); *out = NULL; GITERR_CHECK_VERSION( given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options"); if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) return error; file_opts.favor = opts.file_favor; file_opts.flags = opts.file_flags; /* use the git-inspired labels when virtual base building */ if (opts.flags & GIT_MERGE__VIRTUAL_BASE) { file_opts.ancestor_label = "merged common ancestors"; file_opts.our_label = "Temporary merge branch 1"; file_opts.their_label = "Temporary merge branch 2"; file_opts.flags |= GIT_MERGE_FILE_FAVOR__CONFLICTED; } diff_list = git_merge_diff_list__alloc(repo); GITERR_CHECK_ALLOC(diff_list); ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter); our_iter = iterator_given_or_empty(&empty_ours, our_iter); theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter); if ((error = git_merge_diff_list__find_differences( diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 || (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0) goto done; memcpy(&changes, &diff_list->conflicts, sizeof(git_vector)); git_vector_clear(&diff_list->conflicts); git_vector_foreach(&changes, i, conflict) { int resolved = 0; if ((error = merge_conflict_resolve( &resolved, diff_list, conflict, &opts, &file_opts)) < 0) goto done; if (!resolved) { if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { giterr_set(GITERR_MERGE, "merge conflicts exist"); error = GIT_EMERGECONFLICT; goto done; } git_vector_insert(&diff_list->conflicts, conflict); } } error = index_from_diff_list(out, diff_list, (opts.flags & GIT_MERGE_SKIP_REUC)); done: if (!given_opts || !given_opts->metric) git__free(opts.metric); git__free((char *)opts.default_driver); git_merge_diff_list__free(diff_list); git_iterator_free(empty_ancestor); git_iterator_free(empty_ours); git_iterator_free(empty_theirs); return error; } int git_merge_trees( git_index **out, git_repository *repo, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree *their_tree, const git_merge_options *merge_opts) { git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; assert(out && repo); /* if one side is treesame to the ancestor, take the other side */ if (ancestor_tree && merge_opts && (merge_opts->flags & GIT_MERGE_SKIP_REUC)) { const git_tree *result = NULL; const git_oid *ancestor_tree_id = git_tree_id(ancestor_tree); if (our_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(our_tree))) result = their_tree; else if (their_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(their_tree))) result = our_tree; if (result) { if ((error = git_index_new(out)) == 0) error = git_index_read_tree(*out, result); return error; } } iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; if ((error = git_iterator_for_tree( &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || (error = git_iterator_for_tree( &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || (error = git_iterator_for_tree( &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) goto done; error = git_merge__iterators( out, repo, ancestor_iter, our_iter, their_iter, merge_opts); done: git_iterator_free(ancestor_iter); git_iterator_free(our_iter); git_iterator_free(their_iter); return error; } static int merge_annotated_commits( git_index **index_out, git_annotated_commit **base_out, git_repository *repo, git_annotated_commit *our_commit, git_annotated_commit *their_commit, size_t recursion_level, const git_merge_options *opts); GIT_INLINE(int) insert_head_ids( git_array_oid_t *ids, const git_annotated_commit *annotated_commit) { git_oid *id; size_t i; if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { id = git_array_alloc(*ids); GITERR_CHECK_ALLOC(id); git_oid_cpy(id, git_commit_id(annotated_commit->commit)); } else { for (i = 0; i < annotated_commit->parents.size; i++) { id = git_array_alloc(*ids); GITERR_CHECK_ALLOC(id); git_oid_cpy(id, &annotated_commit->parents.ptr[i]); } } return 0; } static int create_virtual_base( git_annotated_commit **out, git_repository *repo, git_annotated_commit *one, git_annotated_commit *two, const git_merge_options *opts, size_t recursion_level) { git_annotated_commit *result = NULL; git_index *index = NULL; git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; /* Conflicts in the merge base creation do not propagate to conflicts * in the result; the conflicted base will act as the common ancestor. */ if (opts) memcpy(&virtual_opts, opts, sizeof(git_merge_options)); virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; virtual_opts.flags |= GIT_MERGE__VIRTUAL_BASE; if ((merge_annotated_commits(&index, NULL, repo, one, two, recursion_level + 1, &virtual_opts)) < 0) return -1; result = git__calloc(1, sizeof(git_annotated_commit)); GITERR_CHECK_ALLOC(result); result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; result->index = index; insert_head_ids(&result->parents, one); insert_head_ids(&result->parents, two); *out = result; return 0; } static int compute_base( git_annotated_commit **out, git_repository *repo, const git_annotated_commit *one, const git_annotated_commit *two, const git_merge_options *given_opts, size_t recursion_level) { git_array_oid_t head_ids = GIT_ARRAY_INIT; git_oidarray bases = {0}; git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; git_merge_options opts = GIT_MERGE_OPTIONS_INIT; size_t i, base_count; int error; *out = NULL; if (given_opts) memcpy(&opts, given_opts, sizeof(git_merge_options)); /* With more than two commits, merge_bases_many finds the base of * the first commit and a hypothetical merge of the others. Since * "one" may itself be a virtual commit, which insert_head_ids * substitutes multiple ancestors for, it needs to be added * after "two" which is always a single real commit. */ if ((error = insert_head_ids(&head_ids, two)) < 0 || (error = insert_head_ids(&head_ids, one)) < 0 || (error = git_merge_bases_many(&bases, repo, head_ids.size, head_ids.ptr)) < 0) goto done; base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; if (base_count) git_oidarray__reverse(&bases); if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) goto done; for (i = 1; i < base_count; i++) { recursion_level++; if (opts.recursion_limit && recursion_level > opts.recursion_limit) break; if ((error = git_annotated_commit_lookup(&other, repo, &bases.ids[i])) < 0 || (error = create_virtual_base(&new_base, repo, base, other, &opts, recursion_level)) < 0) goto done; git_annotated_commit_free(base); git_annotated_commit_free(other); base = new_base; new_base = NULL; other = NULL; } done: if (error == 0) *out = base; else git_annotated_commit_free(base); git_annotated_commit_free(other); git_annotated_commit_free(new_base); git_oidarray_free(&bases); git_array_clear(head_ids); return error; } static int iterator_for_annotated_commit( git_iterator **out, git_annotated_commit *commit) { git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; int error; opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; if (commit == NULL) { error = git_iterator_for_nothing(out, &opts); } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); } else { if (!commit->tree && (error = git_commit_tree(&commit->tree, commit->commit)) < 0) goto done; error = git_iterator_for_tree(out, commit->tree, &opts); } done: return error; } static int merge_annotated_commits( git_index **index_out, git_annotated_commit **base_out, git_repository *repo, git_annotated_commit *ours, git_annotated_commit *theirs, size_t recursion_level, const git_merge_options *opts) { git_annotated_commit *base = NULL; git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; int error; if ((error = compute_base(&base, repo, ours, theirs, opts, recursion_level)) < 0) { if (error != GIT_ENOTFOUND) goto done; giterr_clear(); } if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || (error = git_merge__iterators(index_out, repo, base_iter, our_iter, their_iter, opts)) < 0) goto done; if (base_out) { *base_out = base; base = NULL; } done: git_annotated_commit_free(base); git_iterator_free(base_iter); git_iterator_free(our_iter); git_iterator_free(their_iter); return error; } int git_merge_commits( git_index **out, git_repository *repo, const git_commit *our_commit, const git_commit *their_commit, const git_merge_options *opts) { git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; int error = 0; if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) goto done; error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); done: git_annotated_commit_free(ours); git_annotated_commit_free(theirs); git_annotated_commit_free(base); return error; } /* Merge setup / cleanup */ static int write_merge_head( git_repository *repo, const git_annotated_commit *heads[], size_t heads_len) { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; size_t i; int error = 0; assert(repo && heads); if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; for (i = 0; i < heads_len; i++) { if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0) goto cleanup; } error = git_filebuf_commit(&file); cleanup: if (error < 0) git_filebuf_cleanup(&file); git_buf_free(&file_path); return error; } static int write_merge_mode(git_repository *repo) { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; int error = 0; assert(repo); if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0) goto cleanup; error = git_filebuf_commit(&file); cleanup: if (error < 0) git_filebuf_cleanup(&file); git_buf_free(&file_path); return error; } struct merge_msg_entry { const git_annotated_commit *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_annotated_commit *heads[], size_t heads_len) { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; 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) { git__free(entries); return -1; } for (i = 0; i < heads_len; i++) entries[i].merge_head = heads[i]; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 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; if ((error = git_filebuf_printf(&file, "%scommit '%s'", (i > 0) ? "; " : "", entries[i].merge_head->id_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; if ((error = git_filebuf_printf(&file, "; commit '%s'", entries[i].merge_head->id_str)) < 0) goto cleanup; } if ((error = git_filebuf_printf(&file, "\n")) < 0 || (error = git_filebuf_commit(&file)) < 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_annotated_commit *our_head, const git_annotated_commit *heads[], size_t heads_len) { int error = 0; assert (repo && our_head && heads); if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 && (error = write_merge_head(repo, heads, heads_len)) == 0 && (error = write_merge_mode(repo)) == 0) { error = write_merge_msg(repo, heads, heads_len); } return error; } /* Merge branches */ static int merge_ancestor_head( git_annotated_commit **ancestor_head, git_repository *repo, const git_annotated_commit *our_head, const git_annotated_commit **their_heads, size_t their_heads_len) { git_oid *oids, ancestor_oid; size_t i, alloc_len; int error = 0; assert(repo && our_head && their_heads); GITERR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1); oids = git__calloc(alloc_len, sizeof(git_oid)); GITERR_CHECK_ALLOC(oids); git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); for (i = 0; i < their_heads_len; i++) git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i])); if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) goto on_error; error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid); on_error: git__free(oids); return error; } const char *merge_their_label(const char *branchname) { const char *slash; if ((slash = strrchr(branchname, '/')) == NULL) return branchname; if (*(slash+1) == '\0') return "theirs"; return slash+1; } static int merge_normalize_checkout_opts( git_checkout_options *out, git_repository *repo, const git_checkout_options *given_checkout_opts, unsigned int checkout_strategy, git_annotated_commit *ancestor, const git_annotated_commit *our_head, const git_annotated_commit **their_heads, size_t their_heads_len) { git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; int error = 0; GIT_UNUSED(repo); if (given_checkout_opts != NULL) memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); else memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); out->checkout_strategy = checkout_strategy; if (!out->ancestor_label) { if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) out->ancestor_label = git_commit_summary(ancestor->commit); else if (ancestor) out->ancestor_label = "merged common ancestors"; else out->ancestor_label = "empty base"; } if (!out->our_label) { if (our_head && our_head->ref_name) out->our_label = our_head->ref_name; else out->our_label = "ours"; } if (!out->their_label) { if (their_heads_len == 1 && their_heads[0]->ref_name) out->their_label = merge_their_label(their_heads[0]->ref_name); else if (their_heads_len == 1) out->their_label = their_heads[0]->id_str; else out->their_label = "theirs"; } return error; } static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) { git_tree *head_tree = NULL; git_index *index_repo = NULL; git_iterator *iter_repo = NULL, *iter_new = NULL; git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *staged_diff_list = NULL, *index_diff_list = NULL; git_diff_delta *delta; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_vector staged_paths = GIT_VECTOR_INIT; size_t i; int error = 0; GIT_UNUSED(merged_paths); *conflicts = 0; /* No staged changes may exist unless the change staged is identical to * the result of the merge. This allows one to apply to merge manually, * then run merge. Any other staged change would be overwritten by * a reset merge. */ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || (error = git_repository_index(&index_repo, repo)) < 0 || (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) goto done; if (staged_diff_list->deltas.length == 0) goto done; git_vector_foreach(&staged_diff_list->deltas, i, delta) { if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) goto done; } iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; iter_opts.pathlist.strings = (char **)staged_paths.contents; iter_opts.pathlist.count = staged_paths.length; if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) goto done; *conflicts = index_diff_list->deltas.length; done: git_tree_free(head_tree); git_index_free(index_repo); git_iterator_free(iter_repo); git_iterator_free(iter_new); git_diff_free(staged_diff_list); git_diff_free(index_diff_list); git_vector_free(&staged_paths); return error; } static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) { git_diff *wd_diff_list = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; int error = 0; GIT_UNUSED(index_new); *conflicts = 0; /* We need to have merged at least 1 file for the possibility to exist to * have conflicts with the workdir. Passing 0 as the pathspec count paramter * will consider all files in the working directory, that is, we may detect * a conflict if there were untracked files in the workdir prior to starting * the merge. This typically happens when cherry-picking a commmit whose * changes have already been applied. */ if (merged_paths->length == 0) return 0; opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* Workdir changes may exist iff they do not conflict with changes that * will be applied by the merge (including conflicts). Ensure that there * are no changes in the workdir to these paths. */ opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; opts.pathspec.count = merged_paths->length; opts.pathspec.strings = (char **)merged_paths->contents; opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0) goto done; *conflicts = wd_diff_list->deltas.length; done: git_diff_free(wd_diff_list); return error; } int git_merge__check_result(git_repository *repo, git_index *index_new) { git_tree *head_tree = NULL; git_iterator *iter_head = NULL, *iter_new = NULL; git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *merged_list = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_delta *delta; git_vector paths = GIT_VECTOR_INIT; size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; const git_index_entry *e; int error = 0; iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) goto done; git_vector_foreach(&merged_list->deltas, i, delta) { if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) goto done; } for (i = 0; i < git_index_entrycount(index_new); i++) { e = git_index_get_byindex(index_new, i); if (git_index_entry_is_conflict(e) && (git_vector_last(&paths) == NULL || strcmp(git_vector_last(&paths), e->path) != 0)) { if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) goto done; } } /* Make sure the index and workdir state do not prevent merging */ if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) goto done; if ((conflicts = index_conflicts + wd_conflicts) > 0) { giterr_set(GITERR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge", conflicts, (conflicts != 1) ? "s" : ""); error = GIT_ECONFLICT; } done: git_vector_free(&paths); git_tree_free(head_tree); git_iterator_free(iter_head); git_iterator_free(iter_new); git_diff_free(merged_list); return error; } int git_merge__append_conflicts_to_merge_msg( git_repository *repo, git_index *index) { git_filebuf file = GIT_FILEBUF_INIT; git_buf file_path = GIT_BUF_INIT; const char *last = NULL; size_t i; int error; if (!git_index_has_conflicts(index)) return 0; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; git_filebuf_printf(&file, "\nConflicts:\n"); for (i = 0; i < git_index_entrycount(index); i++) { const git_index_entry *e = git_index_get_byindex(index, i); if (!git_index_entry_is_conflict(e)) continue; if (last == NULL || strcmp(e->path, last) != 0) git_filebuf_printf(&file, "\t%s\n", e->path); last = e->path; } error = git_filebuf_commit(&file); cleanup: if (error < 0) git_filebuf_cleanup(&file); git_buf_free(&file_path); return error; } static int merge_state_cleanup(git_repository *repo) { const char *state_files[] = { GIT_MERGE_HEAD_FILE, GIT_MERGE_MODE_FILE, GIT_MERGE_MSG_FILE, }; return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); } static int merge_heads( git_annotated_commit **ancestor_head_out, git_annotated_commit **our_head_out, git_repository *repo, const git_annotated_commit **their_heads, size_t their_heads_len) { git_annotated_commit *ancestor_head = NULL, *our_head = NULL; git_reference *our_ref = NULL; int error = 0; *ancestor_head_out = NULL; *our_head_out = NULL; if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) goto done; if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 || (error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) goto done; if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) { if (error != GIT_ENOTFOUND) goto done; giterr_clear(); error = 0; } *ancestor_head_out = ancestor_head; *our_head_out = our_head; done: if (error < 0) { git_annotated_commit_free(ancestor_head); git_annotated_commit_free(our_head); } git_reference_free(our_ref); return error; } static int merge_preference(git_merge_preference_t *out, git_repository *repo) { git_config *config; const char *value; int bool_value, error = 0; *out = GIT_MERGE_PREFERENCE_NONE; if ((error = git_repository_config_snapshot(&config, repo)) < 0) goto done; if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { if (error == GIT_ENOTFOUND) { giterr_clear(); error = 0; } goto done; } if (git_config_parse_bool(&bool_value, value) == 0) { if (!bool_value) *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; } else { if (strcasecmp(value, "only") == 0) *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; } done: git_config_free(config); return error; } int git_merge_analysis( git_merge_analysis_t *analysis_out, git_merge_preference_t *preference_out, git_repository *repo, const git_annotated_commit **their_heads, size_t their_heads_len) { git_annotated_commit *ancestor_head = NULL, *our_head = NULL; int error = 0; assert(analysis_out && preference_out && repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "can only merge a single branch"); error = -1; goto done; } *analysis_out = GIT_MERGE_ANALYSIS_NONE; if ((error = merge_preference(preference_out, repo)) < 0) goto done; if (git_repository_head_unborn(repo)) { *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; goto done; } if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0) goto done; /* We're up-to-date if we're trying to merge our own common ancestor. */ if (ancestor_head && git_oid_equal( git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0]))) *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; /* We're fastforwardable if we're our own common ancestor. */ else if (ancestor_head && git_oid_equal( git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head))) *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; /* Otherwise, just a normal merge is possible. */ else *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; done: git_annotated_commit_free(ancestor_head); git_annotated_commit_free(our_head); return error; } int git_merge( git_repository *repo, const git_annotated_commit **their_heads, size_t their_heads_len, const git_merge_options *merge_opts, const git_checkout_options *given_checkout_opts) { git_reference *our_ref = NULL; git_checkout_options checkout_opts; git_annotated_commit *our_head = NULL, *base = NULL; git_index *index = NULL; git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; unsigned int checkout_strategy; int error = 0; assert(repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "can only merge a single branch"); return -1; } if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) goto done; checkout_strategy = given_checkout_opts ? given_checkout_opts->checkout_strategy : GIT_CHECKOUT_SAFE; if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, &checkout_strategy)) < 0) goto done; /* Write the merge setup files to the repository. */ if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || (error = git_merge__setup(repo, our_head, their_heads, their_heads_len)) < 0) goto done; /* TODO: octopus */ if ((error = merge_annotated_commits(&index, &base, repo, our_head, (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || (error = git_merge__check_result(repo, index)) < 0 || (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) goto done; /* check out the merge results */ if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, given_checkout_opts, checkout_strategy, base, our_head, their_heads, their_heads_len)) < 0 || (error = git_checkout_index(repo, index, &checkout_opts)) < 0) goto done; error = git_indexwriter_commit(&indexwriter); done: if (error < 0) merge_state_cleanup(repo); git_indexwriter_cleanup(&indexwriter); git_index_free(index); git_annotated_commit_free(our_head); git_annotated_commit_free(base); git_reference_free(our_ref); return error; } int git_merge_init_options(git_merge_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT); return 0; } int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT); return 0; } int git_merge_file_init_options( git_merge_file_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT); return 0; }