diff options
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-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 | ||||
-rw-r--r-- | tests/stash/apply.c | 94 |
6 files changed, 323 insertions, 95 deletions
diff --git a/include/git2/errors.h b/include/git2/errors.h index 5ff0f35b5..a81aa05d9 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -47,6 +47,7 @@ typedef enum { GIT_EPEEL = -19, /**< The requested peel operation is not possible */ GIT_EEOF = -20, /**< Unexpected EOF */ GIT_EINVALID = -21, /**< Invalid operation or input */ + GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */ GIT_PASSTHROUGH = -30, /**< Internal only */ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ 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); diff --git a/tests/stash/apply.c b/tests/stash/apply.c index c22a31bef..901667df3 100644 --- a/tests/stash/apply.c +++ b/tests/stash/apply.c @@ -10,14 +10,14 @@ void test_stash_apply__initialize(void) { git_oid oid; - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - - cl_git_pass(git_repository_init(&repo, "stash", 0)); + repo = cl_git_sandbox_init_new("stash"); cl_git_pass(git_repository_index(&repo_index, repo)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ cl_git_mkfile("stash/what", "hello\n"); cl_git_mkfile("stash/how", "small\n"); cl_git_mkfile("stash/who", "world\n"); + cl_git_mkfile("stash/where", "meh\n"); cl_git_pass(git_index_add_bypath(repo_index, "what")); cl_git_pass(git_index_add_bypath(repo_index, "how")); @@ -28,14 +28,23 @@ void test_stash_apply__initialize(void) cl_git_rewritefile("stash/what", "goodbye\n"); cl_git_rewritefile("stash/who", "funky world\n"); cl_git_mkfile("stash/when", "tomorrow\n"); + cl_git_mkfile("stash/why", "would anybody use stash?\n"); + cl_git_mkfile("stash/where", "????\n"); cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_add_bypath(repo_index, "why")); + cl_git_pass(git_index_add_bypath(repo_index, "where")); + git_index_write(repo_index); + + cl_git_rewritefile("stash/where", "....\n"); /* Pre-stash state */ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_MODIFIED); cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); @@ -44,6 +53,8 @@ void test_stash_apply__initialize(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); + assert_status(repo, "where", GIT_ENOTFOUND); } void test_stash_apply__cleanup(void) @@ -54,15 +65,13 @@ void test_stash_apply__cleanup(void) git_index_free(repo_index); repo_index = NULL; - git_repository_free(repo); - repo = NULL; - - cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party"); + cl_git_sandbox_cleanup(); } void test_stash_apply__with_default(void) { + git_buf where = GIT_BUF_INIT; + cl_git_pass(git_stash_apply(repo, 0, NULL)); cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); @@ -70,10 +79,42 @@ void test_stash_apply__with_default(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_futils_readbuffer(&where, "stash/where")); + cl_assert_equal_s("....\n", where.ptr); + + git_buf_free(&where); +} + +void test_stash_apply__with_existing_file(void) +{ + cl_git_mkfile("stash/where", "oops!\n"); + cl_git_fail(git_stash_apply(repo, 0, NULL)); +} + +void test_stash_apply__merges_new_file(void) +{ + git_index_entry *ancestor, *our, *their; + + cl_git_mkfile("stash/where", "committed before stash\n"); + cl_git_pass(git_index_add_bypath(repo_index, "where")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(1, git_index_has_conflicts(repo_index)); + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "where")); /* unmerged */ + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); } void test_stash_apply__with_reinstate_index(void) { + git_buf where = GIT_BUF_INIT; git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; @@ -85,6 +126,13 @@ void test_stash_apply__with_reinstate_index(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED); + + cl_git_pass(git_futils_readbuffer(&where, "stash/where")); + cl_assert_equal_s("....\n", where.ptr); + + git_buf_free(&where); } void test_stash_apply__conflict_index_with_default(void) @@ -96,6 +144,7 @@ void test_stash_apply__conflict_index_with_default(void) cl_git_rewritefile("stash/who", "nothing\n"); cl_git_pass(git_index_add_bypath(repo_index, "who")); cl_git_pass(git_index_write(repo_index)); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); cl_git_pass(git_stash_apply(repo, 0, NULL)); @@ -104,6 +153,7 @@ void test_stash_apply__conflict_index_with_default(void) assert_status(repo, "how", GIT_STATUS_CURRENT); cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "who")); /* unmerged */ assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); } void test_stash_apply__conflict_index_with_reinstate_index(void) @@ -115,14 +165,16 @@ void test_stash_apply__conflict_index_with_reinstate_index(void) cl_git_rewritefile("stash/who", "nothing\n"); cl_git_pass(git_index_add_bypath(repo_index, "who")); cl_git_pass(git_index_write(repo_index)); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); assert_status(repo, "what", GIT_STATUS_CURRENT); assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__conflict_untracked_with_default(void) @@ -138,6 +190,7 @@ void test_stash_apply__conflict_untracked_with_default(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__conflict_untracked_with_reinstate_index(void) @@ -155,6 +208,7 @@ void test_stash_apply__conflict_untracked_with_reinstate_index(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__conflict_workdir_with_default(void) @@ -168,6 +222,7 @@ void test_stash_apply__conflict_workdir_with_default(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__conflict_workdir_with_reinstate_index(void) @@ -185,6 +240,7 @@ void test_stash_apply__conflict_workdir_with_reinstate_index(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_CURRENT); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__conflict_commit_with_default(void) @@ -204,6 +260,7 @@ void test_stash_apply__conflict_commit_with_default(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); } void test_stash_apply__conflict_commit_with_reinstate_index(void) @@ -226,6 +283,23 @@ void test_stash_apply__conflict_commit_with_reinstate_index(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__fails_with_uncommitted_changes_in_index(void) +{ + cl_git_rewritefile("stash/who", "nothing\n"); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_write(repo_index)); + + cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_EUNCOMMITTED); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); } void test_stash_apply__pop(void) @@ -285,6 +359,8 @@ void test_stash_apply__executes_notify_cb(void) assert_status(repo, "how", GIT_STATUS_CURRENT); assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW); cl_assert_equal_b(true, seen_paths.what); cl_assert_equal_b(false, seen_paths.how); |