| /* |
| * Copyright (C) 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 "iterator.h" |
| #include "diff.h" |
| #include "diff_tree.h" |
| |
| #include "git2/diff_tree.h" |
| #include "git2/oid.h" |
| #include "git2/config.h" |
| |
| /** |
| * n-way tree differencing |
| */ |
| |
| static int index_entry_cmp(git_index_entry *a, git_index_entry *b) |
| { |
| int diff; |
| |
| assert (a && b); |
| |
| /* Ignore tree changes */ |
| if (S_ISDIR(a->mode) && S_ISDIR(b->mode)) |
| return 0; |
| |
| if ((diff = a->mode - b->mode) == 0) |
| diff = git_oid_cmp(&a->oid, &b->oid); |
| |
| return diff; |
| } |
| |
| int git_diff_tree_many( |
| git_repository *repo, |
| const git_tree **trees, |
| size_t trees_length, |
| uint32_t flags, |
| git_diff_tree_many_cb callback, |
| void *payload) |
| { |
| git_iterator **iterators; |
| git_index_entry **items = NULL, *best_cur_item, **cur_items; |
| git_vector_cmp entry_compare = git_index_entry__cmp; |
| int cur_item_modified; |
| size_t i; |
| int error = 0; |
| |
| assert(repo && trees && callback); |
| |
| iterators = git__calloc(trees_length, sizeof(git_iterator *)); |
| GITERR_CHECK_ALLOC(iterators); |
| |
| items = git__calloc(trees_length, sizeof(git_index_entry *)); |
| GITERR_CHECK_ALLOC(items); |
| |
| cur_items = git__calloc(trees_length, sizeof(git_index_entry *)); |
| GITERR_CHECK_ALLOC(cur_items); |
| |
| for (i = 0; i < trees_length; i++) { |
| if ((error = git_iterator_for_tree(&iterators[i], repo, trees[i])) < 0) |
| return -1; |
| } |
| |
| /* Set up the iterators */ |
| for (i = 0; i < trees_length; i++) { |
| if ((error = git_iterator_current(iterators[i], |
| (const git_index_entry **)&items[i])) < 0) |
| goto done; |
| } |
| |
| while (true) { |
| memset(cur_items, 0x0, sizeof(git_index_entry *) * trees_length); |
| best_cur_item = NULL; |
| cur_item_modified = 0; |
| |
| /* Find the next path(s) to consume from each iterator */ |
| for (i = 0; i < trees_length; i++) { |
| if (items[i] == NULL) { |
| cur_item_modified = 1; |
| continue; |
| } |
| |
| if (best_cur_item == NULL) { |
| best_cur_item = items[i]; |
| cur_items[i] = items[i]; |
| } else { |
| int path_diff = entry_compare(items[i], best_cur_item); |
| |
| if (path_diff < 0) { |
| /* |
| * Found an item that sorts before our current item, make |
| * our current item this one. |
| */ |
| memset(cur_items, 0x0, sizeof(git_index_entry *) * trees_length); |
| cur_item_modified = 1; |
| best_cur_item = items[i]; |
| cur_items[i] = items[i]; |
| } else if (path_diff > 0) { |
| /* No entry for the current item, this is modified */ |
| cur_item_modified = 1; |
| } else if (path_diff == 0) { |
| cur_items[i] = items[i]; |
| |
| if (!cur_item_modified && !(flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) |
| cur_item_modified = index_entry_cmp(best_cur_item, items[i]); |
| } |
| } |
| } |
| |
| if (best_cur_item == NULL) |
| break; |
| |
| if (cur_item_modified || (flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) { |
| if (callback((const git_index_entry **)cur_items, payload)) { |
| error = GIT_EUSER; |
| goto done; |
| } |
| } |
| |
| /* Advance each iterator that participated */ |
| for (i = 0; i < trees_length; i++) { |
| if (cur_items[i] != NULL && |
| (error = git_iterator_advance(iterators[i], |
| (const git_index_entry **)&items[i])) < 0) |
| goto done; |
| } |
| } |
| |
| done: |
| for (i = 0; i < trees_length; i++) |
| git_iterator_free(iterators[i]); |
| |
| git__free(iterators); |
| git__free(items); |
| git__free(cur_items); |
| |
| return error; |
| } |
| |
| /** |
| * Three-way tree differencing |
| */ |
| |
| typedef enum { |
| INDEX_ANCESTOR = 0, |
| INDEX_OURS = 1, |
| INDEX_THEIRS = 2 |
| } diff_tree_threeway_index; |
| |
| struct diff_tree_threeway_data { |
| git_diff_tree_list *diff_tree; |
| |
| const char *df_path; |
| const char *prev_path; |
| git_diff_tree_delta *prev_delta_tree; |
| }; |
| |
| static git_diff_tree_list *diff_tree__list_alloc(git_repository *repo) |
| { |
| git_diff_tree_list *diff_tree = |
| git__calloc(1, sizeof(git_diff_tree_list)); |
| |
| if (diff_tree == NULL) |
| return NULL; |
| |
| diff_tree->repo = repo; |
| |
| if (git_vector_init(&diff_tree->deltas, 0, git_diff_delta__cmp) < 0 || |
| git_pool_init(&diff_tree->pool, 1, 0) < 0) |
| return NULL; |
| |
| return diff_tree; |
| } |
| |
| GIT_INLINE(const char *) diff_tree__path(const git_diff_tree_delta *delta_tree) |
| { |
| if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->ancestor)) |
| return delta_tree->ancestor.file.path; |
| else if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->ours)) |
| return delta_tree->ours.file.path; |
| else if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->theirs)) |
| return delta_tree->theirs.file.path; |
| |
| return NULL; |
| } |
| |
| GIT_INLINE(bool) diff_tree__delta_added_or_modified( |
| const git_diff_tree_delta *delta_tree) |
| { |
| if (delta_tree->ours.status == GIT_DELTA_ADDED || |
| delta_tree->ours.status == GIT_DELTA_MODIFIED || |
| delta_tree->theirs.status == GIT_DELTA_ADDED || |
| delta_tree->theirs.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) diff_tree__compute_df_conflict( |
| struct diff_tree_threeway_data *threeway_data, |
| git_diff_tree_delta *delta_tree) |
| { |
| const char *cur_path = diff_tree__path(delta_tree); |
| |
| /* Determine if this is a D/F conflict or the child of one */ |
| if (threeway_data->df_path && |
| path_is_prefixed(threeway_data->df_path, cur_path)) |
| delta_tree->df_conflict = GIT_DIFF_TREE_DF_CHILD; |
| else if(threeway_data->df_path) |
| threeway_data->df_path = NULL; |
| else if (threeway_data->prev_path && |
| diff_tree__delta_added_or_modified(threeway_data->prev_delta_tree) && |
| diff_tree__delta_added_or_modified(delta_tree) && |
| path_is_prefixed(threeway_data->prev_path, cur_path)) { |
| delta_tree->df_conflict = GIT_DIFF_TREE_DF_CHILD; |
| |
| threeway_data->prev_delta_tree->df_conflict = GIT_DIFF_TREE_DF_DIRECTORY_FILE; |
| threeway_data->df_path = threeway_data->prev_path; |
| } |
| |
| threeway_data->prev_path = cur_path; |
| threeway_data->prev_delta_tree = delta_tree; |
| |
| return 0; |
| } |
| |
| GIT_INLINE(int) diff_tree__compute_conflict( |
| git_diff_tree_delta *delta_tree) |
| { |
| if (delta_tree->ours.status == GIT_DELTA_ADDED && |
| delta_tree->theirs.status == GIT_DELTA_ADDED) |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_ADDED; |
| else if (delta_tree->ours.status == GIT_DELTA_MODIFIED && |
| delta_tree->theirs.status == GIT_DELTA_MODIFIED) |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_MODIFIED; |
| else if (delta_tree->ours.status == GIT_DELTA_DELETED && |
| delta_tree->theirs.status == GIT_DELTA_DELETED) |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_DELETED; |
| else if (delta_tree->ours.status == GIT_DELTA_MODIFIED && |
| delta_tree->theirs.status == GIT_DELTA_DELETED) |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_MODIFY_DELETE; |
| else if (delta_tree->ours.status == GIT_DELTA_DELETED && |
| delta_tree->theirs.status == GIT_DELTA_MODIFIED) |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_MODIFY_DELETE; |
| else |
| delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_NONE; |
| |
| return 0; |
| } |
| |
| static git_diff_tree_delta *diff_tree__delta_from_entries( |
| struct diff_tree_threeway_data *threeway_data, |
| const git_index_entry **entries) |
| { |
| git_diff_tree_delta *delta_tree; |
| git_diff_tree_entry *tree_entries[3]; |
| size_t i; |
| |
| if ((delta_tree = git_pool_malloc(&threeway_data->diff_tree->pool, sizeof(git_diff_tree_delta))) == NULL) |
| return NULL; |
| |
| tree_entries[INDEX_ANCESTOR] = &delta_tree->ancestor; |
| tree_entries[INDEX_OURS] = &delta_tree->ours; |
| tree_entries[INDEX_THEIRS] = &delta_tree->theirs; |
| |
| for (i = 0; i < 3; i++) { |
| if (entries[i] == NULL) |
| continue; |
| |
| if ((tree_entries[i]->file.path = git_pool_strdup(&threeway_data->diff_tree->pool, entries[i]->path)) == NULL) |
| return NULL; |
| |
| git_oid_cpy(&tree_entries[i]->file.oid, &entries[i]->oid); |
| tree_entries[i]->file.size = entries[i]->file_size; |
| tree_entries[i]->file.mode = entries[i]->mode; |
| tree_entries[i]->file.flags |= GIT_DIFF_FILE_VALID_OID; |
| } |
| |
| for (i = 1; i < 3; i++) { |
| if (entries[INDEX_ANCESTOR] == NULL && entries[i] == NULL) |
| continue; |
| |
| if (entries[INDEX_ANCESTOR] == NULL && entries[i] != NULL) |
| tree_entries[i]->status |= GIT_DELTA_ADDED; |
| else if (entries[INDEX_ANCESTOR] != NULL && entries[i] == NULL) |
| tree_entries[i]->status |= GIT_DELTA_DELETED; |
| else if (S_ISDIR(entries[INDEX_ANCESTOR]->mode) ^ S_ISDIR(entries[i]->mode)) |
| tree_entries[i]->status |= GIT_DELTA_TYPECHANGE; |
| else if(S_ISLNK(entries[INDEX_ANCESTOR]->mode) ^ S_ISLNK(entries[i]->mode)) |
| tree_entries[i]->status |= GIT_DELTA_TYPECHANGE; |
| else if (git_oid_cmp(&entries[INDEX_ANCESTOR]->oid, &entries[i]->oid) || |
| entries[INDEX_ANCESTOR]->mode != entries[i]->mode) |
| tree_entries[i]->status |= GIT_DELTA_MODIFIED; |
| } |
| |
| return delta_tree; |
| } |
| |
| static int diff_tree__create_delta(const git_index_entry **tree_items, void *payload) |
| { |
| struct diff_tree_threeway_data *threeway_data = payload; |
| git_diff_tree_delta *delta_tree; |
| |
| assert(tree_items && threeway_data); |
| |
| if ((delta_tree = diff_tree__delta_from_entries(threeway_data, tree_items)) == NULL || |
| diff_tree__compute_conflict(delta_tree) < 0 || |
| diff_tree__compute_df_conflict(threeway_data, delta_tree) < 0 || |
| git_vector_insert(&threeway_data->diff_tree->deltas, delta_tree) < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int git_diff_tree(git_diff_tree_list **out, |
| git_repository *repo, |
| const git_tree *ancestor_tree, |
| const git_tree *our_tree, |
| const git_tree *their_tree, |
| uint32_t flags) |
| { |
| struct diff_tree_threeway_data threeway_data; |
| git_diff_tree_list *diff_tree; |
| git_tree const *trees[3]; |
| int error = 0; |
| |
| assert(out && repo && ancestor_tree && our_tree && their_tree); |
| |
| *out = NULL; |
| |
| diff_tree = diff_tree__list_alloc(repo); |
| GITERR_CHECK_ALLOC(diff_tree); |
| |
| memset(&threeway_data, 0x0, sizeof(struct diff_tree_threeway_data)); |
| threeway_data.diff_tree = diff_tree; |
| |
| trees[INDEX_ANCESTOR] = ancestor_tree; |
| trees[INDEX_OURS] = our_tree; |
| trees[INDEX_THEIRS] = their_tree; |
| |
| if ((error = git_diff_tree_many(repo, trees, 3, flags, diff_tree__create_delta, &threeway_data)) < 0) |
| git_diff_tree_list_free(diff_tree); |
| |
| if (error >= 0) |
| *out = diff_tree; |
| |
| return error; |
| } |
| |
| int git_diff_tree_foreach( |
| git_diff_tree_list *diff_tree, |
| git_diff_tree_delta_cb callback, |
| void *payload) |
| { |
| git_diff_tree_delta *delta; |
| size_t i; |
| int error = 0; |
| |
| assert (diff_tree && callback); |
| |
| git_vector_foreach(&diff_tree->deltas, i, delta) { |
| if (callback(delta, payload) != 0) { |
| error = GIT_EUSER; |
| break; |
| } |
| } |
| |
| return error; |
| } |
| |
| void git_diff_tree_list_free(git_diff_tree_list *diff_tree) |
| { |
| if (!diff_tree) |
| return; |
| |
| git_vector_free(&diff_tree->deltas); |
| git_pool_clear(&diff_tree->pool); |
| git__free(diff_tree); |
| } |