Commit 795a5b28ad1f6e5dfd82d45bd7721914f49d31d3

Edward Thomson 2018-06-09T18:36:21

Merge pull request #4668 from novalis/bad-stash Fix stash save bug with fast path index check

diff --git a/src/diff_generate.c b/src/diff_generate.c
index e11cbe4..0105c17 100644
--- a/src/diff_generate.c
+++ b/src/diff_generate.c
@@ -273,7 +273,8 @@ static git_diff_delta *diff_delta__last_for_item(
 		break;
 	case GIT_DELTA_MODIFIED:
 		if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
-			git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+		    (delta->new_file.mode == item->mode &&
+			git_oid__cmp(&delta->new_file.id, &item->id) == 0))
 			return delta;
 		break;
 	default:
diff --git a/tests/stash/save.c b/tests/stash/save.c
index edcee82..5c2d494 100644
--- a/tests/stash/save.c
+++ b/tests/stash/save.c
@@ -188,6 +188,46 @@ void test_stash_save__can_include_untracked_and_ignored_files(void)
 	cl_assert(!git_path_exists("stash/just.ignore"));
 }
 
+/*
+ * Note: this test was flaky prior to fixing #4101 -- run it several
+ * times to get a failure.  The issues is that whether the fast
+ * (stat-only) codepath is used inside stash's diff operation depends
+ * on whether files are "racily clean", and there doesn't seem to be
+ * an easy way to force the exact required state.
+ */
+void test_stash_save__untracked_regression(void)
+{
+	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+	const char *paths[] = {"what", "where", "how", "why"};
+	git_reference *head;
+	git_commit *head_commit;
+	git_buf untracked_dir;
+
+	const char* workdir = git_repository_workdir(repo);
+
+	git_buf_init(&untracked_dir, 0);
+	git_buf_printf(&untracked_dir, "%sz", workdir);
+
+	cl_assert(!p_mkdir(untracked_dir.ptr, 0777));
+
+	cl_git_pass(git_repository_head(&head, repo));
+
+	cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT));
+
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+	opts.paths.strings = (char **)paths;
+	opts.paths.count = 4;
+
+	cl_git_pass(git_checkout_tree(repo, (git_object*)head_commit, &opts));
+
+	cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+	assert_commit_message_contains("refs/stash", "WIP on master");
+
+	git_buf_free(&untracked_dir);
+}
+
 #define MESSAGE "Look Ma! I'm on TV!"
 void test_stash_save__can_accept_a_message(void)
 {