Commit 93cf7bb8e26a04d9bd4197c1b938cee352023f63

Russell Belfer 2012-10-24T20:56:32

Add git_diff_patch_to_str API This adds an API to generate a complete single-file patch text from a git_diff_patch object.

diff --git a/include/git2/diff.h b/include/git2/diff.h
index 1932db0..1c2a2f8 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -603,6 +603,17 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
 	size_t hunk_idx,
 	size_t line_of_hunk);
 
+/**
+ * Get the content of a patch as a single diff text.
+ *
+ * @param string Allocated string; caller must free.
+ * @param patch The patch to generate a string from.
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_diff_patch_to_str(
+	char **string,
+	git_diff_patch *patch);
+
 /**@}*/
 
 
diff --git a/src/diff_output.c b/src/diff_output.c
index 5f0d13c..511e931 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -1502,3 +1502,57 @@ notfound:
 	return GIT_ENOTFOUND;
 }
 
+static int print_to_buffer_cb(
+    void *cb_data,
+    const git_diff_delta *delta,
+    const git_diff_range *range,
+    char line_origin,
+    const char *content,
+    size_t content_len)
+{
+	git_buf *output = cb_data;
+	GIT_UNUSED(delta);
+	GIT_UNUSED(range);
+	GIT_UNUSED(line_origin);
+	git_buf_put(output, content, content_len);
+	return 0;
+}
+
+int git_diff_patch_to_str(
+	char **string,
+	git_diff_patch *patch)
+{
+	int error;
+	git_buf output = GIT_BUF_INIT, temp = GIT_BUF_INIT;
+	diff_print_info pi;
+	size_t h, l;
+
+	pi.diff     = patch->diff;
+	pi.print_cb = print_to_buffer_cb;
+	pi.cb_data  = &output;
+	pi.buf      = &temp;
+
+	error = print_patch_file(&pi, patch->delta, 0);
+
+	for (h = 0; h < patch->hunks_size; ++h) {
+		diff_patch_hunk *hunk = &patch->hunks[h];
+
+		error = print_patch_hunk(&pi, patch->delta,
+			&hunk->range, hunk->header, hunk->header_len);
+
+		for (l = 0; l < hunk->line_count; ++l) {
+			diff_patch_line *line = &patch->lines[hunk->line_start + l];
+
+			error = print_patch_line(
+				&pi, patch->delta, &hunk->range,
+				line->origin, line->ptr, line->len);
+		}
+	}
+
+	git_buf_free(&temp);
+
+	*string = git_buf_detach(&output);
+
+	return error;
+}
+
diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c
index f6d9bfc..86e8d1f 100644
--- a/tests-clar/diff/diffiter.c
+++ b/tests-clar/diff/diffiter.c
@@ -342,3 +342,102 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void)
 	cl_assert_equal_i(8, exp.hunks);
 	cl_assert_equal_i(14, exp.lines);
 }
+
+/* This output is taken directly from `git diff` on the status test data */
+static const char *expected_patch_text[8] = {
+	/* 0 */
+	"diff --git a/file_deleted b/file_deleted\n"
+	"deleted file mode 100644\n"
+	"index 5452d32..0000000\n"
+	"--- a/file_deleted\n"
+	"+++ /dev/null\n"
+	"@@ -1 +0,0 @@\n"
+	"-file_deleted\n",
+	/* 1 */
+	"diff --git a/modified_file b/modified_file\n"
+	"index 452e424..0a53963 100644\n"
+	"--- a/modified_file\n"
+	"+++ b/modified_file\n"
+	"@@ -1 +1,2 @@\n"
+	" modified_file\n"
+	"+modified_file\n",
+	/* 2 */
+	"diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n"
+	"deleted file mode 100644\n"
+	"index a6be623..0000000\n"
+	"--- a/staged_changes_file_deleted\n"
+	"+++ /dev/null\n"
+	"@@ -1,2 +0,0 @@\n"
+	"-staged_changes_file_deleted\n"
+	"-staged_changes_file_deleted\n",
+	/* 3 */
+	"diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n"
+	"index 906ee77..011c344 100644\n"
+	"--- a/staged_changes_modified_file\n"
+	"+++ b/staged_changes_modified_file\n"
+	"@@ -1,2 +1,3 @@\n"
+	" staged_changes_modified_file\n"
+	" staged_changes_modified_file\n"
+	"+staged_changes_modified_file\n",
+	/* 4 */
+	"diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n"
+	"deleted file mode 100644\n"
+	"index 90b8c29..0000000\n"
+	"--- a/staged_new_file_deleted_file\n"
+	"+++ /dev/null\n"
+	"@@ -1 +0,0 @@\n"
+	"-staged_new_file_deleted_file\n",
+	/* 5 */
+	"diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n"
+	"index ed06290..8b090c0 100644\n"
+	"--- a/staged_new_file_modified_file\n"
+	"+++ b/staged_new_file_modified_file\n"
+	"@@ -1 +1,2 @@\n"
+	" staged_new_file_modified_file\n"
+	"+staged_new_file_modified_file\n",
+	/* 6 */
+	"diff --git a/subdir/deleted_file b/subdir/deleted_file\n"
+	"deleted file mode 100644\n"
+	"index 1888c80..0000000\n"
+	"--- a/subdir/deleted_file\n"
+	"+++ /dev/null\n"
+	"@@ -1 +0,0 @@\n"
+	"-subdir/deleted_file\n",
+	/* 7 */
+	"diff --git a/subdir/modified_file b/subdir/modified_file\n"
+	"index a619198..57274b7 100644\n"
+	"--- a/subdir/modified_file\n"
+	"+++ b/subdir/modified_file\n"
+	"@@ -1 +1,2 @@\n"
+	" subdir/modified_file\n"
+	"+subdir/modified_file\n"
+};
+
+void test_diff_diffiter__iterate_and_generate_patch_text(void)
+{
+	git_repository *repo = cl_git_sandbox_init("status");
+	git_diff_list *diff;
+	size_t d, num_d;
+
+	cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
+
+	num_d = git_diff_num_deltas(diff);
+	cl_assert_equal_i(8, (int)num_d);
+
+	for (d = 0; d < num_d; ++d) {
+		git_diff_patch *patch;
+		char *text;
+
+		cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d));
+		cl_assert(patch != NULL);
+
+		cl_git_pass(git_diff_patch_to_str(&text, patch));
+
+		cl_assert_equal_s(expected_patch_text[d], text);
+
+		git__free(text);
+		git_diff_patch_free(patch);
+	}
+
+	git_diff_list_free(diff);
+}
diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c
index e846838..dce6d6d 100644
--- a/tests-clar/diff/patch.c
+++ b/tests-clar/diff/patch.c
@@ -97,3 +97,33 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
 	git_tree_free(another);
 	git_tree_free(one);
 }
+
+void test_diff_patch__to_string(void)
+{
+	const char *one_sha = "26a125e";
+	const char *another_sha = "735b6a2";
+	git_tree *one, *another;
+	git_diff_list *diff;
+	git_diff_patch *patch;
+	char *text;
+	const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n";
+
+	one = resolve_commit_oid_to_tree(g_repo, one_sha);
+	another = resolve_commit_oid_to_tree(g_repo, another_sha);
+
+	cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, one, another, &diff));
+
+	cl_assert_equal_i(1, git_diff_num_deltas(diff));
+
+	cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0));
+
+	cl_git_pass(git_diff_patch_to_str(&text, patch));
+
+	cl_assert_equal_s(expected, text);
+
+	git__free(text);
+	git_diff_patch_free(patch);
+	git_diff_list_free(diff);
+	git_tree_free(another);
+	git_tree_free(one);
+}