Git fork

diff: ensure consistent diff behavior with ignore options

In git-diff, options like `-w` and `-I<regex>`, two files are considered
equivalent under the specified "ignore" rules, even when they are not
bit-for-bit identical. For options like `--raw`, `--name-status`,
and `--name-only`, git-diff deliberately compares only the SHA values
to determine whether two files are equivalent, for performance reasons.
As a result, a file shown in `git diff --name-status` may not appear
in `git diff --patch`.

To quickly determine whether two files are equivalent, add a helper
function diff_flush_patch_quietly() in diff.c. Add `.dry_run` field in
`struct diff_options`. When `.dry_run` is true, builtin_diff() returns
immediately upon finding any change. Call diff_flush_patch_quietly()
to determine if we should flush `--raw`, `--name-only` or `--name-status`
output.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Lidong Yan <yldhome2d2@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Lidong Yan and committed by
Junio C Hamano
b55e6d36 866e6a39

+70 -23
+50 -14
diff.c
··· 2444 2444 return 0; 2445 2445 } 2446 2446 2447 + static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED) 2448 + { 2449 + struct emit_callback *ecbdata = priv; 2450 + struct diff_options *o = ecbdata->opt; 2451 + 2452 + o->found_changes = 1; 2453 + return 1; 2454 + } 2455 + 2447 2456 static void pprint_rename(struct strbuf *name, const char *a, const char *b) 2448 2457 { 2449 2458 const char *old_name = a; ··· 3759 3768 3760 3769 if (o->word_diff) 3761 3770 init_diff_words_data(&ecbdata, o, one, two); 3762 - if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, 3763 - &ecbdata, &xpp, &xecfg)) 3771 + if (o->dry_run) { 3772 + /* 3773 + * Unlike the !dry_run case, we need to ignore the 3774 + * return value from xdi_diff_outf() here, because 3775 + * xdi_diff_outf() takes non-zero return from its 3776 + * callback function as a sign of error and returns 3777 + * early (which is why we return non-zero from our 3778 + * callback, quick_consume()). Unfortunately, 3779 + * xdi_diff_outf() signals an error by returning 3780 + * non-zero. 3781 + */ 3782 + xdi_diff_outf(&mf1, &mf2, NULL, quick_consume, 3783 + &ecbdata, &xpp, &xecfg); 3784 + } else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, 3785 + &ecbdata, &xpp, &xecfg)) 3764 3786 die("unable to generate diff for %s", one->path); 3765 3787 if (o->word_diff) 3766 3788 free_diff_words_data(&ecbdata); ··· 6150 6172 run_diff(p, o); 6151 6173 } 6152 6174 6175 + /* return 1 if any change is found; otherwise, return 0 */ 6176 + static int diff_flush_patch_quietly(struct diff_filepair *p, struct diff_options *o) 6177 + { 6178 + int saved_dry_run = o->dry_run; 6179 + int saved_found_changes = o->found_changes; 6180 + int ret; 6181 + 6182 + o->dry_run = 1; 6183 + o->found_changes = 0; 6184 + diff_flush_patch(p, o); 6185 + ret = o->found_changes; 6186 + o->dry_run = saved_dry_run; 6187 + o->found_changes |= saved_found_changes; 6188 + return ret; 6189 + } 6190 + 6153 6191 static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, 6154 6192 struct diffstat_t *diffstat) 6155 6193 { ··· 6778 6816 DIFF_FORMAT_CHECKDIFF)) { 6779 6817 for (i = 0; i < q->nr; i++) { 6780 6818 struct diff_filepair *p = q->queue[i]; 6781 - if (check_pair_status(p)) 6782 - flush_one_pair(p, options); 6819 + 6820 + if (!check_pair_status(p)) 6821 + continue; 6822 + 6823 + if (options->flags.diff_from_contents && 6824 + !diff_flush_patch_quietly(p, options)) 6825 + continue; 6826 + 6827 + flush_one_pair(p, options); 6783 6828 } 6784 6829 separator++; 6785 6830 } ··· 6831 6876 if (output_format & DIFF_FORMAT_NO_OUTPUT && 6832 6877 options->flags.exit_with_status && 6833 6878 options->flags.diff_from_contents) { 6834 - /* 6835 - * run diff_flush_patch for the exit status. setting 6836 - * options->file to /dev/null should be safe, because we 6837 - * aren't supposed to produce any output anyway. 6838 - */ 6839 - diff_free_file(options); 6840 - options->file = xfopen("/dev/null", "w"); 6841 - options->close_file = 1; 6842 - options->color_moved = 0; 6843 6879 for (i = 0; i < q->nr; i++) { 6844 6880 struct diff_filepair *p = q->queue[i]; 6845 6881 if (check_pair_status(p)) 6846 - diff_flush_patch(p, options); 6882 + diff_flush_patch_quietly(p, options); 6847 6883 if (options->found_changes) 6848 6884 break; 6849 6885 }
+2
diff.h
··· 400 400 #define COLOR_MOVED_WS_ERROR (1<<0) 401 401 unsigned color_moved_ws_handling; 402 402 403 + bool dry_run; 404 + 403 405 struct repository *repo; 404 406 struct strmap *additional_path_headers; 405 407
+13
t/t4013-diff-various.sh
··· 648 648 test_grep "invalid regex given to -I: " error 649 649 ' 650 650 651 + test_expect_success 'diff -I<regex>: ignore matching file' ' 652 + test_when_finished "git rm -f file1" && 653 + test_seq 50 >file1 && 654 + git add file1 && 655 + test_seq 50 | sed -e "s/13/ten and three/" -e "s/^[124-9].*/& /" >file1 && 656 + 657 + : >actual && 658 + git diff --raw --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && 659 + git diff --name-only --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && 660 + git diff --name-status --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && 661 + test_grep ! "file1" actual 662 + ' 663 + 651 664 # check_prefix <patch> <src> <dst> 652 665 # check only lines with paths to avoid dependency on exact oid/contents 653 666 check_prefix () {
+2 -6
t/t4015-diff-whitespace.sh
··· 11 11 . "$TEST_DIRECTORY"/lib-diff.sh 12 12 13 13 for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \ 14 - --raw! --name-only! --name-status! 14 + --raw --name-only --name-status 15 15 do 16 - opts=${opt_res%!} expect_failure= 17 - test "$opts" = "$opt_res" || 18 - expect_failure="test_expect_code 1" 19 - 20 16 test_expect_success "status with $opts (different)" ' 21 17 echo foo >x && 22 18 git add x && ··· 43 39 echo foo >x && 44 40 git add x && 45 41 echo " foo" >x && 46 - $expect_failure git diff -w $opts --exit-code x 42 + git diff -w $opts --exit-code x 47 43 ' 48 44 done 49 45
+3 -3
xdiff-interface.h
··· 28 28 * from an error internal to xdiff, xdiff itself will see that 29 29 * non-zero return and translate it to -1. 30 30 * 31 - * See "diff_grep" in diffcore-pickaxe.c for a trick to work around 32 - * this, i.e. using the "consume_callback_data" to note the desired 33 - * early return. 31 + * See "diff_grep" in diffcore-pickaxe.c and "quick_consume" in diff.c 32 + * for a trick to work around this, i.e. using the "consume_callback_data" 33 + * to note the desired early return. 34 34 */ 35 35 typedef int (*xdiff_emit_line_fn)(void *, char *, unsigned long); 36 36 typedef void (*xdiff_emit_hunk_fn)(void *data,