Commit b7f5cb8dd767063719919780d90fc3470d340ac7

Edward Thomson 2015-06-20T19:33:15

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.

diff --git a/src/stash.c b/src/stash.c
index 14cbeb6..59ecd3b 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 db145d5..901667d 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);