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/stash.c b/src/stash.c index 9010c476d..14cbeb642 100644 --- a/src/stash.c +++ b/src/stash.c @@ -733,6 +733,29 @@ 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; +} + int git_stash_apply( git_repository *repo, size_t index, @@ -775,6 +798,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))) { diff --git a/tests/stash/apply.c b/tests/stash/apply.c index bd6bb565d..db145d58d 100644 --- a/tests/stash/apply.c +++ b/tests/stash/apply.c @@ -118,13 +118,14 @@ 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); } @@ -238,6 +239,22 @@ void test_stash_apply__conflict_commit_with_reinstate_index(void) 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) { cl_git_pass(git_stash_pop(repo, 0, NULL));