Commit b0691db32c1349b1d3ea88f815994d2cd162bed0

Patrick Steinhardt 2020-01-31T09:39:12

tests: diff: verify that we are able to diff with empty subtrees While it is not allowed for a tree to have an empty tree as child (e.g. an empty directory), libgit2's tree builder makes it easy to create such trees. As a result, some applications may inadvertently end up with such an invalid tree, and we should try our best and handle them. One such case is when diffing two trees, where one of both trees has such an empty subtree. It was reported that this will cause our diff code to fail. While I wasn't able to reproduce this error, let's still add a test that verifies we continue to handle them correctly.

diff --git a/tests/diff/tree.c b/tests/diff/tree.c
index 2359a83..dfe4d25 100644
--- a/tests/diff/tree.c
+++ b/tests/diff/tree.c
@@ -524,3 +524,52 @@ void test_diff_tree__diff_configs(void)
 	cl_assert_equal_i(7, expect.line_adds);
 	cl_assert_equal_i(15, expect.line_dels);
 }
+
+void test_diff_tree__diff_tree_with_empty_dir_entry_succeeds(void)
+{
+	const char *content = "This is a blob\n";
+	const git_diff_delta *delta;
+	git_oid empty_tree, invalid_tree, blob;
+	git_buf patch = GIT_BUF_INIT;
+	git_treebuilder *builder;
+
+	g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+	cl_git_pass(git_blob_create_from_buffer(&blob, g_repo, content, strlen(content)));
+	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
+	cl_git_pass(git_treebuilder_write(&empty_tree, builder));
+	cl_git_pass(git_treebuilder_insert(NULL, builder, "empty_tree", &empty_tree, GIT_FILEMODE_TREE));
+	cl_git_pass(git_treebuilder_insert(NULL, builder, "blob", &blob, GIT_FILEMODE_BLOB));
+	cl_git_pass(git_treebuilder_write(&invalid_tree, builder));
+
+	cl_git_pass(git_tree_lookup(&a, g_repo, &empty_tree));
+	cl_git_pass(git_tree_lookup(&b, g_repo, &invalid_tree));
+	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL));
+
+	cl_git_pass(git_diff_foreach(diff,
+		diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect));
+	cl_assert_equal_i(1, expect.files);
+	cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]);
+	cl_assert_equal_i(1, expect.hunks);
+	cl_assert_equal_i(1, expect.lines);
+	cl_assert_equal_i(0, expect.line_ctxt);
+	cl_assert_equal_i(1, expect.line_adds);
+	cl_assert_equal_i(0, expect.line_dels);
+
+	cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH));
+	cl_assert_equal_s(patch.ptr,
+		"diff --git a/blob b/blob\n"
+		"new file mode 100644\n"
+		"index 0000000..bbf2e80\n"
+		"--- /dev/null\n"
+		"+++ b/blob\n"
+		"@@ -0,0 +1 @@\n"
+		"+This is a blob\n");
+
+	cl_assert_equal_i(git_diff_num_deltas(diff), 1);
+	delta = git_diff_get_delta(diff, 0);
+	cl_assert_equal_s(delta->new_file.path, "blob");
+
+	git_treebuilder_free(builder);
+	git_buf_dispose(&patch);
+}