diff options
Diffstat (limited to 'src/diff_tree.c')
-rw-r--r-- | src/diff_tree.c | 215 |
1 files changed, 161 insertions, 54 deletions
diff --git a/src/diff_tree.c b/src/diff_tree.c index ec20d0a84..297ac09dd 100644 --- a/src/diff_tree.c +++ b/src/diff_tree.c @@ -23,6 +23,10 @@ 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); @@ -39,9 +43,9 @@ int git_diff_tree_many( void *payload) { git_iterator **iterators; - git_index_entry **items = NULL, *best_next_item, **next_items; + git_index_entry **items = NULL, *best_cur_item, **cur_items; git_vector_cmp entry_compare = git_index_entry__cmp; - int next_item_modified; + int cur_item_modified; size_t i; int error = 0; @@ -53,8 +57,8 @@ int git_diff_tree_many( items = git__calloc(trees_length, sizeof(git_index_entry *)); GITERR_CHECK_ALLOC(items); - next_items = git__calloc(trees_length, sizeof(git_index_entry *)); - GITERR_CHECK_ALLOC(next_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) @@ -69,50 +73,49 @@ int git_diff_tree_many( } while (true) { - memset(next_items, 0x0, sizeof(git_index_entry *) * trees_length); - best_next_item = NULL; - next_item_modified = 0; + 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) { - next_item_modified = 1; + cur_item_modified = 1; continue; } - if (best_next_item == NULL) { - best_next_item = items[i]; - next_items[i] = items[i]; + if (best_cur_item == NULL) { + best_cur_item = items[i]; + cur_items[i] = items[i]; } else { - int diff = entry_compare(items[i], best_next_item); - - if (diff < 0) { + 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(next_items, 0x0, - sizeof(git_index_entry *) * trees_length); - next_item_modified = 1; - best_next_item = items[i]; - next_items[i] = items[i]; - } else if (diff > 0) { + 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 */ - next_item_modified = 1; - } else if (diff == 0) { - next_items[i] = items[i]; + cur_item_modified = 1; + } else if (path_diff == 0) { + cur_items[i] = items[i]; - if (!next_item_modified && !(flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) - next_item_modified = index_entry_cmp(best_next_item, 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_next_item == NULL) + if (best_cur_item == NULL) break; - if (next_item_modified || (flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) { - if (callback((const git_index_entry **)next_items, payload)) { + if (cur_item_modified || (flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) { + if (callback((const git_index_entry **)cur_items, payload)) { error = GIT_EUSER; goto done; } @@ -120,7 +123,7 @@ int git_diff_tree_many( /* Advance each iterator that participated */ for (i = 0; i < trees_length; i++) { - if (next_items[i] != NULL && + if (cur_items[i] != NULL && (error = git_iterator_advance(iterators[i], (const git_index_entry **)&items[i])) < 0) goto done; @@ -133,7 +136,7 @@ done: git__free(iterators); git__free(items); - git__free(next_items); + git__free(cur_items); return error; } @@ -146,7 +149,15 @@ typedef enum { INDEX_ANCESTOR = 0, INDEX_OURS = 1, INDEX_THEIRS = 2 -} threeway_index; +} 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) { @@ -165,15 +176,103 @@ static git_diff_tree_list *diff_tree__list_alloc(git_repository *repo) 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( - git_diff_tree_list *diff_tree, + 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(&diff_tree->pool, sizeof(git_diff_tree_delta))) == NULL) + 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; @@ -183,8 +282,8 @@ static git_diff_tree_delta *diff_tree__delta_from_entries( for (i = 0; i < 3; i++) { if (entries[i] == NULL) continue; - - if ((tree_entries[i]->file.path = git_pool_strdup(&diff_tree->pool, entries[i]->path)) == NULL) + + 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); @@ -196,16 +295,18 @@ static git_diff_tree_delta *diff_tree__delta_from_entries( for (i = 1; i < 3; i++) { if (entries[INDEX_ANCESTOR] == NULL && entries[i] == NULL) continue; - else if (entries[INDEX_ANCESTOR] == NULL && entries[i] != NULL) - tree_entries[i]->status = GIT_DELTA_ADDED; + + 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[i]->mode) ^ S_ISDIR(entries[i]->mode) || - S_ISLNK(entries[i]->mode) ^ S_ISLNK(entries[i]->mode)) - tree_entries[i]->status = GIT_DELTA_TYPECHANGE; + 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; + entries[INDEX_ANCESTOR]->mode != entries[i]->mode) + tree_entries[i]->status |= GIT_DELTA_MODIFIED; } return delta_tree; @@ -213,13 +314,15 @@ static git_diff_tree_delta *diff_tree__delta_from_entries( static int diff_tree__create_delta(const git_index_entry **tree_items, void *payload) { - git_diff_tree_list *diff_tree = payload; - git_diff_tree_delta *delta; + struct diff_tree_threeway_data *threeway_data = payload; + git_diff_tree_delta *delta_tree; - assert(tree_items && diff_tree); + assert(tree_items && threeway_data); - if ((delta = diff_tree__delta_from_entries(diff_tree, tree_items)) == NULL || - git_vector_insert(&diff_tree->deltas, delta) < 0) + 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; @@ -232,6 +335,7 @@ int git_diff_tree(git_diff_tree_list **out, 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; @@ -239,21 +343,24 @@ int git_diff_tree(git_diff_tree_list **out, 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, diff_tree)) < 0) { + if ((error = git_diff_tree_many(repo, trees, 3, flags, diff_tree__create_delta, &threeway_data)) < 0) git_diff_tree_list_free(diff_tree); - return error; - } - *out = diff_tree; - return 0; + if (error >= 0) + *out = diff_tree; + + return error; } int git_diff_tree_foreach( |