diff options
Diffstat (limited to 'diff.c')
-rw-r--r-- | diff.c | 826 |
1 files changed, 575 insertions, 251 deletions
@@ -13,6 +13,8 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule.h" +#include "ll-merge.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -38,6 +40,7 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_GREEN, /* NEW */ GIT_COLOR_YELLOW, /* COMMIT */ GIT_COLOR_BG_RED, /* WHITESPACE */ + GIT_COLOR_NORMAL, /* FUNCINFO */ }; static void diff_filespec_load_driver(struct diff_filespec *one); @@ -59,7 +62,9 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_COMMIT; if (!strcasecmp(var+ofs, "whitespace")) return DIFF_WHITESPACE; - die("bad config variable '%s'", var); + if (!strcasecmp(var+ofs, "func")) + return DIFF_FUNCINFO; + return -1; } static int git_config_rename(const char *var, const char *value) @@ -118,6 +123,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) { int slot = parse_diff_color_slot(var, 11); + if (slot < 0) + return 0; if (!value) return config_error_nonbool(var); color_parse(value, var, diff_colors[slot]); @@ -174,6 +181,213 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + +struct emit_callback { + int color_diff; + unsigned ws_rule; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; + int lno_in_preimage; + int lno_in_postimage; + sane_truncate_fn truncate; + const char **label_path; + struct diff_words_data *diff_words; + int *found_changesp; + FILE *file; + struct strbuf *header; +}; + +static int count_lines(const char *data, int size) +{ + int count, ch, completely_empty = 1, nl_just_seen = 0; + count = 0; + while (0 < size--) { + ch = *data++; + if (ch == '\n') { + count++; + nl_just_seen = 1; + completely_empty = 0; + } + else { + nl_just_seen = 0; + completely_empty = 0; + } + } + if (completely_empty) + return 0; + if (!nl_just_seen) + count++; /* no trailing newline */ + return count; +} + +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = (char *)""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) +{ + int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } + at = count_lines(mf1->ptr, mf1->size); + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; +} + +static void emit_line_0(FILE *file, const char *set, const char *reset, + int first, const char *line, int len) +{ + int has_trailing_newline, has_trailing_carriage_return; + int nofirst; + + if (len == 0) { + has_trailing_newline = (first == '\n'); + has_trailing_carriage_return = (!has_trailing_newline && + (first == '\r')); + nofirst = has_trailing_newline || has_trailing_carriage_return; + } else { + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + nofirst = 0; + } + + if (len || !nofirst) { + fputs(set, file); + if (!nofirst) + fputc(first, file); + fwrite(line, len, 1, file); + fputs(reset, file); + } + if (has_trailing_carriage_return) + fputc('\r', file); + if (has_trailing_newline) + fputc('\n', file); +} + +static void emit_line(FILE *file, const char *set, const char *reset, + const char *line, int len) +{ + emit_line_0(file, set, reset, line[0], line+1, len-1); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line, len, ecbdata->ws_rule); +} + +static void emit_add_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) +{ + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) + emit_line_0(ecbdata->file, set, reset, '+', line, len); + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ + emit_line_0(ecbdata->file, ws, reset, '+', line, len); + else { + /* Emit just the prefix, then the rest. */ + emit_line_0(ecbdata->file, set, reset, '+', "", 0); + ws_check_emit(line, len, ecbdata->ws_rule, + ecbdata->file, set, reset, ws); + } +} + +static void emit_hunk_header(struct emit_callback *ecbdata, + const char *line, int len) +{ + const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); + const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO); + const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + static const char atat[2] = { '@', '@' }; + const char *cp, *ep; + + /* + * As a hunk header must begin with "@@ -<old>, +<new> @@", + * it always is at least 10 bytes long. + */ + if (len < 10 || + memcmp(line, atat, 2) || + !(ep = memmem(line + 2, len - 2, atat, 2))) { + emit_line(ecbdata->file, plain, reset, line, len); + return; + } + ep += 2; /* skip over @@ */ + + /* The hunk header in fraginfo color */ + emit_line(ecbdata->file, frag, reset, line, ep - line); + + /* blank before the func header */ + for (cp = ep; ep - line < len; ep++) + if (*ep != ' ' && *ep != '\t') + break; + if (ep != cp) + emit_line(ecbdata->file, plain, reset, cp, ep - cp); + + if (ep < line + len) + emit_line(ecbdata->file, func, reset, ep, line + len - ep); +} + static struct diff_tempfile *claim_diff_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) @@ -201,29 +415,6 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } -static int count_lines(const char *data, int size) -{ - int count, ch, completely_empty = 1, nl_just_seen = 0; - count = 0; - while (0 < size--) { - ch = *data++; - if (ch == '\n') { - count++; - nl_just_seen = 1; - completely_empty = 0; - } - else { - nl_just_seen = 0; - completely_empty = 0; - } - } - if (completely_empty) - return 0; - if (!nl_just_seen) - count++; /* no trailing newline */ - return count; -} - static void print_line_count(FILE *file, int count) { switch (count) { @@ -239,26 +430,36 @@ static void print_line_count(FILE *file, int count) } } -static void copy_file_with_prefix(FILE *file, - int prefix, const char *data, int size, - const char *set, const char *reset) +static void emit_rewrite_lines(struct emit_callback *ecb, + int prefix, const char *data, int size) { - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) { - fputs(set, file); - putc(prefix, file); + const char *endp = NULL; + static const char *nneof = " No newline at end of file\n"; + const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); + const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); + + while (0 < size) { + int len; + + endp = memchr(data, '\n', size); + len = endp ? (endp - data + 1) : size; + if (prefix != '+') { + ecb->lno_in_preimage++; + emit_line_0(ecb->file, old, reset, '-', + data, len); + } else { + ecb->lno_in_postimage++; + emit_add_line(reset, ecb, data, len); } - if (ch == '\n') { - nl_just_seen = 1; - fputs(reset, file); - } else - nl_just_seen = 0; - putc(ch, file); + size -= len; + data += len; + } + if (!endp) { + const char *plain = diff_get_color(ecb->color_diff, + DIFF_PLAIN); + emit_line_0(ecb->file, plain, reset, '\\', + nneof, strlen(nneof)); } - if (!nl_just_seen) - fprintf(file, "%s\n\\ No newline at end of file\n", reset); } static void emit_rewrite_diff(const char *name_a, @@ -274,13 +475,12 @@ static void emit_rewrite_diff(const char *name_a, const char *name_a_tab, *name_b_tab; const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); - const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); 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; size_t size_one, size_two; + struct emit_callback ecbdata; if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -321,6 +521,22 @@ static void emit_rewrite_diff(const char *name_a, size_two = two->size; } + memset(&ecbdata, 0, sizeof(ecbdata)); + ecbdata.color_diff = color_diff; + ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { + mmfile_t mf1, mf2; + mf1.ptr = (char *)data_one; + mf2.ptr = (char *)data_two; + mf1.size = size_one; + mf2.size = size_two; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + } + ecbdata.lno_in_preimage = 1; + ecbdata.lno_in_postimage = 1; + lc_a = count_lines(data_one, size_one); lc_b = count_lines(data_two, size_two); fprintf(o->file, @@ -332,24 +548,13 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset); + emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) - copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset); -} - -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = (char *)""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - - mf->ptr = one->data; - mf->size = one->size; - return 0; + 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 { @@ -495,7 +700,6 @@ static void diff_words_show(struct diff_words_data *diff_words) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t minus, plus; /* special case: only removal */ @@ -513,11 +717,11 @@ static void diff_words_show(struct diff_words_data *diff_words) memset(&xecfg, 0, sizeof(xecfg)); diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex); diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; /* 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 + @@ -529,26 +733,18 @@ static void diff_words_show(struct diff_words_data *diff_words) diff_words->minus.text.size = diff_words->plus.text.size = 0; } -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); - -struct emit_callback { - int nparents, color_diff; - unsigned ws_rule; - sane_truncate_fn truncate; - const char **label_path; - struct diff_words_data *diff_words; - int *found_changesp; - FILE *file; -}; +/* In "color-words" mode, show word-diff of words accumulated in the buffer */ +static void diff_words_flush(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); +} static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { - /* flush buffers */ - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - + diff_words_flush(ecbdata); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); @@ -566,42 +762,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) -{ - int has_trailing_newline, has_trailing_carriage_return; - - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - - fputs(set, file); - fwrite(line, len, 1, file); - fputs(reset, file); - if (has_trailing_carriage_return) - fputc('\r', file); - if (has_trailing_newline) - fputc('\n', file); -} - -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) -{ - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); - else { - /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); - ws_check_emit(line + ecbdata->nparents, - len - ecbdata->nparents, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); - } -} - static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -620,15 +780,33 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static void find_lno(const char *line, struct emit_callback *ecbdata) +{ + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10); + p = strchr(p, '+'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - int i; - int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); 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]) { @@ -650,31 +828,22 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = 1; } - /* This is not really necessary for now because - * this codepath only deals with two-way diffs. - */ - for (i = 0; i < len && line[i] == '@'; i++) - ; - if (2 <= i && i < len && line[i] == ' ') { - ecbdata->nparents = i - 1; + if (line[0] == '@') { + if (ecbdata->diff_words) + diff_words_flush(ecbdata); len = sane_truncate_line(ecbdata, line, len); - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), - reset, line, len); + find_lno(line, ecbdata); + emit_hunk_header(ecbdata, line, len); if (line[len-1] != '\n') putc('\n', ecbdata->file); return; } - if (len < ecbdata->nparents) { + if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); return; } - color = DIFF_PLAIN; - if (ecbdata->diff_words && ecbdata->nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -685,28 +854,25 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) &ecbdata->diff_words->plus); return; } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); + diff_words_flush(ecbdata); line++; len--; emit_line(ecbdata->file, plain, reset, line, len); return; } - for (i = 0; i < ecbdata->nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); - return; + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + if (line[0] == ' ') + ecbdata->lno_in_postimage++; + emit_line(ecbdata->file, color, reset, line, len); + } else { + ecbdata->lno_in_postimage++; + emit_add_line(reset, ecbdata, line + 1, len - 1); } - emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -786,7 +952,7 @@ struct diffstat_t { unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; - unsigned int added, deleted; + uintmax_t added, deleted; } **files; }; @@ -878,7 +1044,7 @@ static void fill_print_name(struct diffstat_file *file) static void show_stats(struct diffstat_t *data, struct diff_options *options) { int i, len, add, del, adds = 0, dels = 0; - int max_change = 0, max_len = 0; + uintmax_t max_change = 0, max_len = 0; int total_files = data->nr; int width, name_width; const char *reset, *set, *add_c, *del_c; @@ -907,7 +1073,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - int change = file->added + file->deleted; + uintmax_t change = file->added + file->deleted; fill_print_name(file); len = strlen(file->print_name); if (max_len < len) @@ -935,8 +1101,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { const char *prefix = ""; char *name = data->files[i]->print_name; - int added = data->files[i]->added; - int deleted = data->files[i]->deleted; + uintmax_t added = data->files[i]->added; + uintmax_t deleted = data->files[i]->deleted; int name_len; /* @@ -957,9 +1123,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (data->files[i]->is_binary) { show_name(options->file, prefix, name, len); fprintf(options->file, " Bin "); - fprintf(options->file, "%s%d%s", del_c, deleted, reset); + fprintf(options->file, "%s%"PRIuMAX"%s", + del_c, deleted, reset); fprintf(options->file, " -> "); - fprintf(options->file, "%s%d%s", add_c, added, reset); + fprintf(options->file, "%s%"PRIuMAX"%s", + add_c, added, reset); fprintf(options->file, " bytes"); fprintf(options->file, "\n"); continue; @@ -988,7 +1156,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) del = scale_linear(del, width, max_change); } show_name(options->file, prefix, name, len); - fprintf(options->file, "%5d%s", added + deleted, + fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, added + deleted ? " " : ""); show_graph(options->file, '+', add, add_c, reset); show_graph(options->file, '-', del, del_c, reset); @@ -999,7 +1167,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) total_files, adds, dels); } -static void show_shortstats(struct diffstat_t* data, struct diff_options *options) +static void show_shortstats(struct diffstat_t *data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; @@ -1038,7 +1206,8 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) fprintf(options->file, "-\t-\t"); else fprintf(options->file, - "%d\t%d\t", file->added, file->deleted); + "%"PRIuMAX"\t%"PRIuMAX"\t", + file->added, file->deleted); if (options->line_termination) { fill_print_name(file); if (!file->is_renamed) @@ -1208,38 +1377,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; - int trailing_blanks_start; }; -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; } @@ -1247,6 +1410,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); @@ -1255,11 +1419,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (!ws_blank_line(line + 1, len - 1, data->ws_rule)) - data->trailing_blanks_start = 0; - else if (!data->trailing_blanks_start) - data->trailing_blanks_start = 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", @@ -1278,14 +1438,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - data->trailing_blanks_start = 0; } else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); - data->trailing_blanks_start = 0; } } @@ -1442,6 +1600,7 @@ static void builtin_diff(const char *name_a, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, + int must_show_header, struct diff_options *o, int complete_rewrite) { @@ -1452,6 +1611,18 @@ static void builtin_diff(const char *name_a, 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 strbuf header = STRBUF_INIT; + + if (DIFF_OPT_TST(o, SUBMODULE_LOG) && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + 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, two->dirty_submodule, + del, add, reset); + return; + } if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { textconv_one = get_textconv(one); @@ -1475,25 +1646,29 @@ 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); - if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } else if (lbl[1][0] == '/') { - fprintf(o->file, "%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, "%sdeleted file mode %06o%s\n", set, one->mode, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + must_show_header = 1; } 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); + must_show_header = 1; } - if (xfrm_msg && xfrm_msg[0]) - fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); + /* * we do not run diff between different kind * of objects. @@ -1503,6 +1678,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; @@ -1518,8 +1695,13 @@ static void builtin_diff(const char *name_a, (diff_filespec_is_binary(two) && !textconv_two) )) { /* Quite common confusing case */ if (mf1.size == mf2.size && - !memcmp(mf1.ptr, mf2.ptr, mf1.size)) + !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { + if (must_show_header) + fprintf(o->file, "%s", header.buf); 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 @@ -1532,10 +1714,14 @@ 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 (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) { + fprintf(o->file, "%s", header.buf); + strbuf_reset(&header); + } + if (textconv_one) { size_t size; mf1.ptr = run_textconv(textconv_one, one, &size); @@ -1562,8 +1748,11 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + ecbdata.header = header.len ? &header : NULL; + xpp.flags = o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1596,7 +1785,7 @@ static void builtin_diff(const char *name_a, } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); if (textconv_one) @@ -1607,6 +1796,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); @@ -1648,13 +1838,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; + xpp.flags = o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } free_and_return: @@ -1679,6 +1868,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"); @@ -1695,20 +1885,30 @@ 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; + xpp.flags = 0; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg, &ecb); - - if ((data.ws_rule & WS_TRAILING_SPACE) && - data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); - data.status = 1; /* report errors */ + &xpp, &xecfg); + + if (data.ws_rule & WS_BLANK_AT_EOF) { + struct emit_callback ecbdata; + int blank_at_eof; + + ecbdata.ws_rule = data.ws_rule; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + blank_at_eof = ecbdata.blank_at_eof_in_preimage; + + if (blank_at_eof) { + static char *err; + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s.\n", + data.filename, blank_at_eof, err); + data.status = 1; /* report errors */ + } } } free_and_return: @@ -1805,7 +2005,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; /* @@ -1837,9 +2037,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; @@ -2102,7 +2307,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); @@ -2121,36 +2326,43 @@ static void fill_metainfo(struct strbuf *msg, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o, - struct diff_filepair *p) + struct diff_filepair *p, + int *must_show_header, + int use_color) { + const char *set = diff_get_color(use_color, DIFF_METAINFO); + const char *reset = diff_get_color(use_color, DIFF_RESET); + + *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\ncopy from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%scopy from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\ncopy to "); + strbuf_addf(msg, "%s\n%scopy to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_RENAMED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\nrename from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%srename from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\nrename to "); + strbuf_addf(msg, "%s\n%srename to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_MODIFIED: if (p->score) { - strbuf_addf(msg, "dissimilarity index %d%%\n", - similarity_index(p)); + strbuf_addf(msg, "%sdissimilarity index %d%%%s\n", + set, similarity_index(p), reset); break; } /* fallthru */ default: - /* nothing */ - ; + *must_show_header = 0; } if (one && two && hashcmp(one->sha1, two->sha1)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; @@ -2161,15 +2373,13 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", + strbuf_addf(msg, "%sindex %.*s..%.*s", set, abbrev, sha1_to_hex(one->sha1), abbrev, sha1_to_hex(two->sha1)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); } - if (msg->len) - strbuf_setlen(msg, msg->len - 1); } static void run_diff_cmd(const char *pgm, @@ -2184,11 +2394,7 @@ static void run_diff_cmd(const char *pgm, { const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; - - if (msg) { - fill_metainfo(msg, name, other, one, two, o, p); - xfrm_msg = msg->len ? msg->buf : NULL; - } + int must_show_header = 0; if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; @@ -2198,6 +2404,17 @@ static void run_diff_cmd(const char *pgm, pgm = drv->external; } + if (msg) { + /* + * don't use colors when the header is intended for an + * external diff driver + */ + fill_metainfo(msg, name, other, one, two, o, p, + &must_show_header, + DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + xfrm_msg = msg->len ? msg->buf : NULL; + } + if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, complete_rewrite); @@ -2205,7 +2422,8 @@ static void run_diff_cmd(const char *pgm, } if (one && two) builtin_diff(name, other ? other : name, - one, two, xfrm_msg, o, complete_rewrite); + one, two, xfrm_msg, must_show_header, + o, complete_rewrite); else fprintf(o->file, "* Unmerged path %s\n", name); } @@ -2378,6 +2596,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; @@ -2417,6 +2649,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; @@ -2438,7 +2676,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); } @@ -2615,6 +2853,15 @@ 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")) { @@ -2629,7 +2876,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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")) @@ -2640,6 +2887,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg, "--submodule")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!prefixcmp(arg, "--submodule=")) { + if (!strcmp(arg + 12, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + } /* misc options */ else if (!strcmp(arg, "-z")) @@ -2676,6 +2929,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; @@ -2691,7 +2946,7 @@ static int parse_num(const char **cp_p) num = 0; scale = 1; dot = 0; - for(;;) { + for (;;) { ch = *cp; if ( !dot && ch == '.' ) { scale = 1; @@ -2858,7 +3113,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. */ @@ -2993,6 +3249,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 { @@ -3141,7 +3399,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; @@ -3199,11 +3456,11 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) len2, p->two->path); git_SHA1_Update(&ctx, buffer, len1); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; 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); @@ -3303,6 +3560,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); @@ -3330,6 +3610,18 @@ free_queue: q->nr = q->alloc = 0; 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) @@ -3413,7 +3705,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) 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 @@ -3449,6 +3741,23 @@ 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) { if (options->skip_stat_unmatch) @@ -3466,7 +3775,7 @@ 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); @@ -3490,7 +3799,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; @@ -3522,18 +3831,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; @@ -3546,6 +3859,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 && @@ -3556,9 +3871,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, @@ -3585,6 +3903,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; @@ -3592,18 +3911,23 @@ 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) { - close(child.out); - strbuf_release(&buf); + if (start_command(&child)) { remove_tempfile(); - error("error running textconv command '%s'", pgm); 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(); + return NULL; + } remove_tempfile(); return strbuf_detach(&buf, outsize); |