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>
This commit is contained in:
committed by
Junio C Hamano
parent
6555b196f0
commit
882749a04f
139
diff.c
139
diff.c
@ -572,16 +572,68 @@ static void diff_words_append(char *line, unsigned long len,
|
||||
buffer->text.ptr[buffer->text.size] = '\0';
|
||||
}
|
||||
|
||||
struct diff_words_style_elem
|
||||
{
|
||||
const char *prefix;
|
||||
const char *suffix;
|
||||
const char *color; /* NULL; filled in by the setup code if
|
||||
* color is enabled */
|
||||
};
|
||||
|
||||
struct diff_words_style
|
||||
{
|
||||
enum diff_words_type type;
|
||||
struct diff_words_style_elem new, old, ctx;
|
||||
const char *newline;
|
||||
};
|
||||
|
||||
struct diff_words_style diff_words_styles[] = {
|
||||
{ DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
|
||||
{ DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
|
||||
{ DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
|
||||
};
|
||||
|
||||
struct diff_words_data {
|
||||
struct diff_words_buffer minus, plus;
|
||||
const char *current_plus;
|
||||
FILE *file;
|
||||
regex_t *word_regex;
|
||||
enum diff_words_type type;
|
||||
struct diff_words_style *style;
|
||||
};
|
||||
|
||||
static int fn_out_diff_words_write_helper(FILE *fp,
|
||||
struct diff_words_style_elem *st_el,
|
||||
const char *newline,
|
||||
size_t count, const char *buf)
|
||||
{
|
||||
while (count) {
|
||||
char *p = memchr(buf, '\n', count);
|
||||
if (p != buf) {
|
||||
if (st_el->color && fputs(st_el->color, fp) < 0)
|
||||
return -1;
|
||||
if (fputs(st_el->prefix, fp) < 0 ||
|
||||
fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
|
||||
fputs(st_el->suffix, fp) < 0)
|
||||
return -1;
|
||||
if (st_el->color && *st_el->color
|
||||
&& fputs(GIT_COLOR_RESET, fp) < 0)
|
||||
return -1;
|
||||
}
|
||||
if (!p)
|
||||
return 0;
|
||||
if (fputs(newline, fp) < 0)
|
||||
return -1;
|
||||
count -= p + 1 - buf;
|
||||
buf = p + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
|
||||
{
|
||||
struct diff_words_data *diff_words = priv;
|
||||
struct diff_words_style *style = diff_words->style;
|
||||
int minus_first, minus_len, plus_first, plus_len;
|
||||
const char *minus_begin, *minus_end, *plus_begin, *plus_end;
|
||||
|
||||
@ -605,16 +657,17 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
|
||||
plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
|
||||
|
||||
if (diff_words->current_plus != plus_begin)
|
||||
fwrite(diff_words->current_plus,
|
||||
plus_begin - diff_words->current_plus, 1,
|
||||
diff_words->file);
|
||||
fn_out_diff_words_write_helper(diff_words->file,
|
||||
&style->ctx, style->newline,
|
||||
plus_begin - diff_words->current_plus,
|
||||
diff_words->current_plus);
|
||||
if (minus_begin != minus_end)
|
||||
color_fwrite_lines(diff_words->file,
|
||||
diff_get_color(1, DIFF_FILE_OLD),
|
||||
fn_out_diff_words_write_helper(diff_words->file,
|
||||
&style->old, style->newline,
|
||||
minus_end - minus_begin, minus_begin);
|
||||
if (plus_begin != plus_end)
|
||||
color_fwrite_lines(diff_words->file,
|
||||
diff_get_color(1, DIFF_FILE_NEW),
|
||||
fn_out_diff_words_write_helper(diff_words->file,
|
||||
&style->new, style->newline,
|
||||
plus_end - plus_begin, plus_begin);
|
||||
|
||||
diff_words->current_plus = plus_end;
|
||||
@ -697,11 +750,12 @@ static void diff_words_show(struct diff_words_data *diff_words)
|
||||
xdemitconf_t xecfg;
|
||||
xdemitcb_t ecb;
|
||||
mmfile_t minus, plus;
|
||||
struct diff_words_style *style = diff_words->style;
|
||||
|
||||
/* special case: only removal */
|
||||
if (!diff_words->plus.text.size) {
|
||||
color_fwrite_lines(diff_words->file,
|
||||
diff_get_color(1, DIFF_FILE_OLD),
|
||||
fn_out_diff_words_write_helper(diff_words->file,
|
||||
&style->old, style->newline,
|
||||
diff_words->minus.text.size, diff_words->minus.text.ptr);
|
||||
diff_words->minus.text.size = 0;
|
||||
return;
|
||||
@ -722,10 +776,10 @@ static void diff_words_show(struct diff_words_data *diff_words)
|
||||
free(plus.ptr);
|
||||
if (diff_words->current_plus != diff_words->plus.text.ptr +
|
||||
diff_words->plus.text.size)
|
||||
fwrite(diff_words->current_plus,
|
||||
fn_out_diff_words_write_helper(diff_words->file,
|
||||
&style->ctx, style->newline,
|
||||
diff_words->plus.text.ptr + diff_words->plus.text.size
|
||||
- diff_words->current_plus, 1,
|
||||
diff_words->file);
|
||||
- diff_words->current_plus, diff_words->current_plus);
|
||||
diff_words->minus.text.size = diff_words->plus.text.size = 0;
|
||||
}
|
||||
|
||||
@ -837,6 +891,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
|
||||
|
||||
if (len < 1) {
|
||||
emit_line(ecbdata->file, reset, reset, line, len);
|
||||
if (ecbdata->diff_words
|
||||
&& ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
|
||||
fputs("~\n", ecbdata->file);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -851,9 +908,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
|
||||
return;
|
||||
}
|
||||
diff_words_flush(ecbdata);
|
||||
line++;
|
||||
len--;
|
||||
emit_line(ecbdata->file, plain, reset, line, len);
|
||||
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
|
||||
emit_line(ecbdata->file, plain, reset, line, len);
|
||||
fputs("~\n", ecbdata->file);
|
||||
} else {
|
||||
/* don't print the prefix character */
|
||||
emit_line(ecbdata->file, plain, reset, line+1, len-1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1755,10 +1816,13 @@ static void builtin_diff(const char *name_a,
|
||||
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
|
||||
else if (!prefixcmp(diffopts, "-u"))
|
||||
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
|
||||
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) {
|
||||
if (o->word_diff) {
|
||||
int i;
|
||||
|
||||
ecbdata.diff_words =
|
||||
xcalloc(1, sizeof(struct diff_words_data));
|
||||
ecbdata.diff_words->file = o->file;
|
||||
ecbdata.diff_words->type = o->word_diff;
|
||||
if (!o->word_regex)
|
||||
o->word_regex = userdiff_word_regex(one);
|
||||
if (!o->word_regex)
|
||||
@ -1774,10 +1838,23 @@ static void builtin_diff(const char *name_a,
|
||||
die ("Invalid regular expression: %s",
|
||||
o->word_regex);
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
|
||||
if (o->word_diff == diff_words_styles[i].type) {
|
||||
ecbdata.diff_words->style =
|
||||
&diff_words_styles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DIFF_OPT_TST(o, COLOR_DIFF)) {
|
||||
struct diff_words_style *st = ecbdata.diff_words->style;
|
||||
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
|
||||
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
|
||||
st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
|
||||
}
|
||||
}
|
||||
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
|
||||
&xpp, &xecfg, &ecb);
|
||||
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
|
||||
if (o->word_diff)
|
||||
free_diff_words_data(&ecbdata);
|
||||
if (textconv_one)
|
||||
free(mf1.ptr);
|
||||
@ -2845,13 +2922,37 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
|
||||
DIFF_OPT_CLR(options, COLOR_DIFF);
|
||||
else if (!strcmp(arg, "--color-words")) {
|
||||
DIFF_OPT_SET(options, COLOR_DIFF);
|
||||
DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
|
||||
options->word_diff = DIFF_WORDS_COLOR;
|
||||
}
|
||||
else if (!prefixcmp(arg, "--color-words=")) {
|
||||
DIFF_OPT_SET(options, COLOR_DIFF);
|
||||
DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
|
||||
options->word_diff = DIFF_WORDS_COLOR;
|
||||
options->word_regex = arg + 14;
|
||||
}
|
||||
else if (!strcmp(arg, "--word-diff")) {
|
||||
if (options->word_diff == DIFF_WORDS_NONE)
|
||||
options->word_diff = DIFF_WORDS_PLAIN;
|
||||
}
|
||||
else if (!prefixcmp(arg, "--word-diff=")) {
|
||||
const char *type = arg + 12;
|
||||
if (!strcmp(type, "plain"))
|
||||
options->word_diff = DIFF_WORDS_PLAIN;
|
||||
else if (!strcmp(type, "color")) {
|
||||
DIFF_OPT_SET(options, COLOR_DIFF);
|
||||
options->word_diff = DIFF_WORDS_COLOR;
|
||||
}
|
||||
else if (!strcmp(type, "porcelain"))
|
||||
options->word_diff = DIFF_WORDS_PORCELAIN;
|
||||
else if (!strcmp(type, "none"))
|
||||
options->word_diff = DIFF_WORDS_NONE;
|
||||
else
|
||||
die("bad --word-diff argument: %s", type);
|
||||
}
|
||||
else if (!prefixcmp(arg, "--word-diff-regex=")) {
|
||||
if (options->word_diff == DIFF_WORDS_NONE)
|
||||
options->word_diff = DIFF_WORDS_PLAIN;
|
||||
options->word_regex = arg + 18;
|
||||
}
|
||||
else if (!strcmp(arg, "--exit-code"))
|
||||
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
|
||||
else if (!strcmp(arg, "--quiet"))
|
||||
|
||||
Reference in New Issue
Block a user