diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/iterator.c | 88 | ||||
-rw-r--r-- | src/iterator.h | 15 | ||||
-rw-r--r-- | src/merge.c | 116 | ||||
-rw-r--r-- | src/stash.c | 104 |
4 files changed, 237 insertions, 86 deletions
diff --git a/src/iterator.c b/src/iterator.c index d5f7eec34..45eba3955 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1843,3 +1843,91 @@ int git_iterator_advance_over_with_status( return error; } +int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data) +{ + const git_index_entry **iterator_item; /* next in each iterator */ + const git_index_entry **cur_items; /* current path in each iter */ + const git_index_entry *first_match; + int cur_item_modified; + size_t i, j; + int error = 0; + + iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); + cur_items = git__calloc(cnt, sizeof(git_index_entry *)); + + GITERR_CHECK_ALLOC(iterator_item); + GITERR_CHECK_ALLOC(cur_items); + + /* Set up the iterators */ + for (i = 0; i < cnt; i++) { + error = git_iterator_current(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + + while (true) { + for (i = 0; i < cnt; i++) + cur_items[i] = NULL; + + first_match = NULL; + cur_item_modified = 0; + + /* Find the next path(s) to consume from each iterator */ + for (i = 0; i < cnt; i++) { + if (iterator_item[i] == NULL) + continue; + + if (first_match == NULL) { + first_match = iterator_item[i]; + cur_items[i] = iterator_item[i]; + } else { + int path_diff = git_index_entry_cmp(iterator_item[i], first_match); + + if (path_diff < 0) { + /* Found an index entry that sorts before the one we're + * looking at. Forget that we've seen the other and + * look at the other iterators for this path. + */ + for (j = 0; j < i; j++) + cur_items[j] = NULL; + + first_match = iterator_item[i]; + cur_items[i] = iterator_item[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] = iterator_item[i]; + } + } + } + + if (first_match == NULL) + break; + + if ((error = cb(cur_items, data)) != 0) + goto done; + + /* Advance each iterator that participated */ + for (i = 0; i < cnt; i++) { + if (cur_items[i] == NULL) + continue; + + error = git_iterator_advance(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + } + +done: + if (error == GIT_ITEROVER) + error = 0; + + return error; +} diff --git a/src/iterator.h b/src/iterator.h index 57f82416a..893e5db50 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -294,4 +294,19 @@ extern int git_iterator_advance_over_with_status( */ extern int git_iterator_index(git_index **out, git_iterator *iter); +typedef int (*git_iterator_walk_cb)( + const git_index_entry **entries, + void *data); + +/** + * Walk the given iterators in lock-step. The given callback will be + * called for each unique path, with the index entry in each iterator + * (or NULL if the given iterator does not contain that path). + */ +extern int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data); + #endif diff --git a/src/merge.c b/src/merge.c index 517d317de..9d6252ea8 100644 --- a/src/merge.c +++ b/src/merge.c @@ -1449,6 +1449,34 @@ static int merge_diff_list_insert_unmodified( 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, @@ -1456,93 +1484,9 @@ int git_merge_diff_list__find_differences( git_iterator *their_iter) { git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; - const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3]; - git_vector_cmp entry_compare = git_index_entry_cmp; - struct merge_diff_df_data df_data = {0}; - int cur_item_modified; - size_t i, j; - int error = 0; - - assert(diff_list && (our_iter || their_iter)); - - /* Set up the iterators */ - for (i = 0; i < 3; i++) { - error = git_iterator_current(&items[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - - while (true) { - for (i = 0; i < 3; i++) - cur_items[i] = NULL; - - best_cur_item = NULL; - cur_item_modified = 0; - - /* Find the next path(s) to consume from each iterator */ - for (i = 0; i < 3; 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. - */ - for (j = 0; j < i; j++) - cur_items[j] = NULL; - - 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) - cur_item_modified = index_entry_cmp(best_cur_item, items[i]); - } - } - } - - if (best_cur_item == NULL) - break; - - if (cur_item_modified) - error = merge_diff_list_insert_conflict(diff_list, &df_data, cur_items); - else - error = merge_diff_list_insert_unmodified(diff_list, cur_items); - if (error < 0) - goto done; + struct merge_diff_find_data find_data = { diff_list }; - /* Advance each iterator that participated */ - for (i = 0; i < 3; i++) { - if (cur_items[i] == NULL) - continue; - - error = git_iterator_advance(&items[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - } - -done: - if (error == GIT_ITEROVER) - error = 0; - - return error; + return git_iterator_walk(iterators, 3, queue_difference, &find_data); } git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) diff --git a/src/stash.c b/src/stash.c index 9010c476d..59ecd3b07 100644 --- a/src/stash.c +++ b/src/stash.c @@ -671,6 +671,31 @@ cleanup: return error; } +static int merge_indexes( + git_index **out, + git_repository *repo, + git_tree *ancestor_tree, + git_index *ours_index, + git_index *theirs_index) +{ + git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; + const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE; + int error; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 || + (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 || + (error = git_iterator_for_index(&theirs, theirs_index, flags, NULL, NULL)) < 0) + goto done; + + error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); + +done: + git_iterator_free(ancestor); + git_iterator_free(ours); + git_iterator_free(theirs); + return error; +} + static int merge_index_and_tree( git_index **out, git_repository *repo, @@ -733,6 +758,70 @@ int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int ver } \ } while(false); +static int ensure_clean_index(git_repository *repo, git_index *index) +{ + git_tree *head_tree = NULL; + git_diff *index_diff = NULL; + int error = 0; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_diff_tree_to_index( + &index_diff, repo, head_tree, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(index_diff) > 0) { + giterr_set(GITERR_STASH, "%d uncommitted changes exist in the index", + git_diff_num_deltas(index_diff)); + error = GIT_EUNCOMMITTED; + } + +done: + git_diff_free(index_diff); + git_tree_free(head_tree); + return error; +} + +static int stage_new_file(const git_index_entry **entries, void *data) +{ + git_index *index = data; + + if(entries[0] == NULL) + return git_index_add(index, entries[1]); + else + return git_index_add(index, entries[0]); +} + +static int stage_new_files( + git_index **out, + git_repository *repo, + git_tree *parent_tree, + git_tree *tree) +{ + git_iterator *iterators[2] = { NULL, NULL }; + git_index *index = NULL; + int error; + + if ((error = git_index_new(&index)) < 0 || + (error = git_iterator_for_tree(&iterators[0], parent_tree, + GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + (error = git_iterator_for_tree(&iterators[1], tree, + GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + goto done; + + error = git_iterator_walk(iterators, 2, stage_new_file, index); + +done: + if (error < 0) + git_index_free(index); + else + *out = index; + + git_iterator_free(iterators[0]); + git_iterator_free(iterators[1]); + + return error; +} + int git_stash_apply( git_repository *repo, size_t index, @@ -746,6 +835,7 @@ int git_stash_apply( git_tree *index_tree = NULL; git_tree *index_parent_tree = NULL; git_tree *untracked_tree = NULL; + git_index *stash_adds = NULL; git_index *repo_index = NULL; git_index *unstashed_index = NULL; git_index *modified_index = NULL; @@ -775,6 +865,9 @@ int git_stash_apply( NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); + if ((error = ensure_clean_index(repo, repo_index)) < 0) + goto cleanup; + /* Restore index if required */ if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { @@ -787,6 +880,16 @@ int git_stash_apply( error = GIT_ECONFLICT; goto cleanup; } + + /* Otherwise, stage any new files in the stash tree. (Note: their + * previously unstaged contents are staged, not the previously staged.) + */ + } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { + if ((error = stage_new_files( + &stash_adds, repo, stash_parent_tree, stash_tree)) < 0 || + (error = merge_indexes( + &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) + goto cleanup; } NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); @@ -848,6 +951,7 @@ cleanup: git_index_free(untracked_index); git_index_free(modified_index); git_index_free(unstashed_index); + git_index_free(stash_adds); git_index_free(repo_index); git_tree_free(untracked_tree); git_tree_free(index_parent_tree); |