X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=diff.c;h=494f5601e97e7395e647296bc7f8090331a47c43;hb=82c531b3b6d24040443ba739e150bc06ecc762ea;hp=0182237c2dab7fa3aa7e916f2eb8bc75865c0b9a;hpb=0974c117ff4e17e8b6300519cae0fbc67d34adaa;p=git.git diff --git a/diff.c b/diff.c index 0182237c2..494f5601e 100644 --- a/diff.c +++ b/diff.c @@ -14,6 +14,7 @@ #include "userdiff.h" #include "sigchain.h" #include "submodule.h" +#include "ll-merge.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -43,7 +44,8 @@ static char diff_colors[][COLOR_MAXLEN] = { }; 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) { @@ -194,6 +196,7 @@ struct emit_callback { struct diff_words_data *diff_words; int *found_changesp; FILE *file; + struct strbuf *header; }; static int count_lines(const char *data, int size) @@ -464,8 +467,8 @@ static void emit_rewrite_diff(const char *name_a, 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; @@ -476,7 +479,7 @@ static void emit_rewrite_diff(const char *name_a, 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; @@ -498,26 +501,8 @@ static void emit_rewrite_diff(const char *name_a, 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; @@ -549,6 +534,10 @@ static void emit_rewrite_diff(const char *name_a, emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); + if (textconv_one) + free((char *)data_one); + if (textconv_two) + free((char *)data_two); } struct diff_words_buffer { @@ -571,16 +560,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; @@ -604,16 +645,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; @@ -694,13 +736,13 @@ static void diff_words_show(struct diff_words_data *diff_words) { xpparam_t xpp; 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; @@ -716,15 +758,15 @@ static void diff_words_show(struct diff_words_data *diff_words) /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); free(minus.ptr); 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; } @@ -797,6 +839,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + if (ecbdata->header) { + fprintf(ecbdata->file, "%s", ecbdata->header->buf); + strbuf_reset(ecbdata->header); + ecbdata->header = NULL; + } *(ecbdata->found_changesp) = 1; if (ecbdata->label_path[0]) { @@ -831,6 +878,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; } @@ -845,9 +895,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; } @@ -1367,37 +1421,32 @@ static void free_diffstat_info(struct diffstat_t *diffstat) struct checkdiff_t { const char *filename; int lineno; + int conflict_marker_size; struct diff_options *o; unsigned ws_rule; unsigned status; }; -static int is_conflict_marker(const char *line, unsigned long len) +static int is_conflict_marker(const char *line, int marker_size, unsigned long len) { char firstchar; int cnt; - if (len < 8) + if (len < marker_size + 1) return 0; firstchar = line[0]; switch (firstchar) { - case '=': case '>': case '<': + case '=': case '>': case '<': case '|': break; default: return 0; } - for (cnt = 1; cnt < 7; cnt++) + for (cnt = 1; cnt < marker_size; cnt++) if (line[cnt] != firstchar) return 0; - /* line[0] thru line[6] are same as firstchar */ - if (firstchar == '=') { - /* divider between ours and theirs? */ - if (len != 8 || line[7] != '\n') - return 0; - } else if (len < 8 || !isspace(line[7])) { - /* not divider before ours nor after theirs */ + /* line[1] thru line[marker_size-1] are same as firstchar */ + if (len < marker_size + 1 || !isspace(line[marker_size])) return 0; - } return 1; } @@ -1405,6 +1454,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF); + int marker_size = data->conflict_marker_size; const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE); const char *reset = diff_get_color(color_diff, DIFF_RESET); const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); @@ -1413,7 +1463,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (is_conflict_marker(line + 1, len - 1)) { + if (is_conflict_marker(line + 1, marker_size, len - 1)) { data->status |= 1; fprintf(data->o->file, "%s:%d: leftover conflict marker\n", @@ -1579,14 +1629,26 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const 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, @@ -1603,7 +1665,9 @@ 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) && (!one->mode || S_ISGITLINK(one->mode)) && @@ -1611,7 +1675,7 @@ static void builtin_diff(const char *name_a, const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one ? one->path : two->path, - one->sha1, two->sha1, + one->sha1, two->sha1, two->dirty_submodule, del, add, reset); return; } @@ -1638,25 +1702,26 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset); - fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + /* * we do not run diff between different kind * of objects. @@ -1666,6 +1731,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); o->found_changes = 1; @@ -1673,16 +1740,17 @@ static void builtin_diff(const char *name_a, } } - 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)) goto free_ab_and_return; + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) emit_binary_diff(o->file, &mf1, &mf2); else @@ -1695,25 +1763,17 @@ static void builtin_diff(const char *name_a, const char *diffopts = getenv("GIT_DIFF_OPTS"); xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; struct emit_callback ecbdata; const struct userdiff_funcname *pe; - 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; + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); } + 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) pe = diff_funcname_pattern(two); @@ -1728,6 +1788,7 @@ static void builtin_diff(const char *name_a, if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; + ecbdata.header = header.len ? &header : NULL; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; @@ -1740,10 +1801,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) @@ -1759,10 +1823,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)) + &xpp, &xecfg); + if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) free(mf1.ptr); @@ -1772,6 +1849,7 @@ static void builtin_diff(const char *name_a, } free_ab_and_return: + strbuf_release(&header); diff_free_filespec_data(one); diff_free_filespec_data(two); free(a_one); @@ -1813,13 +1891,12 @@ static void builtin_diffstat(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } free_and_return: @@ -1844,6 +1921,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.lineno = 0; data.o = o; data.ws_rule = whitespace_rule(attr_path); + data.conflict_marker_size = ll_merge_marker_size(attr_path); if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1860,14 +1938,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = XDF_NEED_MINIMAL; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); if (data.ws_rule & WS_BLANK_AT_EOF) { struct emit_callback ecbdata; @@ -1981,7 +2058,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * If ce is marked as "assume unchanged", there is no * guarantee that work tree matches what we are looking for. */ - if (ce->ce_flags & CE_VALID) + if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) return 0; /* @@ -2013,9 +2090,14 @@ static int populate_from_stdin(struct diff_filespec *s) static int diff_populate_gitlink(struct diff_filespec *s, int size_only) { int len; - char *data = xmalloc(100); + char *data = xmalloc(100), *dirty = ""; + + /* Are we looking at the work tree? */ + if (s->dirty_submodule) + dirty = "-dirty"; + len = snprintf(data, 100, - "Subproject commit %s\n", sha1_to_hex(s->sha1)); + "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty); s->data = data; s->size = len; s->should_free = 1; @@ -2278,7 +2360,7 @@ static void run_external_diff(const char *pgm, } *arg = NULL; fflush(NULL); - retval = run_command_v_opt(spawn_arg, 0); + retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL); remove_tempfile(); if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); @@ -2518,6 +2600,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) void diff_setup(struct diff_options *options) { memset(options, 0, sizeof(*options)); + memset(&diff_queued_diff, 0, sizeof(diff_queued_diff)); options->file = stdout; @@ -2554,6 +2637,20 @@ int diff_setup_done(struct diff_options *options) if (count > 1) die("--name-only, --name-status, --check and -s are mutually exclusive"); + /* + * Most of the time we can say "there are changes" + * only by checking if there are changed paths, but + * --ignore-whitespace* options force us to look + * inside contents. + */ + + if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || + DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) + DIFF_OPT_SET(options, DIFF_FROM_CONTENTS); + else + DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS); + if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; @@ -2593,6 +2690,12 @@ int diff_setup_done(struct diff_options *options) */ if (options->pickaxe) DIFF_OPT_SET(options, RECURSIVE); + /* + * When patches are generated, submodules diffed against the work tree + * must be checked for dirtiness too so it can be shown in the output + */ + if (options->output_format & DIFF_FORMAT_PATCH) + DIFF_OPT_SET(options, DIRTY_SUBMODULES); if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; @@ -2614,7 +2717,7 @@ int diff_setup_done(struct diff_options *options) * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (DIFF_OPT_TST(options, QUIET)) { + if (DIFF_OPT_TST(options, QUICK)) { options->output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(options, EXIT_WITH_STATUS); } @@ -2676,7 +2779,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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; @@ -2791,21 +2894,54 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) DIFF_OPT_SET(options, COLOR_DIFF); + else if (!prefixcmp(arg, "--color=")) { + int value = git_config_colorbool(NULL, arg+8, -1); + if (value == 0) + DIFF_OPT_CLR(options, COLOR_DIFF); + else if (value > 0) + DIFF_OPT_SET(options, COLOR_DIFF); + else + return error("option `color' expects \"always\", \"auto\", or \"never\""); + } else if (!strcmp(arg, "--no-color")) 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")) - DIFF_OPT_SET(options, QUIET); + DIFF_OPT_SET(options, QUICK); else if (!strcmp(arg, "--ext-diff")) DIFF_OPT_SET(options, ALLOW_EXTERNAL); else if (!strcmp(arg, "--no-ext-diff")) @@ -2858,6 +2994,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); + if (!options->file) + die_errno("Could not open '%s'", arg + strlen("--output=")); options->close_file = 1; } else return 0; @@ -3040,7 +3178,8 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1)) + !hashcmp(one->sha1, two->sha1) && + !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -3175,6 +3314,8 @@ static void diff_resolve_rename_copy(void) } else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode || + p->one->dirty_submodule || + p->two->dirty_submodule || is_null_sha1(p->one->sha1)) p->status = DIFF_STATUS_MODIFIED; else { @@ -3323,7 +3464,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) for (i = 0; i < q->nr; i++) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t mf1, mf2; struct diff_filepair *p = q->queue[i]; int len1, len2; @@ -3385,7 +3525,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } git_SHA1_Final(sha1, &ctx); @@ -3402,8 +3542,7 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1) diff_free_filepair(q->queue[i]); free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); return result; } @@ -3485,6 +3624,29 @@ void diff_flush(struct diff_options *options) separator++; } + if (output_format & DIFF_FORMAT_NO_OUTPUT && + DIFF_OPT_TST(options, EXIT_WITH_STATUS) && + DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + /* + * run diff_flush_patch for the exit status. setting + * options->file to /dev/null should be safe, becaue we + * aren't supposed to produce any output anyway. + */ + if (options->close_file) + fclose(options->file); + options->file = fopen("/dev/null", "w"); + if (!options->file) + die_errno("Could not open /dev/null"); + options->close_file = 1; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, options); + if (options->found_changes) + break; + } + } + if (output_format & DIFF_FORMAT_PATCH) { if (separator) { putc(options->line_termination, options->file); @@ -3508,10 +3670,21 @@ void diff_flush(struct diff_options *options) diff_free_filepair(q->queue[i]); free_queue: free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); if (options->close_file) fclose(options->file); + + /* + * Report the content-level differences with HAS_CHANGES; + * diff_addremove/diff_change does not set the bit when + * DIFF_FROM_CONTENTS is in effect (e.g. with -w). + */ + if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + if (options->found_changes) + DIFF_OPT_SET(options, HAS_CHANGES); + else + DIFF_OPT_CLR(options, HAS_CHANGES); + } } static void diffcore_apply_filter(const char *filter) @@ -3519,8 +3692,7 @@ static void diffcore_apply_filter(const char *filter) int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); if (!filter) return; @@ -3588,14 +3760,13 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; /* - * 1. Entries that come from stat info dirtyness + * 1. Entries that come from stat info dirtiness * always have both sides (iow, not create/delete), * one side of the object name is unknown, with * the same mode and size. Keep the ones that @@ -3631,8 +3802,31 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) *q = outq; } +static int diffnamecmp(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + const char *name_a, *name_b; + + name_a = a->one ? a->one->path : a->two->path; + name_b = b->one ? b->one->path : b->two->path; + return strcmp(name_a, name_b); +} + +void diffcore_fix_diff_index(struct diff_options *options) +{ + struct diff_queue_struct *q = &diff_queued_diff; + qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp); +} + void diffcore_std(struct diff_options *options) { + /* We never run this function more than one time, because the + * rename/copy detection logic can only run once. + */ + if (diff_queued_diff.run) + return; + if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); if (options->break_opt != -1) @@ -3648,10 +3842,12 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); - if (diff_queued_diff.nr) + if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); + + diff_queued_diff.run = 1; } int diff_result_code(struct diff_options *opt, int status) @@ -3672,7 +3868,7 @@ int diff_result_code(struct diff_options *opt, int status) void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *concatpath) + const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -3704,18 +3900,22 @@ void diff_addremove(struct diff_options *options, if (addremove != '+') fill_filespec(one, sha1, mode); - if (addremove != '-') + if (addremove != '-') { fill_filespec(two, sha1, mode); + two->dirty_submodule = dirty_submodule; + } diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *concatpath) + const char *concatpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) { struct diff_filespec *one, *two; @@ -3728,6 +3928,8 @@ void diff_change(struct diff_options *options, const unsigned char *tmp_c; tmp = old_mode; old_mode = new_mode; new_mode = tmp; tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule; + new_dirty_submodule = tmp; } if (options->prefix && @@ -3738,9 +3940,12 @@ void diff_change(struct diff_options *options, two = alloc_filespec(concatpath); fill_filespec(one, old_sha1, old_mode); fill_filespec(two, new_sha1, new_mode); + one->dirty_submodule = old_dirty_submodule; + two->dirty_submodule = new_dirty_submodule; diff_queue(&diff_queued_diff, one, two); - DIFF_OPT_SET(options, HAS_CHANGES); + if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + DIFF_OPT_SET(options, HAS_CHANGES); } void diff_unmerge(struct diff_options *options, @@ -3767,6 +3972,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, const char **arg = argv; struct child_process child; struct strbuf buf = STRBUF_INIT; + int err = 0; temp = prepare_temp_file(spec->path, spec); *arg++ = pgm; @@ -3774,17 +3980,68 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, *arg = NULL; memset(&child, 0, sizeof(child)); + child.use_shell = 1; child.argv = argv; child.out = -1; - if (start_command(&child) != 0 || - strbuf_read(&buf, child.out, 0) < 0 || - finish_command(&child) != 0) { + if (start_command(&child)) { + remove_tempfile(); + return NULL; + } + + if (strbuf_read(&buf, child.out, 0) < 0) + err = error("error reading from textconv command '%s'", pgm); + close(child.out); + + if (finish_command(&child) || err) { strbuf_release(&buf); remove_tempfile(); - error("error running textconv command '%s'", pgm); return NULL; } remove_tempfile(); 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; +}