diff --git a/include/git2/diff.h b/include/git2/diff.h index d216c1303..154264546 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -400,6 +400,20 @@ GIT_EXTERN(int) git_diff_print_compact( void *cb_data, git_diff_data_fn print_cb); +/** + * Look up the single character abbreviation for a delta status code. + * + * When you call `git_diff_print_compact` it prints single letter codes into + * the output such as 'A' for added, 'D' for deleted, 'M' for modified, etc. + * It is sometimes convenient to convert a git_delta_t value into these + * letters for your own purposes. This function does just that. By the + * way, unmodified will return a space (i.e. ' '). + * + * @param delta_t The git_delta_t value to look up + * @return The single character label for that code + */ +GIT_EXTERN(char) git_diff_status_char(git_delta_t status); + /** * Iterate over a diff generating text output like "git diff". * @@ -453,9 +467,9 @@ GIT_EXTERN(size_t) git_diff_num_deltas_of_type( * done with it. You can use the patch object to loop over all the hunks * and lines in the diff of the one delta. * - * For a binary file, no `git_diff_patch` will be created, the output will - * be set to NULL, and the `binary` flag will be set true in the - * `git_diff_delta` structure. + * For an unchanged file or a binary file, no `git_diff_patch` will be + * created, the output will be set to NULL, and the `binary` flag will be + * set true in the `git_diff_delta` structure. * * The `git_diff_delta` pointer points to internal data and you do not have * to release it when you are done with it. It will go away when the diff --git a/src/diff.c b/src/diff.c index 7c8e2a9bb..7ee919b5a 100644 --- a/src/diff.c +++ b/src/diff.c @@ -807,6 +807,28 @@ on_error: return -1; } + +bool git_diff_delta__should_skip( + git_diff_options *opts, git_diff_delta *delta) +{ + uint32_t flags = opts ? opts->flags : 0; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; + + if (delta->status == GIT_DELTA_IGNORED && + (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNTRACKED && + (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; + + return false; +} + + int git_diff_merge( git_diff_list *onto, const git_diff_list *from) @@ -843,6 +865,14 @@ int git_diff_merge( j++; } + /* the ignore rules for the target may not match the source + * or the result of a merged delta could be skippable... + */ + if (git_diff_delta__should_skip(&onto->opts, delta)) { + git__free(delta); + continue; + } + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) break; } diff --git a/src/diff.h b/src/diff.h index 862c33c1b..64fe00928 100644 --- a/src/diff.h +++ b/src/diff.h @@ -50,5 +50,8 @@ extern void git_diff__cleanup_modes( extern void git_diff_list_addref(git_diff_list *diff); +extern bool git_diff_delta__should_skip( + git_diff_options *opts, git_diff_delta *delta); + #endif diff --git a/src/diff_output.c b/src/diff_output.c index b84b0c2a4..141859742 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -51,26 +51,6 @@ static int parse_hunk_header(git_diff_range *range, const char *header) return 0; } -static bool diff_delta_should_skip( - diff_context *ctxt, git_diff_delta *delta) -{ - uint32_t flags = ctxt->opts ? ctxt->opts->flags : 0; - - if (delta->status == GIT_DELTA_UNMODIFIED && - (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - return true; - - if (delta->status == GIT_DELTA_IGNORED && - (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNTRACKED && - (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - return true; - - return false; -} - #define KNOWN_BINARY_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) #define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA) @@ -411,7 +391,7 @@ static void diff_context_init( setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params); } -static bool diff_delta_file_callback( +static int diff_delta_file_callback( diff_context *ctxt, git_diff_delta *delta, size_t idx) { float progress; @@ -419,18 +399,18 @@ static bool diff_delta_file_callback( if (!ctxt->file_cb) return 0; - progress = (float)idx / ctxt->diff->deltas.length; + progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f; if (ctxt->file_cb(ctxt->cb_data, delta, progress) != 0) - return GIT_EUSER; + ctxt->cb_error = GIT_EUSER; - return 0; + return ctxt->cb_error; } static void diff_patch_init( diff_context *ctxt, git_diff_patch *patch) { - memset(patch, 0, sizeof(*patch)); + memset(patch, 0, sizeof(git_diff_patch)); patch->diff = ctxt->diff; patch->ctxt = ctxt; @@ -454,7 +434,7 @@ static git_diff_patch *diff_patch_alloc( git_diff_list_addref(patch->diff); - GIT_REFCOUNT_INC(&patch); + GIT_REFCOUNT_INC(patch); patch->delta = delta; patch->flags = GIT_DIFF_PATCH_ALLOCATED; @@ -836,6 +816,8 @@ static int diff_patch_line_cb( } } + hunk->line_count++; + return 0; } @@ -847,7 +829,7 @@ int git_diff_foreach( git_diff_hunk_fn hunk_cb, git_diff_data_fn data_cb) { - int error; + int error = 0; diff_context ctxt; size_t idx; git_diff_patch patch; @@ -861,14 +843,20 @@ int git_diff_foreach( git_vector_foreach(&diff->deltas, idx, patch.delta) { /* check flags against patch status */ - if (diff_delta_should_skip(&ctxt, patch.delta)) + if (git_diff_delta__should_skip(ctxt.opts, patch.delta)) continue; - if (!(error = diff_patch_load(&ctxt, &patch)) && - !(error = diff_delta_file_callback(&ctxt, patch.delta, idx))) - error = diff_patch_generate(&ctxt, &patch); + if (!(error = diff_patch_load(&ctxt, &patch))) { - diff_patch_unload(&patch); + /* invoke file callback */ + error = diff_delta_file_callback(&ctxt, patch.delta, idx); + + /* generate diffs and invoke hunk and line callbacks */ + if (!error) + error = diff_patch_generate(&ctxt, &patch); + + diff_patch_unload(&patch); + } if (error < 0) break; @@ -899,25 +887,32 @@ static char pick_suffix(int mode) return ' '; } +char git_diff_status_char(git_delta_t status) +{ + char code; + + switch (status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + default: code = ' '; break; + } + + return code; +} + static int print_compact(void *data, git_diff_delta *delta, float progress) { diff_print_info *pi = data; - char code, old_suffix, new_suffix; + char old_suffix, new_suffix, code = git_diff_status_char(delta->status); GIT_UNUSED(progress); - switch (delta->status) { - case GIT_DELTA_ADDED: code = 'A'; break; - case GIT_DELTA_DELETED: code = 'D'; break; - case GIT_DELTA_MODIFIED: code = 'M'; break; - case GIT_DELTA_RENAMED: code = 'R'; break; - case GIT_DELTA_COPIED: code = 'C'; break; - case GIT_DELTA_IGNORED: code = 'I'; break; - case GIT_DELTA_UNTRACKED: code = '?'; break; - default: code = 0; - } - - if (!code) + if (code == ' ') return 0; old_suffix = pick_suffix(delta->old_file.mode); @@ -1288,7 +1283,7 @@ int git_diff_get_patch( &ctxt, diff, diff->repo, &diff->opts, NULL, NULL, diff_patch_hunk_cb, diff_patch_line_cb); - if (diff_delta_should_skip(&ctxt, delta)) + if (git_diff_delta__should_skip(ctxt.opts, delta)) return 0; patch = diff_patch_alloc(&ctxt, delta); diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 1c0435975..0c4721897 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -120,7 +120,7 @@ int diff_foreach_via_iterator( size_t h, num_h; cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d)); - cl_assert(delta && patch); + cl_assert(delta); /* call file_cb for this file */ if (file_cb != NULL && file_cb(data, delta, (float)d / num_d) != 0) { @@ -128,6 +128,12 @@ int diff_foreach_via_iterator( goto abort; } + /* if there are no changes, then the patch will be NULL */ + if (!patch) { + cl_assert(delta->status == GIT_DELTA_UNMODIFIED || delta->binary == 1); + continue; + } + if (!hunk_cb && !line_cb) { git_diff_patch_free(patch); continue; diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c index 9e33d91e1..4273b16dd 100644 --- a/tests-clar/diff/diffiter.c +++ b/tests-clar/diff/diffiter.c @@ -256,21 +256,23 @@ void test_diff_diffiter__iterate_all(void) cl_assert_equal_i(13, exp.files); cl_assert_equal_i(8, exp.hunks); - cl_assert_equal_i(13, exp.lines); + cl_assert_equal_i(14, exp.lines); git_diff_list_free(diff); } static void iterate_over_patch(git_diff_patch *patch, diff_expects *exp) { - size_t h, num_h = git_diff_patch_num_hunks(patch); + size_t h, num_h = git_diff_patch_num_hunks(patch), num_l; exp->files++; exp->hunks += num_h; /* let's iterate in reverse, just because we can! */ - for (h = 1; h <= num_h; ++h) - exp->lines += git_diff_patch_num_lines_in_hunk(patch, num_h - h); + for (h = 1, num_l = 0; h <= num_h; ++h) + num_l += git_diff_patch_num_lines_in_hunk(patch, num_h - h); + + exp->lines += num_l; } #define PATCH_CACHE 5 @@ -338,5 +340,5 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void) /* hopefully it all still added up right */ cl_assert_equal_i(13, exp.files); cl_assert_equal_i(8, exp.hunks); - cl_assert_equal_i(13, exp.lines); + cl_assert_equal_i(14, exp.lines); }