Commit 4ae29053d51968cbf49f30ae14e5405cbd9543cc

Russell Belfer 2013-07-03T11:45:10

Merge pull request #1701 from yorah/fix/gh-1698 Fix segfault in git_status_foreach_ext()

diff --git a/src/diff.c b/src/diff.c
index 26e1174..9e9528e 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -1273,7 +1273,7 @@ int git_diff__paired_foreach(
 			git_vector_sort(&idx2wd->deltas);
 		}
 	}
-	else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
+	else if (head2idx != NULL && head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
 		strcomp = git__strcasecmp;
 
 	for (i = 0, j = 0; i < i_max || j < j_max; ) {
diff --git a/src/status.c b/src/status.c
index e520c10..7da94ed 100644
--- a/src/status.c
+++ b/src/status.c
@@ -257,6 +257,7 @@ int git_status_list_new(
 		opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
 	int error = 0;
 	unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
+	git_diff_list *head2idx = NULL;
 
 	assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
 
@@ -307,8 +308,10 @@ int git_status_list_new(
 				&status->head2idx, repo, head, NULL, &diffopt)) < 0)
 			goto done;
 
+		head2idx = status->head2idx;
+
 		if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
-			(error = git_diff_find_similar(status->head2idx, NULL)) < 0)
+			(error = git_diff_find_similar(head2idx, NULL)) < 0)
 			goto done;
 	}
 
@@ -324,15 +327,14 @@ int git_status_list_new(
 
 	if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
 		if ((error = git_diff__paired_foreach(
-				status->head2idx, NULL, status_collect, status)) < 0)
+				head2idx, NULL, status_collect, status)) < 0)
 			goto done;
 
-		git_diff_list_free(status->head2idx);
-		status->head2idx = NULL;
+		head2idx = NULL;
 	}
 
 	if ((error = git_diff__paired_foreach(
-			status->head2idx, status->idx2wd, status_collect, status)) < 0)
+			head2idx, status->idx2wd, status_collect, status)) < 0)
 		goto done;
 
 	if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h
index a41bde7..2758784 100644
--- a/tests-clar/status/status_data.h
+++ b/tests-clar/status/status_data.h
@@ -250,3 +250,130 @@ static const unsigned int entry_statuses4[] = {
 };
 
 static const int entry_count4 = 23;
+
+
+/* entries for a copy of tests/resources/status with options
+ * passed to the status call in order to only get the differences
+ * between the HEAD and the index (changes to be committed)
+ */
+
+static const char *entry_paths5[] = {
+	"staged_changes",
+	"staged_changes_file_deleted",
+	"staged_changes_modified_file",
+	"staged_delete_file_deleted",
+	"staged_delete_modified_file",
+	"staged_new_file",
+	"staged_new_file_deleted_file",
+	"staged_new_file_modified_file",
+};
+
+static const unsigned int entry_statuses5[] = {
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_DELETED,
+	GIT_STATUS_INDEX_DELETED,
+	GIT_STATUS_INDEX_NEW,
+	GIT_STATUS_INDEX_NEW,
+	GIT_STATUS_INDEX_NEW,
+};
+
+static const int entry_count5 = 8;
+
+
+/* entries for a copy of tests/resources/status with options
+ * passed to the status call in order to only get the differences
+ * between the workdir and the index (changes not staged, untracked files)
+ */
+
+static const char *entry_paths6[] = {
+	"file_deleted",
+	"ignored_file",
+	"modified_file",
+	"new_file",
+	"staged_changes_file_deleted",
+	"staged_changes_modified_file",
+	"staged_delete_modified_file",
+	"staged_new_file_deleted_file",
+	"staged_new_file_modified_file",
+	"subdir/deleted_file",
+	"subdir/modified_file",
+	"subdir/new_file",
+	"\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses6[] = {
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_IGNORED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count6 = 13;
+
+
+/* entries for a copy of tests/resources/status with options
+ * passed to the status call in order to get the differences
+ * between the HEAD and the index and then between the workdir
+ * and the index.
+ */
+
+static const char *entry_paths7[] = {
+	"staged_changes",
+	"staged_changes_file_deleted",
+	"staged_changes_modified_file",
+	"staged_delete_file_deleted",
+	"staged_delete_modified_file",
+	"staged_new_file",
+	"staged_new_file_deleted_file",
+	"staged_new_file_modified_file",
+	"file_deleted",
+	"ignored_file",
+	"modified_file",
+	"new_file",
+	"staged_changes_file_deleted",
+	"staged_changes_modified_file",
+	"staged_delete_modified_file",
+	"staged_new_file_deleted_file",
+	"staged_new_file_modified_file",
+	"subdir/deleted_file",
+	"subdir/modified_file",
+	"subdir/new_file",
+	"\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses7[] = {
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_MODIFIED,
+	GIT_STATUS_INDEX_DELETED,
+	GIT_STATUS_INDEX_DELETED,
+	GIT_STATUS_INDEX_NEW,
+	GIT_STATUS_INDEX_NEW,
+	GIT_STATUS_INDEX_NEW,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_IGNORED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_DELETED,
+	GIT_STATUS_WT_MODIFIED,
+	GIT_STATUS_WT_NEW,
+	GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count7 = 21;
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index 920671e..ac99376 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -40,6 +40,54 @@ void test_status_worktree__whole_repository(void)
 	cl_assert_equal_i(0, counts.wrong_sorted_path);
 }
 
+void assert_show(const int entry_counts, const char *entry_paths[],
+				 const unsigned int entry_statuses[], git_status_show_t show)
+{
+	status_entry_counts counts;
+	git_repository *repo = cl_git_sandbox_init("status");
+	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+	memset(&counts, 0x0, sizeof(status_entry_counts));
+	counts.expected_entry_count = entry_counts;
+	counts.expected_paths = entry_paths;
+	counts.expected_statuses = entry_statuses;
+
+	opts.flags = GIT_STATUS_OPT_DEFAULTS;
+	opts.show = show;
+
+	cl_git_pass(
+		git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+	);
+
+	cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+	cl_assert_equal_i(0, counts.wrong_status_flags_count);
+	cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_worktree__show_index_and_workdir(void)
+{
+	assert_show(entry_count0, entry_paths0, entry_statuses0,
+		GIT_STATUS_SHOW_INDEX_AND_WORKDIR);
+}
+
+void test_status_worktree__show_index_only(void)
+{
+	assert_show(entry_count5, entry_paths5, entry_statuses5,
+		GIT_STATUS_SHOW_INDEX_ONLY);
+}
+
+void test_status_worktree__show_workdir_only(void)
+{
+	assert_show(entry_count6, entry_paths6, entry_statuses6,
+		GIT_STATUS_SHOW_WORKDIR_ONLY);
+}
+
+void test_status_worktree__show_index_then_workdir_only(void)
+{
+	assert_show(entry_count7, entry_paths7, entry_statuses7,
+		GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
+}
+
 /* this test is equivalent to t18-status.c:statuscb1 */
 void test_status_worktree__empty_repository(void)
 {