diff.c: --ws-error-highlight=<kind> option

Traditionally, we only cared about whitespace breakages introduced in new lines. Some people want to paint whitespace breakages on old lines, too. When they see a whitespace breakage on a new line, they can spot the same kind of whitespace breakage on the corresponding old line and want to say "Ah, those breakages are there but they were inherited from the original, so let's not touch them for now." Introduce `--ws-error-highlight=<kind>` option, that lets them pass a comma separated list of `old`, `new`, and `context` to specify what lines to highlight whitespace errors on. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed May 26, 2015 at 10:11 UTC b8767f791c15f119554c1466af60e4f2433ae971
4 files changed +179 -16
Documentation/diff-options.txt
+10
@@ -278,6 +278,16 @@ ifndef::git-format-patch[]
278 initial indent of the line are considered whitespace errors.
279 Exits with non-zero status if problems are found. Not compatible
280 with --exit-code.
281 +
282 +--ws-error-highlight=<kind>::
283 + Highlight whitespace errors on lines specified by <kind>
284 + in the color specified by `color.diff.whitespace`. <kind>
285 + is a comma separated list of `old`, `new`, `context`. When
286 + this option is not given, only whitespace errors in `new`
287 + lines are highlighted. E.g. `--ws-error-highlight=new,old`
288 + highlights whitespace errors on both deleted and added lines.
289 + `all` can be used as a short-hand for `old,new,context`.
290 +
291 endif::git-format-patch[]
292
293 --full-index::
diff.c
+68 -16
@@ -478,42 +478,57 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line
478 return ws_blank_line(line, len, ecbdata->ws_rule);
479 }
480
481 -static void emit_add_line(const char *reset,
482 - struct emit_callback *ecbdata,
483 - const char *line, int len)
481 +static void emit_line_checked(const char *reset,
482 + struct emit_callback *ecbdata,
483 + const char *line, int len,
484 + enum color_diff color,
485 + unsigned ws_error_highlight,
486 + char sign)
487 {
485 - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
486 - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
488 + const char *set = diff_get_color(ecbdata->color_diff, color);
489 + const char *ws = NULL;
490
488 - if (!*ws)
489 - emit_line_0(ecbdata->opt, set, reset, '+', line, len);
490 - else if (new_blank_line_at_eof(ecbdata, line, len))
491 + if (ecbdata->opt->ws_error_highlight & ws_error_highlight) {
492 + ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
493 + if (!*ws)
494 + ws = NULL;
495 + }
496 +
497 + if (!ws)
498 + emit_line_0(ecbdata->opt, set, reset, sign, line, len);
499 + else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
500 /* Blank line at EOF - paint '+' as well */
492 - emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
501 + emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
502 else {
503 /* Emit just the prefix, then the rest. */
495 - emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
504 + emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
505 ws_check_emit(line, len, ecbdata->ws_rule,
506 ecbdata->opt->file, set, reset, ws);
507 }
508 }
509
501 -static void emit_del_line(const char *reset,
510 +static void emit_add_line(const char *reset,
511 struct emit_callback *ecbdata,
512 const char *line, int len)
513 {
505 - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_OLD);
514 + emit_line_checked(reset, ecbdata, line, len,
515 + DIFF_FILE_NEW, WSEH_NEW, '+');
516 +}
517
507 - emit_line_0(ecbdata->opt, set, reset, '-', line, len);
518 +static void emit_del_line(const char *reset,
519 + struct emit_callback *ecbdata,
520 + const char *line, int len)
521 +{
522 + emit_line_checked(reset, ecbdata, line, len,
523 + DIFF_FILE_OLD, WSEH_OLD, '-');
524 }
525
526 static void emit_context_line(const char *reset,
527 struct emit_callback *ecbdata,
528 const char *line, int len)
529 {
514 - const char *set = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
515 -
516 - emit_line_0(ecbdata->opt, set, reset, ' ', line, len);
530 + emit_line_checked(reset, ecbdata, line, len,
531 + DIFF_PLAIN, WSEH_CONTEXT, ' ');
532 }
533
534 static void emit_hunk_header(struct emit_callback *ecbdata,
@@ -3250,6 +3265,7 @@ void diff_setup(struct diff_options *options)
3265 options->rename_limit = -1;
3266 options->dirstat_permille = diff_dirstat_permille_default;
3267 options->context = diff_context_default;
3268 + options->ws_error_highlight = WSEH_NEW;
3269 DIFF_OPT_SET(options, RENAME_EMPTY);
3270
3271 /* pathchange left =NULL by default */
@@ -3636,6 +3652,40 @@ static void enable_patch_output(int *fmt) {
3652 *fmt |= DIFF_FORMAT_PATCH;
3653 }
3654
3655 +static int parse_one_token(const char **arg, const char *token)
3656 +{
3657 + return skip_prefix(*arg, token, arg) && (!**arg || **arg == ',');
3658 +}
3659 +
3660 +static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
3661 +{
3662 + const char *orig_arg = arg;
3663 + unsigned val = 0;
3664 + while (*arg) {
3665 + if (parse_one_token(&arg, "none"))
3666 + val = 0;
3667 + else if (parse_one_token(&arg, "default"))
3668 + val = WSEH_NEW;
3669 + else if (parse_one_token(&arg, "all"))
3670 + val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
3671 + else if (parse_one_token(&arg, "new"))
3672 + val |= WSEH_NEW;
3673 + else if (parse_one_token(&arg, "old"))
3674 + val |= WSEH_OLD;
3675 + else if (parse_one_token(&arg, "context"))
3676 + val |= WSEH_CONTEXT;
3677 + else {
3678 + error("unknown value after ws-error-highlight=%.*s",
3679 + (int)(arg - orig_arg), orig_arg);
3680 + return 0;
3681 + }
3682 + if (*arg)
3683 + arg++;
3684 + }
3685 + opt->ws_error_highlight = val;
3686 + return 1;
3687 +}
3688 +
3689 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
3690 {
3691 const char *arg = av[0];
@@ -3833,6 +3883,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
3883 DIFF_OPT_SET(options, SUBMODULE_LOG);
3884 else if (skip_prefix(arg, "--submodule=", &arg))
3885 return parse_submodule_opt(options, arg);
3886 + else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
3887 + return parse_ws_error_highlight(options, arg);
3888
3889 /* misc options */
3890 else if (!strcmp(arg, "-z"))
diff.h
+5
@@ -137,6 +137,11 @@ struct diff_options {
137 int dirstat_permille;
138 int setup;
139 int abbrev;
140 +/* white-space error highlighting */
141 +#define WSEH_NEW 1
142 +#define WSEH_CONTEXT 2
143 +#define WSEH_OLD 4
144 + unsigned ws_error_highlight;
145 const char *prefix;
146 int prefix_length;
147 const char *stat_sep;
t/t4015-diff-whitespace.sh
+96
@@ -838,4 +838,100 @@ test_expect_success 'diff that introduces a line with only tabs' '
838 test_cmp expected current
839 '
840
841 +test_expect_success 'diff that introduces and removes ws breakages' '
842 + git reset --hard &&
843 + {
844 + echo "0. blank-at-eol " &&
845 + echo "1. blank-at-eol "
846 + } >x &&
847 + git commit -a --allow-empty -m preimage &&
848 + {
849 + echo "0. blank-at-eol " &&
850 + echo "1. still-blank-at-eol " &&
851 + echo "2. and a new line "
852 + } >x &&
853 +
854 + git -c color.diff=always diff |
855 + test_decode_color >current &&
856 +
857 + cat >expected <<-\EOF &&
858 + <BOLD>diff --git a/x b/x<RESET>
859 + <BOLD>index d0233a2..700886e 100644<RESET>
860 + <BOLD>--- a/x<RESET>
861 + <BOLD>+++ b/x<RESET>
862 + <CYAN>@@ -1,2 +1,3 @@<RESET>
863 + 0. blank-at-eol <RESET>
864 + <RED>-1. blank-at-eol <RESET>
865 + <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
866 + <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
867 + EOF
868 +
869 + test_cmp expected current
870 +'
871 +
872 +test_expect_success 'the same with --ws-error-highlight' '
873 + git reset --hard &&
874 + {
875 + echo "0. blank-at-eol " &&
876 + echo "1. blank-at-eol "
877 + } >x &&
878 + git commit -a --allow-empty -m preimage &&
879 + {
880 + echo "0. blank-at-eol " &&
881 + echo "1. still-blank-at-eol " &&
882 + echo "2. and a new line "
883 + } >x &&
884 +
885 + git -c color.diff=always diff --ws-error-highlight=default,old |
886 + test_decode_color >current &&
887 +
888 + cat >expected <<-\EOF &&
889 + <BOLD>diff --git a/x b/x<RESET>
890 + <BOLD>index d0233a2..700886e 100644<RESET>
891 + <BOLD>--- a/x<RESET>
892 + <BOLD>+++ b/x<RESET>
893 + <CYAN>@@ -1,2 +1,3 @@<RESET>
894 + 0. blank-at-eol <RESET>
895 + <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
896 + <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
897 + <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
898 + EOF
899 +
900 + test_cmp expected current &&
901 +
902 + git -c color.diff=always diff --ws-error-highlight=all |
903 + test_decode_color >current &&
904 +
905 + cat >expected <<-\EOF &&
906 + <BOLD>diff --git a/x b/x<RESET>
907 + <BOLD>index d0233a2..700886e 100644<RESET>
908 + <BOLD>--- a/x<RESET>
909 + <BOLD>+++ b/x<RESET>
910 + <CYAN>@@ -1,2 +1,3 @@<RESET>
911 + <RESET>0. blank-at-eol<RESET><BLUE> <RESET>
912 + <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
913 + <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
914 + <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
915 + EOF
916 +
917 + test_cmp expected current &&
918 +
919 + git -c color.diff=always diff --ws-error-highlight=none |
920 + test_decode_color >current &&
921 +
922 + cat >expected <<-\EOF &&
923 + <BOLD>diff --git a/x b/x<RESET>
924 + <BOLD>index d0233a2..700886e 100644<RESET>
925 + <BOLD>--- a/x<RESET>
926 + <BOLD>+++ b/x<RESET>
927 + <CYAN>@@ -1,2 +1,3 @@<RESET>
928 + 0. blank-at-eol <RESET>
929 + <RED>-1. blank-at-eol <RESET>
930 + <GREEN>+1. still-blank-at-eol <RESET>
931 + <GREEN>+2. and a new line <RESET>
932 + EOF
933 +
934 + test_cmp expected current
935 +'
936 +
937 test_done