diff --git a/src/stash.c b/src/stash.c index 14cbeb642..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, @@ -756,6 +781,47 @@ done: 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, @@ -769,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; @@ -813,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); @@ -874,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 db145d58d..901667df3 100644 --- a/tests/stash/apply.c +++ b/tests/stash/apply.c @@ -17,6 +17,7 @@ void test_stash_apply__initialize(void) 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,9 +29,14 @@ void test_stash_apply__initialize(void) 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); @@ -38,6 +44,7 @@ void test_stash_apply__initialize(void) 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)); @@ -47,6 +54,7 @@ void test_stash_apply__initialize(void) 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) @@ -62,6 +70,8 @@ void test_stash_apply__cleanup(void) 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); @@ -69,11 +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_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; @@ -86,6 +127,12 @@ void test_stash_apply__with_reinstate_index(void) 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) @@ -312,7 +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_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);