@matheusbsbs / matheus-bagliano / commits / 882749a04f
Fork 0

diff: add --word-diff option that generalizes --color-words

This teaches the --color-words engine a more general interface that supports two new modes: * --word-diff=plain, inspired by the 'wdiff' utility (most similar to 'wdiff -n <old> <new>'): uses delimiters [-removed-] and {+added+} * --word-diff=porcelain, which generates an ad-hoc machine readable format: - each diff unit is prefixed by [-+ ] and terminated by newline as in unified diff - newlines in the input are output as a line consisting only of a tilde '~' Both of these formats still support color if it is enabled, using it to highlight the differences. --color-words becomes a synonym for --word-diff=color, which is the color-only format. Also adds some compatibility/convenience options. Thanks to Junio C Hamano and Miles Bader for good ideas. Signed-off-by: Thomas Rast <trast@student.ethz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Thomas Rast committed Apr 14, 2010 at 17:59 UTC 882749a04f828fccd795deec4d0bf10ba09ae549
7 files changed +288 -54
Documentation/diff-options.txt
+36 -4
@@ -126,11 +126,39 @@ any of those replacements occurred.
126 gives the default to color output.
127 Same as `--color=never`.
128
129 ---color-words[=<regex>]::
130 - Show colored word diff, i.e., color words which have changed.
131 - By default, words are separated by whitespace.
129 +--word-diff[=<mode>]::
130 + Show a word diff, using the <mode> to delimit changed words.
131 + By default, words are delimited by whitespace; see
132 + `--word-diff-regex` below. The <mode> defaults to 'plain', and
133 + must be one of:
134 ++
135 +--
136 +color::
137 + Highlight changed words using only colors. Implies `--color`.
138 +plain::
139 + Show words as `[-removed-]` and `{+added+}`. Makes no
140 + attempts to escape the delimiters if they appear in the input,
141 + so the output may be ambiguous.
142 +porcelain::
143 + Use a special line-based format intended for script
144 + consumption. Added/removed/unchanged runs are printed in the
145 + usual unified diff format, starting with a `+`/`-`/` `
146 + character at the beginning of the line and extending to the
147 + end of the line. Newlines in the input are represented by a
148 + tilde `~` on a line of its own.
149 +none::
150 + Disable word diff again.
151 +--
152 ++
153 +Note that despite the name of the first mode, color is used to
154 +highlight the changed parts in all modes if enabled.
155 +
156 +--word-diff-regex=<regex>::
157 + Use <regex> to decide what a word is, instead of considering
158 + runs of non-whitespace to be a word. Also implies
159 + `--word-diff` unless it was already enabled.
160 +
133 -When a <regex> is specified, every non-overlapping match of the
161 +Every non-overlapping match of the
162 <regex> is considered a word. Anything between these matches is
163 considered whitespace and ignored(!) for the purposes of finding
164 differences. You may want to append `|[^[:space:]]` to your regular
@@ -142,6 +170,10 @@ The regex can also be set via a diff driver or configuration option, see
170 linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly
171 overrides any diff driver or configuration setting. Diff drivers
172 override configuration settings.
173 +
174 +--color-words[=<regex>]::
175 + Equivalent to `--word-diff=color` plus (if a regex was
176 + specified) `--word-diff-regex=<regex>`.
177 endif::git-format-patch[]
178
179 --no-renames::
Documentation/gitattributes.txt
+1 -1
@@ -360,7 +360,7 @@ patterns are available:
360 Customizing word diff
361 ^^^^^^^^^^^^^^^^^^^^^
362
363 -You can customize the rules that `git diff --color-words` uses to
363 +You can customize the rules that `git diff --word-diff` uses to
364 split words in a line, by specifying an appropriate regular expression
365 in the "diff.*.wordRegex" configuration variable. For example, in TeX
366 a backslash followed by a sequence of letters forms a command, but
color.c
-28
@@ -211,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
211 va_end(args);
212 return r;
213 }
214 -
215 -/*
216 - * This function splits the buffer by newlines and colors the lines individually.
217 - *
218 - * Returns 0 on success.
219 - */
220 -int color_fwrite_lines(FILE *fp, const char *color,
221 - size_t count, const char *buf)
222 -{
223 - if (!*color)
224 - return fwrite(buf, count, 1, fp) != 1;
225 - while (count) {
226 - char *p = memchr(buf, '\n', count);
227 - if (p != buf && (fputs(color, fp) < 0 ||
228 - fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
229 - fputs(GIT_COLOR_RESET, fp) < 0))
230 - return -1;
231 - if (!p)
232 - return 0;
233 - if (fputc('\n', fp) < 0)
234 - return -1;
235 - count -= p + 1 - buf;
236 - buf = p + 1;
237 - }
238 - return 0;
239 -}
240 -
241 -
color.h
-1
@@ -61,6 +61,5 @@ __attribute__((format (printf, 3, 4)))
61 int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
62 __attribute__((format (printf, 3, 4)))
63 int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
64 -int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
64
65 #endif /* COLOR_H */
diff.c
+120 -19
@@ -572,16 +572,68 @@ static void diff_words_append(char *line, unsigned long len,
572 buffer->text.ptr[buffer->text.size] = '\0';
573 }
574
575 +struct diff_words_style_elem
576 +{
577 + const char *prefix;
578 + const char *suffix;
579 + const char *color; /* NULL; filled in by the setup code if
580 + * color is enabled */
581 +};
582 +
583 +struct diff_words_style
584 +{
585 + enum diff_words_type type;
586 + struct diff_words_style_elem new, old, ctx;
587 + const char *newline;
588 +};
589 +
590 +struct diff_words_style diff_words_styles[] = {
591 + { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
592 + { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
593 + { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
594 +};
595 +
596 struct diff_words_data {
597 struct diff_words_buffer minus, plus;
598 const char *current_plus;
599 FILE *file;
600 regex_t *word_regex;
601 + enum diff_words_type type;
602 + struct diff_words_style *style;
603 };
604
605 +static int fn_out_diff_words_write_helper(FILE *fp,
606 + struct diff_words_style_elem *st_el,
607 + const char *newline,
608 + size_t count, const char *buf)
609 +{
610 + while (count) {
611 + char *p = memchr(buf, '\n', count);
612 + if (p != buf) {
613 + if (st_el->color && fputs(st_el->color, fp) < 0)
614 + return -1;
615 + if (fputs(st_el->prefix, fp) < 0 ||
616 + fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
617 + fputs(st_el->suffix, fp) < 0)
618 + return -1;
619 + if (st_el->color && *st_el->color
620 + && fputs(GIT_COLOR_RESET, fp) < 0)
621 + return -1;
622 + }
623 + if (!p)
624 + return 0;
625 + if (fputs(newline, fp) < 0)
626 + return -1;
627 + count -= p + 1 - buf;
628 + buf = p + 1;
629 + }
630 + return 0;
631 +}
632 +
633 static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
634 {
635 struct diff_words_data *diff_words = priv;
636 + struct diff_words_style *style = diff_words->style;
637 int minus_first, minus_len, plus_first, plus_len;
638 const char *minus_begin, *minus_end, *plus_begin, *plus_end;
639
@@ -605,16 +657,17 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
657 plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
658
659 if (diff_words->current_plus != plus_begin)
608 - fwrite(diff_words->current_plus,
609 - plus_begin - diff_words->current_plus, 1,
610 - diff_words->file);
660 + fn_out_diff_words_write_helper(diff_words->file,
661 + &style->ctx, style->newline,
662 + plus_begin - diff_words->current_plus,
663 + diff_words->current_plus);
664 if (minus_begin != minus_end)
612 - color_fwrite_lines(diff_words->file,
613 - diff_get_color(1, DIFF_FILE_OLD),
665 + fn_out_diff_words_write_helper(diff_words->file,
666 + &style->old, style->newline,
667 minus_end - minus_begin, minus_begin);
668 if (plus_begin != plus_end)
616 - color_fwrite_lines(diff_words->file,
617 - diff_get_color(1, DIFF_FILE_NEW),
669 + fn_out_diff_words_write_helper(diff_words->file,
670 + &style->new, style->newline,
671 plus_end - plus_begin, plus_begin);
672
673 diff_words->current_plus = plus_end;
@@ -697,11 +750,12 @@ static void diff_words_show(struct diff_words_data *diff_words)
750 xdemitconf_t xecfg;
751 xdemitcb_t ecb;
752 mmfile_t minus, plus;
753 + struct diff_words_style *style = diff_words->style;
754
755 /* special case: only removal */
756 if (!diff_words->plus.text.size) {
703 - color_fwrite_lines(diff_words->file,
704 - diff_get_color(1, DIFF_FILE_OLD),
757 + fn_out_diff_words_write_helper(diff_words->file,
758 + &style->old, style->newline,
759 diff_words->minus.text.size, diff_words->minus.text.ptr);
760 diff_words->minus.text.size = 0;
761 return;
@@ -722,10 +776,10 @@ static void diff_words_show(struct diff_words_data *diff_words)
776 free(plus.ptr);
777 if (diff_words->current_plus != diff_words->plus.text.ptr +
778 diff_words->plus.text.size)
725 - fwrite(diff_words->current_plus,
779 + fn_out_diff_words_write_helper(diff_words->file,
780 + &style->ctx, style->newline,
781 diff_words->plus.text.ptr + diff_words->plus.text.size
727 - - diff_words->current_plus, 1,
728 - diff_words->file);
782 + - diff_words->current_plus, diff_words->current_plus);
783 diff_words->minus.text.size = diff_words->plus.text.size = 0;
784 }
785
@@ -837,6 +891,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
891
892 if (len < 1) {
893 emit_line(ecbdata->file, reset, reset, line, len);
894 + if (ecbdata->diff_words
895 + && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
896 + fputs("~\n", ecbdata->file);
897 return;
898 }
899
@@ -851,9 +908,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
908 return;
909 }
910 diff_words_flush(ecbdata);
854 - line++;
855 - len--;
856 - emit_line(ecbdata->file, plain, reset, line, len);
911 + if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
912 + emit_line(ecbdata->file, plain, reset, line, len);
913 + fputs("~\n", ecbdata->file);
914 + } else {
915 + /* don't print the prefix character */
916 + emit_line(ecbdata->file, plain, reset, line+1, len-1);
917 + }
918 return;
919 }
920
@@ -1755,10 +1816,13 @@ static void builtin_diff(const char *name_a,
1816 xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
1817 else if (!prefixcmp(diffopts, "-u"))
1818 xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
1758 - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) {
1819 + if (o->word_diff) {
1820 + int i;
1821 +
1822 ecbdata.diff_words =
1823 xcalloc(1, sizeof(struct diff_words_data));
1824 ecbdata.diff_words->file = o->file;
1825 + ecbdata.diff_words->type = o->word_diff;
1826 if (!o->word_regex)
1827 o->word_regex = userdiff_word_regex(one);
1828 if (!o->word_regex)
@@ -1774,10 +1838,23 @@ static void builtin_diff(const char *name_a,
1838 die ("Invalid regular expression: %s",
1839 o->word_regex);
1840 }
1841 + for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
1842 + if (o->word_diff == diff_words_styles[i].type) {
1843 + ecbdata.diff_words->style =
1844 + &diff_words_styles[i];
1845 + break;
1846 + }
1847 + }
1848 + if (DIFF_OPT_TST(o, COLOR_DIFF)) {
1849 + struct diff_words_style *st = ecbdata.diff_words->style;
1850 + st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
1851 + st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
1852 + st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
1853 + }
1854 }
1855 xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
1856 &xpp, &xecfg, &ecb);
1780 - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
1857 + if (o->word_diff)
1858 free_diff_words_data(&ecbdata);
1859 if (textconv_one)
1860 free(mf1.ptr);
@@ -2845,13 +2922,37 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
2922 DIFF_OPT_CLR(options, COLOR_DIFF);
2923 else if (!strcmp(arg, "--color-words")) {
2924 DIFF_OPT_SET(options, COLOR_DIFF);
2848 - DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
2925 + options->word_diff = DIFF_WORDS_COLOR;
2926 }
2927 else if (!prefixcmp(arg, "--color-words=")) {
2928 DIFF_OPT_SET(options, COLOR_DIFF);
2852 - DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
2929 + options->word_diff = DIFF_WORDS_COLOR;
2930 options->word_regex = arg + 14;
2931 }
2932 + else if (!strcmp(arg, "--word-diff")) {
2933 + if (options->word_diff == DIFF_WORDS_NONE)
2934 + options->word_diff = DIFF_WORDS_PLAIN;
2935 + }
2936 + else if (!prefixcmp(arg, "--word-diff=")) {
2937 + const char *type = arg + 12;
2938 + if (!strcmp(type, "plain"))
2939 + options->word_diff = DIFF_WORDS_PLAIN;
2940 + else if (!strcmp(type, "color")) {
2941 + DIFF_OPT_SET(options, COLOR_DIFF);
2942 + options->word_diff = DIFF_WORDS_COLOR;
2943 + }
2944 + else if (!strcmp(type, "porcelain"))
2945 + options->word_diff = DIFF_WORDS_PORCELAIN;
2946 + else if (!strcmp(type, "none"))
2947 + options->word_diff = DIFF_WORDS_NONE;
2948 + else
2949 + die("bad --word-diff argument: %s", type);
2950 + }
2951 + else if (!prefixcmp(arg, "--word-diff-regex=")) {
2952 + if (options->word_diff == DIFF_WORDS_NONE)
2953 + options->word_diff = DIFF_WORDS_PLAIN;
2954 + options->word_regex = arg + 18;
2955 + }
2956 else if (!strcmp(arg, "--exit-code"))
2957 DIFF_OPT_SET(options, EXIT_WITH_STATUS);
2958 else if (!strcmp(arg, "--quiet"))
diff.h
+9 -1
@@ -54,7 +54,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
54 #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6)
55 #define DIFF_OPT_FOLLOW_RENAMES (1 << 7)
56 #define DIFF_OPT_COLOR_DIFF (1 << 8)
57 -#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9)
57 +/* (1 << 9) unused */
58 #define DIFF_OPT_HAS_CHANGES (1 << 10)
59 #define DIFF_OPT_QUICK (1 << 11)
60 #define DIFF_OPT_NO_INDEX (1 << 12)
@@ -79,6 +79,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
79 #define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag)
80 #define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag)
81
82 +enum diff_words_type {
83 + DIFF_WORDS_NONE = 0,
84 + DIFF_WORDS_PORCELAIN,
85 + DIFF_WORDS_PLAIN,
86 + DIFF_WORDS_COLOR
87 +};
88 +
89 struct diff_options {
90 const char *filter;
91 const char *orderfile;
@@ -108,6 +115,7 @@ struct diff_options {
115 int stat_width;
116 int stat_name_width;
117 const char *word_regex;
118 + enum diff_words_type word_diff;
119
120 /* this is set by diffcore for DIFF_FORMAT_PATCH */
121 int found_changes;
t/t4034-diff-words.sh
+122
@@ -55,6 +55,93 @@ test_expect_success 'word diff with runs of whitespace' '
55
56 '
57
58 +test_expect_success '--word-diff=color' '
59 +
60 + word_diff --word-diff=color
61 +
62 +'
63 +
64 +test_expect_success '--color --word-diff=color' '
65 +
66 + word_diff --color --word-diff=color
67 +
68 +'
69 +
70 +sed 's/#.*$//' > expect <<EOF
71 +diff --git a/pre b/post
72 +index 330b04f..5ed8eff 100644
73 +--- a/pre
74 ++++ b/post
75 +@@ -1,3 +1,7 @@
76 +-h(4)
77 ++h(4),hh[44]
78 +~
79 + # significant space
80 +~
81 + a = b + c
82 +~
83 +~
84 ++aa = a
85 +~
86 +~
87 ++aeff = aeff * ( aaa )
88 +~
89 +EOF
90 +
91 +test_expect_success '--word-diff=porcelain' '
92 +
93 + word_diff --word-diff=porcelain
94 +
95 +'
96 +
97 +cat > expect <<EOF
98 +diff --git a/pre b/post
99 +index 330b04f..5ed8eff 100644
100 +--- a/pre
101 ++++ b/post
102 +@@ -1,3 +1,7 @@
103 +[-h(4)-]{+h(4),hh[44]+}
104 +
105 +a = b + c
106 +
107 +{+aa = a+}
108 +
109 +{+aeff = aeff * ( aaa )+}
110 +EOF
111 +
112 +test_expect_success '--word-diff=plain' '
113 +
114 + word_diff --word-diff=plain
115 +
116 +'
117 +
118 +test_expect_success '--word-diff=plain --no-color' '
119 +
120 + word_diff --word-diff=plain --no-color
121 +
122 +'
123 +
124 +cat > expect <<EOF
125 +<WHITE>diff --git a/pre b/post<RESET>
126 +<WHITE>index 330b04f..5ed8eff 100644<RESET>
127 +<WHITE>--- a/pre<RESET>
128 +<WHITE>+++ b/post<RESET>
129 +<CYAN>@@ -1,3 +1,7 @@<RESET>
130 +<RED>[-h(4)-]<RESET><GREEN>{+h(4),hh[44]+}<RESET>
131 +
132 +a = b + c<RESET>
133 +
134 +<GREEN>{+aa = a+}<RESET>
135 +
136 +<GREEN>{+aeff = aeff * ( aaa )+}<RESET>
137 +EOF
138 +
139 +test_expect_success '--word-diff=plain --color' '
140 +
141 + word_diff --word-diff=plain --color
142 +
143 +'
144 +
145 cat > expect <<\EOF
146 <WHITE>diff --git a/pre b/post<RESET>
147 <WHITE>index 330b04f..5ed8eff 100644<RESET>
@@ -143,6 +230,25 @@ test_expect_success 'command-line overrides config' '
230 word_diff --color-words="[a-z]+"
231 '
232
233 +cat > expect <<\EOF
234 +<WHITE>diff --git a/pre b/post<RESET>
235 +<WHITE>index 330b04f..5ed8eff 100644<RESET>
236 +<WHITE>--- a/pre<RESET>
237 +<WHITE>+++ b/post<RESET>
238 +<CYAN>@@ -1,3 +1,7 @@<RESET>
239 +h(4),<GREEN>{+hh+}<RESET>[44]
240 +
241 +a = b + c<RESET>
242 +
243 +<GREEN>{+aa = a+}<RESET>
244 +
245 +<GREEN>{+aeff = aeff * ( aaa+}<RESET> )
246 +EOF
247 +
248 +test_expect_success 'command-line overrides config: --word-diff-regex' '
249 + word_diff --color --word-diff-regex="[a-z]+"
250 +'
251 +
252 cp expect.non-whitespace-is-word expect
253
254 test_expect_success '.gitattributes override config' '
@@ -209,4 +315,20 @@ test_expect_success 'test when words are only removed at the end' '
315
316 '
317
318 +cat > expect <<\EOF
319 +diff --git a/pre b/post
320 +index 289cb9d..2d06f37 100644
321 +--- a/pre
322 ++++ b/post
323 +@@ -1 +1 @@
324 +-(:
325 ++(
326 +EOF
327 +
328 +test_expect_success '--word-diff=none' '
329 +
330 + word_diff --word-diff=plain --word-diff=none
331 +
332 +'
333 +
334 test_done