summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Turner <novalis@novalis.org>2018-06-04 12:56:08 -0400
committerDavid Turner <novalis@novalis.org>2018-06-06 14:45:56 -0400
commit5a7d454bf657fdf4d726f2319bf554ffd4486ed3 (patch)
tree3851bd2fceb18cf3d641670874aac4eb1b44795f
parentbae6ed62010508c2e2b0559f9eee41e62e52eb7a (diff)
downloadlibgit2-5a7d454bf657fdf4d726f2319bf554ffd4486ed3.tar.gz
Fix stash save bug with fast path index check
If the index contains stat data for a modified file, and the file is not racily dirty, and there exists an untracked working tree directory alphabetically after that file, and there are no other changes to the repo, then git_stash_save would fail. It would confuse the untracked working tree directory for the modified file, because they have the same sha: zero. The wt directory has a sha of zero because it's a directory, and the file would have a zero sha because we wouldn't read the file -- we would just know that it doesn't match the index. To fix this confusion, we simply check mode as well as SHA.
-rw-r--r--src/diff_generate.c3
-rw-r--r--tests/stash/save.c40
2 files changed, 42 insertions, 1 deletions
diff --git a/src/diff_generate.c b/src/diff_generate.c
index e11cbe4e4..0105c1701 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 edcee820f..5c2d494d2 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)
{