Commit 79ef3be449c9d81dd0b37a30999563aa92e4679e

Russell Belfer 2013-05-15T14:50:05

Fix diff crash when last item is untracked dir When the last item in a diff was an untracked directory that only contained ignored items, the loop to scan the contents would run off the end of the iterator and dereference a NULL pointer. This includes a test that reproduces the problem and a fix.

diff --git a/src/diff.c b/src/diff.c
index f466546..d935069 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -747,7 +747,8 @@ static int diff_scan_inside_untracked_dir(
 	}
 
 	/* look for actual untracked file */
-	while (!diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+	while (info->nitem != NULL &&
+		   !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
 		is_ignored = git_iterator_current_is_ignored(info->new_iter);
 
 		/* need to recurse into non-ignored directories */
@@ -769,7 +770,8 @@ static int diff_scan_inside_untracked_dir(
 	}
 
 	/* finish off scan */
-	while (!diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
+	while (info->nitem != NULL &&
+		   !diff->pfxcomp(info->nitem->path, git_buf_cstr(&base))) {
 		if ((error = git_iterator_advance(&info->nitem, info->new_iter)) < 0)
 			break;
 	}
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
index 94fd716..18182ea 100644
--- a/tests-clar/diff/workdir.c
+++ b/tests-clar/diff/workdir.c
@@ -1220,3 +1220,28 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
 	git_diff_list_free(diff);
 }
+
+
+void test_diff_workdir__untracked_directory_comes_last(void)
+{
+	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+	git_diff_list *diff = NULL;
+
+	g_repo = cl_git_sandbox_init("renames");
+
+	cl_git_mkfile("renames/.gitignore", "*.ign\n");
+	cl_git_pass(p_mkdir("renames/zzz_untracked", 0777));
+	cl_git_mkfile("renames/zzz_untracked/an.ign", "ignore me please");
+	cl_git_mkfile("renames/zzz_untracked/skip.ign", "ignore me really");
+	cl_git_mkfile("renames/zzz_untracked/test.ign", "ignore me now");
+
+	opts.context_lines = 3;
+	opts.interhunk_lines = 1;
+	opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+	cl_assert(diff != NULL);
+
+	git_diff_list_free(diff);
+}