From 93cf7bb8e26a04d9bd4197c1b938cee352023f63 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 24 Oct 2012 20:56:32 -0700 Subject: [PATCH 1/3] 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. --- include/git2/diff.h | 11 +++++ src/diff_output.c | 54 +++++++++++++++++++++ tests-clar/diff/diffiter.c | 99 ++++++++++++++++++++++++++++++++++++++ tests-clar/diff/patch.c | 30 ++++++++++++ 4 files changed, 194 insertions(+) diff --git a/include/git2/diff.h b/include/git2/diff.h index 1932db029..1c2a2f83a 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 5f0d13c64..511e9318b 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 f6d9bfc38..86e8d1f57 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 e8468386c..dce6d6da2 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); +} From 3943dc78a5c67c1db367c4e13a79884ef72f7f3b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 25 Oct 2012 11:12:56 -0700 Subject: [PATCH 2/3] Check errors while generating diff patch string --- src/diff_output.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/diff_output.c b/src/diff_output.c index 511e9318b..495de251b 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1514,8 +1514,7 @@ static int print_to_buffer_cb( GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); - git_buf_put(output, content, content_len); - return 0; + return git_buf_put(output, content, content_len); } int git_diff_patch_to_str( @@ -1527,6 +1526,8 @@ int git_diff_patch_to_str( diff_print_info pi; size_t h, l; + assert(string && patch); + pi.diff = patch->diff; pi.print_cb = print_to_buffer_cb; pi.cb_data = &output; @@ -1534,13 +1535,13 @@ int git_diff_patch_to_str( error = print_patch_file(&pi, patch->delta, 0); - for (h = 0; h < patch->hunks_size; ++h) { + for (h = 0; h < patch->hunks_size && !error; ++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) { + for (l = 0; l < hunk->line_count && !error; ++l) { diff_patch_line *line = &patch->lines[hunk->line_start + l]; error = print_patch_line( @@ -1549,6 +1550,12 @@ int git_diff_patch_to_str( } } + /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, + * meaning a memory allocation failure, so just map to -1... + */ + if (error == GIT_EUSER) + error = -1; + git_buf_free(&temp); *string = git_buf_detach(&output); From cb7180a6e2bb7e5912c16d2109f273c75731a607 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 25 Oct 2012 11:48:39 -0700 Subject: [PATCH 3/3] Add git_diff_patch_print This adds a `git_diff_patch_print()` API which is more like the existing API to "print" a patch from an entire `git_diff_list` but operates on a single `git_diff_patch` object. Also, it rewrites the `git_diff_patch_to_str()` API to use that function (making it very small). --- include/git2/diff.h | 19 ++++++++++++++++++- src/diff_output.c | 36 +++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 1c2a2f83a..4e80bed56 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -603,11 +603,28 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( size_t hunk_idx, size_t line_of_hunk); +/** + * Serialize the patch to text via callback. + * + * Returning a non-zero value from the callback will terminate the iteration + * and cause this return `GIT_EUSER`. + * + * @param patch A git_diff_patch representing changes to one file + * @param cb_data Reference pointer that will be passed to your callbacks. + * @param print_cb Callback function to output lines of the patch. Will be + * called for file headers, hunk headers, and diff lines. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_diff_patch_print( + git_diff_patch *patch, + void *cb_data, + git_diff_data_fn print_cb); + /** * 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. + * @param patch A git_diff_patch representing changes to one file * @return 0 on success, <0 on failure. */ GIT_EXTERN(int) git_diff_patch_to_str( diff --git a/src/diff_output.c b/src/diff_output.c index 495de251b..e678ec857 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1511,26 +1511,25 @@ static int print_to_buffer_cb( size_t content_len) { git_buf *output = cb_data; - GIT_UNUSED(delta); - GIT_UNUSED(range); - GIT_UNUSED(line_origin); + GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); return git_buf_put(output, content, content_len); } -int git_diff_patch_to_str( - char **string, - git_diff_patch *patch) +int git_diff_patch_print( + git_diff_patch *patch, + void *cb_data, + git_diff_data_fn print_cb) { int error; - git_buf output = GIT_BUF_INIT, temp = GIT_BUF_INIT; + git_buf temp = GIT_BUF_INIT; diff_print_info pi; size_t h, l; - assert(string && patch); + assert(patch && print_cb); pi.diff = patch->diff; - pi.print_cb = print_to_buffer_cb; - pi.cb_data = &output; + pi.print_cb = print_cb; + pi.cb_data = cb_data; pi.buf = &temp; error = print_patch_file(&pi, patch->delta, 0); @@ -1550,16 +1549,27 @@ int git_diff_patch_to_str( } } + git_buf_free(&temp); + + return error; +} + +int git_diff_patch_to_str( + char **string, + git_diff_patch *patch) +{ + int error; + git_buf output = GIT_BUF_INIT; + + error = git_diff_patch_print(patch, &output, print_to_buffer_cb); + /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, * meaning a memory allocation failure, so just map to -1... */ if (error == GIT_EUSER) error = -1; - git_buf_free(&temp); - *string = git_buf_detach(&output); return error; } -