diff options
Diffstat (limited to 'diff.c')
| -rw-r--r-- | diff.c | 1369 |
1 files changed, 864 insertions, 505 deletions
@@ -15,6 +15,8 @@ #include "sigchain.h" #include "submodule.h" #include "ll-merge.h" +#include "string-list.h" +#include "argv-array.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -25,14 +27,18 @@ 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; +static const char *diff_order_file_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; +static long diff_algorithm; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, @@ -46,47 +52,51 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* FUNCINFO */ }; -static int parse_diff_color_slot(const char *var, int ofs) +static int parse_diff_color_slot(const char *var) { - if (!strcasecmp(var+ofs, "plain")) + if (!strcasecmp(var, "plain")) return DIFF_PLAIN; - if (!strcasecmp(var+ofs, "meta")) + if (!strcasecmp(var, "meta")) return DIFF_METAINFO; - if (!strcasecmp(var+ofs, "frag")) + if (!strcasecmp(var, "frag")) return DIFF_FRAGINFO; - if (!strcasecmp(var+ofs, "old")) + if (!strcasecmp(var, "old")) return DIFF_FILE_OLD; - if (!strcasecmp(var+ofs, "new")) + if (!strcasecmp(var, "new")) return DIFF_FILE_NEW; - if (!strcasecmp(var+ofs, "commit")) + if (!strcasecmp(var, "commit")) return DIFF_COMMIT; - if (!strcasecmp(var+ofs, "whitespace")) + if (!strcasecmp(var, "whitespace")) return DIFF_WHITESPACE; - if (!strcasecmp(var+ofs, "func")) + if (!strcasecmp(var, "func")) return DIFF_FUNCINFO; 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 +108,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) @@ -128,6 +146,21 @@ static int git_config_rename(const char *var, const char *value) return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0; } +long parse_algorithm_value(const char *value) +{ + if (!value) + return -1; + else if (!strcasecmp(value, "myers") || !strcasecmp(value, "default")) + return 0; + else if (!strcasecmp(value, "minimal")) + return XDF_NEED_MINIMAL; + else if (!strcasecmp(value, "patience")) + return XDF_PATIENCE_DIFF; + else if (!strcasecmp(value, "histogram")) + return XDF_HISTOGRAM_DIFF; + return -1; +} + /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -140,6 +173,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,14 +195,34 @@ 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")) return git_config_string(&diff_word_regex_cfg, var, value); + if (!strcmp(var, "diff.orderfile")) + return git_config_pathname(&diff_order_file_cfg, var, value); 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 (!strcmp(var, "diff.algorithm")) { + diff_algorithm = parse_algorithm_value(value); + if (diff_algorithm < 0) + return -1; + return 0; + } + if (git_color_config(var, value, cb) < 0) return -1; @@ -172,25 +231,24 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) int git_diff_basic_config(const char *var, const char *value, void *cb) { + const char *name; + if (!strcmp(var, "diff.renamelimit")) { diff_rename_limit_default = git_config_int(var, value); 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); + if (skip_prefix(var, "diff.color.", &name) || + skip_prefix(var, "color.diff.", &name)) { + int slot = parse_diff_color_slot(name); if (slot < 0) return 0; if (!value) return config_error_nonbool(var); - color_parse(value, var, diff_colors[slot]); - return 0; + return color_parse(value, diff_colors[slot]); } /* like GNU diff's --suppress-blank-empty option */ @@ -212,7 +270,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } - if (!prefixcmp(var, "submodule.")) + if (starts_with(var, "submodule.")) return parse_submodule_config_option(var, value); return git_default_config(var, value, cb); @@ -317,7 +375,7 @@ static unsigned long diff_filespec_size(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return 0; - diff_populate_filespec(one, 1); + diff_populate_filespec(one, CHECK_SIZE_ONLY); return one->size; } @@ -373,12 +431,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res int nofirst; FILE *file = o->file; - if (o->output_prefix) { - struct strbuf *msg = NULL; - msg = o->output_prefix(o, o->output_prefix_data); - assert(msg); - fwrite(msg->buf, msg->len, 1, file); - } + fputs(diff_line_prefix(o), file); if (len == 0) { has_trailing_newline = (first == '\n'); @@ -471,9 +524,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, ep += 2; /* skip over @@ */ /* The hunk header in fraginfo color */ - strbuf_add(&msgbuf, frag, strlen(frag)); + strbuf_addstr(&msgbuf, frag); strbuf_add(&msgbuf, line, ep - line); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); /* * trailing "\r\n" @@ -487,15 +540,15 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (*ep != ' ' && *ep != '\t') break; if (ep != cp) { - strbuf_add(&msgbuf, plain, strlen(plain)); + strbuf_addstr(&msgbuf, plain); strbuf_add(&msgbuf, cp, ep - cp); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); } if (ep < line + len) { - strbuf_add(&msgbuf, func, strlen(func)); + strbuf_addstr(&msgbuf, func); strbuf_add(&msgbuf, ep, line + len - ep); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); } strbuf_add(&msgbuf, line + len, org_len - len); @@ -572,6 +625,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)); } @@ -595,13 +649,7 @@ static void emit_rewrite_diff(const char *name_a, char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; - char *line_prefix = ""; - struct strbuf *msgbuf; - - if (o && o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -627,7 +675,7 @@ static void emit_rewrite_diff(const char *name_a, memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = want_color(o->use_color); ecbdata.found_changesp = &o->found_changes; - ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.ws_rule = whitespace_rule(name_b); ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; @@ -797,18 +845,14 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; struct diff_options *opt = diff_words->opt; - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix; if (line[0] != '@' || parse_hunk_header(line, len, &minus_first, &minus_len, &plus_first, &plus_len)) return; assert(opt); - if (opt->output_prefix) { - msgbuf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(opt); /* POSIX requires that first be decremented by one if len == 0... */ if (minus_len) { @@ -932,14 +976,10 @@ static void diff_words_show(struct diff_words_data *diff_words) struct diff_words_style *style = diff_words->style; struct diff_options *opt = diff_words->opt; - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix; assert(opt); - if (opt->output_prefix) { - msgbuf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(opt); /* special case: only removal */ if (!diff_words->plus.text.size) { @@ -987,10 +1027,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); @@ -1011,6 +1115,16 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +const char *diff_line_prefix(struct diff_options *opt) +{ + struct strbuf *msgbuf; + if (!opt->output_prefix) + return ""; + + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + return msgbuf->buf; +} + static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -1051,13 +1165,7 @@ 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); struct diff_options *o = ecbdata->opt; - char *line_prefix = ""; - struct strbuf *msgbuf; - - if (o && o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); if (ecbdata->header) { fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); @@ -1113,6 +1221,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) diff_words_append(line, len, &ecbdata->diff_words->plus); return; + } else if (starts_with(line, "\\ ")) { + /* + * Eat the "no newline at eof" marker as if we + * saw a "+" or "-" line with nothing on it, + * and return without diff_words_flush() to + * defer processing. If this is the end of + * preimage, more "+" lines may come after it. + */ + return; } diff_words_flush(ecbdata); if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { @@ -1153,6 +1270,7 @@ static char *pprint_rename(const char *a, const char *b) const char *new = b; struct strbuf name = STRBUF_INIT; int pfx_length, sfx_length; + int pfx_adjust_for_slash; int len_a = strlen(a); int len_b = strlen(b); int a_midlen, b_midlen; @@ -1179,7 +1297,18 @@ static char *pprint_rename(const char *a, const char *b) old = a + len_a; new = b + len_b; sfx_length = 0; - while (a <= old && b <= new && *old == *new) { + /* + * If there is a common prefix, it must end in a slash. In + * that case we let this loop run 1 into the prefix to see the + * same slash. + * + * If there is no common prefix, we cannot do this as it would + * underrun the input strings. + */ + pfx_adjust_for_slash = (pfx_length ? 1 : 0); + while (a + pfx_length - pfx_adjust_for_slash <= old && + b + pfx_length - pfx_adjust_for_slash <= new && + *old == *new) { if (*old == '/') sfx_length = len_a - (old - a); old--; @@ -1224,6 +1353,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; }; @@ -1233,12 +1363,8 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, const char *name_b) { struct diffstat_file *x; - x = xcalloc(sizeof (*x), 1); - if (diffstat->nr == diffstat->alloc) { - diffstat->alloc = alloc_nr(diffstat->alloc); - diffstat->files = xrealloc(diffstat->files, - diffstat->alloc * sizeof(x)); - } + x = xcalloc(1, sizeof(*x)); + ALLOC_GROW(diffstat->files, diffstat->nr + 1, diffstat->alloc); diffstat->files[diffstat->nr++] = x; if (name_b) { x->from_name = xstrdup(name_a); @@ -1267,13 +1393,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, @@ -1313,49 +1441,73 @@ 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) { + strbuf_addf(&sb, + (insertions == 1) ? ", %d insertion(+)" : ", %d insertions(+)", + insertions); + } + + if (deletions || insertions == 0) { + 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; - struct strbuf *msg = NULL; if (data->nr == 0) return; - if (options->output_prefix) { - msg = options->output_prefix(options, options->output_prefix_data); - line_prefix = msg->buf; - } - - width = options->stat_width ? options->stat_width : 80; - name_width = options->stat_name_width ? options->stat_name_width : 50; + line_prefix = diff_line_prefix(options); 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; } @@ -1364,38 +1516,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). * - * From here on, name_width is the width of the name area, - * and width is the width of the graph area. + * 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. + * + * 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 accommodate 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 */ @@ -1411,11 +1646,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", @@ -1424,10 +1663,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; } @@ -1436,39 +1675,53 @@ 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 = 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) @@ -1479,27 +1732,19 @@ 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) { - struct strbuf *msg = NULL; - msg = options->output_prefix(options, - 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); + fprintf(options->file, "%s", diff_line_prefix(options)); + print_stat_summary(options->file, total_files, adds, dels); } static void show_numstat(struct diffstat_t *data, struct diff_options *options) @@ -1512,12 +1757,7 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - if (options->output_prefix) { - struct strbuf *msg = NULL; - msg = options->output_prefix(options, - options->output_prefix_data); - fprintf(options->file, "%s", msg->buf); - } + fprintf(options->file, "%s", diff_line_prefix(options)); if (file->is_binary) fprintf(options->file, "-\t-\t"); @@ -1559,13 +1799,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, { unsigned long this_dir = 0; unsigned int sources = 0; - const char *line_prefix = ""; - struct strbuf *msg = NULL; - - if (opt->output_prefix) { - msg = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msg->buf; - } + const char *line_prefix = diff_line_prefix(opt); while (dir->nr) { struct dirstat_file *f = dir->files; @@ -1675,11 +1909,11 @@ static void show_dirstat(struct diff_options *options) diff_free_filespec_data(p->one); diff_free_filespec_data(p->two); } else if (DIFF_FILE_VALID(p->one)) { - diff_populate_filespec(p->one, 1); + diff_populate_filespec(p->one, CHECK_SIZE_ONLY); copied = added = 0; diff_free_filespec_data(p->one); } else if (DIFF_FILE_VALID(p->two)) { - diff_populate_filespec(p->two, 1); + diff_populate_filespec(p->two, CHECK_SIZE_ONLY); copied = 0; added = p->two->size; diff_free_filespec_data(p->two); @@ -1815,15 +2049,10 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) const char *reset = diff_get_color(data->o->use_color, DIFF_RESET); const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW); char *err; - char *line_prefix = ""; - struct strbuf *msgbuf; + const char *line_prefix; assert(data->o); - if (data->o->output_prefix) { - msgbuf = data->o->output_prefix(data->o, - data->o->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(data->o); if (line[0] == '+') { unsigned bad; @@ -1864,7 +2093,6 @@ static unsigned char *deflate_it(char *data, unsigned char *deflated; git_zstream stream; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); bound = git_deflate_bound(&stream, size); deflated = xmalloc(bound); @@ -1880,7 +2108,8 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, + const char *prefix) { void *cp; void *delta; @@ -1941,27 +2170,14 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, + const char *prefix) { fprintf(file, "%sGIT binary patch\n", prefix); emit_binary_diff_body(file, one, two, prefix); 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) { @@ -1970,8 +2186,8 @@ int diff_filespec_is_binary(struct diff_filespec *one) one->is_binary = one->driver->binary; else { if (!one->data && DIFF_FILE_VALID(one)) - diff_populate_filespec(one, 0); - if (one->data) + diff_populate_filespec(one, CHECK_BINARY); + if (one->is_binary == -1 && one->data) one->is_binary = buffer_is_binary(one->data, one->size); if (one->is_binary == -1) @@ -1987,12 +2203,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) @@ -2022,28 +2232,23 @@ 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; struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; - struct strbuf *msgbuf; - char *line_prefix = ""; - - if (o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); 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, + show_submodule_summary(o->file, one->path ? one->path : two->path, + line_prefix, one->sha1, two->sha1, two->dirty_submodule, - del, add, reset); + meta, del, add, reset); return; } @@ -2069,24 +2274,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) @@ -2117,6 +2322,19 @@ static void builtin_diff(const char *name_a, } else if (!DIFF_OPT_TST(o, TEXT) && ( (!textconv_one && diff_filespec_is_binary(one)) || (!textconv_two && diff_filespec_is_binary(two)) )) { + if (!one->data && !two->data && + S_ISREG(one->mode) && S_ISREG(two->mode) && + !DIFF_OPT_TST(o, BINARY)) { + if (!hashcmp(one->sha1, two->sha1)) { + if (must_show_header) + fprintf(o->file, "%s", header.buf); + goto free_ab_and_return; + } + fprintf(o->file, "%s", header.buf); + fprintf(o->file, "%sBinary files %s and %s differ\n", + line_prefix, lbl[0], lbl[1]); + goto free_ab_and_return; + } if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); /* Quite common confusing case */ @@ -2137,12 +2355,13 @@ static void builtin_diff(const char *name_a, } else { /* Crazy xdl interfaces.. */ const char *diffopts = getenv("GIT_DIFF_OPTS"); + const char *v; xpparam_t xpp; xdemitconf_t xecfg; 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); } @@ -2160,7 +2379,7 @@ static void builtin_diff(const char *name_a, ecbdata.label_path = lbl; ecbdata.color_diff = want_color(o->use_color); ecbdata.found_changesp = &o->found_changes; - ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.ws_rule = whitespace_rule(name_b); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.opt = o; @@ -2169,50 +2388,18 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; + if (DIFF_OPT_TST(o, FUNCCONTEXT)) + xecfg.flags |= XDL_EMIT_FUNCCONTEXT; if (pe) xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); if (!diffopts) ; - else if (!prefixcmp(diffopts, "--unified=")) - 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); - } - } + else if (skip_prefix(diffopts, "--unified=", &v)) + xecfg.ctxlen = strtoul(v, NULL, 10); + else if (skip_prefix(diffopts, "-u", &v)) + xecfg.ctxlen = strtoul(v, NULL, 10); + 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) @@ -2238,22 +2425,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) { @@ -2263,7 +2465,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; @@ -2274,6 +2476,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, &xpp, &xecfg); } @@ -2371,12 +2575,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; } } @@ -2387,7 +2591,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, */ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file) { - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; int pos, len; @@ -2449,22 +2653,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; @@ -2491,9 +2679,18 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) * grab the data for the blob (or file) for our own in-core comparison. * diff_filespec has data and size fields for this purpose. */ -int diff_populate_filespec(struct diff_filespec *s, int size_only) +int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) { + int size_only = flags & CHECK_SIZE_ONLY; int err = 0; + /* + * demote FAIL to WARN to allow inspecting the situation + * instead of refusing. + */ + enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL + ? SAFE_CRLF_WARN + : safe_crlf); + if (!DIFF_FILE_VALID(s)) die("internal error: asking to populate invalid file."); if (S_ISDIR(s->mode)) @@ -2514,9 +2711,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: @@ -2542,6 +2736,11 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } if (size_only) return 0; + if ((flags & CHECK_BINARY) && + s->size > big_file_threshold && s->is_binary == -1) { + s->is_binary = 1; + return 0; + } fd = open(s->path, O_RDONLY); if (fd < 0) goto err_empty; @@ -2552,7 +2751,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) /* * Convert from working tree format to canonical git format */ - if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) { + if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -2563,16 +2762,21 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } else { enum object_type type; - if (size_only) { + if (size_only || (flags & CHECK_BINARY)) { type = sha1_object_info(s->sha1, &s->size); if (type < 0) die("unable to read %s", sha1_to_hex(s->sha1)); - } else { - s->data = read_sha1_file(s->sha1, &type, &s->size); - if (!s->data) - die("unable to read %s", sha1_to_hex(s->sha1)); - s->should_free = 1; + if (size_only) + return 0; + if (s->size > big_file_threshold && s->is_binary == -1) { + s->is_binary = 1; + return 0; + } } + s->data = read_sha1_file(s->sha1, &type, &s->size); + if (!s->data) + die("unable to read %s", sha1_to_hex(s->sha1)); + s->should_free = 1; } return 0; } @@ -2656,8 +2860,9 @@ static struct diff_tempfile *prepare_temp_file(const char *name, remove_tempfile_installed = 1; } - if (!one->sha1_valid || - reuse_worktree_file(name, one->sha1, 1)) { + if (!S_ISGITLINK(one->mode) && + (!one->sha1_valid || + reuse_worktree_file(name, one->sha1, 1))) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -2701,6 +2906,16 @@ static struct diff_tempfile *prepare_temp_file(const char *name, return temp; } +static void add_external_diff_name(struct argv_array *argv, + const char *name, + struct diff_filespec *df) +{ + struct diff_tempfile *temp = prepare_temp_file(name, df); + argv_array_push(argv, temp->name); + argv_array_push(argv, temp->hex); + argv_array_push(argv, temp->mode); +} + /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -2713,41 +2928,36 @@ static void run_external_diff(const char *pgm, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, - int complete_rewrite) + int complete_rewrite, + struct diff_options *o) { - const char *spawn_arg[10]; - int retval; - const char **arg = &spawn_arg[0]; + struct argv_array argv = ARGV_ARRAY_INIT; + struct argv_array env = ARGV_ARRAY_INIT; + struct diff_queue_struct *q = &diff_queued_diff; + + argv_array_push(&argv, pgm); + argv_array_push(&argv, name); if (one && two) { - struct diff_tempfile *temp_one, *temp_two; - const char *othername = (other ? other : name); - temp_one = prepare_temp_file(name, one); - temp_two = prepare_temp_file(othername, two); - *arg++ = pgm; - *arg++ = name; - *arg++ = temp_one->name; - *arg++ = temp_one->hex; - *arg++ = temp_one->mode; - *arg++ = temp_two->name; - *arg++ = temp_two->hex; - *arg++ = temp_two->mode; - if (other) { - *arg++ = other; - *arg++ = xfrm_msg; + add_external_diff_name(&argv, name, one); + if (!other) + add_external_diff_name(&argv, name, two); + else { + add_external_diff_name(&argv, other, two); + argv_array_push(&argv, other); + argv_array_push(&argv, xfrm_msg); } - } else { - *arg++ = pgm; - *arg++ = name; } - *arg = NULL; - fflush(NULL); - retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL); + + argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter); + argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr); + + if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv)) + die(_("external diff died, stopping at %s"), name); + remove_tempfile(); - if (retval) { - fprintf(stderr, "external diff died, stopping at %s.\n", name); - exit(1); - } + argv_array_clear(&argv); + argv_array_clear(&env); } static int similarity_index(struct diff_filepair *p) @@ -2767,14 +2977,9 @@ static void fill_metainfo(struct strbuf *msg, { const char *set = diff_get_color(use_color, DIFF_METAINFO); const char *reset = diff_get_color(use_color, DIFF_RESET); - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix = diff_line_prefix(o); *must_show_header = 1; - if (o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: @@ -2841,9 +3046,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; @@ -2862,7 +3066,7 @@ static void run_diff_cmd(const char *pgm, if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); + complete_rewrite, o); return; } if (one && two) @@ -2878,7 +3082,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; } @@ -2923,6 +3127,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); @@ -2962,11 +3169,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; } @@ -2979,9 +3185,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) @@ -3018,12 +3222,17 @@ 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); + /* pathchange left =NULL by default */ options->change = diff_change; options->add_remove = diff_addremove; options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; + options->xdl_opts |= diff_algorithm; + + options->orderfile = diff_order_file_cfg; if (diff_no_prefix) { options->a_prefix = options->b_prefix = ""; @@ -3033,10 +3242,13 @@ 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; + if (options->set_default) + options->set_default(options); + if (options->output_format & DIFF_FORMAT_NAME) count++; if (options->output_format & DIFF_FORMAT_NAME_STATUS) @@ -3133,7 +3345,10 @@ int diff_setup_done(struct diff_options *options) DIFF_OPT_SET(options, EXIT_WITH_STATUS); } - return 0; + options->diff_path_counter = 0; + + if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1) + die(_("--follow requires exactly one pathspec")); } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -3163,14 +3378,11 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va if (c != '-') return 0; arg++; - eq = strchr(arg, '='); - if (eq) - len = eq - arg; - else - len = strlen(arg); + eq = strchrnul(arg, '='); + len = eq - arg; if (!len || strncmp(arg, arg_long, len)) return 0; - if (eq) { + if (*eq) { int n; char *end; if (!isdigit(*++eq)) @@ -3205,13 +3417,11 @@ int parse_long_opt(const char *opt, const char **argv, const char **optarg) { const char *arg = argv[0]; - if (arg[0] != '-' || arg[1] != '-') + if (!skip_prefix(arg, "--", &arg)) return 0; - arg += strlen("--"); - if (prefixcmp(arg, opt)) + if (!skip_prefix(arg, opt, &arg)) return 0; - arg += strlen(opt); - if (*arg == '=') { /* sticked form: --option=value */ + if (*arg == '=') { /* stuck form: --option=value */ *optarg = arg + 1; return 1; } @@ -3230,16 +3440,17 @@ 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; - arg += strlen("--stat"); + if (!skip_prefix(arg, "--stat", &arg)) + die("BUG: stat option does not begin with --stat: %s", arg); end = (char *)arg; switch (*arg) { case '-': - if (!prefixcmp(arg, "-width")) { - arg += strlen("-width"); + if (skip_prefix(arg, "-width", &arg)) { if (*arg == '=') width = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3248,8 +3459,7 @@ static int stat_opt(struct diff_options *options, const char **av) width = strtoul(av[1], &end, 10); argcount = 2; } - } else if (!prefixcmp(arg, "-name-width")) { - arg += strlen("-name-width"); + } else if (skip_prefix(arg, "-name-width", &arg)) { if (*arg == '=') name_width = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3258,8 +3468,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, "-count")) { - arg += strlen("-count"); + } else if (skip_prefix(arg, "-graph-width", &arg)) { + 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 (skip_prefix(arg, "-count", &arg)) { if (*arg == '=') count = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3283,6 +3501,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; @@ -3303,6 +3522,93 @@ 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; +} + +static const char diff_status_letters[] = { + DIFF_STATUS_ADDED, + DIFF_STATUS_COPIED, + DIFF_STATUS_DELETED, + DIFF_STATUS_MODIFIED, + DIFF_STATUS_RENAMED, + DIFF_STATUS_TYPE_CHANGED, + DIFF_STATUS_UNKNOWN, + DIFF_STATUS_UNMERGED, + DIFF_STATUS_FILTER_AON, + DIFF_STATUS_FILTER_BROKEN, + '\0', +}; + +static unsigned int filter_bit['Z' + 1]; + +static void prepare_filter_bits(void) +{ + int i; + + if (!filter_bit[DIFF_STATUS_ADDED]) { + for (i = 0; diff_status_letters[i]; i++) + filter_bit[(int) diff_status_letters[i]] = (1 << i); + } +} + +static unsigned filter_bit_tst(char status, const struct diff_options *opt) +{ + return opt->filter & filter_bit[(int) status]; +} + +static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt) +{ + int i, optch; + + prepare_filter_bits(); + + /* + * If there is a negation e.g. 'd' in the input, and we haven't + * initialized the filter field with another --diff-filter, start + * from full set of bits, except for AON. + */ + if (!opt->filter) { + for (i = 0; (optch = optarg[i]) != '\0'; i++) { + if (optch < 'a' || 'z' < optch) + continue; + opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1; + opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON]; + break; + } + } + + for (i = 0; (optch = optarg[i]) != '\0'; i++) { + unsigned int bit; + int negate; + + if ('a' <= optch && optch <= 'z') { + negate = 1; + optch = toupper(optch); + } else { + negate = 0; + } + + bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0; + if (!bit) + return optarg[i]; + if (negate) + opt->filter &= ~bit; + else + opt->filter |= bit; + } + return 0; +} + +static void enable_patch_output(int *fmt) { + *fmt &= ~DIFF_FORMAT_NO_OUTPUT; + *fmt |= DIFF_FORMAT_PATCH; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3310,55 +3616,56 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) int argcount; /* Output format options */ - 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; + if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch") + || opt_arg(arg, 'U', "unified", &options->context)) + enable_patch_output(&options->output_format); else if (!strcmp(arg, "--raw")) options->output_format |= DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--patch-with-raw")) - options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--numstat")) + else if (!strcmp(arg, "--patch-with-raw")) { + enable_patch_output(&options->output_format); + options->output_format |= DIFF_FORMAT_RAW; + } else if (!strcmp(arg, "--numstat")) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) return parse_dirstat_opt(options, ""); - else if (!prefixcmp(arg, "-X")) - return parse_dirstat_opt(options, arg + 2); - else if (!prefixcmp(arg, "--dirstat=")) - return parse_dirstat_opt(options, arg + 10); + else if (skip_prefix(arg, "-X", &arg)) + return parse_dirstat_opt(options, arg); + else if (skip_prefix(arg, "--dirstat=", &arg)) + return parse_dirstat_opt(options, arg); else if (!strcmp(arg, "--cumulative")) return parse_dirstat_opt(options, "cumulative"); else if (!strcmp(arg, "--dirstat-by-file")) return parse_dirstat_opt(options, "files"); - else if (!prefixcmp(arg, "--dirstat-by-file=")) { + else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) { parse_dirstat_opt(options, "files"); - return parse_dirstat_opt(options, arg + 18); + return parse_dirstat_opt(options, arg); } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) options->output_format |= DIFF_FORMAT_SUMMARY; - else if (!strcmp(arg, "--patch-with-stat")) - options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT; - else if (!strcmp(arg, "--name-only")) + else if (!strcmp(arg, "--patch-with-stat")) { + enable_patch_output(&options->output_format); + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } else if (!strcmp(arg, "--name-only")) options->output_format |= DIFF_FORMAT_NAME; else if (!strcmp(arg, "--name-status")) options->output_format |= DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-s")) + else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) options->output_format |= DIFF_FORMAT_NO_OUTPUT; - else if (!prefixcmp(arg, "--stat")) + else if (starts_with(arg, "--stat")) /* --stat, --stat-width, --stat-name-width, or --stat-count */ return stat_opt(options, av); /* renames options */ - else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") || + else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") || !strcmp(arg, "--break-rewrites")) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -B: %s", arg+2); } - else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") || + else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") || !strcmp(arg, "--find-renames")) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -M: %s", arg+2); @@ -3367,7 +3674,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { options->irreversible_delete = 1; } - else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") || + else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") || !strcmp(arg, "--find-copies")) { if (options->detect_rename == DIFF_DETECT_COPY) DIFF_OPT_SET(options, FIND_COPIES_HARDER); @@ -3377,28 +3684,49 @@ 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=")) { + else if (skip_prefix(arg, "--relative=", &arg)) { DIFF_OPT_SET(options, RELATIVE_NAME); - options->prefix = arg + 11; + options->prefix = arg; } /* xdiff options */ + else if (!strcmp(arg, "--minimal")) + DIFF_XDL_SET(options, NEED_MINIMAL); + else if (!strcmp(arg, "--no-minimal")) + DIFF_XDL_CLR(options, NEED_MINIMAL); else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) DIFF_XDL_SET(options, IGNORE_WHITESPACE); else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); else if (!strcmp(arg, "--ignore-space-at-eol")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); + else if (!strcmp(arg, "--ignore-blank-lines")) + DIFF_XDL_SET(options, IGNORE_BLANK_LINES); 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); + else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) { + long value = parse_algorithm_value(optarg); + if (value < 0) + return error("option diff-algorithm accepts \"myers\", " + "\"minimal\", \"patience\" and \"histogram\""); + /* clear out previous settings */ + DIFF_XDL_CLR(options, NEED_MINIMAL); + options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; + options->xdl_opts |= value; + return argcount; + } /* flags options */ else if (!strcmp(arg, "--binary")) { - options->output_format |= DIFF_FORMAT_PATCH; + enable_patch_output(&options->output_format); DIFF_OPT_SET(options, BINARY); } else if (!strcmp(arg, "--full-index")) @@ -3411,10 +3739,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FIND_COPIES_HARDER); else if (!strcmp(arg, "--follow")) DIFF_OPT_SET(options, FOLLOW_RENAMES); + else if (!strcmp(arg, "--no-follow")) + DIFF_OPT_CLR(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) options->use_color = 1; - else if (!prefixcmp(arg, "--color=")) { - int value = git_config_colorbool(NULL, arg+8); + else if (skip_prefix(arg, "--color=", &arg)) { + int value = git_config_colorbool(NULL, arg); if (value < 0) return error("option `color' expects \"always\", \"auto\", or \"never\""); options->use_color = value; @@ -3425,29 +3755,28 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (!prefixcmp(arg, "--color-words=")) { + else if (skip_prefix(arg, "--color-words=", &arg)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; - options->word_regex = arg + 14; + options->word_regex = arg; } 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")) + else if (skip_prefix(arg, "--word-diff=", &arg)) { + if (!strcmp(arg, "plain")) options->word_diff = DIFF_WORDS_PLAIN; - else if (!strcmp(type, "color")) { + else if (!strcmp(arg, "color")) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (!strcmp(type, "porcelain")) + else if (!strcmp(arg, "porcelain")) options->word_diff = DIFF_WORDS_PORCELAIN; - else if (!strcmp(type, "none")) + else if (!strcmp(arg, "none")) options->word_diff = DIFF_WORDS_NONE; else - die("bad --word-diff argument: %s", type); + die("bad --word-diff argument: %s", arg); } else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) { if (options->word_diff == DIFF_WORDS_NONE) @@ -3470,15 +3799,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--ignore-submodules")) { DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, "all"); - } else if (!prefixcmp(arg, "--ignore-submodules=")) { + } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) { DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); - handle_ignore_submodules_arg(options, arg + 20); + handle_ignore_submodules_arg(options, arg); } 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 (skip_prefix(arg, "--submodule=", &arg)) + return parse_submodule_opt(options, arg); /* misc options */ else if (!strcmp(arg, "-z")) @@ -3505,13 +3832,16 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) return argcount; } else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) { - options->filter = optarg; + int offending = parse_diff_filter_opt(optarg, options); + if (offending) + die("unknown change class '%c' in --diff-filter=%s", + offending, optarg); return argcount; } else if (!strcmp(arg, "--abbrev")) options->abbrev = DEFAULT_ABBREV; - else if (!prefixcmp(arg, "--abbrev=")) { - options->abbrev = strtoul(arg + 9, NULL, 10); + else if (skip_prefix(arg, "--abbrev=", &arg)) { + options->abbrev = strtoul(arg, NULL, 10); if (options->abbrev < MINIMUM_ABBREV) options->abbrev = MINIMUM_ABBREV; else if (40 < options->abbrev) @@ -3530,6 +3860,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (opt_arg(arg, '\0', "inter-hunk-context", &options->interhunkcontext)) ; + else if (!strcmp(arg, "-W")) + DIFF_OPT_SET(options, FUNCCONTEXT); + else if (!strcmp(arg, "--function-context")) + DIFF_OPT_SET(options, FUNCCONTEXT); + else if (!strcmp(arg, "--no-function-context")) + DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { options->file = fopen(optarg, "w"); if (!options->file) @@ -3586,22 +3922,19 @@ static int diff_scoreopt_parse(const char *opt) cmd = *opt++; if (cmd == '-') { /* convert the long-form arguments into short-form versions */ - if (!prefixcmp(opt, "break-rewrites")) { - opt += strlen("break-rewrites"); + if (skip_prefix(opt, "break-rewrites", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'B'; - } else if (!prefixcmp(opt, "find-copies")) { - opt += strlen("find-copies"); + } else if (skip_prefix(opt, "find-copies", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'C'; - } else if (!prefixcmp(opt, "find-renames")) { - opt += strlen("find-renames"); + } else if (skip_prefix(opt, "find-renames", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'M'; } } if (cmd != 'M' && cmd != 'C' && cmd != 'B') - return -1; /* that is not a -M, -C nor -B option */ + return -1; /* that is not a -M, -C, or -B option */ opt1 = parse_rename_score(&opt); if (cmd != 'B') @@ -3625,11 +3958,7 @@ struct diff_queue_struct diff_queued_diff; void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) { - if (queue->alloc <= queue->nr) { - queue->alloc = alloc_nr(queue->alloc); - queue->queue = xrealloc(queue->queue, - sizeof(dp) * queue->alloc); - } + ALLOC_GROW(queue->queue, queue->nr + 1, queue->alloc); queue->queue[queue->nr++] = dp; } @@ -3679,12 +4008,8 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) { int line_termination = opt->line_termination; int inter_name_termination = line_termination ? '\t' : '\0'; - if (opt->output_prefix) { - struct strbuf *msg = NULL; - msg = opt->output_prefix(opt, opt->output_prefix_data); - fprintf(opt->file, "%s", msg->buf); - } + fprintf(opt->file, "%s", diff_line_prefix(opt)); if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, diff_unique_abbrev(p->one->sha1, opt->abbrev)); @@ -3803,9 +4128,9 @@ void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) DIFF_FILE_VALID(s) ? "valid" : "invalid", s->mode, s->sha1_valid ? sha1_to_hex(s->sha1) : ""); - fprintf(stderr, "queue[%d] %s size %lu flags %d\n", + fprintf(stderr, "queue[%d] %s size %lu\n", x, one ? one : "", - s->size, s->xfrm_flags); + s->size); } void diff_debug_filepair(const struct diff_filepair *p, int i) @@ -3954,12 +4279,7 @@ static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_fil static void diff_summary(struct diff_options *opt, struct diff_filepair *p) { FILE *file = opt->file; - char *line_prefix = ""; - - if (opt->output_prefix) { - struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = buf->buf; - } + const char *line_prefix = diff_line_prefix(opt); switch(p->status) { case DIFF_STATUS_DELETED: @@ -4013,7 +4333,7 @@ static void patch_id_consume(void *priv, char *line, unsigned long len) int new_len; /* Ignore line numbers when computing the SHA1 of the patch */ - if (!prefixcmp(line, "@@ -")) + if (starts_with(line, "@@ -")) return; new_len = remove_space(line, len); @@ -4220,7 +4540,7 @@ void diff_flush(struct diff_options *options) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat, options); - if (output_format & DIFF_FORMAT_DIRSTAT) + if (output_format & DIFF_FORMAT_DIRSTAT && dirstat_by_line) show_dirstat_by_line(&diffstat, options); free_diffstat_info(&diffstat); separator++; @@ -4240,7 +4560,7 @@ void diff_flush(struct diff_options *options) 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 + * options->file to /dev/null should be safe, because we * aren't supposed to produce any output anyway. */ if (options->close_file) @@ -4260,7 +4580,9 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { - putc(options->line_termination, options->file); + fprintf(options->file, "%s%c", + diff_line_prefix(options), + options->line_termination); if (options->stat_sep) { /* attach patch instead of inline */ fputs(options->stat_sep, options->file); @@ -4298,27 +4620,32 @@ free_queue: } } -static void diffcore_apply_filter(const char *filter) +static int match_filter(const struct diff_options *options, const struct diff_filepair *p) +{ + return (((p->status == DIFF_STATUS_MODIFIED) && + ((p->score && + filter_bit_tst(DIFF_STATUS_FILTER_BROKEN, options)) || + (!p->score && + filter_bit_tst(DIFF_STATUS_MODIFIED, options)))) || + ((p->status != DIFF_STATUS_MODIFIED) && + filter_bit_tst(p->status, options))); +} + +static void diffcore_apply_filter(struct diff_options *options) { int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; + DIFF_QUEUE_CLEAR(&outq); - if (!filter) + if (!options->filter) return; - if (strchr(filter, DIFF_STATUS_FILTER_AON)) { + if (filter_bit_tst(DIFF_STATUS_FILTER_AON, options)) { int found; for (i = found = 0; !found && i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) + if (match_filter(options, q->queue[i])) found++; } if (found) @@ -4336,14 +4663,7 @@ static void diffcore_apply_filter(const char *filter) /* Only the matching ones */ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) + if (match_filter(options, p)) diff_q(&outq, p); else diff_free_filepair(p); @@ -4366,6 +4686,38 @@ static int diff_filespec_is_identical(struct diff_filespec *one, return !memcmp(one->data, two->data, one->size); } +static int diff_filespec_check_stat_unmatch(struct diff_filepair *p) +{ + if (p->done_skip_stat_unmatch) + return p->skip_stat_unmatch_result; + + p->done_skip_stat_unmatch = 1; + p->skip_stat_unmatch_result = 0; + /* + * 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 + * do not match these criteria. They have real + * differences. + * + * 2. At this point, the file is known to be modified, + * with the same mode and size, and the object + * name of one side is unknown. Need to inspect + * the identical contents. + */ + if (!DIFF_FILE_VALID(p->one) || /* (1) */ + !DIFF_FILE_VALID(p->two) || + (p->one->sha1_valid && p->two->sha1_valid) || + (p->one->mode != p->two->mode) || + diff_populate_filespec(p->one, CHECK_SIZE_ONLY) || + diff_populate_filespec(p->two, CHECK_SIZE_ONLY) || + (p->one->size != p->two->size) || + !diff_filespec_is_identical(p->one, p->two)) /* (2) */ + p->skip_stat_unmatch_result = 1; + return p->skip_stat_unmatch_result; +} + static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) { int i; @@ -4376,27 +4728,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - /* - * 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 - * do not match these criteria. They have real - * differences. - * - * 2. At this point, the file is known to be modified, - * with the same mode and size, and the object - * name of one side is unknown. Need to inspect - * the identical contents. - */ - if (!DIFF_FILE_VALID(p->one) || /* (1) */ - !DIFF_FILE_VALID(p->two) || - (p->one->sha1_valid && p->two->sha1_valid) || - (p->one->mode != p->two->mode) || - diff_populate_filespec(p->one, 1) || - diff_populate_filespec(p->two, 1) || - (p->one->size != p->two->size) || - !diff_filespec_is_identical(p->one, p->two)) /* (2) */ + if (diff_filespec_check_stat_unmatch(p)) diff_q(&outq, p); else { /* @@ -4432,6 +4764,7 @@ void diffcore_fix_diff_index(struct diff_options *options) void diffcore_std(struct diff_options *options) { + /* NOTE please keep the following in sync with diff_tree_combined() */ if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); if (!options->found_follow) { @@ -4450,7 +4783,7 @@ void diffcore_std(struct diff_options *options) if (!options->found_follow) /* See try_to_follow_renames() in tree-diff.c */ diff_resolve_rename_copy(); - diffcore_apply_filter(options->filter); + diffcore_apply_filter(options); if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) DIFF_OPT_SET(options, HAS_CHANGES); @@ -4464,7 +4797,7 @@ int diff_result_code(struct diff_options *opt, int status) { int result = 0; - diff_warn_rename_limit("diff.renamelimit", + diff_warn_rename_limit("diff.renameLimit", opt->needed_rename_limit, opt->degraded_cc_to_c); if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && @@ -4507,6 +4840,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; @@ -4538,9 +4872,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; } @@ -4553,10 +4887,12 @@ 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) { struct diff_filespec *one, *two; + struct diff_filepair *p; if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) && is_submodule_ignored(concatpath, options)) @@ -4567,6 +4903,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; } @@ -4577,14 +4915,20 @@ 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; + p = diff_queue(&diff_queued_diff, one, two); - diff_queue(&diff_queued_diff, one, two); - if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) - DIFF_OPT_SET(options, HAS_CHANGES); + if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + return; + + if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch && + !diff_filespec_check_stat_unmatch(p)) + return; + + DIFF_OPT_SET(options, HAS_CHANGES); } struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) @@ -4609,7 +4953,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, struct diff_tempfile *temp; const char *argv[3]; const char **arg = argv; - struct child_process child; + struct child_process child = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; int err = 0; @@ -4618,7 +4962,6 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, *arg++ = temp->name; *arg = NULL; - memset(&child, 0, sizeof(child)); child.use_shell = 1; child.argv = argv; child.out = -1; @@ -4684,3 +5027,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(); +} |
