From bf8dd3f53d25b7f6032b971d3d73931da05982cb Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Latour Date: Fri, 14 Nov 2014 12:32:47 +0900 Subject: [PATCH] Added git_stash_apply() and git_stash_pop() APIs --- AUTHORS | 1 + include/git2/stash.h | 68 +++++++- src/stash.c | 371 +++++++++++++++++++++++++++++++++++++++++++ tests/stash/apply.c | 215 +++++++++++++++++++++++++ 4 files changed, 651 insertions(+), 4 deletions(-) create mode 100644 tests/stash/apply.c diff --git a/AUTHORS b/AUTHORS index 2eaec0ff6..61e2113ec 100644 --- a/AUTHORS +++ b/AUTHORS @@ -49,6 +49,7 @@ Microsoft Corporation Olivier Ramonat Peter Drahoš Pierre Habouzit +Pierre-Olivier Latour Przemyslaw Pawelczyk Ramsay Jones Robert G. Jakabosky diff --git a/include/git2/stash.h b/include/git2/stash.h index 280c647e8..f69ba307a 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -70,6 +70,47 @@ GIT_EXTERN(int) git_stash_save( const char *message, unsigned int flags); +typedef enum { + GIT_APPLY_DEFAULT = 0, + + /* Try to reinstate not only the working tree's changes, + * but also the index's ones. + */ + GIT_APPLY_REINSTATE_INDEX = (1 << 0), +} git_apply_flags; + +/** + * Apply a single stashed state from the stash list. + * + * If any untracked or ignored file saved in the stash already exist in the + * workdir, the function will return GIT_EEXISTS and both the workdir and index + * will be left untouched. + * + * If local changes in the workdir would be overwritten when applying + * modifications saved in the stash, the function will return GIT_EMERGECONFLICT + * and the index will be left untouched. The workdir files will be left + * unmodified as well but restored untracked or ignored files that were saved + * in the stash will be left around in the workdir. + * + * If passing the GIT_APPLY_REINSTATE_INDEX flag and there would be conflicts + * when reinstating the index, the function will return GIT_EUNMERGED and both + * the workdir and index will be left untouched. + * + * @param repo The owning repository. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @param flags Flags to control the applying process. (see GIT_APPLY_* above) + * + * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given + * index, or error code. (see details above) + */ +GIT_EXTERN(int) git_stash_apply( + git_repository *repo, + size_t index, + unsigned int flags); + /** * This is a callback function you can provide to iterate over all the * stashed states that will be invoked per entry. @@ -79,7 +120,7 @@ GIT_EXTERN(int) git_stash_save( * @param message The stash message. * @param stash_id The commit oid of the stashed state. * @param payload Extra parameter to callback function. - * @return 0 to continue iterating or non-zero to stop + * @return 0 to continue iterating or non-zero to stop. */ typedef int (*git_stash_cb)( size_t index, @@ -99,7 +140,7 @@ typedef int (*git_stash_cb)( * * @param payload Extra parameter to callback function. * - * @return 0 on success, non-zero callback return value, or error code + * @return 0 on success, non-zero callback return value, or error code. */ GIT_EXTERN(int) git_stash_foreach( git_repository *repo, @@ -114,13 +155,32 @@ GIT_EXTERN(int) git_stash_foreach( * @param index The position within the stash list. 0 points to the * most recent stashed state. * - * @return 0 on success, or error code + * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given + * index, or error code. */ - GIT_EXTERN(int) git_stash_drop( git_repository *repo, size_t index); +/** + * Apply a single stashed state from the stash list and remove it from the list + * if successful. + * + * @param repo The owning repository. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @param flags Flags to control the applying process. (see GIT_APPLY_* above) + * + * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given + * index, or error code. (see git_stash_apply() above for details) +*/ +GIT_EXTERN(int) git_stash_pop( + git_repository *repo, + size_t index, + unsigned int flags); + /** @} */ GIT_END_DECL #endif diff --git a/src/stash.c b/src/stash.c index 8aa48cafe..dab32552b 100644 --- a/src/stash.c +++ b/src/stash.c @@ -16,6 +16,7 @@ #include "git2/checkout.h" #include "git2/index.h" #include "git2/transaction.h" +#include "git2/merge.h" #include "signature.h" static int create_error(int error, const char *msg) @@ -553,6 +554,363 @@ cleanup: return error; } +static int retrieve_stash_commit( + git_commit **commit, + git_repository *repo, + size_t index) +{ + git_reference *stash = NULL; + git_reflog *reflog = NULL; + int error; + size_t max; + const git_reflog_entry *entry; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + if (index > max - 1) { + error = GIT_ENOTFOUND; + giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index); + goto cleanup; + } + + entry = git_reflog_entry_byindex(reflog, index); + if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) + goto cleanup; + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +static int retrieve_stash_trees( + git_tree **out_stash_tree, + git_tree **out_base_tree, + git_tree **out_index_tree, + git_tree **out_index_parent_tree, + git_tree **out_untracked_tree, + git_commit *stash_commit) +{ + git_tree *stash_tree = NULL; + git_commit *base_commit = NULL; + git_tree *base_tree = NULL; + git_commit *index_commit = NULL; + git_tree *index_tree = NULL; + git_commit *index_parent_commit = NULL; + git_tree *index_parent_tree = NULL; + git_commit *untracked_commit = NULL; + git_tree *untracked_tree = NULL; + int error; + + if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&base_tree, base_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_tree, index_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) + goto cleanup; + + if (git_commit_parentcount(stash_commit) == 3) { + if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) + goto cleanup; + if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) + goto cleanup; + } + + *out_stash_tree = stash_tree; + *out_base_tree = base_tree; + *out_index_tree = index_tree; + *out_index_parent_tree = index_parent_tree; + *out_untracked_tree = untracked_tree; + +cleanup: + git_commit_free(untracked_commit); + git_commit_free(index_parent_commit); + git_commit_free(index_commit); + git_commit_free(base_commit); + if (error < 0) { + git_tree_free(stash_tree); + git_tree_free(base_tree); + git_tree_free(index_tree); + git_tree_free(index_parent_tree); + git_tree_free(untracked_tree); + } + return error; +} + +static int apply_index( + git_tree **unstashed_tree, + git_repository *repo, + git_tree *start_index_tree, + git_tree *index_parent_tree, + git_tree *index_tree) +{ + git_index* unstashed_index = NULL; + git_merge_options options = GIT_MERGE_OPTIONS_INIT; + int error; + git_oid oid; + + if ((error = git_merge_trees( + &unstashed_index, repo, index_parent_tree, + start_index_tree, index_tree, &options)) < 0) + goto cleanup; + + if ((error = git_index_write_tree_to(&oid, unstashed_index, repo)) < 0) + goto cleanup; + + if ((error = git_tree_lookup(unstashed_tree, repo, &oid)) < 0) + goto cleanup; + +cleanup: + git_index_free(unstashed_index); + return error; +} + +static int apply_untracked( + git_repository *repo, + git_tree *untracked_tree) +{ + git_checkout_options options = GIT_CHECKOUT_OPTIONS_INIT; + size_t i, count; + unsigned int status; + int error; + + for (i = 0, count = git_tree_entrycount(untracked_tree); i < count; ++i) { + const git_tree_entry *entry = git_tree_entry_byindex(untracked_tree, i); + const char* path = git_tree_entry_name(entry); + error = git_status_file(&status, repo, path); + if (!error) { + giterr_set(GITERR_STASH, "Untracked or ignored file '%s' already exists", path); + return GIT_EEXISTS; + } + } + + /* + The untracked tree only contains the untracked / ignores files so checking + it out would remove all other files in the workdir. Since git_checkout_tree() + does not have a mode to leave removed files alone, we emulate it by checking + out files from the untracked tree one by one. + */ + + options.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_UPDATE_INDEX; + options.paths.count = 1; + for (i = 0, count = git_tree_entrycount(untracked_tree); i < count; ++i) { + const git_tree_entry *entry = git_tree_entry_byindex(untracked_tree, i); + + const char* name = git_tree_entry_name(entry); + options.paths.strings = (char**)&name; + if ((error = git_checkout_tree( + repo, (git_object*)untracked_tree, &options)) < 0) + return error; + } + + return 0; +} + +static int checkout_modified_notify_callback( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + unsigned int status; + int error; + + GIT_UNUSED(why); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + + if ((error = git_status_file(&status, payload, path)) < 0) + return error; + + if (status & GIT_STATUS_WT_MODIFIED) { + giterr_set(GITERR_STASH, "Local changes to '%s' would be overwritten", path); + return GIT_EMERGECONFLICT; + } + + return 0; +} + +static int apply_modified( + int *has_conflicts, + git_repository *repo, + git_tree *base_tree, + git_tree *start_index_tree, + git_tree *stash_tree, + unsigned int flags) +{ + git_index *index = NULL; + git_merge_options merge_options = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + int error; + + if ((error = git_merge_trees( + &index, repo, base_tree, + start_index_tree, stash_tree, &merge_options)) < 0) + goto cleanup; + + checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; + if ((flags & GIT_APPLY_REINSTATE_INDEX) && !git_index_has_conflicts(index)) { + /* No need to update the index if it will be overridden later on */ + checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + } + checkout_options.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + checkout_options.notify_cb = checkout_modified_notify_callback; + checkout_options.notify_payload = repo; + checkout_options.our_label = "Updated upstream"; + checkout_options.their_label = "Stashed changes"; + if ((error = git_checkout_index(repo, index, &checkout_options)) < 0) + goto cleanup; + + *has_conflicts = git_index_has_conflicts(index); + +cleanup: + git_index_free(index); + return error; +} + +static int unstage_modified_files( + git_repository *repo, + git_index *repo_index, + git_tree *unstashed_tree, + git_tree *start_index_tree) +{ + git_diff *diff = NULL; + git_diff_options options = GIT_DIFF_OPTIONS_INIT; + size_t i, count; + int error; + + if (unstashed_tree) { + if ((error = git_index_read_tree(repo_index, unstashed_tree)) < 0) + goto cleanup; + } else { + options.flags = GIT_DIFF_FORCE_BINARY; + if ((error = git_diff_tree_to_index(&diff, repo, start_index_tree, + repo_index, &options)) < 0) + goto cleanup; + + /* + This behavior is not 100% similar to "git stash apply" as the latter uses + "git-read-tree --reset {treeish}" which preserves the stat()s from the + index instead of replacing them with the tree ones for identical files. + */ + + if ((error = git_index_read_tree(repo_index, start_index_tree)) < 0) + goto cleanup; + + for (i = 0, count = git_diff_num_deltas(diff); i < count; ++i) { + const git_diff_delta* delta = git_diff_get_delta(diff, i); + if (delta->status == GIT_DELTA_ADDED) { + if ((error = git_index_add_bypath( + repo_index, delta->new_file.path)) < 0) + goto cleanup; + } + } + } + +cleanup: + git_diff_free(diff); + return error; +} + +int git_stash_apply( + git_repository *repo, + size_t index, + unsigned int flags) +{ + git_commit *stash_commit = NULL; + git_tree *stash_tree = NULL; + git_tree *base_tree = NULL; + git_tree *index_tree = NULL; + git_tree *index_parent_tree = NULL; + git_tree *untracked_tree = NULL; + git_index *repo_index = NULL; + git_tree *start_index_tree = NULL; + git_tree *unstashed_tree = NULL; + int has_conflicts; + int error; + + /* Retrieve commit corresponding to the given stash */ + if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) + goto cleanup; + + /* Retrieve all trees in the stash */ + if ((error = retrieve_stash_trees( + &stash_tree, &base_tree, &index_tree, + &index_parent_tree, &untracked_tree, stash_commit)) < 0) + goto cleanup; + + /* Load repo index */ + if ((error = git_repository_index(&repo_index, repo)) < 0) + goto cleanup; + + /* Create tree from index */ + if ((error = build_tree_from_index(&start_index_tree, repo_index)) < 0) + goto cleanup; + + /* Restore index if required */ + if ((flags & GIT_APPLY_REINSTATE_INDEX) && + git_oid_cmp(git_tree_id(base_tree), git_tree_id(index_tree)) && + git_oid_cmp(git_tree_id(start_index_tree), git_tree_id(index_tree))) { + + if ((error = apply_index( + &unstashed_tree, repo, start_index_tree, + index_parent_tree, index_tree)) < 0) + goto cleanup; + } + + /* If applicable, restore untracked / ignored files in workdir */ + if (untracked_tree) { + if ((error = apply_untracked(repo, untracked_tree)) < 0) + goto cleanup; + } + + /* Restore modified files in workdir */ + if ((error = apply_modified( + &has_conflicts, repo, base_tree, start_index_tree, + stash_tree, flags)) < 0) + goto cleanup; + + /* Unstage modified files from index unless there were merge conflicts */ + if (!has_conflicts && (error = unstage_modified_files( + repo, repo_index, unstashed_tree, start_index_tree)) < 0) + goto cleanup; + + /* Write updated index */ + if ((error = git_index_write(repo_index)) < 0) + goto cleanup; + +cleanup: + git_tree_free(unstashed_tree); + git_tree_free(start_index_tree); + git_index_free(repo_index); + git_tree_free(untracked_tree); + git_tree_free(index_parent_tree); + git_tree_free(index_tree); + git_tree_free(base_tree); + git_tree_free(stash_tree); + git_commit_free(stash_commit); + return error; +} + int git_stash_foreach( git_repository *repo, git_stash_cb callback, @@ -651,3 +1009,16 @@ cleanup: git_reflog_free(reflog); return error; } + +int git_stash_pop( + git_repository *repo, + size_t index, + unsigned int flags) +{ + int error; + + if ((error = git_stash_apply(repo, index, flags)) < 0) + return error; + + return git_stash_drop(repo, index); +} diff --git a/tests/stash/apply.c b/tests/stash/apply.c new file mode 100644 index 000000000..5f3cc9a16 --- /dev/null +++ b/tests/stash/apply.c @@ -0,0 +1,215 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +static git_signature *signature; +static git_repository *repo; +static git_index *repo_index; + +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)); + cl_git_pass(git_repository_index(&repo_index, repo)); + + cl_git_mkfile("stash/what", "hello\n"); + cl_git_mkfile("stash/how", "small\n"); + cl_git_mkfile("stash/who", "world\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_git_pass(git_index_add_bypath(repo_index, "how")); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); + + cl_git_rewritefile("stash/what", "goodbye\n"); + cl_git_rewritefile("stash/who", "funky world\n"); + cl_git_mkfile("stash/when", "tomorrow\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "who")); + + /* 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); + + cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + /* Post-stash state */ + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_ENOTFOUND); +} + +void test_stash_apply__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; + + 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"); +} + +void test_stash_apply__with_default(void) +{ + cl_git_pass(git_stash_apply(repo, 0, GIT_APPLY_DEFAULT)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__with_reinstate_index(void) +{ + cl_git_pass(git_stash_apply(repo, 0, GIT_APPLY_REINSTATE_INDEX)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + 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); +} + +void test_stash_apply__conflict_index_with_default(void) +{ + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + 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_pass(git_stash_apply(repo, 0, GIT_APPLY_DEFAULT)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + 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); +} + +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_git_fail_with(git_stash_apply(repo, 0, GIT_APPLY_REINSTATE_INDEX), GIT_EUNMERGED); + + 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); +} + +void test_stash_apply__conflict_untracked_with_default(void) +{ + cl_git_mkfile("stash/when", "nothing\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, GIT_APPLY_DEFAULT), GIT_EEXISTS); + + 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_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__conflict_untracked_with_reinstate_index(void) +{ + cl_git_mkfile("stash/when", "nothing\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, GIT_APPLY_REINSTATE_INDEX), GIT_EEXISTS); + + 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_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__conflict_workdir_with_default(void) +{ + cl_git_rewritefile("stash/what", "ciao\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, GIT_APPLY_DEFAULT), GIT_EMERGECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__conflict_workdir_with_reinstate_index(void) +{ + cl_git_rewritefile("stash/what", "ciao\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, GIT_APPLY_REINSTATE_INDEX), GIT_EMERGECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__conflict_commit_with_default(void) +{ + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + cl_git_rewritefile("stash/what", "ciao\n"); + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, GIT_APPLY_DEFAULT)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__conflict_commit_with_reinstate_index(void) +{ + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + cl_git_rewritefile("stash/what", "ciao\n"); + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, GIT_APPLY_REINSTATE_INDEX)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); +} + +void test_stash_apply__pop(void) +{ + cl_git_pass(git_stash_pop(repo, 0, GIT_APPLY_DEFAULT)); + + cl_git_fail_with(git_stash_pop(repo, 0, GIT_APPLY_DEFAULT), GIT_ENOTFOUND); +}