diff options
Diffstat (limited to 'diff.c')
-rw-r--r-- | diff.c | 2565 |
1 files changed, 1861 insertions, 704 deletions
@@ -13,13 +13,17 @@ #include "attr.h" #include "run-command.h" #include "utf8.h" +#include "object-store.h" #include "userdiff.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" +#include "help.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -32,6 +36,8 @@ static int diff_indent_heuristic = 1; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; +static int diff_color_moved_default; +static int diff_color_moved_ws_default; static int diff_context_default = 3; static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; @@ -56,6 +62,45 @@ 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 */ + GIT_COLOR_FAINT, /* CONTEXT_DIM */ + GIT_COLOR_FAINT_RED, /* OLD_DIM */ + GIT_COLOR_FAINT_GREEN, /* NEW_DIM */ + GIT_COLOR_BOLD, /* CONTEXT_BOLD */ + GIT_COLOR_BOLD_RED, /* OLD_BOLD */ + GIT_COLOR_BOLD_GREEN, /* NEW_BOLD */ +}; + +static const char *color_diff_slots[] = { + [DIFF_CONTEXT] = "context", + [DIFF_METAINFO] = "meta", + [DIFF_FRAGINFO] = "frag", + [DIFF_FILE_OLD] = "old", + [DIFF_FILE_NEW] = "new", + [DIFF_COMMIT] = "commit", + [DIFF_WHITESPACE] = "whitespace", + [DIFF_FUNCINFO] = "func", + [DIFF_FILE_OLD_MOVED] = "oldMoved", + [DIFF_FILE_OLD_MOVED_ALT] = "oldMovedAlternative", + [DIFF_FILE_OLD_MOVED_DIM] = "oldMovedDimmed", + [DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed", + [DIFF_FILE_NEW_MOVED] = "newMoved", + [DIFF_FILE_NEW_MOVED_ALT] = "newMovedAlternative", + [DIFF_FILE_NEW_MOVED_DIM] = "newMovedDimmed", + [DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed", + [DIFF_CONTEXT_DIM] = "contextDimmed", + [DIFF_FILE_OLD_DIM] = "oldDimmed", + [DIFF_FILE_NEW_DIM] = "newDimmed", + [DIFF_CONTEXT_BOLD] = "contextBold", + [DIFF_FILE_OLD_BOLD] = "oldBold", + [DIFF_FILE_NEW_BOLD] = "newBold", }; static NORETURN void die_want_option(const char *option_name) @@ -63,25 +108,13 @@ static NORETURN void die_want_option(const char *option_name) die(_("option '%s' requires a value"), option_name); } +define_list_config_array_extra(color_diff_slots, {"plain"}); + static int parse_diff_color_slot(const char *var) { - if (!strcasecmp(var, "context") || !strcasecmp(var, "plain")) + if (!strcasecmp(var, "plain")) return DIFF_CONTEXT; - if (!strcasecmp(var, "meta")) - return DIFF_METAINFO; - if (!strcasecmp(var, "frag")) - return DIFF_FRAGINFO; - if (!strcasecmp(var, "old")) - return DIFF_FILE_OLD; - if (!strcasecmp(var, "new")) - return DIFF_FILE_NEW; - if (!strcasecmp(var, "commit")) - return DIFF_COMMIT; - if (!strcasecmp(var, "whitespace")) - return DIFF_WHITESPACE; - if (!strcasecmp(var, "func")) - return DIFF_FUNCINFO; - return -1; + return LOOKUP_CONFIG(color_diff_slots, var); } static int parse_dirstat_params(struct diff_options *options, const char *params_string, @@ -97,18 +130,18 @@ static int parse_dirstat_params(struct diff_options *options, const char *params 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); + options->flags.dirstat_by_line = 0; + options->flags.dirstat_by_file = 0; } else if (!strcmp(p, "lines")) { - DIFF_OPT_SET(options, DIRSTAT_BY_LINE); - DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); + options->flags.dirstat_by_line = 1; + options->flags.dirstat_by_file = 0; } else if (!strcmp(p, "files")) { - DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); - DIFF_OPT_SET(options, DIRSTAT_BY_FILE); + options->flags.dirstat_by_line = 0; + options->flags.dirstat_by_file = 1; } else if (!strcmp(p, "noncumulative")) { - DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); + options->flags.dirstat_cumulative = 0; } else if (!strcmp(p, "cumulative")) { - DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); + options->flags.dirstat_cumulative = 1; } else if (isdigit(*p)) { char *end; int permille = strtoul(p, &end, 10) * 10; @@ -150,7 +183,7 @@ static int parse_submodule_params(struct diff_options *options, const char *valu return 0; } -static int git_config_rename(const char *var, const char *value) +int git_config_rename(const char *var, const char *value) { if (!value) return DIFF_DETECT_RENAME; @@ -219,7 +252,7 @@ static int parse_ws_error_highlight(const char *arg) */ void init_diff_ui_defaults(void) { - diff_detect_rename_default = 1; + diff_detect_rename_default = DIFF_DETECT_RENAME; } int git_diff_heuristic_config(const char *var, const char *value, void *cb) @@ -229,12 +262,91 @@ int git_diff_heuristic_config(const char *var, const char *value, void *cb) 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, "blocks")) + return COLOR_MOVED_BLOCKS; + 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 if (!strcmp(arg, "dimmed_zebra")) + return COLOR_MOVED_ZEBRA_DIM; + else + return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed-zebra', 'plain'")); +} + +static int parse_color_moved_ws(const char *arg) +{ + int ret = 0; + struct string_list l = STRING_LIST_INIT_DUP; + struct string_list_item *i; + + string_list_split(&l, arg, ',', -1); + + for_each_string_list_item(i, &l) { + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, i->string); + strbuf_trim(&sb); + + if (!strcmp(sb.buf, "ignore-space-change")) + ret |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(sb.buf, "ignore-space-at-eol")) + ret |= XDF_IGNORE_WHITESPACE_AT_EOL; + else if (!strcmp(sb.buf, "ignore-all-space")) + ret |= XDF_IGNORE_WHITESPACE; + else if (!strcmp(sb.buf, "allow-indentation-change")) + ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE; + else + error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf); + + strbuf_release(&sb); + } + + if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) && + (ret & XDF_WHITESPACE_FLAGS)) + die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes")); + + string_list_clear(&l, 0); + + return ret; +} + 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); 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.colormovedws")) { + int cm = parse_color_moved_ws(value); + if (cm < 0) + return -1; + diff_color_moved_ws_default = cm; + return 0; + } if (!strcmp(var, "diff.context")) { diff_context_default = git_config_int(var, value); if (diff_context_default < 0) @@ -346,9 +458,6 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } - if (starts_with(var, "submodule.")) - return parse_submodule_config_option(var, value); - if (git_diff_heuristic_config(var, value, cb) < 0) return -1; @@ -406,11 +515,9 @@ static struct diff_tempfile { * If this diff_tempfile instance refers to a temporary file, * this tempfile object is used to manage its lifetime. */ - struct tempfile tempfile; + struct tempfile *tempfile; } diff_temp[2]; -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); - struct emit_callback { int color_diff; unsigned ws_rule; @@ -418,7 +525,6 @@ 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; @@ -448,14 +554,15 @@ static int count_lines(const char *data, int size) return count; } -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +static int fill_mmfile(struct repository *r, mmfile_t *mf, + struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) { mf->ptr = (char *)""; /* does not matter */ mf->size = 0; return 0; } - else if (diff_populate_filespec(one, 0)) + else if (diff_populate_filespec(r, one, 0)) return -1; mf->ptr = one->data; @@ -464,11 +571,12 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) } /* like fill_mmfile, but only for size, so we can avoid retrieving blob */ -static unsigned long diff_filespec_size(struct diff_filespec *one) +static unsigned long diff_filespec_size(struct repository *r, + struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return 0; - diff_populate_filespec(one, CHECK_SIZE_ONLY); + diff_populate_filespec(r, one, CHECK_SIZE_ONLY); return one->size; } @@ -517,37 +625,55 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line_0(struct diff_options *o, const char *set, const char *reset, +static void emit_line_0(struct diff_options *o, + const char *set_sign, const char *set, unsigned reverse, const char *reset, int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; - int nofirst; + int needs_reset = 0; /* at the end of the line */ FILE *file = o->file; fputs(diff_line_prefix(o), file); - if (len == 0) { - has_trailing_newline = (first == '\n'); - has_trailing_carriage_return = (!has_trailing_newline && - (first == '\r')); - nofirst = has_trailing_newline || has_trailing_carriage_return; - } else { - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - nofirst = 0; + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + + if (!len && !first) + goto end_of_line; + + if (reverse && want_color(o->use_color)) { + fputs(GIT_COLOR_REVERSE, file); + needs_reset = 1; } - if (len || !nofirst) { + if (set_sign) { + fputs(set_sign, file); + needs_reset = 1; + } + + if (first) + fputc(first, file); + + if (!len) + goto end_of_line; + + if (set) { + if (set_sign && set != set_sign) + fputs(reset, file); fputs(set, file); - if (!nofirst) - fputc(first, file); - fwrite(line, len, 1, file); - fputs(reset, file); + needs_reset = 1; } + fwrite(line, len, 1, file); + needs_reset = 1; /* 'line' may contain color codes. */ + +end_of_line: + if (needs_reset) + fputs(reset, file); if (has_trailing_carriage_return) fputc('\r', file); if (has_trailing_newline) @@ -557,71 +683,894 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res static void emit_line(struct diff_options *o, const char *set, const char *reset, const char *line, int len) { - emit_line_0(o, set, reset, line[0], line+1, len-1); + emit_line_0(o, set, NULL, 0, reset, 0, line, len); +} + +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; } -static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +struct moved_entry { + struct hashmap_entry ent; + const struct emitted_diff_symbol *es; + struct moved_entry *next_line; +}; + +/** + * The struct ws_delta holds white space differences between moved lines, i.e. + * between '+' and '-' lines that have been detected to be a move. + * The string contains the difference in leading white spaces, before the + * rest of the line is compared using the white space config for move + * coloring. The current_longer indicates if the first string in the + * comparision is longer than the second. + */ +struct ws_delta { + char *string; + unsigned int current_longer : 1; +}; +#define WS_DELTA_INIT { NULL, 0 } + +struct moved_block { + struct moved_entry *match; + struct ws_delta wsd; +}; + +static void moved_block_clear(struct moved_block *b) { - if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof_in_preimage && - ecbdata->blank_at_eof_in_postimage && - ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && - ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + FREE_AND_NULL(b->wsd.string); + b->match = NULL; +} + +static int compute_ws_delta(const struct emitted_diff_symbol *a, + const struct emitted_diff_symbol *b, + struct ws_delta *out) +{ + const struct emitted_diff_symbol *longer = a->len > b->len ? a : b; + const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a; + int d = longer->len - shorter->len; + + if (strncmp(longer->line + d, shorter->line, shorter->len)) return 0; - return ws_blank_line(line, len, ecbdata->ws_rule); + + out->string = xmemdupz(longer->line, d); + out->current_longer = (a == longer); + + return 1; } -static void emit_line_checked(const char *reset, - struct emit_callback *ecbdata, - const char *line, int len, - enum color_diff color, - unsigned ws_error_highlight, - char sign) +static int cmp_in_block_with_wsd(const struct diff_options *o, + const struct moved_entry *cur, + const struct moved_entry *match, + struct moved_block *pmb, + int n) +{ + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + int al = cur->es->len, cl = l->len; + const char *a = cur->es->line, + *b = match->es->line, + *c = l->line; + + int wslen; + + /* + * We need to check if 'cur' is equal to 'match'. + * As those are from the same (+/-) side, we do not need to adjust for + * indent changes. However these were found using fuzzy matching + * so we do have to check if they are equal. + */ + if (strcmp(a, b)) + return 1; + + if (!pmb->wsd.string) + /* + * The white space delta is not active? This can happen + * when we exit early in this function. + */ + return 1; + + /* + * The indent changes of the block are known and stored in + * pmb->wsd; however we need to check if the indent changes of the + * current line are still the same as before. + * + * To do so we need to compare 'l' to 'cur', adjusting the + * one of them for the white spaces, depending which was longer. + */ + + wslen = strlen(pmb->wsd.string); + if (pmb->wsd.current_longer) { + c += wslen; + cl -= wslen; + } else { + a += wslen; + al -= wslen; + } + + if (al != cl || memcmp(a, c, al)) + return 1; + + return 0; +} + +static int moved_entry_cmp(const void *hashmap_cmp_fn_data, + const void *entry, + const void *entry_or_key, + const void *keydata) +{ + const struct diff_options *diffopt = hashmap_cmp_fn_data; + const struct moved_entry *a = entry; + const struct moved_entry *b = entry_or_key; + unsigned flags = diffopt->color_moved_ws_handling + & XDF_WHITESPACE_FLAGS; + + if (diffopt->color_moved_ws_handling & + COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) + /* + * As there is not specific white space config given, + * we'd need to check for a new block, so ignore all + * white space. The setup of the white space + * configuration for the next block is done else where + */ + flags |= XDF_IGNORE_WHITESPACE; + + return !xdiff_compare_lines(a->es->line, a->es->len, + b->es->line, b->es->len, + flags); +} + +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]; + unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS; + + ret->ent.hash = xdiff_hash_string(l->line, l->len, flags); + 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 void pmb_advance_or_null(struct diff_options *o, + struct moved_entry *match, + struct hashmap *hm, + struct moved_block *pmb, + int pmb_nr) +{ + int i; + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *prev = pmb[i].match; + struct moved_entry *cur = (prev && prev->next_line) ? + prev->next_line : NULL; + if (cur && !hm->cmpfn(o, cur, match, NULL)) { + pmb[i].match = cur; + } else { + pmb[i].match = NULL; + } + } +} + +static void pmb_advance_or_null_multi_match(struct diff_options *o, + struct moved_entry *match, + struct hashmap *hm, + struct moved_block *pmb, + int pmb_nr, int n) +{ + int i; + char *got_match = xcalloc(1, pmb_nr); + + for (; match; match = hashmap_get_next(hm, match)) { + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *prev = pmb[i].match; + struct moved_entry *cur = (prev && prev->next_line) ? + prev->next_line : NULL; + if (!cur) + continue; + if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n)) + got_match[i] |= 1; + } + } + + for (i = 0; i < pmb_nr; i++) { + if (got_match[i]) { + /* Advance to the next line */ + pmb[i].match = pmb[i].match->next_line; + } else { + moved_block_clear(&pmb[i]); + } + } + + free(got_match); +} + +static int shrink_potential_moved_blocks(struct moved_block *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].match) + lp++; + /* lp points at the first NULL now */ + + while (rp > -1 && !pmb[rp].match) + rp--; + /* rp points at the last non-NULL */ + + if (lp < pmb_nr && rp > -1 && lp < rp) { + pmb[lp] = pmb[rp]; + pmb[rp].match = NULL; + pmb[rp].wsd.string = 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_block *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]; + + switch (l->s) { + case DIFF_SYMBOL_PLUS: + hm = del_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, NULL); + free(key); + break; + case DIFF_SYMBOL_MINUS: + hm = add_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, NULL); + free(key); + break; + default: + flipped_block = 1; + } + + if (!match) { + int i; + + adjust_last_block(o, n, block_length); + for(i = 0; i < pmb_nr; i++) + moved_block_clear(&pmb[i]); + pmb_nr = 0; + block_length = 0; + continue; + } + + l->flags |= DIFF_SYMBOL_MOVED_LINE; + + if (o->color_moved == COLOR_MOVED_PLAIN) + continue; + + if (o->color_moved_ws_handling & + COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) + pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n); + else + pmb_advance_or_null(o, match, hm, pmb, pmb_nr); + + 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); + if (o->color_moved_ws_handling & + COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) { + if (compute_ws_delta(l, match->es, + &pmb[pmb_nr].wsd)) + pmb[pmb_nr++].match = match; + } else { + pmb[pmb_nr].wsd.string = NULL; + pmb[pmb_nr++].match = match; + } + } + + flipped_block = (flipped_block + 1) % 2; + + adjust_last_block(o, n, block_length); + block_length = 0; + } + + block_length++; + + if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS) + l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; + } + adjust_last_block(o, n, block_length); + + for(n = 0; n < pmb_nr; n++) + moved_block_clear(&pmb[n]); + 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_sign, const char *set, + const char *reset, + int sign_index, const char *line, int len, + unsigned ws_rule, int blank_at_eof) { - const char *set = diff_get_color(ecbdata->color_diff, color); const char *ws = NULL; + int sign = o->output_indicators[sign_index]; - if (ecbdata->opt->ws_error_highlight & ws_error_highlight) { - ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + if (o->ws_error_highlight & ws_rule) { + ws = diff_get_color_opt(o, DIFF_WHITESPACE); if (!*ws) ws = NULL; } - if (!ws) - emit_line_0(ecbdata->opt, set, reset, sign, line, len); - else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len)) + if (!ws && !set_sign) + emit_line_0(o, set, NULL, 0, reset, sign, line, len); + else if (!ws) { + emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len); + } else if (blank_at_eof) /* Blank line at EOF - paint '+' as well */ - emit_line_0(ecbdata->opt, ws, reset, sign, line, len); + emit_line_0(o, ws, NULL, 0, reset, sign, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line_0(ecbdata->opt, set, reset, sign, "", 0); - ws_check_emit(line, len, ecbdata->ws_rule, - ecbdata->opt->file, set, reset, ws); + emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, 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, *set_sign, *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, NULL, 0, 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); + set_sign = NULL; + if (o->flags.dual_color_diffed_diffs) { + char c = !len ? 0 : line[0]; + + if (c == '+') + set = diff_get_color_opt(o, DIFF_FILE_NEW); + else if (c == '@') + set = diff_get_color_opt(o, DIFF_FRAGINFO); + else if (c == '-') + set = diff_get_color_opt(o, DIFF_FILE_OLD); + } + emit_line_ws_markup(o, set_sign, set, reset, + OUTPUT_INDICATOR_CONTEXT, 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); + if (!o->flags.dual_color_diffed_diffs) + set_sign = NULL; + else { + char c = !len ? 0 : line[0]; + + set_sign = set; + if (c == '-') + set = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD); + else if (c == '@') + set = diff_get_color_opt(o, DIFF_FRAGINFO); + else if (c == '+') + set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD); + else + set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD); + flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK; + } + emit_line_ws_markup(o, set_sign, set, reset, + OUTPUT_INDICATOR_NEW, 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); + if (!o->flags.dual_color_diffed_diffs) + set_sign = NULL; + else { + char c = !len ? 0 : line[0]; + + set_sign = set; + if (c == '+') + set = diff_get_color_opt(o, DIFF_FILE_NEW_DIM); + else if (c == '@') + set = diff_get_color_opt(o, DIFF_FRAGINFO); + else if (c == '-') + set = diff_get_color_opt(o, DIFF_FILE_OLD_DIM); + else + set = diff_get_color_opt(o, DIFF_CONTEXT_DIM); + } + emit_line_ws_markup(o, set_sign, set, reset, + OUTPUT_INDICATOR_OLD, 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: + 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) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line, len, ecbdata->ws_rule); +} + static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_FILE_NEW, WSEH_NEW, '+'); + unsigned flags = WSEH_NEW | ecbdata->ws_rule; + if (new_blank_line_at_eof(ecbdata, line, len)) + flags |= DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF; + + 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) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_FILE_OLD, WSEH_OLD, '-'); + 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) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_CONTEXT, WSEH_CONTEXT, ' '); + 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, @@ -631,6 +1580,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata, 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); + const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : ""; static const char atat[2] = { '@', '@' }; const char *cp, *ep; struct strbuf msgbuf = STRBUF_INIT; @@ -644,12 +1594,15 @@ 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, context, 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 */ + if (ecbdata->opt->flags.dual_color_diffed_diffs) + strbuf_addstr(&msgbuf, reverse); strbuf_addstr(&msgbuf, frag); strbuf_add(&msgbuf, line, ep - line); strbuf_addstr(&msgbuf, reset); @@ -678,7 +1631,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, } 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); } @@ -687,30 +1642,30 @@ static struct diff_tempfile *claim_diff_tempfile(void) { for (i = 0; i < ARRAY_SIZE(diff_temp); i++) if (!diff_temp[i].name) return diff_temp + i; - die("BUG: diff is failing to clean up its tempfiles"); + BUG("diff is failing to clean up its tempfiles"); } static void remove_tempfile(void) { int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) { - if (is_tempfile_active(&diff_temp[i].tempfile)) + if (is_tempfile_active(diff_temp[i].tempfile)) delete_tempfile(&diff_temp[i].tempfile); diff_temp[i].name = NULL; } } -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; } } @@ -719,7 +1674,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 *reset = diff_get_color(ecb->color_diff, DIFF_RESET); while (0 < size) { @@ -737,13 +1691,8 @@ static void emit_rewrite_lines(struct emit_callback *ecb, size -= len; data += len; } - if (!endp) { - const char *context = diff_get_color(ecb->color_diff, - DIFF_CONTEXT); - putc('\n', ecb->opt->file); - emit_line_0(ecb->opt, context, 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, @@ -755,18 +1704,14 @@ static void emit_rewrite_diff(const char *name_a, struct diff_options *o) { int lc_a, lc_b; - const char *name_a_tab, *name_b_tab; - const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO); - const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); - const char *reset = diff_get_color(o->use_color, 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; - const char *line_prefix = diff_line_prefix(o); + struct strbuf out = STRBUF_INIT; - if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { + if (diff_mnemonic_prefix && o->flags.reverse_diff) { a_prefix = o->b_prefix; b_prefix = o->a_prefix; } else { @@ -776,20 +1721,18 @@ 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); quote_two_c_style(&a_name, a_prefix, name_a, 0); quote_two_c_style(&b_name, b_prefix, name_b, 0); - size_one = fill_textconv(textconv_one, one, &data_one); - size_two = fill_textconv(textconv_two, two, &data_two); + size_one = fill_textconv(o->repo, textconv_one, one, &data_one); + size_two = fill_textconv(o->repo, textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = want_color(o->use_color); - ecbdata.ws_rule = whitespace_rule(name_b); + ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; @@ -804,18 +1747,23 @@ 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); + + 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) - print_line_count(o->file, lc_a); + add_line_count(&out, lc_a); else - fprintf(o->file, "?,?"); - fprintf(o->file, " +"); - print_line_count(o->file, lc_b); - fprintf(o->file, " @@%s\n", reset); + 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) @@ -855,7 +1803,7 @@ struct diff_words_style_elem { struct diff_words_style { enum diff_words_type type; - struct diff_words_style_elem new, old, ctx; + struct diff_words_style_elem new_word, old_word, ctx; const char *newline; }; @@ -875,37 +1823,49 @@ 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; } @@ -952,19 +1912,17 @@ static int color_words_output_graph_prefix(struct diff_words_data *diff_words) } } -static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) +static void fn_out_diff_words_aux(void *priv, + long minus_first, long minus_len, + long plus_first, long plus_len, + const char *func, long funclen) { struct diff_words_data *diff_words = priv; struct diff_words_style *style = diff_words->style; - int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; struct diff_options *opt = diff_words->opt; const char *line_prefix; - if (line[0] != '@' || parse_hunk_header(line, len, - &minus_first, &minus_len, &plus_first, &plus_len)) - return; - assert(opt); line_prefix = diff_line_prefix(opt); @@ -987,24 +1945,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, - &style->old, style->newline, - minus_end - minus_begin, minus_begin, - line_prefix); + fn_out_diff_words_write_helper(diff_words->opt, + &style->old_word, style->newline, + minus_end - minus_begin, minus_begin); } if (plus_begin != plus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, - &style->new, style->newline, - plus_end - plus_begin, plus_begin, - line_prefix); + fn_out_diff_words_write_helper(diff_words->opt, + &style->new_word, style->newline, + plus_end - plus_begin, plus_begin); } diff_words->current_plus = plus_end; @@ -1098,11 +2052,12 @@ static void diff_words_show(struct diff_words_data *diff_words) /* 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, - &style->old, style->newline, + 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_word, 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; } @@ -1117,20 +2072,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; - if (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, NULL, + 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; } @@ -1138,28 +2093,50 @@ 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) +static void diff_filespec_load_driver(struct diff_filespec *one, + struct index_state *istate) { /* Use already-loaded driver */ if (one->driver) return; if (S_ISREG(one->mode)) - one->driver = userdiff_find_by_path(one->path); + one->driver = userdiff_find_by_path(istate, 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) +static const char *userdiff_word_regex(struct diff_filespec *one, + struct index_state *istate) { - diff_filespec_load_driver(one); + diff_filespec_load_driver(one, istate); return one->driver->word_regex; } @@ -1176,10 +2153,15 @@ static void init_diff_words_data(struct emit_callback *ecbdata, 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); + o->word_regex = userdiff_word_regex(one, o->repo->index); if (!o->word_regex) - o->word_regex = userdiff_word_regex(two); + o->word_regex = userdiff_word_regex(two, o->repo->index); if (!o->word_regex) o->word_regex = diff_word_regex_cfg; if (o->word_regex) { @@ -1188,8 +2170,8 @@ static void init_diff_words_data(struct emit_callback *ecbdata, if (regcomp(ecbdata->diff_words->word_regex, o->word_regex, REG_EXTENDED | REG_NEWLINE)) - die ("Invalid regular expression: %s", - o->word_regex); + 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) { @@ -1200,8 +2182,8 @@ static void init_diff_words_data(struct emit_callback *ecbdata, } 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->old_word.color = diff_get_color_opt(o, DIFF_FILE_OLD); + st->new_word.color = diff_get_color_opt(o, DIFF_FILE_NEW); st->ctx.color = diff_get_color_opt(o, DIFF_CONTEXT); } } @@ -1210,6 +2192,7 @@ 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); @@ -1246,8 +2229,6 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u unsigned long allot; size_t l = len; - if (ecb->truncate) - return ecb->truncate(line, len); cp = line; allot = l; while (0 < l) { @@ -1276,30 +2257,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 *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); struct diff_options *o = ecbdata->opt; - const char *line_prefix = diff_line_prefix(o); o->found_changes = 1; if (ecbdata->header) { - fprintf(o->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; } 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(o->file, "%s%s--- %s%s%s\n", - line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(o->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; } @@ -1315,12 +2291,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', o->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); @@ -1340,21 +2317,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } diff_words_flush(ecbdata); - if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { - emit_line(o, context, reset, line, len); - fputs("~\n", o->file); - } else { - /* - * 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); - } + emit_diff_symbol(o, s, line, len, 0); return; } @@ -1375,17 +2338,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) default: /* incomplete line at the end */ ecbdata->lno_in_preimage++; - emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT), - reset, line, len); + emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE, + line, len, 0); break; } } -static char *pprint_rename(const char *a, const char *b) +static void pprint_rename(struct strbuf *name, const char *a, const char *b) { - const char *old = a; - const char *new = b; - struct strbuf name = STRBUF_INIT; + const char *old_name = a; + const char *new_name = b; int pfx_length, sfx_length; int pfx_adjust_for_slash; int len_a = strlen(a); @@ -1395,24 +2357,24 @@ static char *pprint_rename(const char *a, const char *b) int qlen_b = quote_c_style(b, NULL, NULL, 0); if (qlen_a || qlen_b) { - quote_c_style(a, &name, NULL, 0); - strbuf_addstr(&name, " => "); - quote_c_style(b, &name, NULL, 0); - return strbuf_detach(&name, NULL); + quote_c_style(a, name, NULL, 0); + strbuf_addstr(name, " => "); + quote_c_style(b, name, NULL, 0); + return; } /* Find common prefix */ pfx_length = 0; - while (*old && *new && *old == *new) { - if (*old == '/') - pfx_length = old - a + 1; - old++; - new++; + while (*old_name && *new_name && *old_name == *new_name) { + if (*old_name == '/') + pfx_length = old_name - a + 1; + old_name++; + new_name++; } /* Find common suffix */ - old = a + len_a; - new = b + len_b; + old_name = a + len_a; + new_name = b + len_b; sfx_length = 0; /* * If there is a common prefix, it must end in a slash. In @@ -1423,13 +2385,13 @@ static char *pprint_rename(const char *a, const char *b) * 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--; - new--; + while (a + pfx_length - pfx_adjust_for_slash <= old_name && + b + pfx_length - pfx_adjust_for_slash <= new_name && + *old_name == *new_name) { + if (*old_name == '/') + sfx_length = len_a - (old_name - a); + old_name--; + new_name--; } /* @@ -1445,19 +2407,18 @@ static char *pprint_rename(const char *a, const char *b) if (b_midlen < 0) b_midlen = 0; - strbuf_grow(&name, pfx_length + a_midlen + b_midlen + sfx_length + 7); + strbuf_grow(name, pfx_length + a_midlen + b_midlen + sfx_length + 7); if (pfx_length + sfx_length) { - strbuf_add(&name, a, pfx_length); - strbuf_addch(&name, '{'); + strbuf_add(name, a, pfx_length); + strbuf_addch(name, '{'); } - strbuf_add(&name, a + pfx_length, a_midlen); - strbuf_addstr(&name, " => "); - strbuf_add(&name, b + pfx_length, b_midlen); + strbuf_add(name, a + pfx_length, a_midlen); + strbuf_addstr(name, " => "); + strbuf_add(name, b + pfx_length, b_midlen); if (pfx_length + sfx_length) { - strbuf_addch(&name, '}'); - strbuf_add(&name, a + len_a - sfx_length, sfx_length); + strbuf_addch(name, '}'); + strbuf_add(name, a + len_a - sfx_length, sfx_length); } - return strbuf_detach(&name, NULL); } struct diffstat_t { @@ -1467,6 +2428,7 @@ struct diffstat_t { char *from_name; char *name; char *print_name; + const char *comments; unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; @@ -1521,51 +2483,44 @@ static int scale_linear(int it, int width, int max_change) return 1 + (it * (width - 1) / max_change); } -static void show_name(FILE *file, - const char *prefix, const char *name, int len) -{ - fprintf(file, " %s%-*s |", prefix, len, name); -} - -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) { - char *pname; + struct strbuf pname = STRBUF_INIT; if (file->print_name) return; - if (!file->is_renamed) { - struct strbuf buf = STRBUF_INIT; - if (quote_c_style(file->name, &buf, NULL, 0)) { - pname = strbuf_detach(&buf, NULL); - } else { - pname = file->name; - strbuf_release(&buf); - } - } else { - pname = pprint_rename(file->from_name, file->name); - } - file->print_name = pname; + if (file->is_renamed) + pprint_rename(&pname, file->from_name, file->name); + else + quote_c_style(file->name, &pname, NULL, 0); + + if (file->comments) + strbuf_addf(&pname, " (%s)", file->comments); + + file->print_name = strbuf_detach(&pname, NULL); } -int print_stat_summary(FILE *fp, int files, int insertions, int deletions) +static void print_stat_summary_inserts_deletes(struct diff_options *options, + int files, int insertions, int deletions) { struct strbuf sb = STRBUF_INIT; - int ret; if (!files) { assert(insertions == 0 && deletions == 0); - return fprintf(fp, "%s\n", " 0 files changed"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_NO_FILES, + NULL, 0, 0); + return; } strbuf_addf(&sb, @@ -1592,9 +2547,19 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions) deletions); } strbuf_addch(&sb, '\n'); - ret = fputs(sb.buf, fp); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES, + sb.buf, sb.len, 0); strbuf_release(&sb); - return ret; +} + +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) @@ -1604,13 +2569,13 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) int total_files = data->nr, count; int width, name_width, graph_width, number_width = 0, bin_width = 0; const char *reset, *add_c, *del_c; - const char *line_prefix = ""; int extra_shown = 0; + const char *line_prefix = diff_line_prefix(options); + struct strbuf out = STRBUF_INIT; if (data->nr == 0) return; - line_prefix = diff_line_prefix(options); count = options->stat_count ? options->stat_count : data->nr; reset = diff_get_color_opt(options, DIFF_RESET); @@ -1764,26 +2729,32 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) } if (file->is_binary) { - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " %*s", number_width, "Bin"); + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*s", number_width, "Bin"); if (!added && !deleted) { - putc('\n', options->file); + strbuf_addch(&out, '\n'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } - fprintf(options->file, " %s%"PRIuMAX"%s", + 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"); + strbuf_addstr(&out, " bytes\n"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } else if (file->is_unmerged) { - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " Unmerged\n"); + 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; } @@ -1806,14 +2777,16 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) add = total - del; } } - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " %*"PRIuMAX"%s", + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*"PRIuMAX"%s", number_width, added + deleted, added + deleted ? " " : ""); - show_graph(options->file, '+', add, add_c, reset); - show_graph(options->file, '-', del, del_c, reset); - fprintf(options->file, "\n"); + 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++) { @@ -1834,11 +2807,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (i < count) continue; if (!extra_shown) - fprintf(options->file, "%s ...\n", line_prefix); + emit_diff_symbol(options, + DIFF_SYMBOL_STATS_SUMMARY_ABBREV, + NULL, 0, 0); extra_shown = 1; } - fprintf(options->file, "%s", line_prefix); - print_stat_summary(options->file, 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) @@ -1850,7 +2826,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option for (i = 0; i < data->nr; i++) { int added = data->files[i]->added; - int deleted= data->files[i]->deleted; + int deleted = data->files[i]->deleted; if (data->files[i]->is_unmerged || (!data->files[i]->is_interesting && (added + deleted == 0))) { @@ -1860,8 +2836,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option dels += deleted; } } - fprintf(options->file, "%s", diff_line_prefix(options)); - print_stat_summary(options->file, 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) @@ -1914,14 +2889,14 @@ struct dirstat_dir { static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen) { - unsigned long this_dir = 0; + unsigned long sum_changes = 0; unsigned int sources = 0; const char *line_prefix = diff_line_prefix(opt); while (dir->nr) { struct dirstat_file *f = dir->files; int namelen = strlen(f->name); - unsigned long this; + unsigned long changes; char *slash; if (namelen < baselen) @@ -1931,15 +2906,15 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, slash = strchr(f->name + baselen, '/'); if (slash) { int newbaselen = slash + 1 - f->name; - this = gather_dirstat(opt, dir, changed, f->name, newbaselen); + changes = gather_dirstat(opt, dir, changed, f->name, newbaselen); sources++; } else { - this = f->changed; + changes = f->changed; dir->files++; dir->nr--; sources += 2; } - this_dir += this; + sum_changes += changes; } /* @@ -1949,8 +2924,8 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, * under this directory (sources == 1). */ if (baselen && sources != 1) { - if (this_dir) { - int permille = this_dir * 1000 / changed; + if (sum_changes) { + int permille = sum_changes * 1000 / changed; if (permille >= dir->permille) { fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, permille / 10, permille % 10, baselen, base); @@ -1959,7 +2934,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, } } } - return this_dir; + return sum_changes; } static int dirstat_compare(const void *_a, const void *_b) @@ -1980,23 +2955,18 @@ static void show_dirstat(struct diff_options *options) dir.alloc = 0; dir.nr = 0; dir.permille = options->dirstat_permille; - dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); + dir.cumulative = options->flags.dirstat_cumulative; changed = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *name; unsigned long copied, added, damage; - int content_changed; 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) { + if (p->one->oid_valid && p->two->oid_valid && + oideq(&p->one->oid, &p->two->oid)) { /* * The SHA1 has not changed, so pre-/post-content is * identical. We can therefore skip looking at the @@ -2006,7 +2976,7 @@ static void show_dirstat(struct diff_options *options) goto found_damage; } - if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) { + if (options->flags.dirstat_by_file) { /* * In --dirstat-by-file mode, we don't really need to * look at the actual file contents at all. @@ -2019,18 +2989,19 @@ static void show_dirstat(struct diff_options *options) } 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, + diff_populate_filespec(options->repo, p->one, 0); + diff_populate_filespec(options->repo, p->two, 0); + diffcore_count_changes(options->repo, + 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, CHECK_SIZE_ONLY); + diff_populate_filespec(options->repo, 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, CHECK_SIZE_ONLY); + diff_populate_filespec(options->repo, p->two, CHECK_SIZE_ONLY); copied = 0; added = p->two->size; diff_free_filespec_data(p->two); @@ -2043,7 +3014,7 @@ static void show_dirstat(struct diff_options *options) * 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 + * be identical, but since the oid changed, we * know that there must have been _some_ kind of change, * so we force all entries to have damage > 0. */ @@ -2081,7 +3052,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o dir.alloc = 0; dir.nr = 0; dir.permille = options->dirstat_permille; - dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE); + dir.cumulative = options->flags.dirstat_cumulative; changed = 0; for (i = 0; i < data->nr; i++) { @@ -2117,8 +3088,7 @@ static void free_diffstat_info(struct diffstat_t *diffstat) int i; for (i = 0; i < diffstat->nr; i++) { struct diffstat_file *f = diffstat->files[i]; - if (f->name != f->print_name) - free(f->print_name); + free(f->print_name); free(f->name); free(f->from_name); free(f); @@ -2158,6 +3128,15 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l return 1; } +static void checkdiff_consume_hunk(void *priv, + long ob, long on, long nb, long nn, + const char *func, long funclen) + +{ + struct checkdiff_t *data = priv; + data->lineno = nb - 1; +} + static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; @@ -2193,12 +3172,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - } else if (line[0] == '@') { - char *plus = strchr(line, '+'); - if (plus) - data->lineno = strtol(plus, NULL, 10) - 1; - else - die("invalid diff"); } } @@ -2225,8 +3198,8 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, - const char *prefix) +static void emit_binary_diff_body(struct diff_options *o, + mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -2255,13 +3228,18 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, } if (delta && delta_size < deflate_size) { - fprintf(file, "%sdelta %lu\n", prefix, orig_size); + char *s = xstrfmt("%"PRIuMAX , (uintmax_t)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; @@ -2270,8 +3248,9 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, /* 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; @@ -2279,31 +3258,36 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, 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, - const 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); + 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) +int diff_filespec_is_binary(struct repository *r, + struct diff_filespec *one) { if (one->is_binary == -1) { - diff_filespec_load_driver(one); + diff_filespec_load_driver(one, r->index); if (one->driver->binary != -1) one->is_binary = one->driver->binary; else { if (!one->data && DIFF_FILE_VALID(one)) - diff_populate_filespec(one, CHECK_BINARY); + diff_populate_filespec(r, one, CHECK_BINARY); if (one->is_binary == -1 && one->data) one->is_binary = buffer_is_binary(one->data, one->size); @@ -2314,9 +3298,10 @@ int diff_filespec_is_binary(struct diff_filespec *one) return one->is_binary; } -static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespec *one) +static const struct userdiff_funcname * +diff_funcname_pattern(struct diff_options *o, struct diff_filespec *one) { - diff_filespec_load_driver(one); + diff_filespec_load_driver(one, o->repo->index); return one->driver->funcname.pattern ? &one->driver->funcname : NULL; } @@ -2328,13 +3313,14 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } -struct userdiff_driver *get_textconv(struct diff_filespec *one) +struct userdiff_driver *get_textconv(struct repository *r, + struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; - diff_filespec_load_driver(one); - return userdiff_get_textconv(one->driver); + diff_filespec_load_driver(one, r->index); + return userdiff_get_textconv(r, one->driver); } static void builtin_diff(const char *name_a, @@ -2358,7 +3344,7 @@ static void builtin_diff(const char *name_a, const char *line_prefix = diff_line_prefix(o); diff_set_mnemonic_prefix(o, "a/", "b/"); - if (DIFF_OPT_TST(o, REVERSE_DIFF)) { + if (o->flags.reverse_diff) { a_prefix = o->b_prefix; b_prefix = o->a_prefix; } else { @@ -2369,30 +3355,22 @@ static void builtin_diff(const char *name_a, if (o->submodule_format == DIFF_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->path ? one->path : two->path, - line_prefix, + show_submodule_summary(o, one->path ? one->path : two->path, &one->oid, &two->oid, - two->dirty_submodule, - meta, del, add, reset); + 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))) { - const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); - const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); - show_submodule_inline_diff(o->file, one->path ? one->path : two->path, - line_prefix, + show_submodule_inline_diff(o, one->path ? one->path : two->path, &one->oid, &two->oid, - two->dirty_submodule, - meta, del, add, reset, o); + two->dirty_submodule); return; } - if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { - textconv_one = get_textconv(one); - textconv_two = get_textconv(two); + if (o->flags.allow_textconv) { + textconv_one = get_textconv(o->repo, one); + textconv_two = get_textconv(o->repo, two); } /* Never use a non-valid filename anywhere if at all possible */ @@ -2433,53 +3411,68 @@ static void builtin_diff(const char *name_a, if ((one->mode ^ two->mode) & S_IFMT) goto free_ab_and_return; if (complete_rewrite && - (textconv_one || !diff_filespec_is_binary(one)) && - (textconv_two || !diff_filespec_is_binary(two))) { - fprintf(o->file, "%s", header.buf); + (textconv_one || !diff_filespec_is_binary(o->repo, one)) && + (textconv_two || !diff_filespec_is_binary(o->repo, two))) { + 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); + textconv_one, textconv_two, o); o->found_changes = 1; goto free_ab_and_return; } } if (o->irreversible_delete && lbl[1][0] == '/') { - fprintf(o->file, "%s", header.buf); + 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)) )) { + } else if (!o->flags.text && + ( (!textconv_one && diff_filespec_is_binary(o->repo, one)) || + (!textconv_two && diff_filespec_is_binary(o->repo, 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)) { + !o->flags.binary) { + if (oideq(&one->oid, &two->oid)) { 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); - fprintf(o->file, "%sBinary files %s and %s differ\n", - line_prefix, lbl[0], lbl[1]); + 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) + if (fill_mmfile(o->repo, &mf1, one) < 0 || + fill_mmfile(o->repo, &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]); + if (o->flags.binary) + 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 { /* Crazy xdl interfaces.. */ @@ -2491,32 +3484,38 @@ static void builtin_diff(const char *name_a, const struct userdiff_funcname *pe; if (must_show_header) { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); } - mf1.size = fill_textconv(textconv_one, one, &mf1.ptr); - mf2.size = fill_textconv(textconv_two, two, &mf2.ptr); + mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr); + mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr); - pe = diff_funcname_pattern(one); + pe = diff_funcname_pattern(o, one); if (!pe) - pe = diff_funcname_pattern(two); + pe = diff_funcname_pattern(o, two); memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); memset(&ecbdata, 0, sizeof(ecbdata)); + if (o->flags.suppress_diff_headers) + lbl[0] = NULL; ecbdata.label_path = lbl; ecbdata.color_diff = want_color(o->use_color); - ecbdata.ws_rule = whitespace_rule(name_b); + ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.opt = o; - ecbdata.header = header.len ? &header : NULL; + if (header.len && !o->flags.suppress_diff_headers) + ecbdata.header = &header; xpp.flags = o->xdl_opts; + xpp.anchors = o->anchors; + xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; - if (DIFF_OPT_TST(o, FUNCCONTEXT)) + if (o->flags.funccontext) xecfg.flags |= XDL_EMIT_FUNCCONTEXT; if (pe) xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); @@ -2528,8 +3527,8 @@ static void builtin_diff(const char *name_a, 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)) + if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, + &ecbdata, &xpp, &xecfg)) die("unable to generate diff for %s", one->path); if (o->word_diff) free_diff_words_data(&ecbdata); @@ -2549,6 +3548,32 @@ static void builtin_diff(const char *name_a, return; } +static char *get_compact_summary(const struct diff_filepair *p, int is_renamed) +{ + if (!is_renamed) { + if (p->status == DIFF_STATUS_ADDED) { + if (S_ISLNK(p->two->mode)) + return "new +l"; + else if ((p->two->mode & 0777) == 0755) + return "new +x"; + else + return "new"; + } else if (p->status == DIFF_STATUS_DELETED) + return "gone"; + } + if (S_ISLNK(p->one->mode) && !S_ISLNK(p->two->mode)) + return "mode -l"; + else if (!S_ISLNK(p->one->mode) && S_ISLNK(p->two->mode)) + return "mode +l"; + else if ((p->one->mode & 0777) == 0644 && + (p->two->mode & 0777) == 0755) + return "mode +x"; + else if ((p->one->mode & 0777) == 0755 && + (p->two->mode & 0777) == 0644) + return "mode -x"; + return NULL; +} + static void builtin_diffstat(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, @@ -2568,28 +3593,31 @@ static void builtin_diffstat(const char *name_a, const char *name_b, data = diffstat_add(diffstat, name_a, name_b); data->is_interesting = p->status != DIFF_STATUS_UNKNOWN; + if (o->flags.stat_with_summary) + data->comments = get_compact_summary(p, data->is_renamed); if (!one || !two) { data->is_unmerged = 1; return; } - same_contents = !oidcmp(&one->oid, &two->oid); + same_contents = oideq(&one->oid, &two->oid); - if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { + if (diff_filespec_is_binary(o->repo, one) || + diff_filespec_is_binary(o->repo, 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); + data->added = diff_filespec_size(o->repo, two); + data->deleted = diff_filespec_size(o->repo, one); } } else if (complete_rewrite) { - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); + diff_populate_filespec(o->repo, one, 0); + diff_populate_filespec(o->repo, two, 0); data->deleted = count_lines(one->data, one->size); data->added = count_lines(two->data, two->size); } @@ -2599,16 +3627,19 @@ static void builtin_diffstat(const char *name_a, const char *name_b, xpparam_t xpp; xdemitconf_t xecfg; - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + if (fill_mmfile(o->repo, &mf1, one) < 0 || + fill_mmfile(o->repo, &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; + xpp.anchors = o->anchors; + xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; - if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg)) + if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line, + diffstat_consume, diffstat, &xpp, &xecfg)) die("unable to generate diffstat for %s", one->path); } @@ -2632,10 +3663,11 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.filename = name_b ? name_b : name_a; data.lineno = 0; data.o = o; - data.ws_rule = whitespace_rule(attr_path); - data.conflict_marker_size = ll_merge_marker_size(attr_path); + data.ws_rule = whitespace_rule(o->repo->index, attr_path); + data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path); - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + if (fill_mmfile(o->repo, &mf1, one) < 0 || + fill_mmfile(o->repo, &mf2, two) < 0) die("unable to read files to diff"); /* @@ -2644,7 +3676,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, * introduced changes, and as long as the "new" side is text, we * can and should check what it introduces. */ - if (diff_filespec_is_binary(two)) + if (diff_filespec_is_binary(o->repo, two)) goto free_and_return; else { /* Crazy xdl interfaces.. */ @@ -2655,7 +3687,8 @@ 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; - if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, + if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume_hunk, + checkdiff_consume, &data, &xpp, &xecfg)) die("unable to generate checkdiff for %s", one->path); @@ -2681,7 +3714,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, diff_free_filespec_data(one); diff_free_filespec_data(two); if (data.status) - DIFF_OPT_SET(o, CHECK_FAILED); + o->flags.check_failed = 1; } struct diff_filespec *alloc_filespec(const char *path) @@ -2717,7 +3750,10 @@ void fill_filespec(struct diff_filespec *spec, const struct object_id *oid, * 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 struct object_id *oid, int want_file) +static int reuse_worktree_file(struct index_state *istate, + const char *name, + const struct object_id *oid, + int want_file) { const struct cache_entry *ce; struct stat st; @@ -2736,7 +3772,7 @@ static int reuse_worktree_file(const char *name, const struct object_id *oid, in * by diff-cache --cached, which does read the cache before * calling us. */ - if (!active_cache) + if (!istate->cache) return 0; /* We want to avoid the working directory if our caller @@ -2748,27 +3784,27 @@ static int reuse_worktree_file(const char *name, const struct object_id *oid, in * 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(oid->hash)) + if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid)) 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)) + if (!want_file && would_convert_to_git(istate, name)) return 0; len = strlen(name); - pos = cache_name_pos(name, len); + pos = index_name_pos(istate, name, len); if (pos < 0) return 0; - ce = active_cache[pos]; + ce = istate->cache[pos]; /* * This is not the sha1 we are looking for, or * unreusable because it is not a regular file. */ - if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode)) + if (!oideq(oid, &ce->oid) || !S_ISREG(ce->ce_mode)) return 0; /* @@ -2782,7 +3818,7 @@ static int reuse_worktree_file(const char *name, const struct object_id *oid, in * If ce matches the file in the work tree, we can reuse it. */ if (ce_uptodate(ce) || - (!lstat(name, &st) && !ce_match_stat(ce, &st, 0))) + (!lstat(name, &st) && !ie_match_stat(istate, ce, &st, 0))) return 1; return 0; @@ -2815,17 +3851,19 @@ 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, unsigned int flags) +int diff_populate_filespec(struct repository *r, + struct diff_filespec *s, + unsigned int flags) { int size_only = flags & CHECK_SIZE_ONLY; int err = 0; + int conv_flags = global_conv_flags_eol; /* * 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 (conv_flags & CONV_EOL_RNDTRP_DIE) + conv_flags = CONV_EOL_RNDTRP_WARN; if (!DIFF_FILE_VALID(s)) die("internal error: asking to populate invalid file."); @@ -2842,20 +3880,18 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) return diff_populate_gitlink(s, size_only); if (!s->oid_valid || - reuse_worktree_file(s->path, &s->oid, 0)) { + reuse_worktree_file(r->index, s->path, &s->oid, 0)) { struct strbuf buf = STRBUF_INIT; struct stat st; int fd; if (lstat(s->path, &st) < 0) { - if (errno == ENOENT) { - err_empty: - err = -1; - empty: - s->data = (char *)""; - s->size = 0; - return err; - } + err_empty: + err = -1; + empty: + s->data = (char *)""; + s->size = 0; + return err; } s->size = xsize_t(st.st_size); if (!s->size) @@ -2877,7 +3913,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) * point if the path requires us to run the content * conversion. */ - if (size_only && !would_convert_to_git(&the_index, s->path)) + if (size_only && !would_convert_to_git(r->index, s->path)) return 0; /* @@ -2904,7 +3940,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) /* * Convert from working tree format to canonical git format */ - if (convert_to_git(&the_index, s->path, s->data, s->size, &buf, crlf_warn)) { + if (convert_to_git(r->index, s->path, s->data, s->size, &buf, conv_flags)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -2916,7 +3952,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) else { enum object_type type; if (size_only || (flags & CHECK_BINARY)) { - type = sha1_object_info(s->oid.hash, &s->size); + type = oid_object_info(r, &s->oid, &s->size); if (type < 0) die("unable to read %s", oid_to_hex(&s->oid)); @@ -2927,7 +3963,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) return 0; } } - s->data = read_sha1_file(s->oid.hash, &type, &s->size); + s->data = read_object_file(&s->oid, &type, &s->size); if (!s->data) die("unable to read %s", oid_to_hex(&s->oid)); s->should_free = 1; @@ -2954,43 +3990,44 @@ void diff_free_filespec_data(struct diff_filespec *s) FREE_AND_NULL(s->cnt_data); } -static void prep_temp_blob(const char *path, struct diff_tempfile *temp, +static void prep_temp_blob(struct index_state *istate, + const char *path, struct diff_tempfile *temp, void *blob, unsigned long size, const struct object_id *oid, int mode) { - int fd; struct strbuf buf = STRBUF_INIT; - struct strbuf template = STRBUF_INIT; + struct strbuf tempfile = STRBUF_INIT; char *path_dup = xstrdup(path); const char *base = basename(path_dup); /* Generate "XXXXXX_basename.ext" */ - strbuf_addstr(&template, "XXXXXX_"); - strbuf_addstr(&template, base); + strbuf_addstr(&tempfile, "XXXXXX_"); + strbuf_addstr(&tempfile, base); - fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1); - if (fd < 0) + temp->tempfile = mks_tempfile_ts(tempfile.buf, strlen(base) + 1); + if (!temp->tempfile) die_errno("unable to create temp-file"); - if (convert_to_working_tree(path, + if (convert_to_working_tree(istate, path, (const char *)blob, (size_t)size, &buf)) { blob = buf.buf; size = buf.len; } - if (write_in_full(fd, blob, size) < 0) + if (write_in_full(temp->tempfile->fd, blob, size) < 0 || + close_tempfile_gently(temp->tempfile)) die_errno("unable to write temp-file"); - close_tempfile(&temp->tempfile); - temp->name = get_tempfile_path(&temp->tempfile); + 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); + strbuf_release(&tempfile); free(path_dup); } -static struct diff_tempfile *prepare_temp_file(const char *name, - struct diff_filespec *one) +static struct diff_tempfile *prepare_temp_file(struct repository *r, + const char *name, + struct diff_filespec *one) { struct diff_tempfile *temp = claim_diff_tempfile(); @@ -3007,7 +4044,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (!S_ISGITLINK(one->mode) && (!one->oid_valid || - reuse_worktree_file(name, &one->oid, 1))) { + reuse_worktree_file(r->index, name, &one->oid, 1))) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -3018,7 +4055,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name, struct strbuf sb = STRBUF_INIT; if (strbuf_readlink(&sb, name, st.st_size) < 0) die_errno("readlink(%s)", name); - prep_temp_blob(name, temp, sb.buf, sb.len, + prep_temp_blob(r->index, name, temp, sb.buf, sb.len, (one->oid_valid ? &one->oid : &null_oid), (one->oid_valid ? @@ -3043,19 +4080,21 @@ static struct diff_tempfile *prepare_temp_file(const char *name, return temp; } else { - if (diff_populate_filespec(one, 0)) + if (diff_populate_filespec(r, one, 0)) die("cannot read data blob for %s", one->path); - prep_temp_blob(name, temp, one->data, one->size, + prep_temp_blob(r->index, name, temp, + one->data, one->size, &one->oid, one->mode); } return temp; } -static void add_external_diff_name(struct argv_array *argv, +static void add_external_diff_name(struct repository *r, + struct argv_array *argv, const char *name, struct diff_filespec *df) { - struct diff_tempfile *temp = prepare_temp_file(name, df); + struct diff_tempfile *temp = prepare_temp_file(r, name, df); argv_array_push(argv, temp->name); argv_array_push(argv, temp->hex); argv_array_push(argv, temp->mode); @@ -3084,11 +4123,11 @@ static void run_external_diff(const char *pgm, argv_array_push(&argv, name); if (one && two) { - add_external_diff_name(&argv, name, one); + add_external_diff_name(o->repo, &argv, name, one); if (!other) - add_external_diff_name(&argv, name, two); + add_external_diff_name(o->repo, &argv, name, two); else { - add_external_diff_name(&argv, other, two); + add_external_diff_name(o->repo, &argv, other, two); argv_array_push(&argv, other); argv_array_push(&argv, xfrm_msg); } @@ -3113,13 +4152,13 @@ static int similarity_index(struct diff_filepair *p) 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); + return find_unique_abbrev(oid, 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 > the_hash_algo->hexsz) + BUG("oid abbreviation out of range: %d", abbrev); if (abbrev) hex[abbrev] = '\0'; return hex; @@ -3175,14 +4214,17 @@ static void fill_metainfo(struct strbuf *msg, default: *must_show_header = 0; } - if (one && two && oidcmp(&one->oid, &two->oid)) { - int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; + if (one && two && !oideq(&one->oid, &two->oid)) { + const unsigned hexsz = the_hash_algo->hexsz; + int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV; - if (DIFF_OPT_TST(o, BINARY)) { + if (o->flags.binary) { mmfile_t mf; - if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) || - (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) - abbrev = 40; + if ((!fill_mmfile(o->repo, &mf, one) && + diff_filespec_is_binary(o->repo, one)) || + (!fill_mmfile(o->repo, &mf, two) && + diff_filespec_is_binary(o->repo, two))) + abbrev = hexsz; } strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set, diff_abbrev_oid(&one->oid, abbrev), @@ -3208,8 +4250,10 @@ static void run_diff_cmd(const char *pgm, int must_show_header = 0; - if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) { - struct userdiff_driver *drv = userdiff_find_by_path(attr_path); + if (o->flags.allow_external) { + struct userdiff_driver *drv; + + drv = userdiff_find_by_path(o->repo->index, attr_path); if (drv && drv->external) pgm = drv->external; } @@ -3238,7 +4282,7 @@ static void run_diff_cmd(const char *pgm, fprintf(o->file, "* Unmerged path %s\n", name); } -static void diff_fill_oid_info(struct diff_filespec *one) +static void diff_fill_oid_info(struct diff_filespec *one, struct index_state *istate) { if (DIFF_FILE_VALID(one)) { if (!one->oid_valid) { @@ -3249,7 +4293,7 @@ static void diff_fill_oid_info(struct diff_filespec *one) } if (lstat(one->path, &st) < 0) die_errno("stat '%s'", one->path); - if (index_path(one->oid.hash, one->path, &st, 0)) + if (index_path(istate, &one->oid, one->path, &st, 0)) die("cannot hash %s", one->path); } } @@ -3260,12 +4304,12 @@ static void diff_fill_oid_info(struct diff_filespec *one) static void strip_prefix(int prefix_length, const char **namep, const char **otherp) { /* Strip the prefix but do not molest /dev/null and absolute paths */ - if (*namep && **namep != '/') { + if (*namep && !is_absolute_path(*namep)) { *namep += prefix_length; if (**namep == '/') ++*namep; } - if (*otherp && **otherp != '/') { + if (*otherp && !is_absolute_path(*otherp)) { *otherp += prefix_length; if (**otherp == '/') ++*otherp; @@ -3282,13 +4326,13 @@ 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)) + if (!o->flags.allow_external) pgm = NULL; if (DIFF_PAIR_UNMERGED(p)) { @@ -3297,8 +4341,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) return; } - diff_fill_oid_info(one); - diff_fill_oid_info(two); + diff_fill_oid_info(one, o->repo->index); + diff_fill_oid_info(two, o->repo->index); if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && @@ -3309,7 +4353,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) */ struct diff_filespec *null = alloc_filespec(two->path); run_diff_cmd(NULL, name, other, attr_path, - one, null, &msg, o, p); + one, null, &msg, + o, p); free(null); strbuf_release(&msg); @@ -3333,7 +4378,8 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p); + builtin_diffstat(p->one->path, NULL, NULL, NULL, + diffstat, o, p); return; } @@ -3343,10 +4389,11 @@ 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_oid_info(p->one); - diff_fill_oid_info(p->two); + diff_fill_oid_info(p->one, o->repo->index); + diff_fill_oid_info(p->two, o->repo->index); - builtin_diffstat(name, other, p->one, p->two, diffstat, o, p); + builtin_diffstat(name, other, p->one, p->two, + diffstat, o, p); } static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) @@ -3367,18 +4414,22 @@ 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_oid_info(p->one); - diff_fill_oid_info(p->two); + diff_fill_oid_info(p->one, o->repo->index); + diff_fill_oid_info(p->two, o->repo->index); builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } -void diff_setup(struct diff_options *options) +void repo_diff_setup(struct repository *r, struct diff_options *options) { memcpy(options, &default_diff_options, sizeof(*options)); options->file = stdout; + options->repo = r; + options->output_indicators[OUTPUT_INDICATOR_NEW] = '+'; + options->output_indicators[OUTPUT_INDICATOR_OLD] = '-'; + options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = ' '; options->abbrev = DEFAULT_ABBREV; options->line_termination = '\n'; options->break_opt = -1; @@ -3387,7 +4438,8 @@ void diff_setup(struct diff_options *options) 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); + options->flags.rename_empty = 1; + options->objfind = NULL; /* pathchange left =NULL by default */ options->change = diff_change; @@ -3406,26 +4458,32 @@ void diff_setup(struct diff_options *options) options->a_prefix = "a/"; options->b_prefix = "b/"; } + + options->color_moved = diff_color_moved_default; + options->color_moved_ws_handling = diff_color_moved_ws_default; } void diff_setup_done(struct diff_options *options) { - int count = 0; + unsigned check_mask = DIFF_FORMAT_NAME | + DIFF_FORMAT_NAME_STATUS | + DIFF_FORMAT_CHECKDIFF | + DIFF_FORMAT_NO_OUTPUT; + /* + * This must be signed because we're comparing against a potentially + * negative value. + */ + const int hexsz = the_hash_algo->hexsz; if (options->set_default) options->set_default(options); - if (options->output_format & DIFF_FORMAT_NAME) - count++; - if (options->output_format & DIFF_FORMAT_NAME_STATUS) - count++; - if (options->output_format & DIFF_FORMAT_CHECKDIFF) - count++; - if (options->output_format & DIFF_FORMAT_NO_OUTPUT) - count++; - if (count > 1) + if (HAS_MULTI_BITS(options->output_format & check_mask)) die(_("--name-only, --name-status, --check and -s are mutually exclusive")); + if (HAS_MULTI_BITS(options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK)) + die(_("-G, -S and --find-object are mutually exclusive")); + /* * Most of the time we can say "there are changes" * only by checking if there are changed paths, but @@ -3433,17 +4491,15 @@ void diff_setup_done(struct diff_options *options) * inside contents. */ - if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || - DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || - DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) - DIFF_OPT_SET(options, DIFF_FROM_CONTENTS); + if ((options->xdl_opts & XDF_WHITESPACE_FLAGS)) + options->flags.diff_from_contents = 1; else - DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS); + options->flags.diff_from_contents = 0; - if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) + if (options->flags.find_copies_harder) options->detect_rename = DIFF_DETECT_COPY; - if (!DIFF_OPT_TST(options, RELATIVE_NAME)) + if (!options->flags.relative_name) options->prefix = NULL; if (options->prefix) options->prefix_length = strlen(options->prefix); @@ -3473,48 +4529,41 @@ void diff_setup_done(struct diff_options *options) DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) - DIFF_OPT_SET(options, RECURSIVE); + options->flags.recursive = 1; /* * Also pickaxe would not work very well if you do not say recursive */ - if (options->pickaxe) - DIFF_OPT_SET(options, RECURSIVE); + if (options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) + options->flags.recursive = 1; /* * When patches are generated, submodules diffed against the work tree * must be checked for dirtiness too so it can be shown in the output */ if (options->output_format & DIFF_FORMAT_PATCH) - DIFF_OPT_SET(options, DIRTY_SUBMODULES); + options->flags.dirty_submodules = 1; if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; - if (options->setup & DIFF_SETUP_USE_CACHE) { - if (!active_cache) - /* read-cache does not die even when it fails - * so it is safe for us to do this here. Also - * it does not smudge active_cache or active_nr - * when it fails, so we do not have to worry about - * cleaning it up ourselves either. - */ - read_cache(); - } - if (40 < options->abbrev) - options->abbrev = 40; /* full */ + if (hexsz < options->abbrev) + options->abbrev = hexsz; /* full */ /* * It does not make sense to show the first hit we happened * to have found. It does not make sense not to return with * exit code in such a case either. */ - if (DIFF_OPT_TST(options, QUICK)) { + if (options->flags.quick) { options->output_format = DIFF_FORMAT_NO_OUTPUT; - DIFF_OPT_SET(options, EXIT_WITH_STATUS); + options->flags.exit_with_status = 1; } options->diff_path_counter = 0; - if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1) + if (options->flags.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) @@ -3611,7 +4660,7 @@ static int stat_opt(struct diff_options *options, const char **av) int argcount = 1; if (!skip_prefix(arg, "--stat", &arg)) - die("BUG: stat option does not begin with --stat: %s", arg); + BUG("stat option does not begin with --stat: %s", arg); end = (char *)arg; switch (*arg) { @@ -3788,6 +4837,23 @@ static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *ar return 1; } +static int parse_objfind_opt(struct diff_options *opt, const char *arg) +{ + struct object_id oid; + + if (get_oid(arg, &oid)) + return error("unable to resolve '%s'", arg); + + if (!opt->objfind) + opt->objfind = xcalloc(1, sizeof(*opt->objfind)); + + opt->pickaxe_opts |= DIFF_PICKAXE_KIND_OBJFIND; + opt->flags.recursive = 1; + opt->flags.tree_in_recursive = 1; + oidset_insert(opt->objfind, &oid); + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac, const char *prefix) { @@ -3811,17 +4877,12 @@ int diff_opt_parse(struct diff_options *options, options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; - else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) - return parse_dirstat_opt(options, ""); - else if (skip_prefix(arg, "-X", &arg)) - return parse_dirstat_opt(options, arg); - else if (skip_prefix(arg, "--dirstat=", &arg)) + else if (skip_prefix(arg, "-X", &arg) || + skip_to_optional_arg(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)) { + else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) { parse_dirstat_opt(options, "files"); return parse_dirstat_opt(options, arg); } @@ -3841,15 +4902,26 @@ int diff_opt_parse(struct diff_options *options, else if (starts_with(arg, "--stat")) /* --stat, --stat-width, --stat-name-width, or --stat-count */ return stat_opt(options, av); + else if (!strcmp(arg, "--compact-summary")) { + options->flags.stat_with_summary = 1; + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } else if (!strcmp(arg, "--no-compact-summary")) + options->flags.stat_with_summary = 0; + else if (skip_prefix(arg, "--output-indicator-new=", &arg)) + options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0]; + else if (skip_prefix(arg, "--output-indicator-old=", &arg)) + options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0]; + else if (skip_prefix(arg, "--output-indicator-context=", &arg)) + options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = arg[0]; /* renames options */ - else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") || - !strcmp(arg, "--break-rewrites")) { + else if (starts_with(arg, "-B") || + skip_to_optional_arg(arg, "--break-rewrites", NULL)) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -B: %s", arg+2); } - else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") || - !strcmp(arg, "--find-renames")) { + else if (starts_with(arg, "-M") || + skip_to_optional_arg(arg, "--find-renames", NULL)) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -M: %s", arg+2); options->detect_rename = DIFF_DETECT_RENAME; @@ -3857,10 +4929,10 @@ int diff_opt_parse(struct diff_options *options, 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")) { + else if (starts_with(arg, "-C") || + skip_to_optional_arg(arg, "--find-copies", NULL)) { if (options->detect_rename == DIFF_DETECT_COPY) - DIFF_OPT_SET(options, FIND_COPIES_HARDER); + options->flags.find_copies_harder = 1; if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -C: %s", arg+2); options->detect_rename = DIFF_DETECT_COPY; @@ -3868,14 +4940,13 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else if (!strcmp(arg, "--rename-empty")) - DIFF_OPT_SET(options, RENAME_EMPTY); + options->flags.rename_empty = 1; 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 (skip_prefix(arg, "--relative=", &arg)) { - DIFF_OPT_SET(options, RELATIVE_NAME); - options->prefix = arg; + options->flags.rename_empty = 0; + else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) { + options->flags.relative_name = 1; + if (arg) + options->prefix = arg; } /* xdiff options */ @@ -3889,15 +4960,26 @@ int diff_opt_parse(struct diff_options *options, 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-cr-at-eol")) + DIFF_XDL_SET(options, IGNORE_CR_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")) + else if (!strcmp(arg, "--patience")) { + int i; options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); - else if (!strcmp(arg, "--histogram")) + /* + * Both --patience and --anchored use PATIENCE_DIFF + * internally, so remove any anchors previously + * specified. + */ + for (i = 0; i < options->anchors_nr; i++) + free(options->anchors[i]); + options->anchors_nr = 0; + } 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); @@ -3909,29 +4991,32 @@ int diff_opt_parse(struct diff_options *options, options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; options->xdl_opts |= value; return argcount; + } else if (skip_prefix(arg, "--anchored=", &arg)) { + options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); + ALLOC_GROW(options->anchors, options->anchors_nr + 1, + options->anchors_alloc); + options->anchors[options->anchors_nr++] = xstrdup(arg); } /* flags options */ else if (!strcmp(arg, "--binary")) { enable_patch_output(&options->output_format); - DIFF_OPT_SET(options, BINARY); + options->flags.binary = 1; } else if (!strcmp(arg, "--full-index")) - DIFF_OPT_SET(options, FULL_INDEX); + options->flags.full_index = 1; else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) - DIFF_OPT_SET(options, TEXT); + options->flags.text = 1; else if (!strcmp(arg, "-R")) - DIFF_OPT_SET(options, REVERSE_DIFF); + options->flags.reverse_diff = 1; else if (!strcmp(arg, "--find-copies-harder")) - DIFF_OPT_SET(options, FIND_COPIES_HARDER); + options->flags.find_copies_harder = 1; else if (!strcmp(arg, "--follow")) - DIFF_OPT_SET(options, FOLLOW_RENAMES); + options->flags.follow_renames = 1; 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)) { + options->flags.follow_renames = 0; + options->flags.default_follow_renames = 0; + } else if (skip_to_optional_arg_default(arg, "--color", &arg, "always")) { int value = git_config_colorbool(NULL, arg); if (value < 0) return error("option `color' expects \"always\", \"auto\", or \"never\""); @@ -3939,15 +5024,24 @@ int diff_opt_parse(struct diff_options *options, } else if (!strcmp(arg, "--no-color")) options->use_color = 0; - else if (!strcmp(arg, "--color-words")) { + 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 (skip_prefix(arg, "--color-moved-ws=", &arg)) { + options->color_moved_ws_handling = parse_color_moved_ws(arg); + } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (skip_prefix(arg, "--color-words=", &arg)) { - options->use_color = 1; - options->word_diff = DIFF_WORDS_COLOR; - options->word_regex = arg; - } else if (!strcmp(arg, "--word-diff")) { if (options->word_diff == DIFF_WORDS_NONE) options->word_diff = DIFF_WORDS_PLAIN; @@ -3973,26 +5067,22 @@ int diff_opt_parse(struct diff_options *options, return argcount; } else if (!strcmp(arg, "--exit-code")) - DIFF_OPT_SET(options, EXIT_WITH_STATUS); + options->flags.exit_with_status = 1; else if (!strcmp(arg, "--quiet")) - DIFF_OPT_SET(options, QUICK); + options->flags.quick = 1; else if (!strcmp(arg, "--ext-diff")) - DIFF_OPT_SET(options, ALLOW_EXTERNAL); + options->flags.allow_external = 1; else if (!strcmp(arg, "--no-ext-diff")) - DIFF_OPT_CLR(options, ALLOW_EXTERNAL); - else if (!strcmp(arg, "--textconv")) - DIFF_OPT_SET(options, ALLOW_TEXTCONV); - else if (!strcmp(arg, "--no-textconv")) - DIFF_OPT_CLR(options, ALLOW_TEXTCONV); - else if (!strcmp(arg, "--ignore-submodules")) { - DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); - handle_ignore_submodules_arg(options, "all"); - } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) { - DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); + options->flags.allow_external = 0; + else if (!strcmp(arg, "--textconv")) { + options->flags.allow_textconv = 1; + options->flags.textconv_set_via_cmdline = 1; + } else if (!strcmp(arg, "--no-textconv")) + options->flags.allow_textconv = 0; + else if (skip_to_optional_arg_default(arg, "--ignore-submodules", &arg, "all")) { + options->flags.override_submodule_config = 1; handle_ignore_submodules_arg(options, arg); - } else if (!strcmp(arg, "--submodule")) - options->submodule_format = DIFF_SUBMODULE_LOG; - else if (skip_prefix(arg, "--submodule=", &arg)) + } else if (skip_to_optional_arg_default(arg, "--submodule", &arg, "log")) return parse_submodule_opt(options, arg); else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) return parse_ws_error_highlight_opt(options, arg); @@ -4024,7 +5114,8 @@ int diff_opt_parse(struct diff_options *options, else if ((argcount = short_opt('O', av, &optarg))) { options->orderfile = prefix_filename(prefix, optarg); return argcount; - } + } else if (skip_prefix(arg, "--find-object=", &arg)) + return parse_objfind_opt(options, arg); else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) { int offending = parse_diff_filter_opt(optarg, options); if (offending) @@ -4040,8 +5131,8 @@ int diff_opt_parse(struct diff_options *options, options->abbrev = strtoul(arg, NULL, 10); if (options->abbrev < MINIMUM_ABBREV) options->abbrev = MINIMUM_ABBREV; - else if (40 < options->abbrev) - options->abbrev = 40; + else if (the_hash_algo->hexsz < options->abbrev) + options->abbrev = the_hash_algo->hexsz; } else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) { options->a_prefix = optarg; @@ -4063,11 +5154,11 @@ int diff_opt_parse(struct diff_options *options, &options->interhunkcontext)) ; else if (!strcmp(arg, "-W")) - DIFF_OPT_SET(options, FUNCCONTEXT); + options->flags.funccontext = 1; else if (!strcmp(arg, "--function-context")) - DIFF_OPT_SET(options, FUNCCONTEXT); + options->flags.funccontext = 1; else if (!strcmp(arg, "--no-function-context")) - DIFF_OPT_CLR(options, FUNCCONTEXT); + options->flags.funccontext = 0; else if ((argcount = parse_long_opt("output", av, &optarg))) { char *path = prefix_filename(prefix, optarg); options->file = xfopen(path, "w"); @@ -4190,14 +5281,20 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len) int abblen; const char *abbrev; - if (len == GIT_SHA1_HEXSZ) + /* Do we want all 40 hex characters? */ + if (len == the_hash_algo->hexsz) return oid_to_hex(oid); + /* An abbreviated value is fine, possibly followed by an ellipsis. */ abbrev = diff_abbrev_oid(oid, len); + + if (!print_sha1_ellipsis()) + return abbrev; + abblen = strlen(abbrev); /* - * In well-behaved cases, where the abbbreviated result is the + * In well-behaved cases, where the abbreviated 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 @@ -4215,7 +5312,7 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len) * the automatic sizing is supposed to give abblen that ensures * uniqueness across all objects (statistically speaking). */ - if (abblen < GIT_SHA1_HEXSZ - 3) { + if (abblen < the_hash_algo->hexsz - 3) { static char hex[GIT_MAX_HEXSZ + 1]; if (len < abblen && abblen <= len + 2) xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, ".."); @@ -4287,7 +5384,7 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->oid_valid && two->oid_valid && - !oidcmp(&one->oid, &two->oid) && + oideq(&one->oid, &two->oid) && !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ if (!one->oid_valid && !two->oid_valid) @@ -4421,7 +5518,7 @@ static void diff_resolve_rename_copy(void) else p->status = DIFF_STATUS_RENAMED; } - else if (oidcmp(&p->one->oid, &p->two->oid) || + else if (!oideq(&p->one->oid, &p->two->oid) || p->one->mode != p->two->mode || p->one->dirty_submodule || p->two->dirty_submodule || @@ -4469,67 +5566,81 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) } } -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) { - char *names = pprint_rename(p->one->path, p->two->path); - - fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); - free(names); - show_mode_change(file, p, 0, line_prefix); + struct strbuf sb = STRBUF_INIT; + struct strbuf names = STRBUF_INIT; + + pprint_rename(&names, p->one->path, p->two->path); + strbuf_addf(&sb, " %s %s (%d%%)\n", + renamecopy, names.buf, similarity_index(p)); + strbuf_release(&names); + 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; - const char *line_prefix = diff_line_prefix(opt); - 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; } } @@ -4557,10 +5668,6 @@ static void patch_id_consume(void *priv, char *line, unsigned long len) struct patch_id_t *data = priv; int new_len; - /* Ignore line numbers when computing the SHA1 of the patch */ - if (starts_with(line, "@@ -")) - return; - new_len = remove_space(line, len); git_SHA1_Update(data->ctx, line, new_len); @@ -4613,8 +5720,8 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid if (DIFF_PAIR_UNMERGED(p)) continue; - diff_fill_oid_info(p->one); - diff_fill_oid_info(p->two); + diff_fill_oid_info(p->one, options->repo->index); + diff_fill_oid_info(p->two, options->repo->index); len1 = remove_space(p->one->path, strlen(p->one->path)); len2 = remove_space(p->two->path, strlen(p->two->path)); @@ -4646,12 +5753,12 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid if (diff_header_only) continue; - if (fill_mmfile(&mf1, p->one) < 0 || - fill_mmfile(&mf2, p->two) < 0) + if (fill_mmfile(options->repo, &mf1, p->one) < 0 || + fill_mmfile(options->repo, &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)) { + if (diff_filespec_is_binary(options->repo, p->one) || + diff_filespec_is_binary(options->repo, p->two)) { git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid), GIT_SHA1_HEXSZ); git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid), @@ -4662,8 +5769,8 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = 0; - if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg)) + if (xdi_diff_outf(&mf1, &mf2, discard_hunk_line, + patch_id_consume, &data, &xpp, &xecfg)) return error("unable to generate patch-id diff for %s", p->one->path); } @@ -4724,16 +5831,64 @@ N_("you may want to set your %s variable to at least " void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) { + fflush(stdout); if (degraded_cc) warning(_(degrade_cc_to_c_warning)); else if (needed) warning(_(rename_limit_warning)); else return; - if (0 < needed && needed < 32767) + if (0 < needed) 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) + 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; + + if (o->color_moved_ws_handling & + COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) + o->color_moved_ws_handling |= XDF_IGNORE_WHITESPACE; + + hashmap_init(&del_lines, moved_entry_cmp, o, 0); + hashmap_init(&add_lines, 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, 1); + hashmap_free(&del_lines, 1); + } + + 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; @@ -4760,7 +5915,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE)) + if (output_format & DIFF_FORMAT_DIRSTAT && options->flags.dirstat_by_line) dirstat_by_line = 1; if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) || @@ -4795,8 +5950,8 @@ void diff_flush(struct diff_options *options) } if (output_format & DIFF_FORMAT_NO_OUTPUT && - DIFF_OPT_TST(options, EXIT_WITH_STATUS) && - DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + options->flags.exit_with_status && + options->flags.diff_from_contents) { /* * run diff_flush_patch for the exit status. setting * options->file to /dev/null should be safe, because we @@ -4806,6 +5961,7 @@ void diff_flush(struct diff_options *options) fclose(options->file); 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)) @@ -4817,20 +5973,14 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { - fprintf(options->file, "%s%c", - diff_line_prefix(options), - options->line_termination); - 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) @@ -4849,11 +5999,11 @@ free_queue: * diff_addremove/diff_change does not set the bit when * DIFF_FROM_CONTENTS is in effect (e.g. with -w). */ - if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + if (options->flags.diff_from_contents) { if (options->found_changes) - DIFF_OPT_SET(options, HAS_CHANGES); + options->flags.has_changes = 1; else - DIFF_OPT_CLR(options, HAS_CHANGES); + options->flags.has_changes = 0; } } @@ -4911,19 +6061,21 @@ static void diffcore_apply_filter(struct diff_options *options) } /* Check whether two filespecs with the same mode and size are identical */ -static int diff_filespec_is_identical(struct diff_filespec *one, +static int diff_filespec_is_identical(struct repository *r, + struct diff_filespec *one, struct diff_filespec *two) { if (S_ISGITLINK(one->mode)) return 0; - if (diff_populate_filespec(one, 0)) + if (diff_populate_filespec(r, one, 0)) return 0; - if (diff_populate_filespec(two, 0)) + if (diff_populate_filespec(r, two, 0)) return 0; return !memcmp(one->data, two->data, one->size); } -static int diff_filespec_check_stat_unmatch(struct diff_filepair *p) +static int diff_filespec_check_stat_unmatch(struct repository *r, + struct diff_filepair *p) { if (p->done_skip_stat_unmatch) return p->skip_stat_unmatch_result; @@ -4947,10 +6099,10 @@ static int diff_filespec_check_stat_unmatch(struct diff_filepair *p) !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) || + diff_populate_filespec(r, p->one, CHECK_SIZE_ONLY) || + diff_populate_filespec(r, p->two, CHECK_SIZE_ONLY) || (p->one->size != p->two->size) || - !diff_filespec_is_identical(p->one, p->two)) /* (2) */ + !diff_filespec_is_identical(r, p->one, p->two)) /* (2) */ p->skip_stat_unmatch_result = 1; return p->skip_stat_unmatch_result; } @@ -4965,7 +6117,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]; - if (diff_filespec_check_stat_unmatch(p)) + if (diff_filespec_check_stat_unmatch(diffopt->repo, p)) diff_q(&outq, p); else { /* @@ -4973,7 +6125,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) * to determine how many paths were dirty only * due to stat info mismatch. */ - if (!DIFF_OPT_TST(diffopt, NO_INDEX)) + if (!diffopt->flags.no_index) diffopt->skip_stat_unmatch++; diff_free_filepair(p); } @@ -5007,13 +6159,14 @@ void diffcore_std(struct diff_options *options) if (!options->found_follow) { /* See try_to_follow_renames() in tree-diff.c */ if (options->break_opt != -1) - diffcore_break(options->break_opt); + diffcore_break(options->repo, + options->break_opt); if (options->detect_rename) diffcore_rename(options); if (options->break_opt != -1) diffcore_merge_broken(); } - if (options->pickaxe) + if (options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) diffcore_pickaxe(options); if (options->orderfile) diffcore_order(options->orderfile); @@ -5022,10 +6175,10 @@ void diffcore_std(struct diff_options *options) diff_resolve_rename_copy(); diffcore_apply_filter(options); - if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) - DIFF_OPT_SET(options, HAS_CHANGES); + if (diff_queued_diff.nr && !options->flags.diff_from_contents) + options->flags.has_changes = 1; else - DIFF_OPT_CLR(options, HAS_CHANGES); + options->flags.has_changes = 0; options->found_follow = 0; } @@ -5037,23 +6190,23 @@ int diff_result_code(struct diff_options *opt, int status) diff_warn_rename_limit("diff.renameLimit", opt->needed_rename_limit, opt->degraded_cc_to_c); - if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && + if (!opt->flags.exit_with_status && !(opt->output_format & DIFF_FORMAT_CHECKDIFF)) return status; - if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && - DIFF_OPT_TST(opt, HAS_CHANGES)) + if (opt->flags.exit_with_status && + opt->flags.has_changes) result |= 01; if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) && - DIFF_OPT_TST(opt, CHECK_FAILED)) + opt->flags.check_failed) result |= 02; return result; } int diff_can_quit_early(struct diff_options *opt) { - return (DIFF_OPT_TST(opt, QUICK) && + return (opt->flags.quick && !opt->filter && - DIFF_OPT_TST(opt, HAS_CHANGES)); + opt->flags.has_changes); } /* @@ -5065,10 +6218,10 @@ int diff_can_quit_early(struct diff_options *opt) static int is_submodule_ignored(const char *path, struct diff_options *options) { int ignored = 0; - unsigned orig_flags = options->flags; - if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG)) + struct diff_flags orig_flags = options->flags; + if (!options->flags.override_submodule_config) set_diffopt_flags_from_submodule_config(options, path); - if (DIFF_OPT_TST(options, IGNORE_SUBMODULES)) + if (options->flags.ignore_submodules) ignored = 1; options->flags = orig_flags; return ignored; @@ -5097,7 +6250,7 @@ void diff_addremove(struct diff_options *options, * Before the final output happens, they are pruned after * merged into rename/copy pairs as appropriate. */ - if (DIFF_OPT_TST(options, REVERSE_DIFF)) + if (options->flags.reverse_diff) addremove = (addremove == '+' ? '-' : addremove == '-' ? '+' : addremove); @@ -5116,8 +6269,8 @@ void diff_addremove(struct diff_options *options, } diff_queue(&diff_queued_diff, one, two); - if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) - DIFF_OPT_SET(options, HAS_CHANGES); + if (!options->flags.diff_from_contents) + options->flags.has_changes = 1; } void diff_change(struct diff_options *options, @@ -5135,7 +6288,7 @@ void diff_change(struct diff_options *options, is_submodule_ignored(concatpath, options)) return; - if (DIFF_OPT_TST(options, REVERSE_DIFF)) { + if (options->flags.reverse_diff) { SWAP(old_mode, new_mode); SWAP(old_oid, new_oid); SWAP(old_oid_valid, new_oid_valid); @@ -5154,14 +6307,14 @@ void diff_change(struct diff_options *options, two->dirty_submodule = new_dirty_submodule; p = diff_queue(&diff_queued_diff, one, two); - if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) + if (options->flags.diff_from_contents) return; - if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch && - !diff_filespec_check_stat_unmatch(p)) + if (options->flags.quick && options->skip_stat_unmatch && + !diff_filespec_check_stat_unmatch(options->repo, p)) return; - DIFF_OPT_SET(options, HAS_CHANGES); + options->flags.has_changes = 1; } struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) @@ -5180,8 +6333,10 @@ struct diff_filepair *diff_unmerge(struct diff_options *options, const char *pat return pair; } -static char *run_textconv(const char *pgm, struct diff_filespec *spec, - size_t *outsize) +static char *run_textconv(struct repository *r, + const char *pgm, + struct diff_filespec *spec, + size_t *outsize) { struct diff_tempfile *temp; const char *argv[3]; @@ -5190,7 +6345,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, struct strbuf buf = STRBUF_INIT; int err = 0; - temp = prepare_temp_file(spec->path, spec); + temp = prepare_temp_file(r, spec->path, spec); *arg++ = pgm; *arg++ = temp->name; *arg = NULL; @@ -5217,7 +6372,8 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } -size_t fill_textconv(struct userdiff_driver *driver, +size_t fill_textconv(struct repository *r, + struct userdiff_driver *driver, struct diff_filespec *df, char **outbuf) { @@ -5228,14 +6384,14 @@ size_t fill_textconv(struct userdiff_driver *driver, *outbuf = ""; return 0; } - if (diff_populate_filespec(df, 0)) + if (diff_populate_filespec(r, df, 0)) die("unable to read files to diff"); *outbuf = df->data; return df->size; } if (!driver->textconv) - die("BUG: fill_textconv called with non-textconv driver"); + BUG("fill_textconv called with non-textconv driver"); if (driver->textconv_cache && df->oid_valid) { *outbuf = notes_cache_get(driver->textconv_cache, @@ -5245,7 +6401,7 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } - *outbuf = run_textconv(driver->textconv, df, &size); + *outbuf = run_textconv(r, driver->textconv, df, &size); if (!*outbuf) die("unable to read files to diff"); @@ -5265,7 +6421,8 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } -int textconv_object(const char *path, +int textconv_object(struct repository *r, + const char *path, unsigned mode, const struct object_id *oid, int oid_valid, @@ -5277,13 +6434,13 @@ int textconv_object(const char *path, df = alloc_filespec(path); fill_filespec(df, oid, oid_valid, mode); - textconv = get_textconv(df); + textconv = get_textconv(r, df); if (!textconv) { free_filespec(df); return 0; } - *buf_size = fill_textconv(textconv, df, buf); + *buf_size = fill_textconv(r, textconv, df, buf); free_filespec(df); return 1; } @@ -5299,7 +6456,7 @@ void setup_diff_pager(struct diff_options *opt) * 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) && + if (!opt->flags.exit_with_status && check_pager_config("diff") != 0) setup_pager(); } |