/* * Copyright (C) 2009-2012 the libgit2 contributors * * 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 "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_tree.h" #include "checkout.h" #include "git2/diff_tree.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 "xdiff/xdiff.h" /* Merge base computation */ int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length) { git_revwalk *walk; git_vector list; git_commit_list *result = NULL; int error = -1; unsigned int i; git_commit_list_node *commit; assert(out && repo && input_array); if (length < 2) { giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length); return -1; } if (git_vector_init(&list, length - 1, NULL) < 0) return -1; if (git_revwalk_new(&walk, repo) < 0) goto cleanup; for (i = 1; i < length; i++) { commit = git_revwalk__commit_lookup(walk, &input_array[i]); if (commit == NULL) goto cleanup; git_vector_insert(&list, commit); } commit = git_revwalk__commit_lookup(walk, &input_array[0]); if (commit == NULL) goto cleanup; if (git_merge__bases_many(&result, walk, commit, &list) < 0) goto cleanup; if (!result) { error = GIT_ENOTFOUND; goto cleanup; } git_oid_cpy(out, &result->item->oid); error = 0; cleanup: git_commit_list_free(&result); git_revwalk_free(walk); git_vector_free(&list); return error; } int git_merge_base(git_oid *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_clear(); return GIT_ENOTFOUND; } git_oid_cpy(out, &result->item->oid); git_commit_list_free(&result); git_revwalk_free(walk); return 0; on_error: git_revwalk_free(walk); return -1; } static int interesting(git_pqueue *list) { unsigned int i; /* element 0 isn't used - we need to start at 1 */ for (i = 1; i < list->size; i++) { git_commit_list_node *commit = list->d[i]; if ((commit->flags & STALE) == 0) return 1; } return 0; } 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; git_pqueue list; /* 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_pqueue_init(&list, twos->length * 2, git_commit_list_time_cmp) < 0) return -1; if (git_commit_list_parse(walk, one) < 0) return -1; one->flags |= PARENT1; if (git_pqueue_insert(&list, one) < 0) return -1; git_vector_foreach(twos, i, two) { git_commit_list_parse(walk, two); 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; int flags; commit = git_pqueue_pop(&list); 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); /* filter out any stale commits in the results */ tmp = result; result = NULL; while (tmp) { struct git_commit_list *next = tmp->next; if (!(tmp->item->flags & STALE)) if (git_commit_list_insert_by_date(tmp->item, &result) == NULL) return -1; git__free(tmp); tmp = next; } *out = result; return 0; } /* Merge setup */ static int write_orig_head(git_repository *repo, const git_merge_head *our_head) { git_filebuf orig_head_file = GIT_FILEBUF_INIT; git_buf orig_head_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(&orig_head_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 && (error = git_filebuf_open(&orig_head_file, orig_head_path.ptr, GIT_FILEBUF_FORCE)) == 0 && (error = git_filebuf_printf(&orig_head_file, "%s\n", orig_oid_str)) == 0) error = git_filebuf_commit(&orig_head_file, MERGE_CONFIG_FILE_MODE); if (error < 0) git_filebuf_cleanup(&orig_head_file); git_buf_free(&orig_head_path); return error; } static int write_merge_head(git_repository *repo, const git_merge_head *their_heads[], size_t their_heads_len) { git_filebuf merge_head_file = GIT_FILEBUF_INIT; git_buf merge_head_path = GIT_BUF_INIT; char merge_oid_str[GIT_OID_HEXSZ + 1]; size_t i; int error = 0; assert(repo && their_heads); if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 || (error = git_filebuf_open(&merge_head_file, merge_head_path.ptr, GIT_FILEBUF_FORCE)) < 0) goto cleanup; for (i = 0; i < their_heads_len; i++) { git_oid_tostr(merge_oid_str, GIT_OID_HEXSZ+1, &their_heads[i]->oid); if ((error = git_filebuf_printf(&merge_head_file, "%s\n", merge_oid_str)) < 0) goto cleanup; } error = git_filebuf_commit(&merge_head_file, MERGE_CONFIG_FILE_MODE); cleanup: if (error < 0) git_filebuf_cleanup(&merge_head_file); git_buf_free(&merge_head_path); return error; } static int write_merge_mode(git_repository *repo, unsigned int flags) { git_filebuf merge_mode_file = GIT_FILEBUF_INIT; git_buf merge_mode_path = GIT_BUF_INIT; int error = 0; assert(repo); if ((error = git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 || (error = git_filebuf_open(&merge_mode_file, merge_mode_path.ptr, GIT_FILEBUF_FORCE)) < 0) goto cleanup; /* * TODO: no-ff is the only thing allowed here at present. One would * presume they would be space-delimited when there are more, but * this needs to be revisited. */ if (flags & GIT_MERGE_NO_FASTFORWARD) { if ((error = git_filebuf_write(&merge_mode_file, "no-ff", 5)) < 0) goto cleanup; } error = git_filebuf_commit(&merge_mode_file, MERGE_CONFIG_FILE_MODE); cleanup: if (error < 0) git_filebuf_cleanup(&merge_mode_file); git_buf_free(&merge_mode_path); return error; } static int write_merge_msg(git_repository *repo, const git_merge_head *their_heads[], size_t their_heads_len) { git_filebuf merge_msg_file = GIT_FILEBUF_INIT; git_buf merge_msg_path = GIT_BUF_INIT; char merge_oid_str[GIT_OID_HEXSZ + 1]; size_t i, j; bool *wrote; int error = 0; assert(repo && their_heads); if ((wrote = git__calloc(their_heads_len, sizeof(bool))) == NULL) return -1; if ((error = git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&merge_msg_file, merge_msg_path.ptr, GIT_FILEBUF_FORCE)) < 0 || (error = git_filebuf_write(&merge_msg_file, "Merge", 5)) < 0) goto cleanup; /* * This is to emulate the format of MERGE_MSG by core git. * * Yes. Really. */ for (i = 0; i < their_heads_len; i++) { if (wrote[i]) continue; /* At the first branch, write all the branches */ if (their_heads[i]->branch_name != NULL) { bool multiple_branches = 0; size_t last_branch_idx = i; for (j = i+1; j < their_heads_len; j++) { if (their_heads[j]->branch_name != NULL) { multiple_branches = 1; last_branch_idx = j; } } if ((error = git_filebuf_printf(&merge_msg_file, "%s %s", (i > 0) ? ";" : "", multiple_branches ? "branches" : "branch")) < 0) goto cleanup; for (j = i; j < their_heads_len; j++) { if (their_heads[j]->branch_name == NULL) continue; if (j > i) { if ((error = git_filebuf_printf(&merge_msg_file, "%s", (last_branch_idx == j) ? " and" : ",")) < 0) goto cleanup; } if ((error = git_filebuf_printf(&merge_msg_file, " '%s'", their_heads[j]->branch_name)) < 0) goto cleanup; wrote[j] = 1; } } else { git_oid_fmt(merge_oid_str, &their_heads[i]->oid); merge_oid_str[GIT_OID_HEXSZ] = '\0'; if ((error = git_filebuf_printf(&merge_msg_file, "%s commit '%s'", (i > 0) ? ";" : "", merge_oid_str)) < 0) goto cleanup; } } if ((error = git_filebuf_printf(&merge_msg_file, "\n")) < 0 || (error = git_filebuf_commit(&merge_msg_file, MERGE_CONFIG_FILE_MODE)) < 0) goto cleanup; cleanup: if (error < 0) git_filebuf_cleanup(&merge_msg_file); git_buf_free(&merge_msg_path); git__free(wrote); return error; } 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) { int error = 0; assert (repo && our_head && their_heads); if ((error = write_orig_head(repo, our_head)) == 0 && (error = write_merge_head(repo, their_heads, their_heads_len)) == 0 && (error = write_merge_mode(repo, flags)) == 0) { error = write_merge_msg(repo, their_heads, their_heads_len); } return error; } GIT_INLINE(int) merge_file_cmp(const git_diff_file *a, const git_diff_file *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->oid, &b->oid)) == 0) value = strcmp(a->path, b->path); return value; } /* Xdiff (automerge/diff3) computation */ typedef struct { bool automergeable; const char *path; int mode; unsigned char *data; size_t len; } merge_filediff_result; #define MERGE_FILEDIFF_RESULT_INIT {0} static const char *merge_filediff_best_path(const git_diff_tree_delta *delta) { if (!GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor)) { if (strcmp(delta->ours.file.path, delta->theirs.file.path) == 0) return delta->ours.file.path; return NULL; } if (strcmp(delta->ancestor.file.path, delta->ours.file.path) == 0) return delta->theirs.file.path; else if(strcmp(delta->ancestor.file.path, delta->theirs.file.path) == 0) return delta->ours.file.path; return NULL; } static int merge_filediff_best_mode(const git_diff_tree_delta *delta) { /* * If ancestor didn't exist and either ours or theirs is executable, * assume executable. Otherwise, if any mode changed from the ancestor, * use that one. */ if (!GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor)) { if (delta->ours.file.mode == GIT_FILEMODE_BLOB_EXECUTABLE || delta->theirs.file.mode == GIT_FILEMODE_BLOB_EXECUTABLE) return GIT_FILEMODE_BLOB_EXECUTABLE; return GIT_FILEMODE_BLOB; } if (delta->ancestor.file.mode == delta->ours.file.mode) return delta->theirs.file.mode; else if(delta->ancestor.file.mode == delta->theirs.file.mode) return delta->ours.file.mode; return 0; } static char *merge_filediff_entry_name(const git_merge_head *merge_head, const git_diff_tree_entry *entry, bool rename) { char oid_str[GIT_OID_HEXSZ]; git_buf name = GIT_BUF_INIT; assert(merge_head && entry); if (merge_head->branch_name) git_buf_puts(&name, merge_head->branch_name); else { git_oid_fmt(oid_str, &merge_head->oid); git_buf_put(&name, oid_str, GIT_OID_HEXSZ); } if (rename) { git_buf_putc(&name, ':'); git_buf_puts(&name, entry->file.path); } return name.ptr; } static int merge_filediff_entry_names(char **our_path, char **their_path, const git_merge_head *merge_heads[], const git_diff_tree_delta *delta) { bool rename; *our_path = NULL; *their_path = NULL; if (!merge_heads) return 0; /* * If all the paths are identical, decorate the diff3 file with the branch * names. Otherwise, use branch_name:path */ rename = GIT_DIFF_TREE_FILE_EXISTS(delta->ours) && GIT_DIFF_TREE_FILE_EXISTS(delta->theirs) && strcmp(delta->ours.file.path, delta->theirs.file.path) != 0; if (GIT_DIFF_TREE_FILE_EXISTS(delta->ours) && (*our_path = merge_filediff_entry_name(merge_heads[1], &delta->ours, rename)) == NULL) return -1; if (GIT_DIFF_TREE_FILE_EXISTS(delta->theirs) && (*their_path = merge_filediff_entry_name(merge_heads[2], &delta->theirs, rename)) == NULL) return -1; return 0; } static int merge_filediff( merge_filediff_result *result, git_odb *odb, const git_merge_head *merge_heads[], const git_diff_tree_delta *delta, unsigned int flags) { git_odb_object *ancestor_odb = NULL, *our_odb = NULL, *their_odb = NULL; char *our_name = NULL, *their_name = NULL; mmfile_t ancestor_mmfile, our_mmfile, their_mmfile; xmparam_t xmparam; mmbuffer_t mmbuffer; int xdl_result; int error = 0; assert(result && odb && delta); memset(result, 0x0, sizeof(merge_filediff_result)); /* Can't automerge unless ours and theirs exist */ if (!GIT_DIFF_TREE_FILE_EXISTS(delta->ours) || !GIT_DIFF_TREE_FILE_EXISTS(delta->theirs)) return 0; /* Reject filename collisions */ result->path = merge_filediff_best_path(delta); result->mode = merge_filediff_best_mode(delta); if (result->path == NULL || result->mode == 0) return 0; memset(&xmparam, 0x0, sizeof(xmparam_t)); if (merge_heads && (error = merge_filediff_entry_names(&our_name, &their_name, merge_heads, delta)) < 0) return -1; /* Ancestor isn't decorated in diff3, use NULL. */ xmparam.ancestor = NULL; xmparam.file1 = our_name ? our_name : delta->ours.file.path; xmparam.file2 = their_name ? their_name : delta->theirs.file.path; if (GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor)) { if ((error = git_odb_read(&ancestor_odb, odb, &delta->ancestor.file.oid)) < 0) goto done; ancestor_mmfile.size = git_odb_object_size(ancestor_odb); ancestor_mmfile.ptr = (char *)git_odb_object_data(ancestor_odb); } else memset(&ancestor_mmfile, 0x0, sizeof(mmfile_t)); if (GIT_DIFF_TREE_FILE_EXISTS(delta->ours)) { if ((error = git_odb_read(&our_odb, odb, &delta->ours.file.oid)) < 0) goto done; our_mmfile.size = git_odb_object_size(our_odb); our_mmfile.ptr = (char *)git_odb_object_data(our_odb); } else memset(&our_mmfile, 0x0, sizeof(mmfile_t)); if (GIT_DIFF_TREE_FILE_EXISTS(delta->theirs)) { if ((error = git_odb_read(&their_odb, odb, &delta->theirs.file.oid)) < 0) goto done; their_mmfile.size = git_odb_object_size(their_odb); their_mmfile.ptr = (char *)git_odb_object_data(their_odb); } else memset(&their_mmfile, 0x0, sizeof(mmfile_t)); if (flags & GIT_MERGE_RESOLVE_FAVOR_OURS) xmparam.favor = XDL_MERGE_FAVOR_OURS; if (flags & GIT_MERGE_RESOLVE_FAVOR_THEIRS) xmparam.favor = XDL_MERGE_FAVOR_THEIRS; if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, &their_mmfile, &xmparam, &mmbuffer)) < 0) { giterr_set(GITERR_MERGE, "Failed to perform automerge."); error = -1; goto done; } result->automergeable = (xdl_result == 0); result->data = (unsigned char *)mmbuffer.ptr; result->len = mmbuffer.size; done: git__free(our_name); git__free(their_name); git_odb_object_free(ancestor_odb); git_odb_object_free(our_odb); git_odb_object_free(their_odb); return error; } static void merge_filediff_result_free(merge_filediff_result *result) { /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */ if (result->data != NULL) free(result->data); } /* Conflict resolution */ static int merge_file_index_remove(git_index *index, const git_diff_tree_delta *delta) { if (!GIT_DIFF_TREE_FILE_EXISTS(delta->ours)) return 0; return git_index_remove(index, delta->ours.file.path, 0); } static int merge_file_apply(git_index *index, const git_diff_tree_delta *delta, const git_diff_tree_entry *entry) { git_index_entry index_entry; int error = 0; assert (index && entry); if (!GIT_DIFF_TREE_FILE_EXISTS(*entry)) error = merge_file_index_remove(index, delta); else { memset(&index_entry, 0x0, sizeof(git_index_entry)); index_entry.path = (char *)entry->file.path; index_entry.mode = entry->file.mode; index_entry.file_size = entry->file.size; git_oid_cpy(&index_entry.oid, &entry->file.oid); error = git_index_add(index, &index_entry); } return error; } static int merge_mark_conflict_resolved(git_index *index, const git_diff_tree_delta *delta) { const char *path; assert(index && delta); if (GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor)) path = delta->ancestor.file.path; else if (GIT_DIFF_TREE_FILE_EXISTS(delta->ours)) path = delta->ours.file.path; else if (GIT_DIFF_TREE_FILE_EXISTS(delta->theirs)) path = delta->theirs.file.path; return git_index_reuc_add(index, path, delta->ancestor.file.mode, &delta->ancestor.file.oid, delta->ours.file.mode, &delta->ours.file.oid, delta->theirs.file.mode, &delta->theirs.file.oid); } static int merge_mark_conflict_unresolved(git_index *index, const git_diff_tree_delta *delta) { bool ancestor_exists = 0, ours_exists = 0, theirs_exists = 0; git_index_entry ancestor_entry, our_entry, their_entry; int error = 0; assert(index && delta); if ((ancestor_exists = GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor))) { memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); ancestor_entry.path = (char *)delta->ancestor.file.path; ancestor_entry.mode = delta->ancestor.file.mode; git_oid_cpy(&ancestor_entry.oid, &delta->ancestor.file.oid); } if ((ours_exists = GIT_DIFF_TREE_FILE_EXISTS(delta->ours))) { memset(&our_entry, 0x0, sizeof(git_index_entry)); our_entry.path = (char *)delta->ours.file.path; our_entry.mode = delta->ours.file.mode; git_oid_cpy(&our_entry.oid, &delta->ours.file.oid); } if ((theirs_exists = GIT_DIFF_TREE_FILE_EXISTS(delta->theirs))) { memset(&their_entry, 0x0, sizeof(git_index_entry)); their_entry.path = (char *)delta->theirs.file.path; their_entry.mode = delta->theirs.file.mode; git_oid_cpy(&their_entry.oid, &delta->theirs.file.oid); } if ((error = merge_file_index_remove(index, delta)) >= 0) error = git_index_conflict_add(index, ancestor_exists ? &ancestor_entry : NULL, ours_exists ? &our_entry : NULL, theirs_exists ? &their_entry : NULL); return error; } static int merge_conflict_resolve_trivial( int *resolved, git_repository *repo, git_index *index, const git_diff_tree_delta *delta, unsigned int resolve_flags) { int ancestor_empty, ours_empty, theirs_empty; int ours_changed, theirs_changed, ours_theirs_differ; git_diff_tree_entry const *result = NULL; int error = 0; GIT_UNUSED(resolve_flags); assert(resolved && repo && index && delta); *resolved = 0; /* TODO: (optionally) reject children of d/f conflicts */ if (delta->df_conflict == GIT_DIFF_TREE_DF_DIRECTORY_FILE) return 0; ancestor_empty = !GIT_DIFF_TREE_FILE_EXISTS(delta->ancestor); ours_empty = !GIT_DIFF_TREE_FILE_EXISTS(delta->ours); theirs_empty = !GIT_DIFF_TREE_FILE_EXISTS(delta->theirs); ours_changed = (delta->ours.status != GIT_DELTA_UNMODIFIED); theirs_changed = (delta->theirs.status != GIT_DELTA_UNMODIFIED); ours_theirs_differ = ours_changed && theirs_changed && merge_file_cmp(&delta->ours.file, &delta->theirs.file); /* * 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 = &delta->ours; /* 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 = &delta->ours; /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ else if (!ours_changed && theirs_changed) result = &delta->theirs; else *resolved = 0; if (result != NULL && (error = merge_file_apply(index, delta, result)) >= 0) *resolved = 1; /* Note: trivial resolution does not update the REUC. */ return error; } static int merge_conflict_resolve_removed( int *resolved, git_repository *repo, git_index *index, const git_diff_tree_delta *delta, unsigned int resolve_flags) { int ours_empty, theirs_empty; int ours_changed, theirs_changed; git_diff_tree_entry const *result = NULL; int error = 0; GIT_UNUSED(resolve_flags); assert(resolved && repo && index && delta); *resolved = 0; if (resolve_flags & GIT_MERGE_RESOLVE_NO_REMOVED) return 0; /* TODO: (optionally) reject children of d/f conflicts */ if (delta->df_conflict == GIT_DIFF_TREE_DF_DIRECTORY_FILE) return 0; ours_empty = !GIT_DIFF_TREE_FILE_EXISTS(delta->ours); theirs_empty = !GIT_DIFF_TREE_FILE_EXISTS(delta->theirs); ours_changed = (delta->ours.status != GIT_DELTA_UNMODIFIED); theirs_changed = (delta->theirs.status != GIT_DELTA_UNMODIFIED); /* Handle some cases that are not "trivial" but are, well, trivial. */ /* Removed in both */ if (ours_changed && ours_empty && theirs_empty) result = &delta->ours; /* Removed in ours */ else if (ours_empty && !theirs_changed) result = &delta->ours; /* Removed in theirs */ else if (!ours_changed && theirs_empty) result = &delta->theirs; if (result != NULL && (error = merge_file_apply(index, delta, result)) >= 0 && (error = merge_mark_conflict_resolved(index, delta)) >= 0) *resolved = 1; return error; } static int merge_conflict_resolve_automerge( int *resolved, git_repository *repo, git_index *index, const git_diff_tree_delta *delta, unsigned int resolve_flags) { git_odb *odb = NULL; merge_filediff_result result = MERGE_FILEDIFF_RESULT_INIT; git_index_entry index_entry; git_oid automerge_oid; int error = 0; assert(resolved && repo && index && delta); *resolved = 0; if (resolve_flags & GIT_MERGE_RESOLVE_NO_AUTOMERGE) return 0; /* Reject D/F conflicts */ if (delta->df_conflict == GIT_DIFF_TREE_DF_DIRECTORY_FILE) return 0; /* Reject link/file conflicts. */ if ((S_ISLNK(delta->ancestor.file.mode) ^ S_ISLNK(delta->ours.file.mode)) || (S_ISLNK(delta->ancestor.file.mode) ^ S_ISLNK(delta->theirs.file.mode))) return 0; /* TODO: reject children of d/f conflicts */ /* TODO: reject name conflicts */ if ((error = git_repository_odb(&odb, repo)) < 0) goto done; if ((error = merge_filediff(&result, odb, NULL, delta, resolve_flags)) < 0 || !result.automergeable || (error = git_odb_write(&automerge_oid, odb, result.data, result.len, GIT_OBJ_BLOB)) < 0) goto done; memset(&index_entry, 0x0, sizeof(git_index_entry)); index_entry.path = (char *)result.path; index_entry.file_size = result.len; index_entry.mode = result.mode; git_oid_cpy(&index_entry.oid, &automerge_oid); if ((error = git_index_add(index, &index_entry)) >= 0 && (error = merge_mark_conflict_resolved(index, delta)) >= 0) *resolved = 1; done: merge_filediff_result_free(&result); git_odb_free(odb); return error; } static int merge_conflict_resolve( int *out, git_repository *repo, git_index *index, const git_diff_tree_delta *delta, unsigned int resolve_flags) { int resolved = 0; int error = 0; *out = 0; if ((error = merge_conflict_resolve_trivial(&resolved, repo, index, delta, resolve_flags)) < 0) goto done; if (!resolved && (error = merge_conflict_resolve_removed(&resolved, repo, index, delta, resolve_flags)) < 0) goto done; if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, repo, index, delta, resolve_flags)) < 0) goto done; if (!resolved) error = merge_mark_conflict_unresolved(index, delta); *out = resolved; done: return error; } static int merge_conflict_write_diff3(int *conflict_written, git_repository *repo, const git_merge_head *ancestor_head, const git_merge_head *our_head, const git_merge_head *their_head, const git_diff_tree_delta *delta, unsigned int flags) { git_odb *odb = NULL; merge_filediff_result result = MERGE_FILEDIFF_RESULT_INIT; git_merge_head const *merge_heads[3] = { ancestor_head, our_head, their_head }; git_buf workdir_path = GIT_BUF_INIT; git_filebuf output = GIT_FILEBUF_INIT; int error = 0; assert(conflict_written && repo && ancestor_head && our_head && their_head && delta); *conflict_written = 0; if (flags & GIT_MERGE_CONFLICT_NO_DIFF3) return 0; /* Reject link/file conflicts. */ if ((S_ISLNK(delta->ancestor.file.mode) ^ S_ISLNK(delta->ours.file.mode)) || (S_ISLNK(delta->ancestor.file.mode) ^ S_ISLNK(delta->theirs.file.mode))) return 0; /* Reject D/F conflicts */ if (delta->df_conflict == GIT_DIFF_TREE_DF_DIRECTORY_FILE) return 0; /* TODO: reject name conflicts? */ git_repository_odb(&odb, repo); /* TODO: mkpath2file mode */ if (!GIT_DIFF_TREE_FILE_EXISTS(delta->ours) || !GIT_DIFF_TREE_FILE_EXISTS(delta->theirs) || (error = git_repository_odb(&odb, repo)) < 0 || (error = merge_filediff(&result, odb, merge_heads, delta, 0)) < 0 || result.path == NULL || result.mode == 0 || (error = git_buf_joinpath(&workdir_path, git_repository_workdir(repo), result.path)) < 0 || (error = git_futils_mkpath2file(workdir_path.ptr, 0755) < 0) || (error = git_filebuf_open(&output, workdir_path.ptr, GIT_FILEBUF_DO_NOT_BUFFER)) < 0 || (error = git_filebuf_write(&output, result.data, result.len)) < 0 || (error = git_filebuf_commit(&output, result.mode)) < 0) goto done; *conflict_written = 1; done: merge_filediff_result_free(&result); git_odb_free(odb); git_buf_free(&workdir_path); return error; } static int merge_conflict_write_file( git_repository *repo, const git_diff_tree_entry *entry, const char *path) { git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; opts.file_open_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL; if (path == NULL) path = entry->file.path; return git_checkout_blob(repo, &entry->file.oid, path, entry->file.mode, &opts); } static int merge_conflict_write_side( git_repository *repo, const git_merge_head *merge_head, const git_diff_tree_delta *delta, const git_diff_tree_entry *entry, unsigned int flags) { const char *path = entry->file.path; git_buf path_with_branch = GIT_BUF_INIT; char oid_str[GIT_OID_HEXSZ]; int error = 0; assert(repo && merge_head && entry); /* TODO: what if this file exists? */ /* * Mutate the name if we're D/F conflicted or if we didn't write a diff3 * file. */ if (delta->df_conflict == GIT_DIFF_TREE_DF_DIRECTORY_FILE || (flags & GIT_MERGE_CONFLICT_NO_DIFF3)) { git_buf_puts(&path_with_branch, entry->file.path); git_buf_putc(&path_with_branch, '~'); if (merge_head->branch_name) git_buf_puts(&path_with_branch, merge_head->branch_name); else { git_oid_fmt(oid_str, &merge_head->oid); git_buf_put(&path_with_branch, oid_str, GIT_OID_HEXSZ); } path = git_buf_cstr(&path_with_branch); } error = merge_conflict_write_file(repo, entry, path); git_buf_free(&path_with_branch); return error; } static int merge_conflict_write_sides( int *conflict_written, git_repository *repo, const git_merge_head *ancestor_head, const git_merge_head *our_head, const git_merge_head *their_head, const git_diff_tree_delta *delta, unsigned int flags) { int error = 0; GIT_UNUSED(flags); assert(conflict_written && repo && ancestor_head && our_head && their_head && delta); *conflict_written = 0; if (GIT_DIFF_TREE_FILE_EXISTS(delta->ours) && (error = merge_conflict_write_side(repo, our_head, delta, &delta->ours, flags)) < 0) goto done; if (GIT_DIFF_TREE_FILE_EXISTS(delta->theirs) && (error = merge_conflict_write_side(repo, their_head, delta, &delta->theirs, flags)) < 0) goto done; done: if (error >= 0) *conflict_written = 1; return error; } static int merge_conflict_write(int *out, git_repository *repo, const git_merge_head *ancestor_head, const git_merge_head *our_head, const git_merge_head *their_head, const git_diff_tree_delta *delta, unsigned int flags) { int conflict_written = 0; int error = 0; assert(out && repo && ancestor_head && our_head && their_head && delta); *out = 0; if ((error = merge_conflict_write_diff3(&conflict_written, repo, ancestor_head, our_head, their_head, delta, flags)) < 0) goto done; if (!conflict_written) error = merge_conflict_write_sides(&conflict_written, repo, ancestor_head, our_head, their_head, delta, flags); *out = conflict_written; done: return error; } /* Merge trees */ static int merge_trees( git_merge_result *result, git_repository *repo, git_index *index, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree *their_tree, const git_merge_trees_opts *opts) { git_diff_tree_delta *delta; size_t i; int error = 0; if ((error = git_diff_tree(&result->diff_tree, repo, ancestor_tree, our_tree, their_tree, opts->diff_flags)) < 0) return error; git_vector_foreach(&result->diff_tree->deltas, i, delta) { int resolved = 0; if ((error = merge_conflict_resolve(&resolved, repo, index, delta, opts->resolve_flags)) < 0) return error; if (!resolved) git_vector_insert(&result->conflicts, delta); } return 0; } static int merge_trees_octopus( git_merge_result *result, git_repository *repo, git_index *index, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree **their_trees, size_t their_trees_len, const git_merge_trees_opts *opts) { GIT_UNUSED(result); GIT_UNUSED(repo); GIT_UNUSED(index); GIT_UNUSED(ancestor_tree); GIT_UNUSED(our_tree); GIT_UNUSED(their_trees); GIT_UNUSED(their_trees_len); GIT_UNUSED(opts); giterr_set(GITERR_MERGE, "Merge octopus is not yet implemented."); return -1; } static int merge_trees_normalize_opts( git_merge_trees_opts *opts, const git_merge_trees_opts *given) { if (given != NULL) memcpy(opts, given, sizeof(git_merge_trees_opts)); else memset(opts, 0x0, sizeof(git_merge_trees_opts)); return 0; } int git_merge_trees( git_merge_result **out, git_repository *repo, git_index *index, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree *their_tree, const git_merge_trees_opts *given_opts) { git_merge_trees_opts opts; git_merge_result *result; int error = 0; assert(out && repo && index && ancestor_tree && our_tree && their_tree); *out = NULL; if ((error = merge_trees_normalize_opts(&opts, given_opts)) < 0) return error; result = git__calloc(1, sizeof(git_merge_result)); GITERR_CHECK_ALLOC(result); if ((error = merge_trees(result, repo, index, ancestor_tree, our_tree, their_tree, &opts)) >= 0) *out = result; else git__free(result); return error; } /* Merge branches */ static int merge_ancestor_head( git_merge_head **ancestor_head, git_repository *repo, const git_merge_head *our_head, const git_merge_head **their_heads, size_t their_heads_len) { git_oid *oids, ancestor_oid; size_t i; int error = 0; assert(repo && our_head && their_heads); oids = git__calloc(their_heads_len + 1, 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], &their_heads[i]->oid); if ((error = git_merge_base_many(&ancestor_oid, repo, oids, their_heads_len + 1)) < 0) goto on_error; error = git_merge_head_from_oid(ancestor_head, repo, &ancestor_oid); on_error: git__free(oids); return error; } GIT_INLINE(bool) merge_check_uptodate( git_merge_result *result, const git_merge_head *our_head, const git_merge_head *their_head) { if (git_oid_cmp(&our_head->oid, &their_head->oid) == 0) { result->is_uptodate = 1; return true; } return false; } GIT_INLINE(bool) merge_check_fastforward( git_merge_result *result, const git_merge_head *ancestor_head, const git_merge_head *our_head, const git_merge_head *their_head, unsigned int flags) { if ((flags & GIT_MERGE_NO_FASTFORWARD) == 0 && git_oid_cmp(&ancestor_head->oid, &our_head->oid) == 0) { result->is_fastforward = 1; git_oid_cpy(&result->fastforward_oid, &their_head->oid); return true; } return false; } static int merge_normalize_opts( git_merge_opts *opts, const git_merge_opts *given) { int error = 0; unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MISSING | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_REMOVE_UNTRACKED | GIT_CHECKOUT_ALLOW_CONFLICTS; if (given != NULL) { memcpy(opts, given, sizeof(git_merge_opts)); if (!opts->checkout_opts.checkout_strategy) opts->checkout_opts.checkout_strategy = default_checkout_strategy; error = merge_trees_normalize_opts(&opts->merge_trees_opts, &given->merge_trees_opts); } else { git_merge_opts default_opts = GIT_MERGE_OPTS_INIT; memcpy(opts, &default_opts, sizeof(git_merge_opts)); opts->checkout_opts.checkout_strategy = default_checkout_strategy; error = merge_trees_normalize_opts(&opts->merge_trees_opts, NULL); } return error; } int git_merge( git_merge_result **out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len, const git_merge_opts *given_opts) { git_merge_result *result; git_merge_opts opts; git_reference *our_ref = NULL; git_merge_head *ancestor_head = NULL, *our_head = NULL; git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL; git_index *index; git_diff_tree_delta *delta; size_t i; int error = 0; assert(out && repo && their_heads); *out = NULL; result = git__calloc(1, sizeof(git_merge_result)); GITERR_CHECK_ALLOC(result); their_trees = git__calloc(their_heads_len, sizeof(git_tree *)); GITERR_CHECK_ALLOC(their_trees); if (merge_normalize_opts(&opts, given_opts) < 0) goto on_error; if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) goto on_error; if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 || (error = git_merge_head_from_ref(&our_head, repo, our_ref)) < 0 || (error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) goto on_error; if (their_heads_len == 1 && (merge_check_uptodate(result, our_head, their_heads[0]) || merge_check_fastforward(result, ancestor_head, our_head, their_heads[0], opts.merge_flags))) { *out = result; goto done; } /* Write the merge files to the repository. */ if ((error = git_merge__setup(repo, our_head, their_heads, their_heads_len, opts.merge_flags)) < 0) goto on_error; if ((error = git_commit_tree(&ancestor_tree, ancestor_head->commit)) < 0 || (error = git_commit_tree(&our_tree, our_head->commit)) < 0) goto on_error; for (i = 0; i < their_heads_len; i++) { if ((error = git_commit_tree(&their_trees[i], their_heads[i]->commit)) < 0) goto on_error; } if ((error = git_repository_index__weakptr(&index, repo)) < 0) goto on_error; /* TODO: recursive */ if (their_heads_len == 1) error = merge_trees(result, repo, index, ancestor_tree, our_tree, their_trees[0], &opts.merge_trees_opts); else error = merge_trees_octopus(result, repo, index, ancestor_tree, our_tree, (const git_tree **)their_trees, their_heads_len, &opts.merge_trees_opts); if (error < 0) goto on_error; /* TODO: hack to workaround checkout dir/file stuff */ if ((error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || (error = git_index_write(index)) < 0) goto on_error; if (their_heads_len == 1) { git_vector_foreach(&result->conflicts, i, delta) { int conflict_written = 0; if ((error = merge_conflict_write(&conflict_written, repo, ancestor_head, our_head, their_heads[0], delta, opts.conflict_flags)) < 0) goto on_error; } } *out = result; goto done; on_error: git__free(result); done: git_tree_free(ancestor_tree); git_tree_free(our_tree); for (i = 0; i < their_heads_len; i++) git_tree_free(their_trees[i]); git__free(their_trees); git_merge_head_free(our_head); git_merge_head_free(ancestor_head); git_reference_free(our_ref); return error; } int git_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_mode_path, repo->path_repository, GIT_MERGE_MODE_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 result data */ int git_merge_result_is_uptodate(git_merge_result *merge_result) { assert(merge_result); return merge_result->is_uptodate; } int git_merge_result_is_fastforward(git_merge_result *merge_result) { assert(merge_result); return merge_result->is_fastforward; } int git_merge_result_fastforward_oid(git_oid *out, git_merge_result *merge_result) { assert(out && merge_result); git_oid_cpy(out, &merge_result->fastforward_oid); return 0; } int git_merge_result_delta_foreach(git_merge_result *merge_result, git_diff_tree_delta_cb delta_cb, void *payload) { git_diff_tree_delta *delta; size_t i; int error = 0; assert(merge_result && delta_cb); git_vector_foreach(&merge_result->conflicts, i, delta) { if (delta_cb(delta, payload) != 0) { error = GIT_EUSER; break; } } return error; } int git_merge_result_conflict_foreach(git_merge_result *merge_result, git_diff_tree_delta_cb conflict_cb, void *payload) { git_diff_tree_delta *delta; size_t i; int error = 0; assert(merge_result && conflict_cb); git_vector_foreach(&merge_result->conflicts, i, delta) { if (conflict_cb(delta, payload) != 0) { error = GIT_EUSER; break; } } return error; } void git_merge_result_free(git_merge_result *merge_result) { if (merge_result == NULL) return; git_vector_free(&merge_result->conflicts); git_diff_tree_list_free(merge_result->diff_tree); merge_result->diff_tree = NULL; git__free(merge_result); } /* git_merge_head functions */ static int merge_head_init(git_merge_head **out, git_repository *repo, const char *branch_name, 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 (branch_name) { head->branch_name = git__strdup(branch_name); GITERR_CHECK_ALLOC(head->branch_name); } 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; char const *ref_name = NULL; int error = 0; assert(out && ref); *out = NULL; if ((error = git_reference_resolve(&resolved, ref)) < 0) return error; ref_name = git_reference_name(ref); if (git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0) ref_name += strlen(GIT_REFS_HEADS_DIR); error = merge_head_init(out, repo, ref_name, 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) { return merge_head_init(out, repo, NULL, oid); } void git_merge_head_free(git_merge_head *head) { if (head == NULL) return; if (head->commit != NULL) git_object_free((git_object *)head->commit); if (head->branch_name != NULL) git__free(head->branch_name); git__free(head); }