};
static void diff_filespec_load_driver(struct diff_filespec *one);
-static char *run_textconv(const char *, struct diff_filespec *, size_t *);
+static size_t fill_textconv(struct userdiff_driver *driver,
+ struct diff_filespec *df, char **outbuf);
static int parse_diff_color_slot(const char *var, int ofs)
{
const char *name_b,
struct diff_filespec *one,
struct diff_filespec *two,
- const char *textconv_one,
- const char *textconv_two,
+ struct userdiff_driver *textconv_one,
+ struct userdiff_driver *textconv_two,
struct diff_options *o)
{
int lc_a, lc_b;
const char *reset = diff_get_color(color_diff, DIFF_RESET);
static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
const char *a_prefix, *b_prefix;
- const char *data_one, *data_two;
+ char *data_one, *data_two;
size_t size_one, size_two;
struct emit_callback ecbdata;
quote_two_c_style(&a_name, a_prefix, name_a, 0);
quote_two_c_style(&b_name, b_prefix, name_b, 0);
- diff_populate_filespec(one, 0);
- diff_populate_filespec(two, 0);
- if (textconv_one) {
- data_one = run_textconv(textconv_one, one, &size_one);
- if (!data_one)
- die("unable to read files to diff");
- }
- else {
- data_one = one->data;
- size_one = one->size;
- }
- if (textconv_two) {
- data_two = run_textconv(textconv_two, two, &size_two);
- if (!data_two)
- die("unable to read files to diff");
- }
- else {
- data_two = two->data;
- size_two = two->size;
- }
+ size_one = fill_textconv(textconv_one, one, &data_one);
+ size_two = fill_textconv(textconv_two, two, &data_two);
memset(&ecbdata, 0, sizeof(ecbdata));
ecbdata.color_diff = color_diff;
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;
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;
xpparam_t xpp;
xdemitconf_t xecfg;
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;
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;
}
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;
}
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;
}
options->b_prefix = b;
}
-static const char *get_textconv(struct diff_filespec *one)
+static struct userdiff_driver *get_textconv(struct diff_filespec *one)
{
if (!DIFF_FILE_VALID(one))
return NULL;
if (!S_ISREG(one->mode))
return NULL;
diff_filespec_load_driver(one);
- return one->driver->textconv;
+ if (!one->driver->textconv)
+ return NULL;
+
+ if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
+ struct notes_cache *c = xmalloc(sizeof(*c));
+ struct strbuf name = STRBUF_INIT;
+
+ strbuf_addf(&name, "textconv/%s", one->driver->name);
+ notes_cache_init(c, name.buf, one->driver->textconv);
+ one->driver->textconv_cache = c;
+ }
+
+ return one->driver;
}
static void builtin_diff(const char *name_a,
const char *set = diff_get_color_opt(o, DIFF_METAINFO);
const char *reset = diff_get_color_opt(o, DIFF_RESET);
const char *a_prefix, *b_prefix;
- const char *textconv_one = NULL, *textconv_two = NULL;
+ struct userdiff_driver *textconv_one = NULL;
+ struct userdiff_driver *textconv_two = NULL;
struct strbuf header = STRBUF_INIT;
if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
}
}
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
-
if (!DIFF_OPT_TST(o, TEXT) &&
- ( (diff_filespec_is_binary(one) && !textconv_one) ||
- (diff_filespec_is_binary(two) && !textconv_two) )) {
+ ( (!textconv_one && diff_filespec_is_binary(one)) ||
+ (!textconv_two && diff_filespec_is_binary(two)) )) {
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
/* Quite common confusing case */
if (mf1.size == mf2.size &&
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
strbuf_reset(&header);
}
- if (textconv_one) {
- size_t size;
- mf1.ptr = run_textconv(textconv_one, one, &size);
- if (!mf1.ptr)
- die("unable to read files to diff");
- mf1.size = size;
- }
- if (textconv_two) {
- size_t size;
- mf2.ptr = run_textconv(textconv_two, two, &size);
- if (!mf2.ptr)
- die("unable to read files to diff");
- mf2.size = size;
- }
+ mf1.size = fill_textconv(textconv_one, one, &mf1.ptr);
+ mf2.size = fill_textconv(textconv_two, two, &mf2.ptr);
pe = diff_funcname_pattern(one);
if (!pe)
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)
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);
- if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
+ if (o->word_diff)
free_diff_words_data(&ecbdata);
if (textconv_one)
free(mf1.ptr);
const char *arg = av[0];
/* Output format options */
- if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+ if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
options->output_format |= DIFF_FORMAT_PATCH;
else if (opt_arg(arg, 'U', "unified", &options->context))
options->output_format |= DIFF_FORMAT_PATCH;
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"))
return strbuf_detach(&buf, outsize);
}
+
+static size_t fill_textconv(struct userdiff_driver *driver,
+ struct diff_filespec *df,
+ char **outbuf)
+{
+ size_t size;
+
+ if (!driver || !driver->textconv) {
+ if (!DIFF_FILE_VALID(df)) {
+ *outbuf = "";
+ return 0;
+ }
+ if (diff_populate_filespec(df, 0))
+ die("unable to read files to diff");
+ *outbuf = df->data;
+ return df->size;
+ }
+
+ if (driver->textconv_cache) {
+ *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
+ &size);
+ if (*outbuf)
+ return size;
+ }
+
+ *outbuf = run_textconv(driver->textconv, df, &size);
+ if (!*outbuf)
+ die("unable to read files to diff");
+
+ if (driver->textconv_cache) {
+ /* ignore errors, as we might be in a readonly repository */
+ notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
+ size);
+ /*
+ * we could save up changes and flush them all at the end,
+ * but we would need an extra call after all diffing is done.
+ * Since generating a cache entry is the slow path anyway,
+ * this extra overhead probably isn't a big deal.
+ */
+ notes_cache_write(driver->textconv_cache);
+ }
+
+ return size;
+}