stash: stage new files when unstashing them

Files that were new (staged additions) in the stash tree should
be staged when unstashing, even when not applying the index.
This commit is contained in:
Edward Thomson 2015-06-20 19:33:15 -04:00
parent 8960dc1ec6
commit b7f5cb8dd7
2 changed files with 128 additions and 2 deletions

View File

@ -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);

View File

@ -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);