diff options
Diffstat (limited to 'diff.c')
-rw-r--r-- | diff.c | 646 |
1 files changed, 434 insertions, 212 deletions
@@ -15,6 +15,7 @@ #include "sigchain.h" #include "submodule.h" #include "ll-merge.h" +#include "string-list.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -25,12 +26,14 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; -int diff_use_color_default = -1; +static int diff_use_color_default = -1; +static int diff_context_default = 3; static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; +static int diff_stat_graph_width; static int diff_dirstat_permille_default = 30; static struct diff_options default_diff_options; @@ -67,26 +70,30 @@ static int parse_diff_color_slot(const char *var, int ofs) return -1; } -static int parse_dirstat_params(struct diff_options *options, const char *params, +static int parse_dirstat_params(struct diff_options *options, const char *params_string, struct strbuf *errmsg) { - const char *p = params; - int p_len, ret = 0; + char *params_copy = xstrdup(params_string); + struct string_list params = STRING_LIST_INIT_NODUP; + int ret = 0; + int i; - while (*p) { - p_len = strchrnul(p, ',') - p; - if (!memcmp(p, "changes", p_len)) { + if (*params_copy) + string_list_split_in_place(¶ms, params_copy, ',', -1); + for (i = 0; i < params.nr; i++) { + const char *p = params.items[i].string; + if (!strcmp(p, "changes")) { DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "lines", p_len)) { + } else if (!strcmp(p, "lines")) { DIFF_OPT_SET(options, DIRSTAT_BY_LINE); DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "files", p_len)) { + } else if (!strcmp(p, "files")) { DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); DIFF_OPT_SET(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "noncumulative", p_len)) { + } else if (!strcmp(p, "noncumulative")) { DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); - } else if (!memcmp(p, "cumulative", p_len)) { + } else if (!strcmp(p, "cumulative")) { DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); } else if (isdigit(*p)) { char *end; @@ -98,27 +105,35 @@ static int parse_dirstat_params(struct diff_options *options, const char *params while (isdigit(*++end)) ; /* nothing */ } - if (end - p == p_len) + if (!*end) options->dirstat_permille = permille; else { - strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"), - p_len, p); + strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%s'\n"), + p); ret++; } } else { - strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"), - p_len, p); + strbuf_addf(errmsg, _(" Unknown dirstat parameter '%s'\n"), p); ret++; } - p += p_len; - - if (*p) - p++; /* more parameters, swallow separator */ } + string_list_clear(¶ms, 0); + free(params_copy); return ret; } +static int parse_submodule_params(struct diff_options *options, const char *value) +{ + if (!strcmp(value, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!strcmp(value, "short")) + DIFF_OPT_CLR(options, SUBMODULE_LOG); + else + return -1; + return 0; +} + static int git_config_rename(const char *var, const char *value) { if (!value) @@ -140,6 +155,12 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_use_color_default = git_config_colorbool(var, value); return 0; } + if (!strcmp(var, "diff.context")) { + diff_context_default = git_config_int(var, value); + if (diff_context_default < 0) + return -1; + return 0; + } if (!strcmp(var, "diff.renames")) { diff_detect_rename_default = git_config_rename(var, value); return 0; @@ -156,6 +177,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_no_prefix = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.statgraphwidth")) { + diff_stat_graph_width = git_config_int(var, value); + return 0; + } if (!strcmp(var, "diff.external")) return git_config_string(&external_diff_cmd_cfg, var, value); if (!strcmp(var, "diff.wordregex")) @@ -164,6 +189,13 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) if (!strcmp(var, "diff.ignoresubmodules")) handle_ignore_submodules_arg(&default_diff_options, value); + if (!strcmp(var, "diff.submodule")) { + if (parse_submodule_params(&default_diff_options, value)) + warning(_("Unknown value for 'diff.submodule' config variable: '%s'"), + value); + return 0; + } + if (git_color_config(var, value, cb) < 0) return -1; @@ -177,11 +209,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } - switch (userdiff_config(var, value)) { - case 0: break; - case -1: return -1; - default: return 0; - } + if (userdiff_config(var, value) < 0) + return -1; if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) { int slot = parse_diff_color_slot(var, 11); @@ -572,6 +601,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, if (!endp) { const char *plain = diff_get_color(ecb->color_diff, DIFF_PLAIN); + putc('\n', ecb->opt->file); emit_line_0(ecb->opt, plain, reset, '\\', nneof, strlen(nneof)); } @@ -987,10 +1017,74 @@ static void diff_words_flush(struct emit_callback *ecbdata) diff_words_show(ecbdata->diff_words); } +static void diff_filespec_load_driver(struct diff_filespec *one) +{ + /* Use already-loaded driver */ + if (one->driver) + return; + + if (S_ISREG(one->mode)) + one->driver = userdiff_find_by_path(one->path); + + /* Fallback to default settings */ + if (!one->driver) + one->driver = userdiff_find_by_name("default"); +} + +static const char *userdiff_word_regex(struct diff_filespec *one) +{ + diff_filespec_load_driver(one); + return one->driver->word_regex; +} + +static void init_diff_words_data(struct emit_callback *ecbdata, + struct diff_options *orig_opts, + struct diff_filespec *one, + struct diff_filespec *two) +{ + int i; + struct diff_options *o = xmalloc(sizeof(struct diff_options)); + memcpy(o, orig_opts, sizeof(struct diff_options)); + + ecbdata->diff_words = + xcalloc(1, sizeof(struct diff_words_data)); + ecbdata->diff_words->type = o->word_diff; + ecbdata->diff_words->opt = o; + if (!o->word_regex) + o->word_regex = userdiff_word_regex(one); + if (!o->word_regex) + o->word_regex = userdiff_word_regex(two); + if (!o->word_regex) + o->word_regex = diff_word_regex_cfg; + if (o->word_regex) { + ecbdata->diff_words->word_regex = (regex_t *) + xmalloc(sizeof(regex_t)); + if (regcomp(ecbdata->diff_words->word_regex, + o->word_regex, + REG_EXTENDED | REG_NEWLINE)) + 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 (want_color(o->use_color)) { + 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); + } +} + static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { diff_words_flush(ecbdata); + free (ecbdata->diff_words->opt); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); @@ -1233,6 +1327,7 @@ struct diffstat_t { unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; + unsigned is_interesting:1; uintmax_t added, deleted; } **files; }; @@ -1276,13 +1371,15 @@ const char mime_boundary_leader[] = "------------"; static int scale_linear(int it, int width, int max_change) { + if (!it) + return 0; /* - * make sure that at least one '-' is printed if there were deletions, - * and likewise for '+'. + * make sure that at least one '-' or '+' is printed if + * there is any change to this path. The easiest way is to + * scale linearly as if the alloted width is one column shorter + * than it is, and then add 1 to the result. */ - if (max_change < 2) - return it; - return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1); + return 1 + (it * (width - 1) / max_change); } static void show_name(FILE *file, @@ -1322,12 +1419,59 @@ static void fill_print_name(struct diffstat_file *file) file->print_name = pname; } +int print_stat_summary(FILE *fp, int files, int insertions, int deletions) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + if (!files) { + assert(insertions == 0 && deletions == 0); + return fprintf(fp, "%s\n", " 0 files changed"); + } + + strbuf_addf(&sb, + (files == 1) ? " %d file changed" : " %d files changed", + files); + + /* + * For binary diff, the caller may want to print "x files + * changed" with insertions == 0 && deletions == 0. + * + * Not omitting "0 insertions(+), 0 deletions(-)" in this case + * is probably less confusing (i.e skip over "2 files changed + * but nothing about added/removed lines? Is this a bug in Git?"). + */ + if (insertions || deletions == 0) { + /* + * TRANSLATORS: "+" in (+) is a line addition marker; + * do not translate it. + */ + strbuf_addf(&sb, + (insertions == 1) ? ", %d insertion(+)" : ", %d insertions(+)", + insertions); + } + + if (deletions || insertions == 0) { + /* + * TRANSLATORS: "-" in (-) is a line removal marker; + * do not translate it. + */ + strbuf_addf(&sb, + (deletions == 1) ? ", %d deletion(-)" : ", %d deletions(-)", + deletions); + } + strbuf_addch(&sb, '\n'); + ret = fputs(sb.buf, fp); + strbuf_release(&sb); + return ret; +} + static void show_stats(struct diffstat_t *data, struct diff_options *options) { int i, len, add, del, adds = 0, dels = 0; uintmax_t max_change = 0, max_len = 0; - int total_files = data->nr; - int width, name_width, count; + int total_files = data->nr, count; + int width, name_width, graph_width, number_width = 0, bin_width = 0; const char *reset, *add_c, *del_c; const char *line_prefix = ""; int extra_shown = 0; @@ -1341,30 +1485,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) line_prefix = msg->buf; } - width = options->stat_width ? options->stat_width : 80; - name_width = options->stat_name_width ? options->stat_name_width : 50; count = options->stat_count ? options->stat_count : data->nr; - /* Sanity: give at least 5 columns to the graph, - * but leave at least 10 columns for the name. - */ - if (width < 25) - width = 25; - if (name_width < 10) - name_width = 10; - else if (width < name_width + 15) - name_width = width - 15; - - /* Find the longest filename and max number of changes */ reset = diff_get_color_opt(options, DIFF_RESET); add_c = diff_get_color_opt(options, DIFF_FILE_NEW); del_c = diff_get_color_opt(options, DIFF_FILE_OLD); + /* + * Find the longest filename and max number of changes + */ for (i = 0; (i < count) && (i < data->nr); i++) { struct diffstat_file *file = data->files[i]; uintmax_t change = file->added + file->deleted; - if (!data->files[i]->is_renamed && - (change == 0)) { + + if (!file->is_interesting && (change == 0)) { count++; /* not shown == room for one more */ continue; } @@ -1373,38 +1507,121 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (max_len < len) max_len = len; - if (file->is_binary || file->is_unmerged) + if (file->is_unmerged) { + /* "Unmerged" is 8 characters */ + bin_width = bin_width < 8 ? 8 : bin_width; + continue; + } + if (file->is_binary) { + /* "Bin XXX -> YYY bytes" */ + int w = 14 + decimal_width(file->added) + + decimal_width(file->deleted); + bin_width = bin_width < w ? w : bin_width; + /* Display change counts aligned with "Bin" */ + number_width = 3; continue; + } + if (max_change < change) max_change = change; } - count = i; /* min(count, data->nr) */ + count = i; /* where we can stop scanning in data->files[] */ - /* Compute the width of the graph part; - * 10 is for one blank at the beginning of the line plus - * " | count " between the name and the graph. + /* + * We have width = stat_width or term_columns() columns total. + * We want a maximum of min(max_len, stat_name_width) for the name part. + * We want a maximum of min(max_change, stat_graph_width) for the +- part. + * We also need 1 for " " and 4 + decimal_width(max_change) + * for " | NNNN " and one the empty column at the end, altogether + * 6 + decimal_width(max_change). + * + * If there's not enough space, we will use the smaller of + * stat_name_width (if set) and 5/8*width for the filename, + * and the rest for constant elements + graph part, but no more + * than stat_graph_width for the graph part. + * (5/8 gives 50 for filename and 30 for the constant parts + graph + * for the standard terminal size). + * + * In other words: stat_width limits the maximum width, and + * stat_name_width fixes the maximum width of the filename, + * and is also used to divide available columns if there + * aren't enough. * - * From here on, name_width is the width of the name area, - * and width is the width of the graph area. + * Binary files are displayed with "Bin XXX -> YYY bytes" + * instead of the change count and graph. This part is treated + * similarly to the graph part, except that it is not + * "scaled". If total width is too small to accomodate the + * guaranteed minimum width of the filename part and the + * separators and this message, this message will "overflow" + * making the line longer than the maximum width. */ - name_width = (name_width < max_len) ? name_width : max_len; - if (width < (name_width + 10) + max_change) - width = width - (name_width + 10); + + if (options->stat_width == -1) + width = term_columns() - options->output_prefix_length; else - width = max_change; + width = options->stat_width ? options->stat_width : 80; + number_width = decimal_width(max_change) > number_width ? + decimal_width(max_change) : number_width; + + if (options->stat_graph_width == -1) + options->stat_graph_width = diff_stat_graph_width; + + /* + * Guarantee 3/8*16==6 for the graph part + * and 5/8*16==10 for the filename part + */ + if (width < 16 + 6 + number_width) + width = 16 + 6 + number_width; + + /* + * First assign sizes that are wanted, ignoring available width. + * strlen("Bin XXX -> YYY bytes") == bin_width, and the part + * starting from "XXX" should fit in graph_width. + */ + graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4; + if (options->stat_graph_width && + options->stat_graph_width < graph_width) + graph_width = options->stat_graph_width; + + name_width = (options->stat_name_width > 0 && + options->stat_name_width < max_len) ? + options->stat_name_width : max_len; + + /* + * Adjust adjustable widths not to exceed maximum width + */ + if (name_width + number_width + 6 + graph_width > width) { + if (graph_width > width * 3/8 - number_width - 6) { + graph_width = width * 3/8 - number_width - 6; + if (graph_width < 6) + graph_width = 6; + } + + if (options->stat_graph_width && + graph_width > options->stat_graph_width) + graph_width = options->stat_graph_width; + if (name_width > width - number_width - 6 - graph_width) + name_width = width - number_width - 6 - graph_width; + else + graph_width = width - number_width - 6 - name_width; + } + /* + * From here name_width is the width of the name area, + * and graph_width is the width of the graph area. + * max_change is used to scale graph properly. + */ for (i = 0; i < count; i++) { const char *prefix = ""; - char *name = data->files[i]->print_name; - uintmax_t added = data->files[i]->added; - uintmax_t deleted = data->files[i]->deleted; + struct diffstat_file *file = data->files[i]; + char *name = file->print_name; + uintmax_t added = file->added; + uintmax_t deleted = file->deleted; int name_len; - if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; + if (!file->is_interesting && (added + deleted == 0)) continue; - } + /* * "scale" the filename */ @@ -1420,11 +1637,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) name = slash; } - if (data->files[i]->is_binary) { + if (file->is_binary) { fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); - fprintf(options->file, " Bin "); - fprintf(options->file, "%s%"PRIuMAX"%s", + fprintf(options->file, " %*s", number_width, "Bin"); + if (!added && !deleted) { + putc('\n', options->file); + continue; + } + fprintf(options->file, " %s%"PRIuMAX"%s", del_c, deleted, reset); fprintf(options->file, " -> "); fprintf(options->file, "%s%"PRIuMAX"%s", @@ -1433,10 +1654,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) fprintf(options->file, "\n"); continue; } - else if (data->files[i]->is_unmerged) { + else if (file->is_unmerged) { fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); - fprintf(options->file, " Unmerged\n"); + fprintf(options->file, " Unmerged\n"); continue; } @@ -1445,39 +1666,55 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) */ add = added; del = deleted; - adds += add; - dels += del; - if (width <= max_change) { - add = scale_linear(add, width, max_change); - del = scale_linear(del, width, max_change); + if (graph_width <= max_change) { + int total = add + del; + + total = scale_linear(add + del, graph_width, max_change); + if (total < 2 && add && del) + /* width >= 2 due to the sanity check */ + total = 2; + if (add < del) { + add = scale_linear(add, graph_width, max_change); + del = total - add; + } else { + del = scale_linear(del, graph_width, max_change); + add = total - del; + } } fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); - fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, - added + deleted ? " " : ""); + fprintf(options->file, " %*"PRIuMAX"%s", + number_width, added + deleted, + added + deleted ? " " : ""); show_graph(options->file, '+', add, add_c, reset); show_graph(options->file, '-', del, del_c, reset); fprintf(options->file, "\n"); } - for (i = count; i < data->nr; i++) { - uintmax_t added = data->files[i]->added; - uintmax_t deleted = data->files[i]->deleted; - if (!data->files[i]->is_renamed && - (added + deleted == 0)) { + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + uintmax_t added = file->added; + uintmax_t deleted = file->deleted; + + if (file->is_unmerged || + (!file->is_interesting && (added + deleted == 0))) { total_files--; continue; } - adds += added; - dels += deleted; + + if (!file->is_binary) { + adds += added; + dels += deleted; + } + if (i < count) + continue; if (!extra_shown) fprintf(options->file, "%s ...\n", line_prefix); extra_shown = 1; } fprintf(options->file, "%s", line_prefix); - fprintf(options->file, - " %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); + print_stat_summary(options->file, total_files, adds, dels); } static void show_shortstats(struct diffstat_t *data, struct diff_options *options) @@ -1488,17 +1725,15 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option return; for (i = 0; i < data->nr; i++) { - if (!data->files[i]->is_binary && - !data->files[i]->is_unmerged) { - int added = data->files[i]->added; - int deleted= data->files[i]->deleted; - if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; - } else { - adds += added; - dels += deleted; - } + int added = data->files[i]->added; + int deleted= data->files[i]->deleted; + + if (data->files[i]->is_unmerged || + (!data->files[i]->is_interesting && (added + deleted == 0))) { + total_files--; + } else if (!data->files[i]->is_binary) { /* don't count bytes */ + adds += added; + dels += deleted; } } if (options->output_prefix) { @@ -1507,8 +1742,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option options->output_prefix_data); fprintf(options->file, "%s", msg->buf); } - fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); + print_stat_summary(options->file, total_files, adds, dels); } static void show_numstat(struct diffstat_t *data, struct diff_options *options) @@ -1957,20 +2191,6 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre emit_binary_diff_body(file, two, one, prefix); } -static void diff_filespec_load_driver(struct diff_filespec *one) -{ - /* Use already-loaded driver */ - if (one->driver) - return; - - if (S_ISREG(one->mode)) - one->driver = userdiff_find_by_path(one->path); - - /* Fallback to default settings */ - if (!one->driver) - one->driver = userdiff_find_by_name("default"); -} - int diff_filespec_is_binary(struct diff_filespec *one) { if (one->is_binary == -1) { @@ -1996,12 +2216,6 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe return one->driver->funcname.pattern ? &one->driver->funcname : NULL; } -static const char *userdiff_word_regex(struct diff_filespec *one) -{ - diff_filespec_load_driver(one); - return one->driver->word_regex; -} - void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b) { if (!options->a_prefix) @@ -2031,7 +2245,7 @@ static void builtin_diff(const char *name_a, mmfile_t mf1, mf2; const char *lbl[2]; char *a_one, *b_two; - const char *set = diff_get_color_opt(o, DIFF_METAINFO); + const char *meta = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; struct userdiff_driver *textconv_one = NULL; @@ -2052,7 +2266,7 @@ static void builtin_diff(const char *name_a, 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); + meta, del, add, reset); return; } @@ -2078,24 +2292,24 @@ 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"; - strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset); + strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset); + strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); must_show_header = 1; } else if (lbl[1][0] == '/') { - strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset); + strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); must_show_header = 1; } else { if (one->mode != two->mode) { - strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset); - strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset); + strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset); + strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset); must_show_header = 1; } if (xfrm_msg) @@ -2151,7 +2365,7 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; - if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) { + if (must_show_header) { fprintf(o->file, "%s", header.buf); strbuf_reset(&header); } @@ -2188,42 +2402,8 @@ 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 (o->word_diff) { - int i; - - ecbdata.diff_words = - xcalloc(1, sizeof(struct diff_words_data)); - ecbdata.diff_words->type = o->word_diff; - ecbdata.diff_words->opt = o; - if (!o->word_regex) - o->word_regex = userdiff_word_regex(one); - if (!o->word_regex) - o->word_regex = userdiff_word_regex(two); - if (!o->word_regex) - o->word_regex = diff_word_regex_cfg; - if (o->word_regex) { - ecbdata.diff_words->word_regex = (regex_t *) - xmalloc(sizeof(regex_t)); - if (regcomp(ecbdata.diff_words->word_regex, - o->word_regex, - REG_EXTENDED | REG_NEWLINE)) - 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 (want_color(o->use_color)) { - 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); - } - } + if (o->word_diff) + init_diff_words_data(&ecbdata, o, one, two); xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, &xpp, &xecfg); if (o->word_diff) @@ -2249,22 +2429,37 @@ static void builtin_diffstat(const char *name_a, const char *name_b, struct diff_filespec *two, struct diffstat_t *diffstat, struct diff_options *o, - int complete_rewrite) + struct diff_filepair *p) { mmfile_t mf1, mf2; struct diffstat_file *data; + int same_contents; + int complete_rewrite = 0; + + if (!DIFF_PAIR_UNMERGED(p)) { + if (p->status == DIFF_STATUS_MODIFIED && p->score) + complete_rewrite = 1; + } data = diffstat_add(diffstat, name_a, name_b); + data->is_interesting = p->status != DIFF_STATUS_UNKNOWN; if (!one || !two) { data->is_unmerged = 1; return; } + same_contents = !hashcmp(one->sha1, two->sha1); + if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { data->is_binary = 1; - data->added = diff_filespec_size(two); - data->deleted = diff_filespec_size(one); + if (same_contents) { + data->added = 0; + data->deleted = 0; + } else { + data->added = diff_filespec_size(two); + data->deleted = diff_filespec_size(one); + } } else if (complete_rewrite) { @@ -2274,7 +2469,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, data->added = count_lines(two->data, two->size); } - else { + else if (!same_contents) { /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; @@ -2384,12 +2579,12 @@ void free_filespec(struct diff_filespec *spec) } void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, - unsigned short mode) + int sha1_valid, unsigned short mode) { if (mode) { spec->mode = canon_mode(mode); hashcpy(spec->sha1, sha1); - spec->sha1_valid = !is_null_sha1(sha1); + spec->sha1_valid = sha1_valid; } } @@ -2462,22 +2657,6 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int return 0; } -static int populate_from_stdin(struct diff_filespec *s) -{ - struct strbuf buf = STRBUF_INIT; - size_t size = 0; - - if (strbuf_read(&buf, 0, 0) < 0) - return error("error while reading from stdin %s", - strerror(errno)); - - s->should_munmap = 0; - s->data = strbuf_detach(&buf, &size); - s->size = size; - s->should_free = 1; - return 0; -} - static int diff_populate_gitlink(struct diff_filespec *s, int size_only) { int len; @@ -2527,9 +2706,6 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) struct stat st; int fd; - if (!strcmp(s->path, "-")) - return populate_from_stdin(s); - if (lstat(s->path, &st) < 0) { if (errno == ENOENT) { err_empty: @@ -2854,9 +3030,8 @@ static void run_diff_cmd(const char *pgm, int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; int must_show_header = 0; - if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) - pgm = NULL; - else { + + if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) { struct userdiff_driver *drv = userdiff_find_by_path(attr_path); if (drv && drv->external) pgm = drv->external; @@ -2891,7 +3066,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) if (DIFF_FILE_VALID(one)) { if (!one->sha1_valid) { struct stat st; - if (!strcmp(one->path, "-")) { + if (one->is_stdin) { hashcpy(one->sha1, null_sha1); return; } @@ -2936,6 +3111,9 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); + if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) + pgm = NULL; + if (DIFF_PAIR_UNMERGED(p)) { run_diff_cmd(pgm, name, NULL, attr_path, NULL, NULL, NULL, o, p); @@ -2975,11 +3153,10 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, { const char *name; const char *other; - int complete_rewrite = 0; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0); + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p); return; } @@ -2992,9 +3169,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - if (p->status == DIFF_STATUS_MODIFIED && p->score) - complete_rewrite = 1; - builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite); + builtin_diffstat(name, other, p->one, p->two, diffstat, o, p); } static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) @@ -3031,7 +3206,8 @@ void diff_setup(struct diff_options *options) options->break_opt = -1; options->rename_limit = -1; options->dirstat_permille = diff_dirstat_permille_default; - options->context = 3; + options->context = diff_context_default; + DIFF_OPT_SET(options, RENAME_EMPTY); options->change = diff_change; options->add_remove = diff_addremove; @@ -3046,7 +3222,7 @@ void diff_setup(struct diff_options *options) } } -int diff_setup_done(struct diff_options *options) +void diff_setup_done(struct diff_options *options) { int count = 0; @@ -3145,8 +3321,6 @@ int diff_setup_done(struct diff_options *options) options->output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(options, EXIT_WITH_STATUS); } - - return 0; } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -3243,6 +3417,7 @@ static int stat_opt(struct diff_options *options, const char **av) char *end; int width = options->stat_width; int name_width = options->stat_name_width; + int graph_width = options->stat_graph_width; int count = options->stat_count; int argcount = 1; @@ -3271,6 +3446,16 @@ static int stat_opt(struct diff_options *options, const char **av) name_width = strtoul(av[1], &end, 10); argcount = 2; } + } else if (!prefixcmp(arg, "-graph-width")) { + arg += strlen("-graph-width"); + if (*arg == '=') + graph_width = strtoul(arg + 1, &end, 10); + else if (!*arg && !av[1]) + die("Option '--stat-graph-width' requires a value"); + else if (!*arg) { + graph_width = strtoul(av[1], &end, 10); + argcount = 2; + } } else if (!prefixcmp(arg, "-count")) { arg += strlen("-count"); if (*arg == '=') @@ -3296,6 +3481,7 @@ static int stat_opt(struct diff_options *options, const char **av) return 0; options->output_format |= DIFF_FORMAT_DIFFSTAT; options->stat_name_width = name_width; + options->stat_graph_width = graph_width; options->stat_width = width; options->stat_count = count; return argcount; @@ -3316,6 +3502,14 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params) return 1; } +static int parse_submodule_opt(struct diff_options *options, const char *value) +{ + if (parse_submodule_params(options, value)) + die(_("Failed to parse --submodule option parameter: '%s'"), + value); + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3390,6 +3584,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; + else if (!strcmp(arg, "--rename-empty")) + DIFF_OPT_SET(options, RENAME_EMPTY); + else if (!strcmp(arg, "--no-rename-empty")) + DIFF_OPT_CLR(options, RENAME_EMPTY); else if (!strcmp(arg, "--relative")) DIFF_OPT_SET(options, RELATIVE_NAME); else if (!prefixcmp(arg, "--relative=")) { @@ -3409,9 +3607,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--ignore-space-at-eol")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--patience")) - DIFF_XDL_SET(options, PATIENCE_DIFF); + options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) - DIFF_XDL_SET(options, HISTOGRAM_DIFF); + options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF); /* flags options */ else if (!strcmp(arg, "--binary")) { @@ -3492,10 +3690,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) handle_ignore_submodules_arg(options, arg + 20); } 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); - } + else if (!prefixcmp(arg, "--submodule=")) + return parse_submodule_opt(options, arg + 12); /* misc options */ else if (!strcmp(arg, "-z")) @@ -4283,6 +4479,12 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { + if (options->output_prefix) { + struct strbuf *msg = NULL; + msg = options->output_prefix(options, + options->output_prefix_data); + fwrite(msg->buf, msg->len, 1, stdout); + } putc(options->line_termination, options->file); if (options->stat_sep) { /* attach patch instead of inline */ @@ -4530,6 +4732,7 @@ static int is_submodule_ignored(const char *path, struct diff_options *options) void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, + int sha1_valid, const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -4561,9 +4764,9 @@ void diff_addremove(struct diff_options *options, two = alloc_filespec(concatpath); if (addremove != '+') - fill_filespec(one, sha1, mode); + fill_filespec(one, sha1, sha1_valid, mode); if (addremove != '-') { - fill_filespec(two, sha1, mode); + fill_filespec(two, sha1, sha1_valid, mode); two->dirty_submodule = dirty_submodule; } @@ -4576,6 +4779,7 @@ void diff_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, + int old_sha1_valid, int new_sha1_valid, const char *concatpath, unsigned old_dirty_submodule, unsigned new_dirty_submodule) { @@ -4590,6 +4794,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_sha1_valid; old_sha1_valid = new_sha1_valid; + new_sha1_valid = tmp; tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule; new_dirty_submodule = tmp; } @@ -4600,8 +4806,8 @@ void diff_change(struct diff_options *options, one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); + fill_filespec(one, old_sha1, old_sha1_valid, old_mode); + fill_filespec(two, new_sha1, new_sha1_valid, new_mode); one->dirty_submodule = old_dirty_submodule; two->dirty_submodule = new_dirty_submodule; @@ -4707,3 +4913,19 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } + +void setup_diff_pager(struct diff_options *opt) +{ + /* + * If the user asked for our exit code, then either they want --quiet + * or --exit-code. We should definitely not bother with a pager in the + * former case, as we will generate no output. Since we still properly + * report our exit code even when a pager is run, we _could_ run a + * pager with --exit-code. But since we have not done so historically, + * and because it is easy to find people oneline advising "git diff + * --exit-code" in hooks and other scripts, we do not do so. + */ + if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && + check_pager_config("diff") != 0) + setup_pager(); +} |