diff options
Diffstat (limited to 'diff.c')
| -rw-r--r-- | diff.c | 3649 |
1 files changed, 2674 insertions, 975 deletions
@@ -2,6 +2,8 @@ * Copyright (C) 2005 Junio C Hamano */ #include "cache.h" +#include "config.h" +#include "tempfile.h" #include "quote.h" #include "diff.h" #include "diffcore.h" @@ -12,9 +14,14 @@ #include "run-command.h" #include "utf8.h" #include "userdiff.h" -#include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" +#include "hashmap.h" #include "ll-merge.h" +#include "string-list.h" +#include "argv-array.h" +#include "graph.h" +#include "packfile.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -23,19 +30,28 @@ #endif static int diff_detect_rename_default; -static int diff_rename_limit_default = 200; +static int diff_indent_heuristic = 1; +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_color_moved_default; +static int diff_context_default = 3; +static int diff_interhunk_context_default; 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 unsigned ws_error_highlight_default = WSEH_NEW; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_NORMAL, /* CONTEXT */ GIT_COLOR_BOLD, /* METAINFO */ GIT_COLOR_CYAN, /* FRAGINFO */ GIT_COLOR_RED, /* OLD */ @@ -43,29 +59,124 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_YELLOW, /* COMMIT */ GIT_COLOR_BG_RED, /* WHITESPACE */ GIT_COLOR_NORMAL, /* FUNCINFO */ + GIT_COLOR_BOLD_MAGENTA, /* OLD_MOVED */ + GIT_COLOR_BOLD_BLUE, /* OLD_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* OLD_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* OLD_MOVED_ALTERNATIVE_DIM */ + GIT_COLOR_BOLD_CYAN, /* NEW_MOVED */ + GIT_COLOR_BOLD_YELLOW, /* NEW_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* NEW_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */ }; -static int parse_diff_color_slot(const char *var, int ofs) +static NORETURN void die_want_option(const char *option_name) { - if (!strcasecmp(var+ofs, "plain")) - return DIFF_PLAIN; - if (!strcasecmp(var+ofs, "meta")) + die(_("option '%s' requires a value"), option_name); +} + +static int parse_diff_color_slot(const char *var) +{ + if (!strcasecmp(var, "context") || !strcasecmp(var, "plain")) + return DIFF_CONTEXT; + 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; + if (!strcasecmp(var, "oldmoved")) + return DIFF_FILE_OLD_MOVED; + if (!strcasecmp(var, "oldmovedalternative")) + return DIFF_FILE_OLD_MOVED_ALT; + if (!strcasecmp(var, "oldmoveddimmed")) + return DIFF_FILE_OLD_MOVED_DIM; + if (!strcasecmp(var, "oldmovedalternativedimmed")) + return DIFF_FILE_OLD_MOVED_ALT_DIM; + if (!strcasecmp(var, "newmoved")) + return DIFF_FILE_NEW_MOVED; + if (!strcasecmp(var, "newmovedalternative")) + return DIFF_FILE_NEW_MOVED_ALT; + if (!strcasecmp(var, "newmoveddimmed")) + return DIFF_FILE_NEW_MOVED_DIM; + if (!strcasecmp(var, "newmovedalternativedimmed")) + return DIFF_FILE_NEW_MOVED_ALT_DIM; return -1; } +static int parse_dirstat_params(struct diff_options *options, const char *params_string, + struct strbuf *errmsg) +{ + char *params_copy = xstrdup(params_string); + struct string_list params = STRING_LIST_INIT_NODUP; + int ret = 0; + int i; + + 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 (!strcmp(p, "lines")) { + DIFF_OPT_SET(options, DIRSTAT_BY_LINE); + DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + } else if (!strcmp(p, "files")) { + DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); + DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + } else if (!strcmp(p, "noncumulative")) { + DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); + } else if (!strcmp(p, "cumulative")) { + DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); + } else if (isdigit(*p)) { + char *end; + int permille = strtoul(p, &end, 10) * 10; + if (*end == '.' && isdigit(*++end)) { + /* only use first digit */ + permille += *end - '0'; + /* .. and ignore any further digits */ + while (isdigit(*++end)) + ; /* nothing */ + } + if (!*end) + options->dirstat_permille = permille; + else { + strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%s'\n"), + p); + ret++; + } + } else { + strbuf_addf(errmsg, _(" Unknown dirstat parameter '%s'\n"), p); + ret++; + } + + } + 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")) + options->submodule_format = DIFF_SUBMODULE_LOG; + else if (!strcmp(value, "short")) + options->submodule_format = DIFF_SUBMODULE_SHORT; + else if (!strcmp(value, "diff")) + options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF; + else + return -1; + return 0; +} + static int git_config_rename(const char *var, const char *value) { if (!value) @@ -75,16 +186,124 @@ 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; +} + +static int parse_one_token(const char **arg, const char *token) +{ + const char *rest; + if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) { + *arg = rest; + return 1; + } + return 0; +} + +static int parse_ws_error_highlight(const char *arg) +{ + const char *orig_arg = arg; + unsigned val = 0; + + while (*arg) { + if (parse_one_token(&arg, "none")) + val = 0; + else if (parse_one_token(&arg, "default")) + val = WSEH_NEW; + else if (parse_one_token(&arg, "all")) + val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT; + else if (parse_one_token(&arg, "new")) + val |= WSEH_NEW; + else if (parse_one_token(&arg, "old")) + val |= WSEH_OLD; + else if (parse_one_token(&arg, "context")) + val |= WSEH_CONTEXT; + else { + return -1 - (int)(arg - orig_arg); + } + if (*arg) + arg++; + } + return val; +} + /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should * never be affected by the setting of diff.renames * the user happens to have in the configuration file. */ +void init_diff_ui_defaults(void) +{ + diff_detect_rename_default = 1; +} + +int git_diff_heuristic_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.indentheuristic")) + diff_indent_heuristic = git_config_bool(var, value); + return 0; +} + +static int parse_color_moved(const char *arg) +{ + switch (git_parse_maybe_bool(arg)) { + case 0: + return COLOR_MOVED_NO; + case 1: + return COLOR_MOVED_DEFAULT; + default: + break; + } + + if (!strcmp(arg, "no")) + return COLOR_MOVED_NO; + else if (!strcmp(arg, "plain")) + return COLOR_MOVED_PLAIN; + else if (!strcmp(arg, "zebra")) + return COLOR_MOVED_ZEBRA; + else if (!strcmp(arg, "default")) + return COLOR_MOVED_DEFAULT; + else if (!strcmp(arg, "dimmed_zebra")) + return COLOR_MOVED_ZEBRA_DIM; + else + return error(_("color moved setting must be one of 'no', 'default', 'zebra', 'dimmed_zebra', 'plain'")); +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { - diff_use_color_default = git_config_colorbool(var, value, -1); + diff_use_color_default = git_config_colorbool(var, value); + return 0; + } + if (!strcmp(var, "diff.colormoved")) { + int cm = parse_color_moved(value); + if (cm < 0) + return -1; + diff_color_moved_default = cm; + 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.interhunkcontext")) { + diff_interhunk_context_default = git_config_int(var, value); + if (diff_interhunk_context_default < 0) + return -1; return 0; } if (!strcmp(var, "diff.renames")) { @@ -103,38 +322,68 @@ 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 (!strcmp(var, "diff.wserrorhighlight")) { + int val = parse_ws_error_highlight(value); + if (val < 0) + return -1; + ws_error_highlight_default = val; + return 0; + } + + if (git_color_config(var, value, cb) < 0) + return -1; + return git_diff_basic_config(var, value, 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 */ @@ -145,10 +394,21 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } - if (!prefixcmp(var, "submodule.")) - return parse_submodule_config_option(var, value); + if (!strcmp(var, "diff.dirstat")) { + struct strbuf errmsg = STRBUF_INIT; + default_diff_options.dirstat_permille = diff_dirstat_permille_default; + if (parse_dirstat_params(&default_diff_options, value, &errmsg)) + warning(_("Found errors in 'diff.dirstat' config variable:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + diff_dirstat_permille_default = default_diff_options.dirstat_permille; + return 0; + } + + if (git_diff_heuristic_config(var, value, cb) < 0) + return -1; - return git_color_default_config(var, value, cb); + return git_default_config(var, value, cb); } static char *quote_two(const char *one, const char *two) @@ -183,14 +443,27 @@ static const char *external_diff(void) return external_diff_cmd; } +/* + * Keep track of files used for diffing. Sometimes such an entry + * refers to a temporary file, sometimes to an existing file, and + * sometimes to "/dev/null". + */ static struct diff_tempfile { - const char *name; /* filename external diff should read from */ - char hex[41]; + /* + * filename external diff should read from, or NULL if this + * entry is currently not in use: + */ + const char *name; + + char hex[GIT_MAX_HEXSZ + 1]; char mode[10]; - char tmp_path[PATH_MAX]; -} diff_temp[2]; -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + /* + * If this diff_tempfile instance refers to a temporary file, + * this tempfile object is used to manage its lifetime. + */ + struct tempfile *tempfile; +} diff_temp[2]; struct emit_callback { int color_diff; @@ -199,11 +472,9 @@ struct emit_callback { int blank_at_eof_in_postimage; int lno_in_preimage; int lno_in_postimage; - sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; struct diff_options *opt; - int *found_changesp; struct strbuf *header; }; @@ -245,6 +516,15 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) return 0; } +/* like fill_mmfile, but only for size, so we can avoid retrieving blob */ +static unsigned long diff_filespec_size(struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) + return 0; + diff_populate_filespec(one, CHECK_SIZE_ONLY); + return one->size; +} + static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) { char *ptr = mf->ptr; @@ -297,12 +577,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'); @@ -338,6 +613,701 @@ static void emit_line(struct diff_options *o, const char *set, const char *reset emit_line_0(o, set, reset, line[0], line+1, len-1); } +enum diff_symbol { + DIFF_SYMBOL_BINARY_DIFF_HEADER, + DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA, + DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL, + DIFF_SYMBOL_BINARY_DIFF_BODY, + DIFF_SYMBOL_BINARY_DIFF_FOOTER, + DIFF_SYMBOL_STATS_SUMMARY_NO_FILES, + DIFF_SYMBOL_STATS_SUMMARY_ABBREV, + DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES, + DIFF_SYMBOL_STATS_LINE, + DIFF_SYMBOL_WORD_DIFF, + DIFF_SYMBOL_STAT_SEP, + DIFF_SYMBOL_SUMMARY, + DIFF_SYMBOL_SUBMODULE_ADD, + DIFF_SYMBOL_SUBMODULE_DEL, + DIFF_SYMBOL_SUBMODULE_UNTRACKED, + DIFF_SYMBOL_SUBMODULE_MODIFIED, + DIFF_SYMBOL_SUBMODULE_HEADER, + DIFF_SYMBOL_SUBMODULE_ERROR, + DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, + DIFF_SYMBOL_REWRITE_DIFF, + DIFF_SYMBOL_BINARY_FILES, + DIFF_SYMBOL_HEADER, + DIFF_SYMBOL_FILEPAIR_PLUS, + DIFF_SYMBOL_FILEPAIR_MINUS, + DIFF_SYMBOL_WORDS_PORCELAIN, + DIFF_SYMBOL_WORDS, + DIFF_SYMBOL_CONTEXT, + DIFF_SYMBOL_CONTEXT_INCOMPLETE, + DIFF_SYMBOL_PLUS, + DIFF_SYMBOL_MINUS, + DIFF_SYMBOL_NO_LF_EOF, + DIFF_SYMBOL_CONTEXT_FRAGINFO, + DIFF_SYMBOL_CONTEXT_MARKER, + DIFF_SYMBOL_SEPARATOR +}; +/* + * Flags for content lines: + * 0..12 are whitespace rules + * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT + * 16 is marking if the line is blank at EOF + */ +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16) +#define DIFF_SYMBOL_MOVED_LINE (1<<17) +#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18) +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19) +#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK) + +/* + * This struct is used when we need to buffer the output of the diff output. + * + * NEEDSWORK: Instead of storing a copy of the line, add an offset pointer + * into the pre/post image file. This pointer could be a union with the + * line pointer. By storing an offset into the file instead of the literal line, + * we can decrease the memory footprint for the buffered output. At first we + * may want to only have indirection for the content lines, but we could also + * enhance the state for emitting prefabricated lines, e.g. the similarity + * score line or hunk/file headers would only need to store a number or path + * and then the output can be constructed later on depending on state. + */ +struct emitted_diff_symbol { + const char *line; + int len; + int flags; + enum diff_symbol s; +}; +#define EMITTED_DIFF_SYMBOL_INIT {NULL} + +struct emitted_diff_symbols { + struct emitted_diff_symbol *buf; + int nr, alloc; +}; +#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0} + +static void append_emitted_diff_symbol(struct diff_options *o, + struct emitted_diff_symbol *e) +{ + struct emitted_diff_symbol *f; + + ALLOC_GROW(o->emitted_symbols->buf, + o->emitted_symbols->nr + 1, + o->emitted_symbols->alloc); + f = &o->emitted_symbols->buf[o->emitted_symbols->nr++]; + + memcpy(f, e, sizeof(struct emitted_diff_symbol)); + f->line = e->line ? xmemdupz(e->line, e->len) : NULL; +} + +struct moved_entry { + struct hashmap_entry ent; + const struct emitted_diff_symbol *es; + struct moved_entry *next_line; +}; + +static int next_byte(const char **cp, const char **endp, + const struct diff_options *diffopt) +{ + int retval; + + if (*cp > *endp) + return -1; + + if (isspace(**cp)) { + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* + * After skipping a couple of whitespaces, + * we still have to account for one space. + */ + return (int)' '; + } + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* return the first non-ws character via the usual below */ + } + } + + retval = (unsigned char)(**cp); + (*cp)++; + return retval; +} + +static int moved_entry_cmp(const struct diff_options *diffopt, + const struct moved_entry *a, + const struct moved_entry *b, + const void *keydata) +{ + const char *ap = a->es->line, *ae = a->es->line + a->es->len; + const char *bp = b->es->line, *be = b->es->line + b->es->len; + + if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS)) + return a->es->len != b->es->len || memcmp(ap, bp, a->es->len); + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) { + while (ae > ap && isspace(*ae)) + ae--; + while (be > bp && isspace(*be)) + be--; + } + + while (1) { + int ca, cb; + ca = next_byte(&ap, &ae, diffopt); + cb = next_byte(&bp, &be, diffopt); + if (ca != cb) + return 1; + if (ca < 0) + return 0; + } +} + +static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o) +{ + if (o->xdl_opts & XDF_WHITESPACE_FLAGS) { + static struct strbuf sb = STRBUF_INIT; + const char *ap = es->line, *ae = es->line + es->len; + int c; + + strbuf_reset(&sb); + while (ae > ap && isspace(*ae)) + ae--; + while ((c = next_byte(&ap, &ae, o)) > 0) + strbuf_addch(&sb, c); + + return memhash(sb.buf, sb.len); + } else { + return memhash(es->line, es->len); + } +} + +static struct moved_entry *prepare_entry(struct diff_options *o, + int line_no) +{ + struct moved_entry *ret = xmalloc(sizeof(*ret)); + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no]; + + ret->ent.hash = get_string_hash(l, o); + ret->es = l; + ret->next_line = NULL; + + return ret; +} + +static void add_lines_to_move_detection(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry *prev_line = NULL; + + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm; + struct moved_entry *key; + + switch (o->emitted_symbols->buf[n].s) { + case DIFF_SYMBOL_PLUS: + hm = add_lines; + break; + case DIFF_SYMBOL_MINUS: + hm = del_lines; + break; + default: + prev_line = NULL; + continue; + } + + key = prepare_entry(o, n); + if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s) + prev_line->next_line = key; + + hashmap_add(hm, key); + prev_line = key; + } +} + +static int shrink_potential_moved_blocks(struct moved_entry **pmb, + int pmb_nr) +{ + int lp, rp; + + /* Shrink the set of potential block to the remaining running */ + for (lp = 0, rp = pmb_nr - 1; lp <= rp;) { + while (lp < pmb_nr && pmb[lp]) + lp++; + /* lp points at the first NULL now */ + + while (rp > -1 && !pmb[rp]) + rp--; + /* rp points at the last non-NULL */ + + if (lp < pmb_nr && rp > -1 && lp < rp) { + pmb[lp] = pmb[rp]; + pmb[rp] = NULL; + rp--; + lp++; + } + } + + /* Remember the number of running sets */ + return rp + 1; +} + +/* + * If o->color_moved is COLOR_MOVED_PLAIN, this function does nothing. + * + * Otherwise, if the last block has fewer alphanumeric characters than + * COLOR_MOVED_MIN_ALNUM_COUNT, unset DIFF_SYMBOL_MOVED_LINE on all lines in + * that block. + * + * The last block consists of the (n - block_length)'th line up to but not + * including the nth line. + * + * NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c. + * Think of a way to unify them. + */ +static void adjust_last_block(struct diff_options *o, int n, int block_length) +{ + int i, alnum_count = 0; + if (o->color_moved == COLOR_MOVED_PLAIN) + return; + for (i = 1; i < block_length + 1; i++) { + const char *c = o->emitted_symbols->buf[n - i].line; + for (; *c; c++) { + if (!isalnum(*c)) + continue; + alnum_count++; + if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT) + return; + } + } + for (i = 1; i < block_length + 1; i++) + o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE; +} + +/* Find blocks of moved code, delegate actual coloring decision to helper */ +static void mark_color_as_moved(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry **pmb = NULL; /* potentially moved blocks */ + int pmb_nr = 0, pmb_alloc = 0; + int n, flipped_block = 1, block_length = 0; + + + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm = NULL; + struct moved_entry *key; + struct moved_entry *match = NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + int i; + + switch (l->s) { + case DIFF_SYMBOL_PLUS: + hm = del_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + case DIFF_SYMBOL_MINUS: + hm = add_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + default: + flipped_block = 1; + } + + if (!match) { + adjust_last_block(o, n, block_length); + pmb_nr = 0; + block_length = 0; + continue; + } + + l->flags |= DIFF_SYMBOL_MOVED_LINE; + + if (o->color_moved == COLOR_MOVED_PLAIN) + continue; + + /* Check any potential block runs, advance each or nullify */ + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *p = pmb[i]; + struct moved_entry *pnext = (p && p->next_line) ? + p->next_line : NULL; + if (pnext && !hm->cmpfn(o, pnext, match, NULL)) { + pmb[i] = p->next_line; + } else { + pmb[i] = NULL; + } + } + + pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr); + + if (pmb_nr == 0) { + /* + * The current line is the start of a new block. + * Setup the set of potential blocks. + */ + for (; match; match = hashmap_get_next(hm, match)) { + ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc); + pmb[pmb_nr++] = match; + } + + flipped_block = (flipped_block + 1) % 2; + + adjust_last_block(o, n, block_length); + block_length = 0; + } + + block_length++; + + if (flipped_block) + l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; + } + adjust_last_block(o, n, block_length); + + free(pmb); +} + +#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \ + (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT) +static void dim_moved_lines(struct diff_options *o) +{ + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct emitted_diff_symbol *prev = (n != 0) ? + &o->emitted_symbols->buf[n - 1] : NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + struct emitted_diff_symbol *next = + (n < o->emitted_symbols->nr - 1) ? + &o->emitted_symbols->buf[n + 1] : NULL; + + /* Not a plus or minus line? */ + if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS) + continue; + + /* Not a moved line? */ + if (!(l->flags & DIFF_SYMBOL_MOVED_LINE)) + continue; + + /* + * If prev or next are not a plus or minus line, + * pretend they don't exist + */ + if (prev && prev->s != DIFF_SYMBOL_PLUS && + prev->s != DIFF_SYMBOL_MINUS) + prev = NULL; + if (next && next->s != DIFF_SYMBOL_PLUS && + next->s != DIFF_SYMBOL_MINUS) + next = NULL; + + /* Inside a block? */ + if ((prev && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) && + (next && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) { + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + continue; + } + + /* Check if we are at an interesting bound: */ + if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + + /* + * The boundary to prev and next are not interesting, + * so this line is not interesting as a whole + */ + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + } +} + +static void emit_line_ws_markup(struct diff_options *o, + const char *set, const char *reset, + const char *line, int len, char sign, + unsigned ws_rule, int blank_at_eof) +{ + const char *ws = NULL; + + if (o->ws_error_highlight & ws_rule) { + ws = diff_get_color_opt(o, DIFF_WHITESPACE); + if (!*ws) + ws = NULL; + } + + if (!ws) + emit_line_0(o, set, reset, sign, line, len); + else if (blank_at_eof) + /* Blank line at EOF - paint '+' as well */ + emit_line_0(o, ws, reset, sign, line, len); + else { + /* Emit just the prefix, then the rest. */ + emit_line_0(o, set, reset, sign, "", 0); + ws_check_emit(line, len, ws_rule, + o->file, set, reset, ws); + } +} + +static void emit_diff_symbol_from_struct(struct diff_options *o, + struct emitted_diff_symbol *eds) +{ + static const char *nneof = " No newline at end of file\n"; + const char *context, *reset, *set, *meta, *fraginfo; + struct strbuf sb = STRBUF_INIT; + + enum diff_symbol s = eds->s; + const char *line = eds->line; + int len = eds->len; + unsigned flags = eds->flags; + + switch (s) { + case DIFF_SYMBOL_NO_LF_EOF: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + putc('\n', o->file); + emit_line_0(o, context, reset, '\\', + nneof, strlen(nneof)); + break; + case DIFF_SYMBOL_SUBMODULE_HEADER: + case DIFF_SYMBOL_SUBMODULE_ERROR: + case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH: + case DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES: + case DIFF_SYMBOL_SUMMARY: + case DIFF_SYMBOL_STATS_LINE: + case DIFF_SYMBOL_BINARY_DIFF_BODY: + case DIFF_SYMBOL_CONTEXT_FRAGINFO: + emit_line(o, "", "", line, len); + break; + case DIFF_SYMBOL_CONTEXT_INCOMPLETE: + case DIFF_SYMBOL_CONTEXT_MARKER: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_SEPARATOR: + fprintf(o->file, "%s%c", + diff_line_prefix(o), + o->line_termination); + break; + case DIFF_SYMBOL_CONTEXT: + set = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, ' ', + flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0); + break; + case DIFF_SYMBOL_PLUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '+', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, + flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF); + break; + case DIFF_SYMBOL_MINUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '-', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0); + break; + case DIFF_SYMBOL_WORDS_PORCELAIN: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + fputs("~\n", o->file); + break; + case DIFF_SYMBOL_WORDS: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + /* + * Skip the prefix character, if any. With + * diff_suppress_blank_empty, there may be + * none. + */ + if (line[0] != '\n') { + line++; + len--; + } + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_FILEPAIR_PLUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_FILEPAIR_MINUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s--- %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_BINARY_FILES: + case DIFF_SYMBOL_HEADER: + fprintf(o->file, "%s", line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER: + fprintf(o->file, "%sGIT binary patch\n", diff_line_prefix(o)); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA: + fprintf(o->file, "%sdelta %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL: + fprintf(o->file, "%sliteral %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_FOOTER: + fputs(diff_line_prefix(o), o->file); + fputc('\n', o->file); + break; + case DIFF_SYMBOL_REWRITE_DIFF: + fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, fraginfo, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_ADD: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_DEL: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_UNTRACKED: + fprintf(o->file, "%sSubmodule %s contains untracked content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_SUBMODULE_MODIFIED: + fprintf(o->file, "%sSubmodule %s contains modified content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_STATS_SUMMARY_NO_FILES: + emit_line(o, "", "", " 0 files changed\n", + strlen(" 0 files changed\n")); + break; + case DIFF_SYMBOL_STATS_SUMMARY_ABBREV: + emit_line(o, "", "", " ...\n", strlen(" ...\n")); + break; + case DIFF_SYMBOL_WORD_DIFF: + fprintf(o->file, "%.*s", len, line); + break; + case DIFF_SYMBOL_STAT_SEP: + fputs(o->stat_sep, o->file); + break; + default: + die("BUG: unknown diff symbol"); + } + strbuf_release(&sb); +} + +static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s, + const char *line, int len, unsigned flags) +{ + struct emitted_diff_symbol e = {line, len, flags, s}; + + if (o->emitted_symbols) + append_emitted_diff_symbol(o, &e); + else + emit_diff_symbol_from_struct(o, &e); +} + +void diff_emit_submodule_del(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_DEL, line, strlen(line), 0); +} + +void diff_emit_submodule_add(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ADD, line, strlen(line), 0); +} + +void diff_emit_submodule_untracked(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_UNTRACKED, + path, strlen(path), 0); +} + +void diff_emit_submodule_modified(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_MODIFIED, + path, strlen(path), 0); +} + +void diff_emit_submodule_header(struct diff_options *o, const char *header) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_HEADER, + header, strlen(header), 0); +} + +void diff_emit_submodule_error(struct diff_options *o, const char *err) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ERROR, err, strlen(err), 0); +} + +void diff_emit_submodule_pipethrough(struct diff_options *o, + const char *line, int len) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, line, len, 0); +} + static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) { if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && @@ -353,26 +1323,33 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + unsigned flags = WSEH_NEW | ecbdata->ws_rule; + if (new_blank_line_at_eof(ecbdata, line, len)) + flags |= DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF; - if (!*ws) - emit_line_0(ecbdata->opt, set, reset, '+', line, len); - else if (new_blank_line_at_eof(ecbdata, line, len)) - /* Blank line at EOF - paint '+' as well */ - emit_line_0(ecbdata->opt, ws, reset, '+', line, len); - else { - /* Emit just the prefix, then the rest. */ - emit_line_0(ecbdata->opt, set, reset, '+', "", 0); - ws_check_emit(line, len, ecbdata->ws_rule, - ecbdata->opt->file, set, reset, ws); - } + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags); +} + +static void emit_del_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) +{ + unsigned flags = WSEH_OLD | ecbdata->ws_rule; + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags); +} + +static void emit_context_line(const char *reset, + struct emit_callback *ecbdata, + const char *line, int len) +{ + unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule; + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags); } static void emit_hunk_header(struct emit_callback *ecbdata, const char *line, int len) { - const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); + const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT); const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); @@ -389,15 +1366,16 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (len < 10 || memcmp(line, atat, 2) || !(ep = memmem(line + 2, len - 2, atat, 2))) { - emit_line(ecbdata->opt, plain, reset, line, len); + emit_diff_symbol(ecbdata->opt, + DIFF_SYMBOL_CONTEXT_MARKER, line, len, 0); return; } 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" @@ -411,19 +1389,21 @@ 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, context); 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); - emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len); + strbuf_complete_line(&msgbuf); + emit_diff_symbol(ecbdata->opt, + DIFF_SYMBOL_CONTEXT_FRAGINFO, msgbuf.buf, msgbuf.len, 0); strbuf_release(&msgbuf); } @@ -435,36 +1415,27 @@ static struct diff_tempfile *claim_diff_tempfile(void) { die("BUG: diff is failing to clean up its tempfiles"); } -static int remove_tempfile_installed; - static void remove_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { - if (diff_temp[i].name == diff_temp[i].tmp_path) - unlink_or_warn(diff_temp[i].name); + if (is_tempfile_active(diff_temp[i].tempfile)) + delete_tempfile(&diff_temp[i].tempfile); diff_temp[i].name = NULL; } } -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - sigchain_pop(signo); - raise(signo); -} - -static void print_line_count(FILE *file, int count) +static void add_line_count(struct strbuf *out, int count) { switch (count) { case 0: - fprintf(file, "0,0"); + strbuf_addstr(out, "0,0"); break; case 1: - fprintf(file, "1"); + strbuf_addstr(out, "1"); break; default: - fprintf(file, "1,%d", count); + strbuf_addf(out, "1,%d", count); break; } } @@ -473,8 +1444,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb, int prefix, const char *data, int size) { const char *endp = NULL; - static const char *nneof = " No newline at end of file\n"; - const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); while (0 < size) { @@ -484,8 +1453,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, len = endp ? (endp - data + 1) : size; if (prefix != '+') { ecb->lno_in_preimage++; - emit_line_0(ecb->opt, old, reset, '-', - data, len); + emit_del_line(reset, ecb, data, len); } else { ecb->lno_in_postimage++; emit_add_line(reset, ecb, data, len); @@ -493,12 +1461,8 @@ static void emit_rewrite_lines(struct emit_callback *ecb, size -= len; data += len; } - if (!endp) { - const char *plain = diff_get_color(ecb->color_diff, - DIFF_PLAIN); - emit_line_0(ecb->opt, plain, reset, '\\', - nneof, strlen(nneof)); - } + if (!endp) + emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0); } static void emit_rewrite_diff(const char *name_a, @@ -510,23 +1474,12 @@ static void emit_rewrite_diff(const char *name_a, struct diff_options *o) { int lc_a, lc_b; - int color_diff = DIFF_OPT_TST(o, COLOR_DIFF); - const char *name_a_tab, *name_b_tab; - const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); - const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; 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; - } + struct strbuf out = STRBUF_INIT; if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -538,8 +1491,6 @@ static void emit_rewrite_diff(const char *name_a, name_a += (*name_a == '/'); name_b += (*name_b == '/'); - name_a_tab = strchr(name_a, ' ') ? "\t" : ""; - name_b_tab = strchr(name_b, ' ') ? "\t" : ""; strbuf_reset(&a_name); strbuf_reset(&b_name); @@ -550,9 +1501,8 @@ static void emit_rewrite_diff(const char *name_a, size_two = fill_textconv(textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); - ecbdata.color_diff = color_diff; - ecbdata.found_changesp = &o->found_changes; - ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.color_diff = want_color(o->use_color); + ecbdata.ws_rule = whitespace_rule(name_b); ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; @@ -567,16 +1517,24 @@ static void emit_rewrite_diff(const char *name_a, lc_a = count_lines(data_one, size_one); lc_b = count_lines(data_two, size_two); - fprintf(o->file, - "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -", - line_prefix, metainfo, a_name.buf, name_a_tab, reset, - line_prefix, metainfo, b_name.buf, name_b_tab, reset, - line_prefix, fraginfo); - print_line_count(o->file, lc_a); - fprintf(o->file, " +"); - print_line_count(o->file, lc_b); - fprintf(o->file, " @@%s\n", reset); - if (lc_a) + + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS, + a_name.buf, a_name.len, 0); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS, + b_name.buf, b_name.len, 0); + + strbuf_addstr(&out, "@@ -"); + if (!o->irreversible_delete) + add_line_count(&out, lc_a); + else + strbuf_addstr(&out, "?,?"); + strbuf_addstr(&out, " +"); + add_line_count(&out, lc_b); + strbuf_addstr(&out, " @@\n"); + emit_diff_symbol(o, DIFF_SYMBOL_REWRITE_DIFF, out.buf, out.len, 0); + strbuf_release(&out); + + if (lc_a && !o->irreversible_delete) emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); @@ -588,7 +1546,7 @@ static void emit_rewrite_diff(const char *name_a, struct diff_words_buffer { mmfile_t text; - long alloc; + unsigned long alloc; struct diff_words_orig { const char *begin, *end; } *orig; @@ -606,22 +1564,20 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } -struct diff_words_style_elem -{ +struct diff_words_style_elem { const char *prefix; const char *suffix; const char *color; /* NULL; filled in by the setup code if * color is enabled */ }; -struct diff_words_style -{ +struct diff_words_style { enum diff_words_type type; struct diff_words_style_elem new, old, ctx; const char *newline; }; -struct diff_words_style diff_words_styles[] = { +static struct diff_words_style diff_words_styles[] = { { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" }, { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" }, { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" } @@ -637,44 +1593,56 @@ struct diff_words_data { struct diff_words_style *style; }; -static int fn_out_diff_words_write_helper(FILE *fp, +static int fn_out_diff_words_write_helper(struct diff_options *o, struct diff_words_style_elem *st_el, const char *newline, - size_t count, const char *buf, - const char *line_prefix) + size_t count, const char *buf) { int print = 0; + struct strbuf sb = STRBUF_INIT; while (count) { char *p = memchr(buf, '\n', count); if (print) - fputs(line_prefix, fp); + strbuf_addstr(&sb, diff_line_prefix(o)); + if (p != buf) { - if (st_el->color && fputs(st_el->color, fp) < 0) - return -1; - if (fputs(st_el->prefix, fp) < 0 || - fwrite(buf, p ? p - buf : count, 1, fp) != 1 || - fputs(st_el->suffix, fp) < 0) - return -1; - if (st_el->color && *st_el->color - && fputs(GIT_COLOR_RESET, fp) < 0) - return -1; + const char *reset = st_el->color && *st_el->color ? + GIT_COLOR_RESET : NULL; + if (st_el->color && *st_el->color) + strbuf_addstr(&sb, st_el->color); + strbuf_addstr(&sb, st_el->prefix); + strbuf_add(&sb, buf, p ? p - buf : count); + strbuf_addstr(&sb, st_el->suffix); + if (reset) + strbuf_addstr(&sb, reset); } if (!p) - return 0; - if (fputs(newline, fp) < 0) - return -1; + goto out; + + strbuf_addstr(&sb, newline); count -= p + 1 - buf; buf = p + 1; print = 1; + if (count) { + emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF, + sb.buf, sb.len, 0); + strbuf_reset(&sb); + } } + +out: + if (sb.len) + emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF, + sb.buf, sb.len, 0); + strbuf_release(&sb); return 0; } /* * '--color-words' algorithm can be described as: * - * 1. collect a the minus/plus lines of a diff hunk, divided into + * 1. collect the minus/plus lines of a diff hunk, divided into * minus-lines and plus-lines; * * 2. break both minus-lines and plus-lines into words and @@ -721,18 +1689,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) { @@ -753,24 +1717,20 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) fputs(line_prefix, diff_words->opt->file); } if (diff_words->current_plus != plus_begin) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->ctx, style->newline, plus_begin - diff_words->current_plus, - diff_words->current_plus, line_prefix); - if (*(plus_begin - 1) == '\n') - fputs(line_prefix, diff_words->opt->file); + diff_words->current_plus); } if (minus_begin != minus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, - minus_end - minus_begin, minus_begin, - line_prefix); + minus_end - minus_begin, minus_begin); } if (plus_begin != plus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->new, style->newline, - plus_end - plus_begin, plus_begin, - line_prefix); + plus_end - plus_begin, plus_begin); } diff_words->current_plus = plus_end; @@ -783,7 +1743,8 @@ static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex, { if (word_regex && *begin < buffer->size) { regmatch_t match[1]; - if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) { + if (!regexec_buf(word_regex, buffer->ptr + *begin, + buffer->size - *begin, 1, match, 0)) { char *p = memchr(buffer->ptr + *begin + match[0].rm_so, '\n', match[0].rm_eo - match[0].rm_so); *end = p ? p - buffer->ptr : match[0].rm_eo + *begin; @@ -856,22 +1817,19 @@ 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) { - fputs(line_prefix, diff_words->opt->file); - fn_out_diff_words_write_helper(diff_words->opt->file, + emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF, + line_prefix, strlen(line_prefix), 0); + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, diff_words->minus.text.size, - diff_words->minus.text.ptr, line_prefix); + diff_words->minus.text.ptr); diff_words->minus.text.size = 0; return; } @@ -886,19 +1844,20 @@ static void diff_words_show(struct diff_words_data *diff_words) xpp.flags = 0; /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; - xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, - &xpp, &xecfg); + if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, + &xpp, &xecfg)) + die("unable to generate word diff"); free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + diff_words->plus.text.size) { if (color_words_output_graph_prefix(diff_words)) - fputs(line_prefix, diff_words->opt->file); - fn_out_diff_words_write_helper(diff_words->opt->file, + emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF, + line_prefix, strlen(line_prefix), 0); + fn_out_diff_words_write_helper(diff_words->opt, &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, diff_words->current_plus, - line_prefix); + - diff_words->current_plus, diff_words->current_plus); } diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -906,15 +1865,105 @@ static void diff_words_show(struct diff_words_data *diff_words) /* In "color-words" mode, show word-diff of words accumulated in the buffer */ static void diff_words_flush(struct emit_callback *ecbdata) { + struct diff_options *wo = ecbdata->diff_words->opt; + if (ecbdata->diff_words->minus.text.size || ecbdata->diff_words->plus.text.size) diff_words_show(ecbdata->diff_words); + + if (wo->emitted_symbols) { + struct diff_options *o = ecbdata->opt; + struct emitted_diff_symbols *wol = wo->emitted_symbols; + int i; + + /* + * NEEDSWORK: + * Instead of appending each, concat all words to a line? + */ + for (i = 0; i < wol->nr; i++) + append_emitted_diff_symbol(o, &wol->buf[i]); + + for (i = 0; i < wol->nr; i++) + free((void *)wol->buf[i].line); + + wol->nr = 0; + } +} + +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 (orig_opts->emitted_symbols) + o->emitted_symbols = + xcalloc(1, sizeof(struct emitted_diff_symbols)); + + 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_CONTEXT); + } } static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { diff_words_flush(ecbdata); + free (ecbdata->diff_words->opt->emitted_symbols); + 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); @@ -923,26 +1972,33 @@ static void free_diff_words_data(struct emit_callback *ecbdata) regfree(ecbdata->diff_words->word_regex); free(ecbdata->diff_words->word_regex); } - free(ecbdata->diff_words); - ecbdata->diff_words = NULL; + FREE_AND_NULL(ecbdata->diff_words); } } const char *diff_get_color(int diff_use_color, enum color_diff ix) { - if (diff_use_color) + if (want_color(diff_use_color)) return diff_colors[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; unsigned long allot; size_t l = len; - if (ecb->truncate) - return ecb->truncate(line, len); cp = line; allot = l; while (0 < l) { @@ -971,35 +2027,25 @@ static void find_lno(const char *line, struct emit_callback *ecbdata) static void fn_out_consume(void *priv, char *line, unsigned long len) { struct emit_callback *ecbdata = priv; - const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); - const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); 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; - } + o->found_changes = 1; if (ecbdata->header) { - fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + ecbdata->header->buf, ecbdata->header->len, 0); strbuf_reset(ecbdata->header); ecbdata->header = NULL; } - *(ecbdata->found_changesp) = 1; if (ecbdata->label_path[0]) { - const char *name_a_tab, *name_b_tab; - - name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; - name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - - fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n", - line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n", - line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS, + ecbdata->label_path[0], + strlen(ecbdata->label_path[0]), 0); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS, + ecbdata->label_path[1], + strlen(ecbdata->label_path[1]), 0); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -1015,20 +2061,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = sane_truncate_line(ecbdata, line, len); find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); - if (line[len-1] != '\n') - putc('\n', ecbdata->opt->file); - return; - } - - if (len < 1) { - emit_line(ecbdata->opt, reset, reset, line, len); - if (ecbdata->diff_words - && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) - fputs("~\n", ecbdata->opt->file); return; } if (ecbdata->diff_words) { + enum diff_symbol s = + ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN ? + DIFF_SYMBOL_WORDS_PORCELAIN : DIFF_SYMBOL_WORDS; if (line[0] == '-') { diff_words_append(line, len, &ecbdata->diff_words->minus); @@ -1037,29 +2076,41 @@ 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) { - emit_line(ecbdata->opt, plain, reset, line, len); - fputs("~\n", ecbdata->opt->file); - } else { - /* don't print the prefix character */ - emit_line(ecbdata->opt, plain, reset, line+1, len-1); - } + emit_diff_symbol(o, s, line, len, 0); return; } - if (line[0] != '+') { - const char *color = - diff_get_color(ecbdata->color_diff, - line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); - ecbdata->lno_in_preimage++; - if (line[0] == ' ') - ecbdata->lno_in_postimage++; - emit_line(ecbdata->opt, color, reset, line, len); - } else { + switch (line[0]) { + case '+': ecbdata->lno_in_postimage++; emit_add_line(reset, ecbdata, line + 1, len - 1); + break; + case '-': + ecbdata->lno_in_preimage++; + emit_del_line(reset, ecbdata, line + 1, len - 1); + break; + case ' ': + ecbdata->lno_in_postimage++; + ecbdata->lno_in_preimage++; + emit_context_line(reset, ecbdata, line + 1, len - 1); + break; + default: + /* incomplete line at the end */ + ecbdata->lno_in_preimage++; + emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE, + line, len, 0); + break; } } @@ -1069,6 +2120,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; @@ -1095,7 +2147,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--; @@ -1140,6 +2203,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; }; @@ -1149,12 +2213,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); @@ -1183,29 +2243,25 @@ 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); -} - -static void show_name(FILE *file, - const char *prefix, const char *name, int len) -{ - fprintf(file, " %s%-*s |", prefix, len, name); + return 1 + (it * (width - 1) / max_change); } -static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset) +static void show_graph(struct strbuf *out, char ch, int cnt, + const char *set, const char *reset) { if (cnt <= 0) return; - fprintf(file, "%s", set); - while (cnt--) - putc(ch, file); - fprintf(file, "%s", reset); + strbuf_addstr(out, set); + strbuf_addchars(out, ch, cnt); + strbuf_addstr(out, reset); } static void fill_print_name(struct diffstat_file *file) @@ -1229,77 +2285,208 @@ static void fill_print_name(struct diffstat_file *file) file->print_name = pname; } +static void print_stat_summary_inserts_deletes(struct diff_options *options, + int files, int insertions, int deletions) +{ + struct strbuf sb = STRBUF_INIT; + + if (!files) { + assert(insertions == 0 && deletions == 0); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_NO_FILES, + NULL, 0, 0); + return; + } + + 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'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES, + sb.buf, sb.len, 0); + strbuf_release(&sb); +} + +void print_stat_summary(FILE *fp, int files, + int insertions, int deletions) +{ + struct diff_options o; + memset(&o, 0, sizeof(o)); + o.file = fp; + + print_stat_summary_inserts_deletes(&o, files, insertions, deletions); +} + 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; - const char *reset, *set, *add_c, *del_c; - const char *line_prefix = ""; - struct strbuf *msg = NULL; + 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; + int extra_shown = 0; + const char *line_prefix = diff_line_prefix(options); + struct strbuf out = STRBUF_INIT; 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; + 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); - set = diff_get_color_opt(options, DIFF_PLAIN); add_c = diff_get_color_opt(options, DIFF_FILE_NEW); del_c = diff_get_color_opt(options, DIFF_FILE_OLD); - for (i = 0; i < data->nr; i++) { + /* + * 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 (!file->is_interesting && (change == 0)) { + count++; /* not shown == room for one more */ + continue; + } fill_print_name(file); len = strlen(file->print_name); 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; /* 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). * - * From here on, name_width is the width of the name area, - * and width is the width of the graph area. + * 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. + * + * 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() - strlen(line_prefix); 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; - for (i = 0; i < data->nr; i++) { + 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 (!file->is_interesting && (added + deleted == 0)) + continue; + /* * "scale" the filename */ @@ -1315,28 +2502,33 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) name = slash; } - if (data->files[i]->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", + if (file->is_binary) { + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*s", number_width, "Bin"); + if (!added && !deleted) { + strbuf_addch(&out, '\n'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); + continue; + } + strbuf_addf(&out, " %s%"PRIuMAX"%s", del_c, deleted, reset); - fprintf(options->file, " -> "); - fprintf(options->file, "%s%"PRIuMAX"%s", + strbuf_addstr(&out, " -> "); + strbuf_addf(&out, "%s%"PRIuMAX"%s", add_c, added, reset); - fprintf(options->file, " bytes"); - fprintf(options->file, "\n"); - continue; - } - else if (data->files[i]->is_unmerged) { - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " Unmerged\n"); + strbuf_addstr(&out, " bytes\n"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } - else if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; + else if (file->is_unmerged) { + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addstr(&out, " Unmerged\n"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } @@ -1345,25 +2537,58 @@ 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; + } + } + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*"PRIuMAX"%s", + number_width, added + deleted, + added + deleted ? " " : ""); + show_graph(&out, '+', add, add_c, reset); + show_graph(&out, '-', del, del_c, reset); + strbuf_addch(&out, '\n'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); + } + + 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; + } + + if (!file->is_binary) { + adds += added; + dels += deleted; } - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, - added + deleted ? " " : ""); - show_graph(options->file, '+', add, add_c, reset); - show_graph(options->file, '-', del, del_c, reset); - fprintf(options->file, "\n"); + if (i < count) + continue; + if (!extra_shown) + emit_diff_symbol(options, + DIFF_SYMBOL_STATS_SUMMARY_ABBREV, + NULL, 0, 0); + 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_inserts_deletes(options, total_files, adds, dels); + strbuf_release(&out); } static void show_shortstats(struct diffstat_t *data, struct diff_options *options) @@ -1374,27 +2599,18 @@ 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); + print_stat_summary_inserts_deletes(options, total_files, adds, dels); } static void show_numstat(struct diffstat_t *data, struct diff_options *options) @@ -1407,12 +2623,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"); @@ -1446,7 +2657,7 @@ struct dirstat_file { struct dirstat_dir { struct dirstat_file *files; - int alloc, nr, percent, cumulative; + int alloc, nr, permille, cumulative; }; static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, @@ -1454,13 +2665,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; @@ -1493,12 +2698,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, * under this directory (sources == 1). */ if (baselen && sources != 1) { - int permille = this_dir * 1000 / changed; - if (permille) { - int percent = permille / 10; - if (percent >= dir->percent) { + if (this_dir) { + int permille = this_dir * 1000 / changed; + if (permille >= dir->permille) { fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, - percent, permille % 10, baselen, base); + permille / 10, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1524,7 +2728,7 @@ static void show_dirstat(struct diff_options *options) dir.files = NULL; dir.alloc = 0; dir.nr = 0; - dir.percent = options->dirstat_percent; + dir.permille = options->dirstat_permille; dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); changed = 0; @@ -1532,22 +2736,50 @@ static void show_dirstat(struct diff_options *options) struct diff_filepair *p = q->queue[i]; const char *name; unsigned long copied, added, damage; + int content_changed; - name = p->one->path ? p->one->path : p->two->path; + name = p->two->path ? p->two->path : p->one->path; + + if (p->one->oid_valid && p->two->oid_valid) + content_changed = oidcmp(&p->one->oid, &p->two->oid); + else + content_changed = 1; + + if (!content_changed) { + /* + * The SHA1 has not changed, so pre-/post-content is + * identical. We can therefore skip looking at the + * file contents altogether. + */ + damage = 0; + goto found_damage; + } + + if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) { + /* + * In --dirstat-by-file mode, we don't really need to + * look at the actual file contents at all. + * The fact that the SHA1 changed is enough for us to + * add this file to the list of results + * (with each file contributing equal damage). + */ + damage = 1; + goto found_damage; + } if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) { diff_populate_filespec(p->one, 0); diff_populate_filespec(p->two, 0); - diffcore_count_changes(p->one, p->two, NULL, NULL, 0, + diffcore_count_changes(p->one, p->two, NULL, NULL, &copied, &added); 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); @@ -1557,14 +2789,18 @@ static void show_dirstat(struct diff_options *options) /* * Original minus copied is the removed material, * added is the new material. They are both damages - * made to the preimage. In --dirstat-by-file mode, count - * damaged files, not damaged lines. This is done by - * counting only a single damaged line per file. + * made to the preimage. + * If the resulting damage is zero, we know that + * diffcore_count_changes() considers the two entries to + * be identical, but since content_changed is true, we + * know that there must have been _some_ kind of change, + * so we force all entries to have damage > 0. */ damage = (p->one->size - copied) + added; - if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0) + if (!damage) damage = 1; +found_damage: ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); dir.files[dir.nr].name = name; dir.files[dir.nr].changed = damage; @@ -1577,7 +2813,51 @@ static void show_dirstat(struct diff_options *options) return; /* Show all directories with more than x% of the changes */ - qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); + QSORT(dir.files, dir.nr, dirstat_compare); + gather_dirstat(options, &dir, changed, "", 0); +} + +static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct dirstat_dir dir; + + if (data->nr == 0) + return; + + dir.files = NULL; + dir.alloc = 0; + dir.nr = 0; + dir.permille = options->dirstat_permille; + dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); + + changed = 0; + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + unsigned long damage = file->added + file->deleted; + if (file->is_binary) + /* + * binary files counts bytes, not lines. Must find some + * way to normalize binary bytes vs. textual lines. + * The following heuristic assumes that there are 64 + * bytes per "line". + * This is stupid and ugly, but very cheap... + */ + damage = DIV_ROUND_UP(damage, 64); + ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc); + dir.files[dir.nr].name = file->name; + dir.files[dir.nr].changed = damage; + changed += damage; + dir.nr++; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + QSORT(dir.files, dir.nr, dirstat_compare); gather_dirstat(options, &dir, changed, "", 0); } @@ -1630,21 +2910,15 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; - int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF); int marker_size = data->conflict_marker_size; - const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE); - const char *reset = diff_get_color(color_diff, DIFF_RESET); - const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); + const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE); + 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; @@ -1683,25 +2957,25 @@ static unsigned char *deflate_it(char *data, { int bound; unsigned char *deflated; - z_stream stream; + git_zstream stream; - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - bound = deflateBound(&stream, size); + git_deflate_init(&stream, zlib_compression_level); + bound = git_deflate_bound(&stream, size); deflated = xmalloc(bound); stream.next_out = deflated; stream.avail_out = bound; stream.next_in = (unsigned char *)data; stream.avail_in = size; - while (deflate(&stream, Z_FINISH) == Z_OK) + while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ - deflateEnd(&stream); + git_deflate_end(&stream); *result_size = stream.total_out; return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff_body(struct diff_options *o, + mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -1730,13 +3004,18 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char } if (delta && delta_size < deflate_size) { - fprintf(file, "%sdelta %lu\n", prefix, orig_size); + char *s = xstrfmt("%lu", orig_size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA, + s, strlen(s), 0); + free(s); free(deflated); data = delta; data_size = delta_size; - } - else { - fprintf(file, "%sliteral %lu\n", prefix, two->size); + } else { + char *s = xstrfmt("%lu", two->size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL, + s, strlen(s), 0); + free(s); free(delta); data = deflated; data_size = deflate_size; @@ -1745,8 +3024,9 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char /* emit data encoded in base85 */ cp = data; while (data_size) { + int len; int bytes = (52 < data_size) ? 52 : data_size; - char line[70]; + char line[71]; data_size -= bytes; if (bytes <= 26) line[0] = bytes + 'A' - 1; @@ -1754,33 +3034,24 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char line[0] = bytes - 26 + 'a' - 1; encode_85(line + 1, cp, bytes); cp = (char *) cp + bytes; - fprintf(file, "%s", prefix); - fputs(line, file); - fputc('\n', file); + + len = strlen(line); + line[len++] = '\n'; + line[len] = '\0'; + + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_BODY, + line, len, 0); } - fprintf(file, "%s\n", prefix); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_FOOTER, NULL, 0, 0); free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff(struct diff_options *o, + mmfile_t *one, mmfile_t *two) { - 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"); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER, NULL, 0, 0); + emit_binary_diff_body(o, one, two); + emit_binary_diff_body(o, two, one); } int diff_filespec_is_binary(struct diff_filespec *one) @@ -1791,8 +3062,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) @@ -1808,12 +3079,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) @@ -1828,19 +3093,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one) return NULL; diff_filespec_load_driver(one); - if (!one->driver->textconv) - return NULL; - - if (one->driver->textconv_want_cache && !one->driver->textconv_cache) { - struct notes_cache *c = xmalloc(sizeof(*c)); - struct strbuf name = STRBUF_INIT; - - strbuf_addf(&name, "textconv/%s", one->driver->name); - notes_cache_init(c, name.buf, one->driver->textconv); - one->driver->textconv_cache = c; - } - - return one->driver; + return userdiff_get_textconv(one->driver); } static void builtin_diff(const char *name_a, @@ -1855,28 +3108,36 @@ 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 = ""; + const char *line_prefix = diff_line_prefix(o); - if (o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; + diff_set_mnemonic_prefix(o, "a/", "b/"); + if (DIFF_OPT_TST(o, REVERSE_DIFF)) { + a_prefix = o->b_prefix; + b_prefix = o->a_prefix; + } else { + a_prefix = o->a_prefix; + b_prefix = o->b_prefix; } - if (DIFF_OPT_TST(o, SUBMODULE_LOG) && - (!one->mode || S_ISGITLINK(one->mode)) && - (!two->mode || S_ISGITLINK(two->mode))) { - const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); - const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); - show_submodule_summary(o->file, one ? one->path : two->path, - one->sha1, two->sha1, two->dirty_submodule, - del, add, reset); + if (o->submodule_format == DIFF_SUBMODULE_LOG && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + show_submodule_summary(o, one->path ? one->path : two->path, + &one->oid, &two->oid, + two->dirty_submodule); + return; + } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + show_submodule_inline_diff(o, one->path ? one->path : two->path, + &one->oid, &two->oid, + two->dirty_submodule); return; } @@ -1885,15 +3146,6 @@ static void builtin_diff(const char *name_a, textconv_two = get_textconv(two); } - diff_set_mnemonic_prefix(o, "a/", "b/"); - if (DIFF_OPT_TST(o, REVERSE_DIFF)) { - a_prefix = o->b_prefix; - b_prefix = o->a_prefix; - } else { - a_prefix = o->a_prefix; - b_prefix = o->b_prefix; - } - /* Never use a non-valid filename anywhere if at all possible */ name_a = DIFF_FILE_VALID(one) ? name_a : name_b; name_b = DIFF_FILE_VALID(two) ? name_b : name_a; @@ -1902,24 +3154,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) @@ -1934,7 +3186,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); @@ -1943,37 +3196,68 @@ static void builtin_diff(const char *name_a, } } - if (!DIFF_OPT_TST(o, TEXT) && + if (o->irreversible_delete && lbl[1][0] == '/') { + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, + header.len, 0); + strbuf_reset(&header); + goto free_ab_and_return; + } else if (!DIFF_OPT_TST(o, TEXT) && ( (!textconv_one && diff_filespec_is_binary(one)) || (!textconv_two && diff_filespec_is_binary(two)) )) { + struct strbuf sb = STRBUF_INIT; + if (!one->data && !two->data && + S_ISREG(one->mode) && S_ISREG(two->mode) && + !DIFF_OPT_TST(o, BINARY)) { + if (!oidcmp(&one->oid, &two->oid)) { + if (must_show_header) + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, + 0); + goto free_ab_and_return; + } + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); + strbuf_addf(&sb, "%sBinary files %s and %s differ\n", + diff_line_prefix(o), lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES, + sb.buf, sb.len, 0); + strbuf_release(&sb); + 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 */ if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { if (must_show_header) - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); goto free_ab_and_return; } - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(o->file, &mf1, &mf2, line_prefix); - else - fprintf(o->file, "%sBinary files %s and %s differ\n", - line_prefix, lbl[0], lbl[1]); + emit_binary_diff(o, &mf1, &mf2); + else { + strbuf_addf(&sb, "%sBinary files %s and %s differ\n", + diff_line_prefix(o), lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES, + sb.buf, sb.len, 0); + strbuf_release(&sb); + } o->found_changes = 1; - } - else { + } 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) { - fprintf(o->file, "%s", header.buf); + if (must_show_header) { + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); } @@ -1988,9 +3272,8 @@ static void builtin_diff(const char *name_a, memset(&xecfg, 0, sizeof(xecfg)); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.label_path = lbl; - ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); - ecbdata.found_changesp = &o->found_changes; - ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.color_diff = want_color(o->use_color); + 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; @@ -1999,52 +3282,21 @@ 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 (DIFF_OPT_TST(o, COLOR_DIFF)) { - struct diff_words_style *st = ecbdata.diff_words->style; - st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD); - st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW); - st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN); - } - } - xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, - &xpp, &xecfg); + 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); + if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, + &xpp, &xecfg)) + die("unable to generate diff for %s", one->path); if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) @@ -2068,44 +3320,64 @@ 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; } - if (complete_rewrite) { + + same_contents = !oidcmp(&one->oid, &two->oid); + + if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { + data->is_binary = 1; + 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) { diff_populate_filespec(one, 0); diff_populate_filespec(two, 0); data->deleted = count_lines(one->data, one->size); data->added = count_lines(two->data, two->size); - goto free_and_return; } - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { - data->is_binary = 1; - data->added = mf2.size; - data->deleted = mf1.size; - } else { + else if (!same_contents) { /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); + memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; - xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg); + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; + if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, + &xpp, &xecfg)) + die("unable to generate diffstat for %s", one->path); } - free_and_return: diff_free_filespec_data(one); diff_free_filespec_data(two); } @@ -2149,8 +3421,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = 0; - xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, + &xpp, &xecfg)) + die("unable to generate checkdiff for %s", one->path); if (data.ws_rule & WS_BLANK_AT_EOF) { struct emit_callback ecbdata; @@ -2179,12 +3452,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, struct diff_filespec *alloc_filespec(const char *path) { - int namelen = strlen(path); - struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1); + struct diff_filespec *spec; - memset(spec, 0, sizeof(*spec)); - spec->path = (char *)(spec + 1); - memcpy(spec->path, path, namelen+1); + FLEXPTR_ALLOC_STR(spec, path, path); spec->count = 1; spec->is_binary = -1; return spec; @@ -2198,13 +3468,13 @@ void free_filespec(struct diff_filespec *spec) } } -void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, - unsigned short mode) +void fill_filespec(struct diff_filespec *spec, const struct object_id *oid, + int oid_valid, unsigned short mode) { if (mode) { spec->mode = canon_mode(mode); - hashcpy(spec->sha1, sha1); - spec->sha1_valid = !is_null_sha1(sha1); + oidcpy(&spec->oid, oid); + spec->oid_valid = oid_valid; } } @@ -2213,9 +3483,9 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, * the work tree has that object contents, return true, so that * prepare_temp_file() does not have to inflate and extract. */ -static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file) +static int reuse_worktree_file(const char *name, const struct object_id *oid, int want_file) { - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; int pos, len; @@ -2244,7 +3514,14 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * objects however would tend to be slower as they need * to be individually opened and inflated. */ - if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1)) + if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash)) + return 0; + + /* + * Similarly, if we'd have to convert the file contents anyway, that + * makes the optimization not worthwhile. + */ + if (!want_file && would_convert_to_git(&the_index, name)) return 0; len = strlen(name); @@ -2257,7 +3534,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * This is not the sha1 we are looking for, or * unreusable because it is not a regular file. */ - if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode)) + if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode)) return 0; /* @@ -2277,39 +3554,24 @@ 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; - char *data = xmalloc(100), *dirty = ""; + struct strbuf buf = STRBUF_INIT; + char *dirty = ""; /* Are we looking at the work tree? */ if (s->dirty_submodule) dirty = "-dirty"; - len = snprintf(data, 100, - "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty); - s->data = data; - s->size = len; - s->should_free = 1; + strbuf_addf(&buf, "Subproject commit %s%s\n", + oid_to_hex(&s->oid), dirty); + s->size = buf.len; if (size_only) { s->data = NULL; - free(data); + strbuf_release(&buf); + } else { + s->data = strbuf_detach(&buf, NULL); + s->should_free = 1; } return 0; } @@ -2319,9 +3581,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)) @@ -2336,15 +3607,12 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) if (S_ISGITLINK(s->mode)) return diff_populate_gitlink(s, size_only); - if (!s->sha1_valid || - reuse_worktree_file(s->path, s->sha1, 0)) { + if (!s->oid_valid || + reuse_worktree_file(s->path, &s->oid, 0)) { struct strbuf buf = STRBUF_INIT; 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: @@ -2368,8 +3636,30 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) s->should_free = 1; return 0; } - if (size_only) + + /* + * Even if the caller would be happy with getting + * only the size, we cannot return early at this + * point if the path requires us to run the content + * conversion. + */ + if (size_only && !would_convert_to_git(&the_index, s->path)) + return 0; + + /* + * Note: this check uses xsize_t(st.st_size) that may + * not be the true size of the blob after it goes + * through convert_to_git(). This may not strictly be + * correct, but the whole point of big_file_threshold + * and is_binary check being that we want to avoid + * opening the file and inspecting the contents, this + * is probably fine. + */ + 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; @@ -2380,7 +3670,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(&the_index, s->path, s->data, s->size, &buf, crlf_warn)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -2391,16 +3681,22 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) } else { enum object_type type; - if (size_only) { - type = sha1_object_info(s->sha1, &s->size); + if (size_only || (flags & CHECK_BINARY)) { + type = sha1_object_info(s->oid.hash, &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; + die("unable to read %s", + oid_to_hex(&s->oid)); + 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->oid.hash, &type, &s->size); + if (!s->data) + die("unable to read %s", oid_to_hex(&s->oid)); + s->should_free = 1; } return 0; } @@ -2421,17 +3717,15 @@ void diff_free_filespec_blob(struct diff_filespec *s) void diff_free_filespec_data(struct diff_filespec *s) { diff_free_filespec_blob(s); - free(s->cnt_data); - s->cnt_data = NULL; + FREE_AND_NULL(s->cnt_data); } static void prep_temp_blob(const char *path, struct diff_tempfile *temp, void *blob, unsigned long size, - const unsigned char *sha1, + const struct object_id *oid, int mode) { - int fd; struct strbuf buf = STRBUF_INIT; struct strbuf template = STRBUF_INIT; char *path_dup = xstrdup(path); @@ -2441,22 +3735,20 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, strbuf_addstr(&template, "XXXXXX_"); strbuf_addstr(&template, base); - fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf, - strlen(base) + 1); - if (fd < 0) + temp->tempfile = mks_tempfile_ts(template.buf, strlen(base) + 1); + if (!temp->tempfile) die_errno("unable to create temp-file"); if (convert_to_working_tree(path, (const char *)blob, (size_t)size, &buf)) { blob = buf.buf; size = buf.len; } - if (write_in_full(fd, blob, size) != size) + if (write_in_full(temp->tempfile->fd, blob, size) < 0 || + close_tempfile_gently(temp->tempfile)) die_errno("unable to write temp-file"); - close(fd); - temp->name = temp->tmp_path; - strcpy(temp->hex, sha1_to_hex(sha1)); - temp->hex[40] = 0; - sprintf(temp->mode, "%06o", mode); + temp->name = get_tempfile_path(temp->tempfile); + oid_to_hex_r(temp->hex, oid); + xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode); strbuf_release(&buf); strbuf_release(&template); free(path_dup); @@ -2473,19 +3765,14 @@ static struct diff_tempfile *prepare_temp_file(const char *name, * a '+' entry produces this for file-1. */ temp->name = "/dev/null"; - strcpy(temp->hex, "."); - strcpy(temp->mode, "."); + xsnprintf(temp->hex, sizeof(temp->hex), "."); + xsnprintf(temp->mode, sizeof(temp->mode), "."); return temp; } - if (!remove_tempfile_installed) { - atexit(remove_tempfile); - sigchain_push_common(remove_tempfile_on_signal); - remove_tempfile_installed = 1; - } - - if (!one->sha1_valid || - reuse_worktree_file(name, one->sha1, 1)) { + if (!S_ISGITLINK(one->mode) && + (!one->oid_valid || + reuse_worktree_file(name, &one->oid, 1))) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -2497,26 +3784,26 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (strbuf_readlink(&sb, name, st.st_size) < 0) die_errno("readlink(%s)", name); prep_temp_blob(name, temp, sb.buf, sb.len, - (one->sha1_valid ? - one->sha1 : null_sha1), - (one->sha1_valid ? + (one->oid_valid ? + &one->oid : &null_oid), + (one->oid_valid ? one->mode : S_IFLNK)); strbuf_release(&sb); } else { /* we can borrow from the file in the work tree */ temp->name = name; - if (!one->sha1_valid) - strcpy(temp->hex, sha1_to_hex(null_sha1)); + if (!one->oid_valid) + oid_to_hex_r(temp->hex, &null_oid); else - strcpy(temp->hex, sha1_to_hex(one->sha1)); + oid_to_hex_r(temp->hex, &one->oid); /* Even though we may sometimes borrow the * contents from the work tree, we always want * one->mode. mode is trustworthy even when - * !(one->sha1_valid), as long as + * !(one->oid_valid), as long as * DIFF_FILE_VALID(one). */ - sprintf(temp->mode, "%06o", one->mode); + xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode); } return temp; } @@ -2524,11 +3811,21 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (diff_populate_filespec(one, 0)) die("cannot read data blob for %s", one->path); prep_temp_blob(name, temp, one->data, one->size, - one->sha1, one->mode); + &one->oid, one->mode); } 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 \ @@ -2541,41 +3838,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) @@ -2583,6 +3875,22 @@ static int similarity_index(struct diff_filepair *p) return p->score * 100 / MAX_SCORE; } +static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev) +{ + if (startup_info->have_repository) + return find_unique_abbrev(oid->hash, abbrev); + else { + char *hex = oid_to_hex(oid); + if (abbrev < 0) + abbrev = FALLBACK_DEFAULT_ABBREV; + if (abbrev > GIT_SHA1_HEXSZ) + die("BUG: oid abbreviation out of range: %d", abbrev); + if (abbrev) + hex[abbrev] = '\0'; + return hex; + } +} + static void fill_metainfo(struct strbuf *msg, const char *name, const char *other, @@ -2595,14 +3903,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: @@ -2637,7 +3940,7 @@ static void fill_metainfo(struct strbuf *msg, default: *must_show_header = 0; } - if (one && two && hashcmp(one->sha1, two->sha1)) { + if (one && two && oidcmp(&one->oid, &two->oid)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; if (DIFF_OPT_TST(o, BINARY)) { @@ -2646,9 +3949,9 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "%s%sindex %s..", line_prefix, set, - find_unique_abbrev(one->sha1, abbrev)); - strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev)); + strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set, + diff_abbrev_oid(&one->oid, abbrev), + diff_abbrev_oid(&two->oid, abbrev)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); strbuf_addf(msg, "%s\n", reset); @@ -2669,9 +3972,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; @@ -2684,13 +3986,13 @@ static void run_diff_cmd(const char *pgm, */ fill_metainfo(msg, name, other, one, two, o, p, &must_show_header, - DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + want_color(o->use_color) && !pgm); xfrm_msg = msg->len ? msg->buf : NULL; } if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); + complete_rewrite, o); return; } if (one && two) @@ -2701,23 +4003,23 @@ static void run_diff_cmd(const char *pgm, fprintf(o->file, "* Unmerged path %s\n", name); } -static void diff_fill_sha1_info(struct diff_filespec *one) +static void diff_fill_oid_info(struct diff_filespec *one) { if (DIFF_FILE_VALID(one)) { - if (!one->sha1_valid) { + if (!one->oid_valid) { struct stat st; - if (!strcmp(one->path, "-")) { - hashcpy(one->sha1, null_sha1); + if (one->is_stdin) { + oidclr(&one->oid); return; } if (lstat(one->path, &st) < 0) die_errno("stat '%s'", one->path); - if (index_path(one->sha1, one->path, &st, 0)) + if (index_path(&one->oid, one->path, &st, 0)) die("cannot hash %s", one->path); } } else - hashclr(one->sha1); + oidclr(&one->oid); } static void strip_prefix(int prefix_length, const char **namep, const char **otherp) @@ -2745,20 +4047,23 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) const char *other; const char *attr_path; - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); + name = one->path; + other = (strcmp(name, two->path) ? two->path : NULL); attr_path = name; 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); return; } - diff_fill_sha1_info(one); - diff_fill_sha1_info(two); + diff_fill_oid_info(one); + diff_fill_oid_info(two); if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && @@ -2790,11 +4095,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; } @@ -2804,12 +4108,10 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); + diff_fill_oid_info(p->one); + diff_fill_oid_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) @@ -2830,8 +4132,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); + diff_fill_oid_info(p->one); + diff_fill_oid_info(p->two); builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } @@ -2842,17 +4144,26 @@ void diff_setup(struct diff_options *options) options->file = stdout; + options->abbrev = DEFAULT_ABBREV; options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; - options->dirstat_percent = 3; - options->context = 3; + options->dirstat_permille = diff_dirstat_permille_default; + options->context = diff_context_default; + options->interhunkcontext = diff_interhunk_context_default; + options->ws_error_highlight = ws_error_highlight_default; + DIFF_OPT_SET(options, RENAME_EMPTY); + /* pathchange left =NULL by default */ options->change = diff_change; options->add_remove = diff_addremove; - if (diff_use_color_default > 0) - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; + options->xdl_opts |= diff_algorithm; + if (diff_indent_heuristic) + DIFF_XDL_SET(options, INDENT_HEURISTIC); + + options->orderfile = diff_order_file_cfg; if (diff_no_prefix) { options->a_prefix = options->b_prefix = ""; @@ -2860,12 +4171,17 @@ void diff_setup(struct diff_options *options) options->a_prefix = "a/"; options->b_prefix = "b/"; } + + options->color_moved = diff_color_moved_default; } -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) @@ -2875,7 +4191,7 @@ int diff_setup_done(struct diff_options *options) if (options->output_format & DIFF_FORMAT_NO_OUTPUT) count++; if (count > 1) - die("--name-only, --name-status, --check and -s are mutually exclusive"); + die(_("--name-only, --name-status, --check and -s are mutually exclusive")); /* * Most of the time we can say "there are changes" @@ -2949,7 +4265,7 @@ int diff_setup_done(struct diff_options *options) */ read_cache(); } - if (options->abbrev <= 0 || 40 < options->abbrev) + if (40 < options->abbrev) options->abbrev = 40; /* full */ /* @@ -2962,7 +4278,13 @@ 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")); + + if (!options->use_color || external_diff()) + options->color_moved = 0; } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -2992,14 +4314,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)) @@ -3034,13 +4353,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; } @@ -3059,39 +4376,60 @@ 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]) - die("Option '--stat-width' requires a value"); + die_want_option("--stat-width"); else if (!*arg) { 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]) - die("Option '--stat-name-width' requires a value"); + die_want_option("--stat-name-width"); else if (!*arg) { name_width = strtoul(av[1], &end, 10); argcount = 2; } + } else if (skip_prefix(arg, "-graph-width", &arg)) { + if (*arg == '=') + graph_width = strtoul(arg + 1, &end, 10); + else if (!*arg && !av[1]) + die_want_option("--stat-graph-width"); + 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]) + die_want_option("--stat-count"); + else if (!*arg) { + count = strtoul(av[1], &end, 10); + argcount = 2; + } } break; case '=': width = strtoul(arg+1, &end, 10); if (*end == ',') name_width = strtoul(end+1, &end, 10); + if (*end == ',') + count = strtoul(end+1, &end, 10); } /* Important! This checks all the error cases! */ @@ -3099,68 +4437,197 @@ 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; } -int diff_opt_parse(struct diff_options *options, const char **av, int ac) +static int parse_dirstat_opt(struct diff_options *options, const char *params) +{ + struct strbuf errmsg = STRBUF_INIT; + if (parse_dirstat_params(options, params, &errmsg)) + die(_("Failed to parse --dirstat/-X option parameter:\n%s"), + errmsg.buf); + strbuf_release(&errmsg); + /* + * The caller knows a dirstat-related option is given from the command + * line; allow it to say "return this_function();" + */ + options->output_format |= DIFF_FORMAT_DIRSTAT; + 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; +} + +static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg) +{ + int val = parse_ws_error_highlight(arg); + + if (val < 0) { + error("unknown value after ws-error-highlight=%.*s", + -1 - val, arg); + return 0; + } + opt->ws_error_highlight = val; + return 1; +} + +int diff_opt_parse(struct diff_options *options, + const char **av, int ac, const char *prefix) { const char *arg = av[0]; const char *optarg; int argcount; + if (!prefix) + prefix = ""; + /* 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 (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) - options->output_format |= DIFF_FORMAT_DIRSTAT; - else if (!strcmp(arg, "--cumulative")) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); - } else if (opt_arg(arg, 0, "dirstat-by-file", - &options->dirstat_percent)) { - options->output_format |= DIFF_FORMAT_DIRSTAT; - DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) + return parse_dirstat_opt(options, ""); + 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 (skip_prefix(arg, "--dirstat-by-file=", &arg)) { + parse_dirstat_opt(options, "files"); + 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")) - /* --stat, --stat-width, or --stat-name-width */ + 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); options->detect_rename = DIFF_DETECT_RENAME; } - else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") || + else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { + options->irreversible_delete = 1; + } + 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); @@ -3170,26 +4637,53 @@ 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, "--indent-heuristic")) + DIFF_XDL_SET(options, INDENT_HEURISTIC); + else if (!strcmp(arg, "--no-indent-heuristic")) + DIFF_XDL_CLR(options, INDENT_HEURISTIC); 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")) + 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")) @@ -3202,46 +4696,57 @@ 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, "--color")) - DIFF_OPT_SET(options, COLOR_DIFF); - else if (!prefixcmp(arg, "--color=")) { - int value = git_config_colorbool(NULL, arg+8, -1); - if (value == 0) - DIFF_OPT_CLR(options, COLOR_DIFF); - else if (value > 0) - DIFF_OPT_SET(options, COLOR_DIFF); - else + else if (!strcmp(arg, "--no-follow")) { + DIFF_OPT_CLR(options, FOLLOW_RENAMES); + DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES); + } else if (!strcmp(arg, "--color")) + options->use_color = 1; + 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; } else if (!strcmp(arg, "--no-color")) - DIFF_OPT_CLR(options, COLOR_DIFF); - else if (!strcmp(arg, "--color-words")) { - DIFF_OPT_SET(options, COLOR_DIFF); + options->use_color = 0; + else if (!strcmp(arg, "--color-moved")) { + if (diff_color_moved_default) + options->color_moved = diff_color_moved_default; + if (options->color_moved == COLOR_MOVED_NO) + options->color_moved = COLOR_MOVED_DEFAULT; + } else if (!strcmp(arg, "--no-color-moved")) + options->color_moved = COLOR_MOVED_NO; + else if (skip_prefix(arg, "--color-moved=", &arg)) { + int cm = parse_color_moved(arg); + if (cm < 0) + die("bad --color-moved argument: %s", arg); + options->color_moved = cm; + } else if (!strcmp(arg, "--color-words")) { + options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (!prefixcmp(arg, "--color-words=")) { - DIFF_OPT_SET(options, COLOR_DIFF); + 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")) { - DIFF_OPT_SET(options, COLOR_DIFF); + 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) @@ -3264,15 +4769,19 @@ 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); - } + options->submodule_format = DIFF_SUBMODULE_LOG; + else if (skip_prefix(arg, "--submodule=", &arg)) + return parse_submodule_opt(options, arg); + else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) + return parse_ws_error_highlight_opt(options, arg); + else if (!strcmp(arg, "--ita-invisible-in-index")) + options->ita_invisible_in_index = 1; + else if (!strcmp(arg, "--ita-visible-in-index")) + options->ita_invisible_in_index = 0; /* misc options */ else if (!strcmp(arg, "-z")) @@ -3295,17 +4804,22 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--pickaxe-regex")) options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { - options->orderfile = optarg; + options->orderfile = prefix_filename(prefix, optarg); 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, "--no-abbrev")) + options->abbrev = 0; 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) @@ -3315,6 +4829,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->a_prefix = optarg; return argcount; } + else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) { + options->line_prefix = optarg; + options->line_prefix_length = strlen(options->line_prefix); + graph_setup_line_prefix(options); + return argcount; + } else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) { options->b_prefix = optarg; return argcount; @@ -3324,11 +4844,19 @@ 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) - die_errno("Could not open '%s'", optarg); + char *path = prefix_filename(prefix, optarg); + options->file = xfopen(path, "w"); options->close_file = 1; + if (options->use_color != GIT_COLOR_ALWAYS) + options->use_color = GIT_COLOR_NEVER; + free(path); return argcount; } else return 0; @@ -3380,22 +4908,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') @@ -3419,11 +4944,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; } @@ -3446,43 +4967,59 @@ void diff_free_filepair(struct diff_filepair *p) free(p); } -/* This is different from find_unique_abbrev() in that - * it stuffs the result with dots for alignment. - */ -const char *diff_unique_abbrev(const unsigned char *sha1, int len) +const char *diff_aligned_abbrev(const struct object_id *oid, int len) { int abblen; const char *abbrev; - if (len == 40) - return sha1_to_hex(sha1); - abbrev = find_unique_abbrev(sha1, len); + if (len == GIT_SHA1_HEXSZ) + return oid_to_hex(oid); + + abbrev = diff_abbrev_oid(oid, len); abblen = strlen(abbrev); - if (abblen < 37) { - static char hex[41]; + + /* + * In well-behaved cases, where the abbbreviated result is the + * same as the requested length, append three dots after the + * abbreviation (hence the whole logic is limited to the case + * where abblen < 37); when the actual abbreviated result is a + * bit longer than the requested length, we reduce the number + * of dots so that they match the well-behaved ones. However, + * if the actual abbreviation is longer than the requested + * length by more than three, we give up on aligning, and add + * three dots anyway, to indicate that the output is not the + * full object name. Yes, this may be suboptimal, but this + * appears only in "diff --raw --abbrev" output and it is not + * worth the effort to change it now. Note that this would + * likely to work fine when the automatic sizing of default + * abbreviation length is used--we would be fed -1 in "len" in + * that case, and will end up always appending three-dots, but + * the automatic sizing is supposed to give abblen that ensures + * uniqueness across all objects (statistically speaking). + */ + if (abblen < GIT_SHA1_HEXSZ - 3) { + static char hex[GIT_MAX_HEXSZ + 1]; if (len < abblen && abblen <= len + 2) - sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); + xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, ".."); else - sprintf(hex, "%s...", abbrev); + xsnprintf(hex, sizeof(hex), "%s...", abbrev); return hex; } - return sha1_to_hex(sha1); + + return oid_to_hex(oid); } 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)); - fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); + diff_aligned_abbrev(&p->one->oid, opt->abbrev)); + fprintf(opt->file, "%s ", + diff_aligned_abbrev(&p->two->oid, opt->abbrev)); } if (p->score) { fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p), @@ -3531,11 +5068,11 @@ int diff_unmodified_pair(struct diff_filepair *p) /* both are valid and point at the same path. that is, we are * dealing with a change. */ - if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1) && + if (one->oid_valid && two->oid_valid && + !oidcmp(&one->oid, &two->oid) && !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ - if (!one->sha1_valid && !two->sha1_valid) + if (!one->oid_valid && !two->oid_valid) return 1; /* both look at the same file on the filesystem. */ return 0; } @@ -3596,10 +5133,10 @@ void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) s->path, 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", + s->oid_valid ? oid_to_hex(&s->oid) : ""); + 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) @@ -3666,11 +5203,11 @@ static void diff_resolve_rename_copy(void) else p->status = DIFF_STATUS_RENAMED; } - else if (hashcmp(p->one->sha1, p->two->sha1) || + else if (oidcmp(&p->one->oid, &p->two->oid) || p->one->mode != p->two->mode || p->one->dirty_submodule || p->two->dirty_submodule || - is_null_sha1(p->one->sha1)) + is_null_oid(&p->one->oid)) p->status = DIFF_STATUS_MODIFIED; else { /* This is a "no-change" entry and should not @@ -3709,76 +5246,84 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) name_a = p->two->path; name_b = NULL; strip_prefix(opt->prefix_length, &name_a, &name_b); + fprintf(opt->file, "%s", diff_line_prefix(opt)); write_name_quoted(name_a, opt->file, opt->line_termination); } } -static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_filespec *fs) +static void show_file_mode_name(struct diff_options *opt, const char *newdelete, struct diff_filespec *fs) { + struct strbuf sb = STRBUF_INIT; if (fs->mode) - fprintf(file, " %s mode %06o ", newdelete, fs->mode); + strbuf_addf(&sb, " %s mode %06o ", newdelete, fs->mode); else - fprintf(file, " %s ", newdelete); - write_name_quoted(fs->path, file, '\n'); -} + strbuf_addf(&sb, " %s ", newdelete); + quote_c_style(fs->path, &sb, NULL, 0); + strbuf_addch(&sb, '\n'); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); +} -static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name, - const char *line_prefix) +static void show_mode_change(struct diff_options *opt, struct diff_filepair *p, + int show_name) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { - fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode, - p->two->mode, show_name ? ' ' : '\n'); + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, " mode change %06o => %06o", + p->one->mode, p->two->mode); if (show_name) { - write_name_quoted(p->two->path, file, '\n'); + strbuf_addch(&sb, ' '); + quote_c_style(p->two->path, &sb, NULL, 0); } + strbuf_addch(&sb, '\n'); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); } } -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p, - const char *line_prefix) +static void show_rename_copy(struct diff_options *opt, const char *renamecopy, + struct diff_filepair *p) { + struct strbuf sb = STRBUF_INIT; char *names = pprint_rename(p->one->path, p->two->path); - - fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); + strbuf_addf(&sb, " %s %s (%d%%)\n", + renamecopy, names, similarity_index(p)); free(names); - show_mode_change(file, p, 0, line_prefix); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + show_mode_change(opt, p, 0); + strbuf_release(&sb); } 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; - } - switch(p->status) { case DIFF_STATUS_DELETED: - fputs(line_prefix, file); - show_file_mode_name(file, "delete", p->one); + show_file_mode_name(opt, "delete", p->one); break; case DIFF_STATUS_ADDED: - fputs(line_prefix, file); - show_file_mode_name(file, "create", p->two); + show_file_mode_name(opt, "create", p->two); break; case DIFF_STATUS_COPIED: - fputs(line_prefix, file); - show_rename_copy(file, "copy", p, line_prefix); + show_rename_copy(opt, "copy", p); break; case DIFF_STATUS_RENAMED: - fputs(line_prefix, file); - show_rename_copy(file, "rename", p, line_prefix); + show_rename_copy(opt, "rename", p); break; default: if (p->score) { - fprintf(file, "%s rewrite ", line_prefix); - write_name_quoted(p->two->path, file, ' '); - fprintf(file, "(%d%%)\n", similarity_index(p)); + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, " rewrite "); + quote_c_style(p->two->path, &sb, NULL, 0); + strbuf_addf(&sb, " (%d%%)\n", similarity_index(p)); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); } - show_mode_change(file, p, !p->score, line_prefix); + show_mode_change(opt, p, !p->score); break; } } @@ -3807,7 +5352,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); @@ -3816,14 +5361,26 @@ static void patch_id_consume(void *priv, char *line, unsigned long len) data->patchlen += new_len; } +static void patch_id_add_string(git_SHA_CTX *ctx, const char *str) +{ + git_SHA1_Update(ctx, str, strlen(str)); +} + +static void patch_id_add_mode(git_SHA_CTX *ctx, unsigned mode) +{ + /* large enough for 2^32 in octal */ + char buf[12]; + int len = xsnprintf(buf, sizeof(buf), "%06o", mode); + git_SHA1_Update(ctx, buf, len); +} + /* returns 0 upon success, and writes result into sha1 */ -static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) +static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; git_SHA_CTX ctx; struct patch_id_t data; - char buffer[PATH_MAX * 4 + 20]; git_SHA1_Init(&ctx); memset(&data, 0, sizeof(struct patch_id_t)); @@ -3850,68 +5407,70 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) if (DIFF_PAIR_UNMERGED(p)) continue; - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); - if (fill_mmfile(&mf1, p->one) < 0 || - fill_mmfile(&mf2, p->two) < 0) - return error("unable to read files to diff"); + diff_fill_oid_info(p->one); + diff_fill_oid_info(p->two); len1 = remove_space(p->one->path, strlen(p->one->path)); len2 = remove_space(p->two->path, strlen(p->two->path)); - if (p->one->mode == 0) - len1 = snprintf(buffer, sizeof(buffer), - "diff--gita/%.*sb/%.*s" - "newfilemode%06o" - "---/dev/null" - "+++b/%.*s", - len1, p->one->path, - len2, p->two->path, - p->two->mode, - len2, p->two->path); - else if (p->two->mode == 0) - len1 = snprintf(buffer, sizeof(buffer), - "diff--gita/%.*sb/%.*s" - "deletedfilemode%06o" - "---a/%.*s" - "+++/dev/null", - len1, p->one->path, - len2, p->two->path, - p->one->mode, - len1, p->one->path); - else - len1 = snprintf(buffer, sizeof(buffer), - "diff--gita/%.*sb/%.*s" - "---a/%.*s" - "+++b/%.*s", - len1, p->one->path, - len2, p->two->path, - len1, p->one->path, - len2, p->two->path); - git_SHA1_Update(&ctx, buffer, len1); + patch_id_add_string(&ctx, "diff--git"); + patch_id_add_string(&ctx, "a/"); + git_SHA1_Update(&ctx, p->one->path, len1); + patch_id_add_string(&ctx, "b/"); + git_SHA1_Update(&ctx, p->two->path, len2); + + if (p->one->mode == 0) { + patch_id_add_string(&ctx, "newfilemode"); + patch_id_add_mode(&ctx, p->two->mode); + patch_id_add_string(&ctx, "---/dev/null"); + patch_id_add_string(&ctx, "+++b/"); + git_SHA1_Update(&ctx, p->two->path, len2); + } else if (p->two->mode == 0) { + patch_id_add_string(&ctx, "deletedfilemode"); + patch_id_add_mode(&ctx, p->one->mode); + patch_id_add_string(&ctx, "---a/"); + git_SHA1_Update(&ctx, p->one->path, len1); + patch_id_add_string(&ctx, "+++/dev/null"); + } else { + patch_id_add_string(&ctx, "---a/"); + git_SHA1_Update(&ctx, p->one->path, len1); + patch_id_add_string(&ctx, "+++b/"); + git_SHA1_Update(&ctx, p->two->path, len2); + } + + if (diff_header_only) + continue; + + if (fill_mmfile(&mf1, p->one) < 0 || + fill_mmfile(&mf2, p->two) < 0) + return error("unable to read files to diff"); if (diff_filespec_is_binary(p->one) || diff_filespec_is_binary(p->two)) { - git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40); - git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40); + git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid), + GIT_SHA1_HEXSZ); + git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid), + GIT_SHA1_HEXSZ); continue; } xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = 0; - xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg); + if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, + &xpp, &xecfg)) + return error("unable to generate patch-id diff for %s", + p->one->path); } - git_SHA1_Final(sha1, &ctx); + git_SHA1_Final(oid->hash, &ctx); return 0; } -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1) +int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; - int result = diff_get_patch_id(options, sha1); + int result = diff_get_patch_id(options, oid, diff_header_only); for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); @@ -3947,11 +5506,79 @@ static int is_summary_empty(const struct diff_queue_struct *q) return 1; } +static const char rename_limit_warning[] = +N_("inexact rename detection was skipped due to too many files."); + +static const char degrade_cc_to_c_warning[] = +N_("only found copies from modified paths due to too many files."); + +static const char rename_limit_advice[] = +N_("you may want to set your %s variable to at least " + "%d and retry the command."); + +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) +{ + if (degraded_cc) + warning(_(degrade_cc_to_c_warning)); + else if (needed) + warning(_(rename_limit_warning)); + else + return; + if (0 < needed && needed < 32767) + warning(_(rename_limit_advice), varname, needed); +} + +static void diff_flush_patch_all_file_pairs(struct diff_options *o) +{ + int i; + static struct emitted_diff_symbols esm = EMITTED_DIFF_SYMBOLS_INIT; + struct diff_queue_struct *q = &diff_queued_diff; + + if (WSEH_NEW & WS_RULE_MASK) + die("BUG: WS rules bit mask overlaps with diff symbol flags"); + + if (o->color_moved) + o->emitted_symbols = &esm; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, o); + } + + if (o->emitted_symbols) { + if (o->color_moved) { + struct hashmap add_lines, del_lines; + + hashmap_init(&del_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + hashmap_init(&add_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + + add_lines_to_move_detection(o, &add_lines, &del_lines); + mark_color_as_moved(o, &add_lines, &del_lines); + if (o->color_moved == COLOR_MOVED_ZEBRA_DIM) + dim_moved_lines(o); + + hashmap_free(&add_lines, 0); + hashmap_free(&del_lines, 0); + } + + for (i = 0; i < esm.nr; i++) + emit_diff_symbol_from_struct(o, &esm.buf[i]); + + for (i = 0; i < esm.nr; i++) + free((void *)esm.buf[i].line); + } + esm.nr = 0; +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; int i, output_format = options->output_format; int separator = 0; + int dirstat_by_line = 0; /* * Order: raw, stat, summary, patch @@ -3972,7 +5599,11 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE)) + dirstat_by_line = 1; + + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) || + dirstat_by_line) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -3987,10 +5618,12 @@ 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 && dirstat_by_line) + show_dirstat_by_line(&diffstat, options); free_diffstat_info(&diffstat); separator++; } - if (output_format & DIFF_FORMAT_DIRSTAT) + if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line) show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { @@ -4005,15 +5638,14 @@ 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) fclose(options->file); - options->file = fopen("/dev/null", "w"); - if (!options->file) - die_errno("Could not open /dev/null"); + options->file = xfopen("/dev/null", "w"); options->close_file = 1; + options->color_moved = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) @@ -4025,18 +5657,14 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { - putc(options->line_termination, options->file); - if (options->stat_sep) { + emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0); + if (options->stat_sep) /* attach patch instead of inline */ - fputs(options->stat_sep, options->file); - } + emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP, + NULL, 0, 0); } - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - diff_flush_patch(p, options); - } + diff_flush_patch_all_file_pairs(options); } if (output_format & DIFF_FORMAT_CALLBACK) @@ -4063,27 +5691,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) @@ -4101,14 +5734,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); @@ -4131,6 +5757,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->oid_valid && p->two->oid_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; @@ -4141,27 +5799,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 { /* @@ -4192,11 +5830,12 @@ static int diffnamecmp(const void *a_, const void *b_) void diffcore_fix_diff_index(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; - qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp); + QSORT(q->queue, q->nr, diffnamecmp); } 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) { @@ -4215,7 +5854,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); @@ -4228,6 +5867,10 @@ void diffcore_std(struct diff_options *options) int diff_result_code(struct diff_options *opt, int status) { int result = 0; + + diff_warn_rename_limit("diff.renameLimit", + opt->needed_rename_limit, + opt->degraded_cc_to_c); if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && !(opt->output_format & DIFF_FORMAT_CHECKDIFF)) return status; @@ -4240,6 +5883,13 @@ int diff_result_code(struct diff_options *opt, int status) return result; } +int diff_can_quit_early(struct diff_options *opt) +{ + return (DIFF_OPT_TST(opt, QUICK) && + !opt->filter && + DIFF_OPT_TST(opt, HAS_CHANGES)); +} + /* * Shall changes to this submodule be ignored? * @@ -4260,7 +5910,8 @@ 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, + const struct object_id *oid, + int oid_valid, const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -4292,9 +5943,9 @@ void diff_addremove(struct diff_options *options, two = alloc_filespec(concatpath); if (addremove != '+') - fill_filespec(one, sha1, mode); + fill_filespec(one, oid, oid_valid, mode); if (addremove != '-') { - fill_filespec(two, sha1, mode); + fill_filespec(two, oid, oid_valid, mode); two->dirty_submodule = dirty_submodule; } @@ -4305,24 +5956,24 @@ void diff_addremove(struct diff_options *options, void diff_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_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)) return; if (DIFF_OPT_TST(options, REVERSE_DIFF)) { - unsigned tmp; - const unsigned char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; - tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule; - new_dirty_submodule = tmp; + SWAP(old_mode, new_mode); + SWAP(old_oid, new_oid); + SWAP(old_oid_valid, new_oid_valid); + SWAP(old_dirty_submodule, new_dirty_submodule); } if (options->prefix && @@ -4331,30 +5982,36 @@ 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_oid, old_oid_valid, old_mode); + fill_filespec(two, new_oid, new_oid_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); } -void diff_unmerge(struct diff_options *options, - const char *path, - unsigned mode, const unsigned char *sha1) +struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) { + struct diff_filepair *pair; struct diff_filespec *one, *two; if (options->prefix && strncmp(path, options->prefix, options->prefix_length)) - return; + return NULL; one = alloc_filespec(path); two = alloc_filespec(path); - fill_filespec(one, sha1, mode); - diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1; + pair = diff_queue(&diff_queued_diff, one, two); + pair->is_unmerged = 1; + return pair; } static char *run_textconv(const char *pgm, struct diff_filespec *spec, @@ -4363,7 +6020,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; @@ -4372,7 +6029,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; @@ -4401,7 +6057,7 @@ size_t fill_textconv(struct userdiff_driver *driver, { size_t size; - if (!driver || !driver->textconv) { + if (!driver) { if (!DIFF_FILE_VALID(df)) { *outbuf = ""; return 0; @@ -4412,8 +6068,12 @@ size_t fill_textconv(struct userdiff_driver *driver, return df->size; } - if (driver->textconv_cache && df->sha1_valid) { - *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, + if (!driver->textconv) + die("BUG: fill_textconv called with non-textconv driver"); + + if (driver->textconv_cache && df->oid_valid) { + *outbuf = notes_cache_get(driver->textconv_cache, + &df->oid, &size); if (*outbuf) return size; @@ -4423,9 +6083,9 @@ size_t fill_textconv(struct userdiff_driver *driver, if (!*outbuf) die("unable to read files to diff"); - if (driver->textconv_cache && df->sha1_valid) { + if (driver->textconv_cache && df->oid_valid) { /* ignore errors, as we might be in a readonly repository */ - notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, + notes_cache_put(driver->textconv_cache, &df->oid, *outbuf, size); /* * we could save up changes and flush them all at the end, @@ -4438,3 +6098,42 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } + +int textconv_object(const char *path, + unsigned mode, + const struct object_id *oid, + int oid_valid, + char **buf, + unsigned long *buf_size) +{ + struct diff_filespec *df; + struct userdiff_driver *textconv; + + df = alloc_filespec(path); + fill_filespec(df, oid, oid_valid, mode); + textconv = get_textconv(df); + if (!textconv) { + free_filespec(df); + return 0; + } + + *buf_size = fill_textconv(textconv, df, buf); + free_filespec(df); + return 1; +} + +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(); +} |
