diff options
author | Junio C Hamano <gitster@pobox.com> | 2017-08-26 22:55:03 -0700 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2017-08-26 22:55:03 -0700 |
commit | b6c4058f978433c51581df87283309b611bde87b (patch) | |
tree | a15051b3c035c9e36ef0e731079c78ccbfab5afd | |
parent | 3dc57ebfbd1bf30b9a6987f551af9b74641382a9 (diff) | |
parent | 61e89eaae8824926dc03c47e6ab3aa990b5b832e (diff) | |
download | git-b6c4058f978433c51581df87283309b611bde87b.tar.gz |
Merge branch 'sb/diff-color-move'
"git diff" has been taught to optionally paint new lines that are
the same as deleted lines elsewhere differently from genuinely new
lines.
* sb/diff-color-move: (25 commits)
diff: document the new --color-moved setting
diff.c: add dimming to moved line detection
diff.c: color moved lines differently, plain mode
diff.c: color moved lines differently
diff.c: buffer all output if asked to
diff.c: emit_diff_symbol learns about DIFF_SYMBOL_SUMMARY
diff.c: emit_diff_symbol learns about DIFF_SYMBOL_STAT_SEP
diff.c: convert word diffing to use emit_diff_symbol
diff.c: convert show_stats to use emit_diff_symbol
diff.c: convert emit_binary_diff_body to use emit_diff_symbol
submodule.c: migrate diff output to use emit_diff_symbol
diff.c: emit_diff_symbol learns DIFF_SYMBOL_REWRITE_DIFF
diff.c: emit_diff_symbol learns about DIFF_SYMBOL_BINARY_FILES
diff.c: emit_diff_symbol learns DIFF_SYMBOL_HEADER
diff.c: emit_diff_symbol learns DIFF_SYMBOL_FILEPAIR_{PLUS, MINUS}
diff.c: emit_diff_symbol learns DIFF_SYMBOL_CONTEXT_INCOMPLETE
diff.c: emit_diff_symbol learns DIFF_SYMBOL_WORDS[_PORCELAIN]
diff.c: migrate emit_line_checked to use emit_diff_symbol
diff.c: emit_diff_symbol learns DIFF_SYMBOL_NO_LF_EOF
diff.c: emit_diff_symbol learns DIFF_SYMBOL_CONTEXT_FRAGINFO
...
-rw-r--r-- | Documentation/config.txt | 15 | ||||
-rw-r--r-- | Documentation/diff-options.txt | 36 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rw-r--r-- | color.h | 2 | ||||
-rw-r--r-- | diff.c | 1309 | ||||
-rw-r--r-- | diff.h | 39 | ||||
-rw-r--r-- | submodule.c | 84 | ||||
-rw-r--r-- | submodule.h | 13 | ||||
-rwxr-xr-x | t/t4015-diff-whitespace.sh | 434 |
9 files changed, 1618 insertions, 316 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 2271809d90..602c6bef68 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1077,14 +1077,25 @@ This does not affect linkgit:git-format-patch[1] or the 'git-diff-{asterisk}' plumbing commands. Can be overridden on the command line with the `--color[=<when>]` option. +diff.colorMoved:: + If set to either a valid `<mode>` or a true value, moved lines + in a diff are colored differently, for details of valid modes + see '--color-moved' in linkgit:git-diff[1]. If simply set to + true the default color mode will be used. When set to false, + moved lines are not colored. + color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies which part of the patch to use the specified color, and is one of `context` (context text - `plain` is a historical synonym), `meta` (metainformation), `frag` (hunk header), 'func' (function in hunk header), `old` (removed lines), - `new` (added lines), `commit` (commit headers), or `whitespace` - (highlighting whitespace errors). + `new` (added lines), `commit` (commit headers), `whitespace` + (highlighting whitespace errors), `oldMoved` (deleted lines), + `newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`, + `oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative` + and `newMovedAlternativeDimmed` (See the '<mode>' + setting of '--color-moved' in linkgit:git-diff[1] for details). color.decorate.<slot>:: Use customized color for 'git log --decorate' output. `<slot>` is one diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 56dedafcd4..b1ab96477b 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -231,6 +231,42 @@ ifdef::git-diff[] endif::git-diff[] It is the same as `--color=never`. +--color-moved[=<mode>]:: + Moved lines of code are colored differently. +ifdef::git-diff[] + It can be changed by the `diff.colorMoved` configuration setting. +endif::git-diff[] + The <mode> defaults to 'no' if the option is not given + and to 'zebra' if the option with no mode is given. + The mode must be one of: ++ +-- +no:: + Moved lines are not highlighted. +default:: + Is a synonym for `zebra`. This may change to a more sensible mode + in the future. +plain:: + Any line that is added in one location and was removed + in another location will be colored with 'color.diff.newMoved'. + Similarly 'color.diff.oldMoved' will be used for removed lines + that are added somewhere else in the diff. This mode picks up any + moved line, but it is not very useful in a review to determine + if a block of code was moved without permutation. +zebra:: + Blocks of moved code are detected greedily. The detected blocks are + painted using either the 'color.diff.{old,new}Moved' color or + 'color.diff.{old,new}MovedAlternative'. The change between + the two colors indicates that a new block was detected. If there + are fewer than 3 adjacent moved lines, they are not marked up + as moved, but the regular colors 'color.diff.{old,new}' will be + used. +dimmed_zebra:: + Similar to 'zebra', but additional dimming of uninteresting parts + of moved code is performed. The bordering lines of two adjacent + blocks are considered interesting, the rest is uninteresting. +-- + --word-diff[=<mode>]:: Show a word diff, using the <mode> to delimit changed words. By default, words are delimited by whitespace; see @@ -1955,6 +1955,8 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob #define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8) #define WS_TAB_WIDTH_MASK 077 +/* All WS_* -- when extended, adapt diff.c emit_symbol */ +#define WS_RULE_MASK 07777 extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); @@ -42,6 +42,8 @@ struct strbuf; #define GIT_COLOR_BG_BLUE "\033[44m" #define GIT_COLOR_BG_MAGENTA "\033[45m" #define GIT_COLOR_BG_CYAN "\033[46m" +#define GIT_COLOR_FAINT "\033[2m" +#define GIT_COLOR_FAINT_ITALIC "\033[2;3m" /* A special value meaning "no color selected" */ #define GIT_COLOR_NIL "NIL" @@ -16,6 +16,7 @@ #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" @@ -32,6 +33,7 @@ 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_context_default = 3; static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; @@ -56,6 +58,14 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_YELLOW, /* COMMIT */ GIT_COLOR_BG_RED, /* WHITESPACE */ GIT_COLOR_NORMAL, /* FUNCINFO */ + GIT_COLOR_BOLD_MAGENTA, /* OLD_MOVED */ + GIT_COLOR_BOLD_BLUE, /* OLD_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* OLD_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* OLD_MOVED_ALTERNATIVE_DIM */ + GIT_COLOR_BOLD_CYAN, /* NEW_MOVED */ + GIT_COLOR_BOLD_YELLOW, /* NEW_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* NEW_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */ }; static NORETURN void die_want_option(const char *option_name) @@ -81,6 +91,22 @@ static int parse_diff_color_slot(const char *var) return DIFF_WHITESPACE; if (!strcasecmp(var, "func")) return DIFF_FUNCINFO; + if (!strcasecmp(var, "oldmoved")) + return DIFF_FILE_OLD_MOVED; + if (!strcasecmp(var, "oldmovedalternative")) + return DIFF_FILE_OLD_MOVED_ALT; + if (!strcasecmp(var, "oldmoveddimmed")) + return DIFF_FILE_OLD_MOVED_DIM; + if (!strcasecmp(var, "oldmovedalternativedimmed")) + return DIFF_FILE_OLD_MOVED_ALT_DIM; + if (!strcasecmp(var, "newmoved")) + return DIFF_FILE_NEW_MOVED; + if (!strcasecmp(var, "newmovedalternative")) + return DIFF_FILE_NEW_MOVED_ALT; + if (!strcasecmp(var, "newmoveddimmed")) + return DIFF_FILE_NEW_MOVED_DIM; + if (!strcasecmp(var, "newmovedalternativedimmed")) + return DIFF_FILE_NEW_MOVED_ALT_DIM; return -1; } @@ -229,12 +255,44 @@ 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, "zebra")) + return COLOR_MOVED_ZEBRA; + else if (!strcmp(arg, "default")) + return COLOR_MOVED_DEFAULT; + else if (!strcmp(arg, "dimmed_zebra")) + return COLOR_MOVED_ZEBRA_DIM; + else + return error(_("color moved setting must be one of 'no', 'default', 'zebra', 'dimmed_zebra', 'plain'")); +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff_use_color_default = git_config_colorbool(var, value); return 0; } + if (!strcmp(var, "diff.colormoved")) { + int cm = parse_color_moved(value); + if (cm < 0) + return -1; + diff_color_moved_default = cm; + return 0; + } if (!strcmp(var, "diff.context")) { diff_context_default = git_config_int(var, value); if (diff_context_default < 0) @@ -554,68 +612,704 @@ static void emit_line(struct diff_options *o, const char *set, const char *reset emit_line_0(o, set, reset, line[0], line+1, len-1); } -static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int 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) { - 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); + 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 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) +struct moved_entry { + struct hashmap_entry ent; + const struct emitted_diff_symbol *es; + struct moved_entry *next_line; +}; + +static int next_byte(const char **cp, const char **endp, + const struct diff_options *diffopt) +{ + int retval; + + if (*cp > *endp) + return -1; + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* + * After skipping a couple of whitespaces, we still have to + * account for one space. + */ + return (int)' '; + } + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* return the first non-ws character via the usual below */ + } + + retval = (unsigned char)(**cp); + (*cp)++; + return retval; +} + +static int moved_entry_cmp(const struct diff_options *diffopt, + const struct moved_entry *a, + const struct moved_entry *b, + const void *keydata) +{ + const char *ap = a->es->line, *ae = a->es->line + a->es->len; + const char *bp = b->es->line, *be = b->es->line + b->es->len; + + if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS)) + return a->es->len != b->es->len || memcmp(ap, bp, a->es->len); + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) { + while (ae > ap && isspace(*ae)) + ae--; + while (be > bp && isspace(*be)) + be--; + } + + while (1) { + int ca, cb; + ca = next_byte(&ap, &ae, diffopt); + cb = next_byte(&bp, &be, diffopt); + if (ca != cb) + return 1; + if (ca < 0) + return 0; + } +} + +static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o) +{ + if (o->xdl_opts & XDF_WHITESPACE_FLAGS) { + static struct strbuf sb = STRBUF_INIT; + const char *ap = es->line, *ae = es->line + es->len; + int c; + + strbuf_reset(&sb); + while (ae > ap && isspace(*ae)) + ae--; + while ((c = next_byte(&ap, &ae, o)) > 0) + strbuf_addch(&sb, c); + + return memhash(sb.buf, sb.len); + } else { + return memhash(es->line, es->len); + } +} + +static struct moved_entry *prepare_entry(struct diff_options *o, + int line_no) +{ + struct moved_entry *ret = xmalloc(sizeof(*ret)); + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no]; + + ret->ent.hash = get_string_hash(l, o); + ret->es = l; + ret->next_line = NULL; + + return ret; +} + +static void add_lines_to_move_detection(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry *prev_line = NULL; + + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm; + struct moved_entry *key; + + switch (o->emitted_symbols->buf[n].s) { + case DIFF_SYMBOL_PLUS: + hm = add_lines; + break; + case DIFF_SYMBOL_MINUS: + hm = del_lines; + break; + default: + prev_line = NULL; + continue; + } + + key = prepare_entry(o, n); + if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s) + prev_line->next_line = key; + + hashmap_add(hm, key); + prev_line = key; + } +} + +static int shrink_potential_moved_blocks(struct moved_entry **pmb, + int pmb_nr) +{ + int lp, rp; + + /* Shrink the set of potential block to the remaining running */ + for (lp = 0, rp = pmb_nr - 1; lp <= rp;) { + while (lp < pmb_nr && pmb[lp]) + lp++; + /* lp points at the first NULL now */ + + while (rp > -1 && !pmb[rp]) + rp--; + /* rp points at the last non-NULL */ + + if (lp < pmb_nr && rp > -1 && lp < rp) { + pmb[lp] = pmb[rp]; + pmb[rp] = NULL; + rp--; + lp++; + } + } + + /* Remember the number of running sets */ + return rp + 1; +} + +/* Find blocks of moved code, delegate actual coloring decision to helper */ +static void mark_color_as_moved(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry **pmb = NULL; /* potentially moved blocks */ + int pmb_nr = 0, pmb_alloc = 0; + int n, flipped_block = 1, block_length = 0; + + + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm = NULL; + struct moved_entry *key; + struct moved_entry *match = NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + int i; + + switch (l->s) { + case DIFF_SYMBOL_PLUS: + hm = del_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + case DIFF_SYMBOL_MINUS: + hm = add_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + default: + flipped_block = 1; + } + + if (!match) { + if (block_length < COLOR_MOVED_MIN_BLOCK_LENGTH && + o->color_moved != COLOR_MOVED_PLAIN) { + for (i = 0; i < block_length + 1; i++) { + l = &o->emitted_symbols->buf[n - i]; + l->flags &= ~DIFF_SYMBOL_MOVED_LINE; + } + } + pmb_nr = 0; + block_length = 0; + continue; + } + + l->flags |= DIFF_SYMBOL_MOVED_LINE; + block_length++; + + if (o->color_moved == COLOR_MOVED_PLAIN) + continue; + + /* Check any potential block runs, advance each or nullify */ + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *p = pmb[i]; + struct moved_entry *pnext = (p && p->next_line) ? + p->next_line : NULL; + if (pnext && !hm->cmpfn(o, pnext, match, NULL)) { + pmb[i] = p->next_line; + } else { + pmb[i] = NULL; + } + } + + pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr); + + if (pmb_nr == 0) { + /* + * The current line is the start of a new block. + * Setup the set of potential blocks. + */ + for (; match; match = hashmap_get_next(hm, match)) { + ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc); + pmb[pmb_nr++] = match; + } + + flipped_block = (flipped_block + 1) % 2; + } + + if (flipped_block) + l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; + } + + free(pmb); +} + +#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \ + (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT) +static void dim_moved_lines(struct diff_options *o) +{ + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct emitted_diff_symbol *prev = (n != 0) ? + &o->emitted_symbols->buf[n - 1] : NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + struct emitted_diff_symbol *next = + (n < o->emitted_symbols->nr - 1) ? + &o->emitted_symbols->buf[n + 1] : NULL; + + /* Not a plus or minus line? */ + if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS) + continue; + + /* Not a moved line? */ + if (!(l->flags & DIFF_SYMBOL_MOVED_LINE)) + continue; + + /* + * If prev or next are not a plus or minus line, + * pretend they don't exist + */ + if (prev && prev->s != DIFF_SYMBOL_PLUS && + prev->s != DIFF_SYMBOL_MINUS) + prev = NULL; + if (next && next->s != DIFF_SYMBOL_PLUS && + next->s != DIFF_SYMBOL_MINUS) + next = NULL; + + /* Inside a block? */ + if ((prev && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) && + (next && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) { + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + continue; + } + + /* Check if we are at an interesting bound: */ + if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + + /* + * The boundary to prev and next are not interesting, + * so this line is not interesting as a whole + */ + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + } +} + +static void emit_line_ws_markup(struct diff_options *o, + const char *set, const char *reset, + const char *line, int len, char sign, + unsigned ws_rule, int blank_at_eof) { - const char *set = diff_get_color(ecbdata->color_diff, color); const char *ws = NULL; - 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)) + emit_line_0(o, set, 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, 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, reset, sign, "", 0); + ws_check_emit(line, len, ws_rule, + o->file, set, reset, ws); } } +static void emit_diff_symbol_from_struct(struct diff_options *o, + struct emitted_diff_symbol *eds) +{ + static const char *nneof = " No newline at end of file\n"; + const char *context, *reset, *set, *meta, *fraginfo; + struct strbuf sb = STRBUF_INIT; + + enum diff_symbol s = eds->s; + const char *line = eds->line; + int len = eds->len; + unsigned flags = eds->flags; + + switch (s) { + case DIFF_SYMBOL_NO_LF_EOF: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + putc('\n', o->file); + emit_line_0(o, context, reset, '\\', + nneof, strlen(nneof)); + break; + case DIFF_SYMBOL_SUBMODULE_HEADER: + case DIFF_SYMBOL_SUBMODULE_ERROR: + case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH: + case DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES: + case DIFF_SYMBOL_SUMMARY: + case DIFF_SYMBOL_STATS_LINE: + case DIFF_SYMBOL_BINARY_DIFF_BODY: + case DIFF_SYMBOL_CONTEXT_FRAGINFO: + emit_line(o, "", "", line, len); + break; + case DIFF_SYMBOL_CONTEXT_INCOMPLETE: + case DIFF_SYMBOL_CONTEXT_MARKER: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_SEPARATOR: + fprintf(o->file, "%s%c", + diff_line_prefix(o), + o->line_termination); + break; + case DIFF_SYMBOL_CONTEXT: + set = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, ' ', + flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0); + break; + case DIFF_SYMBOL_PLUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '+', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, + flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF); + break; + case DIFF_SYMBOL_MINUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '-', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0); + break; + case DIFF_SYMBOL_WORDS_PORCELAIN: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + fputs("~\n", o->file); + break; + case DIFF_SYMBOL_WORDS: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + /* + * Skip the prefix character, if any. With + * diff_suppress_blank_empty, there may be + * none. + */ + if (line[0] != '\n') { + line++; + len--; + } + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_FILEPAIR_PLUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_FILEPAIR_MINUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s--- %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_BINARY_FILES: + case DIFF_SYMBOL_HEADER: + fprintf(o->file, "%s", line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER: + fprintf(o->file, "%sGIT binary patch\n", diff_line_prefix(o)); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA: + fprintf(o->file, "%sdelta %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL: + fprintf(o->file, "%sliteral %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_FOOTER: + fputs(diff_line_prefix(o), o->file); + fputc('\n', o->file); + break; + case DIFF_SYMBOL_REWRITE_DIFF: + fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, fraginfo, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_ADD: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_DEL: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_UNTRACKED: + fprintf(o->file, "%sSubmodule %s contains untracked content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_SUBMODULE_MODIFIED: + fprintf(o->file, "%sSubmodule %s contains modified content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_STATS_SUMMARY_NO_FILES: + emit_line(o, "", "", " 0 files changed\n", + strlen(" 0 files changed\n")); + break; + case DIFF_SYMBOL_STATS_SUMMARY_ABBREV: + emit_line(o, "", "", " ...\n", strlen(" ...\n")); + break; + case DIFF_SYMBOL_WORD_DIFF: + fprintf(o->file, "%.*s", len, line); + break; + case DIFF_SYMBOL_STAT_SEP: + fputs(o->stat_sep, o->file); + break; + default: + die("BUG: unknown diff symbol"); + } + strbuf_release(&sb); +} + +static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s, + const char *line, int len, unsigned flags) +{ + struct emitted_diff_symbol e = {line, len, flags, s}; + + if (o->emitted_symbols) + append_emitted_diff_symbol(o, &e); + else + emit_diff_symbol_from_struct(o, &e); +} + +void diff_emit_submodule_del(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_DEL, line, strlen(line), 0); +} + +void diff_emit_submodule_add(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ADD, line, strlen(line), 0); +} + +void diff_emit_submodule_untracked(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_UNTRACKED, + path, strlen(path), 0); +} + +void diff_emit_submodule_modified(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_MODIFIED, + path, strlen(path), 0); +} + +void diff_emit_submodule_header(struct diff_options *o, const char *header) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_HEADER, + header, strlen(header), 0); +} + +void diff_emit_submodule_error(struct diff_options *o, const char *err) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ERROR, err, strlen(err), 0); +} + +void diff_emit_submodule_pipethrough(struct diff_options *o, + const char *line, int len) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, line, len, 0); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + 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, @@ -638,7 +1332,8 @@ 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 @@ */ @@ -672,7 +1367,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); } @@ -694,17 +1391,17 @@ static void remove_tempfile(void) } } -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; } } @@ -713,7 +1410,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) { @@ -731,13 +1427,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, @@ -749,16 +1440,12 @@ 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)) { a_prefix = o->b_prefix; @@ -770,8 +1457,6 @@ static void emit_rewrite_diff(const char *name_a, name_a += (*name_a == '/'); name_b += (*name_b == '/'); - name_a_tab = strchr(name_a, ' ') ? "\t" : ""; - name_b_tab = strchr(name_b, ' ') ? "\t" : ""; strbuf_reset(&a_name); strbuf_reset(&b_name); @@ -798,18 +1483,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) @@ -869,37 +1559,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; } @@ -981,24 +1683,20 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) fputs(line_prefix, diff_words->opt->file); } if (diff_words->current_plus != plus_begin) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->ctx, style->newline, plus_begin - diff_words->current_plus, - diff_words->current_plus, line_prefix); - if (*(plus_begin - 1) == '\n') - fputs(line_prefix, diff_words->opt->file); + diff_words->current_plus); } if (minus_begin != minus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, - minus_end - minus_begin, minus_begin, - line_prefix); + minus_end - minus_begin, minus_begin); } if (plus_begin != plus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->new, style->newline, - plus_end - plus_begin, plus_begin, - line_prefix); + plus_end - plus_begin, plus_begin); } diff_words->current_plus = plus_end; @@ -1092,11 +1790,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, + emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF, + line_prefix, strlen(line_prefix), 0); + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, diff_words->minus.text.size, - diff_words->minus.text.ptr, line_prefix); + diff_words->minus.text.ptr); diff_words->minus.text.size = 0; return; } @@ -1119,12 +1818,12 @@ static void diff_words_show(struct diff_words_data *diff_words) 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; } @@ -1132,9 +1831,29 @@ 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) @@ -1170,6 +1889,11 @@ 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); if (!o->word_regex) @@ -1204,6 +1928,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); @@ -1268,30 +1993,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; } @@ -1307,12 +2027,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); @@ -1332,21 +2053,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; } @@ -1367,8 +2074,8 @@ 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; } } @@ -1513,20 +2220,14 @@ 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) @@ -1550,14 +2251,16 @@ static void fill_print_name(struct diffstat_file *file) file->print_name = pname; } -int print_stat_summary(FILE *fp, int files, int insertions, int deletions) +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, @@ -1584,9 +2287,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) @@ -1596,13 +2309,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); @@ -1756,26 +2469,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; } @@ -1798,14 +2517,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++) { @@ -1826,11 +2547,13 @@ 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); } static void show_shortstats(struct diffstat_t *data, struct diff_options *options) @@ -1842,7 +2565,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))) { @@ -1852,8 +2575,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) @@ -2217,8 +2939,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; @@ -2247,13 +2969,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("%lu", orig_size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA, + s, strlen(s), 0); + free(s); free(deflated); data = delta; data_size = delta_size; - } - else { - fprintf(file, "%sliteral %lu\n", prefix, two->size); + } else { + char *s = xstrfmt("%lu", two->size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL, + s, strlen(s), 0); + free(s); free(delta); data = deflated; data_size = deflate_size; @@ -2262,8 +2989,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; @@ -2271,20 +2999,24 @@ 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) @@ -2361,24 +3093,16 @@ 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; } @@ -2427,7 +3151,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); @@ -2437,23 +3162,31 @@ static void builtin_diff(const char *name_a, } 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)) )) { + struct strbuf sb = STRBUF_INIT; if (!one->data && !two->data && S_ISREG(one->mode) && S_ISREG(two->mode) && !DIFF_OPT_TST(o, BINARY)) { if (!oidcmp(&one->oid, &two->oid)) { if (must_show_header) - 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) @@ -2462,16 +3195,21 @@ static void builtin_diff(const char *name_a, if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { if (must_show_header) - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); goto free_ab_and_return; } - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(o->file, &mf1, &mf2, line_prefix); - else - fprintf(o->file, "%sBinary files %s and %s differ\n", - line_prefix, lbl[0], lbl[1]); + emit_binary_diff(o, &mf1, &mf2); + else { + strbuf_addf(&sb, "%sBinary files %s and %s differ\n", + diff_line_prefix(o), lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES, + sb.buf, sb.len, 0); + strbuf_release(&sb); + } o->found_changes = 1; } else { /* Crazy xdl interfaces.. */ @@ -2483,7 +3221,8 @@ 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); } @@ -3274,8 +4013,8 @@ 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); @@ -3398,6 +4137,8 @@ void diff_setup(struct diff_options *options) options->a_prefix = "a/"; options->b_prefix = "b/"; } + + options->color_moved = diff_color_moved_default; } void diff_setup_done(struct diff_options *options) @@ -3507,6 +4248,9 @@ void diff_setup_done(struct diff_options *options) if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1) die(_("--follow requires exactly one pathspec")); + + if (!options->use_color || external_diff()) + options->color_moved = 0; } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -3931,7 +4675,19 @@ 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 (!strcmp(arg, "--color-words")) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } @@ -4461,67 +5217,76 @@ 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); } + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); } } -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p, - const char *line_prefix) +static void show_rename_copy(struct diff_options *opt, const char *renamecopy, + struct diff_filepair *p) { + struct strbuf sb = STRBUF_INIT; char *names = pprint_rename(p->one->path, p->two->path); - - fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); + strbuf_addf(&sb, " %s %s (%d%%)\n", + renamecopy, names, similarity_index(p)); free(names); - show_mode_change(file, p, 0, line_prefix); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + show_mode_change(opt, p, 0); } 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); } - show_mode_change(file, p, !p->score, line_prefix); + show_mode_change(opt, p, !p->score); break; } } @@ -4726,6 +5491,51 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) warning(_(rename_limit_advice), varname, needed); } +static void diff_flush_patch_all_file_pairs(struct diff_options *o) +{ + int i; + static struct emitted_diff_symbols esm = EMITTED_DIFF_SYMBOLS_INIT; + struct diff_queue_struct *q = &diff_queued_diff; + + if (WSEH_NEW & WS_RULE_MASK) + die("BUG: WS rules bit mask overlaps with diff symbol flags"); + + if (o->color_moved) + o->emitted_symbols = &esm; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, o); + } + + if (o->emitted_symbols) { + if (o->color_moved) { + struct hashmap add_lines, del_lines; + + hashmap_init(&del_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + hashmap_init(&add_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + + add_lines_to_move_detection(o, &add_lines, &del_lines); + mark_color_as_moved(o, &add_lines, &del_lines); + if (o->color_moved == COLOR_MOVED_ZEBRA_DIM) + dim_moved_lines(o); + + hashmap_free(&add_lines, 0); + hashmap_free(&del_lines, 0); + } + + for (i = 0; i < esm.nr; i++) + emit_diff_symbol_from_struct(o, &esm.buf[i]); + + for (i = 0; i < esm.nr; i++) + free((void *)esm.buf[i].line); + } + esm.nr = 0; +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; @@ -4798,6 +5608,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)) @@ -4809,20 +5620,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) @@ -148,9 +148,9 @@ struct diff_options { int abbrev; int ita_invisible_in_index; /* white-space error highlighting */ -#define WSEH_NEW 1 -#define WSEH_CONTEXT 2 -#define WSEH_OLD 4 +#define WSEH_NEW (1<<12) +#define WSEH_CONTEXT (1<<13) +#define WSEH_OLD (1<<14) unsigned ws_error_highlight; const char *prefix; int prefix_length; @@ -186,8 +186,27 @@ struct diff_options { void *output_prefix_data; int diff_path_counter; + + struct emitted_diff_symbols *emitted_symbols; + enum { + COLOR_MOVED_NO = 0, + COLOR_MOVED_PLAIN = 1, + COLOR_MOVED_ZEBRA = 2, + COLOR_MOVED_ZEBRA_DIM = 3, + } color_moved; + #define COLOR_MOVED_DEFAULT COLOR_MOVED_ZEBRA + #define COLOR_MOVED_MIN_BLOCK_LENGTH 3 }; +void diff_emit_submodule_del(struct diff_options *o, const char *line); +void diff_emit_submodule_add(struct diff_options *o, const char *line); +void diff_emit_submodule_untracked(struct diff_options *o, const char *path); +void diff_emit_submodule_modified(struct diff_options *o, const char *path); +void diff_emit_submodule_header(struct diff_options *o, const char *header); +void diff_emit_submodule_error(struct diff_options *o, const char *err); +void diff_emit_submodule_pipethrough(struct diff_options *o, + const char *line, int len); + enum color_diff { DIFF_RESET = 0, DIFF_CONTEXT = 1, @@ -197,7 +216,15 @@ enum color_diff { DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, - DIFF_FUNCINFO = 8 + DIFF_FUNCINFO = 8, + DIFF_FILE_OLD_MOVED = 9, + DIFF_FILE_OLD_MOVED_ALT = 10, + DIFF_FILE_OLD_MOVED_DIM = 11, + DIFF_FILE_OLD_MOVED_ALT_DIM = 12, + DIFF_FILE_NEW_MOVED = 13, + DIFF_FILE_NEW_MOVED_ALT = 14, + DIFF_FILE_NEW_MOVED_DIM = 15, + DIFF_FILE_NEW_MOVED_ALT_DIM = 16 }; const char *diff_get_color(int diff_use_color, enum color_diff ix); #define diff_get_color_opt(o, ix) \ @@ -396,8 +423,8 @@ extern int parse_rename_score(const char **cp_p); extern long parse_algorithm_value(const char *value); -extern int print_stat_summary(FILE *fp, int files, - int insertions, int deletions); +extern void print_stat_summary(FILE *fp, int files, + int insertions, int deletions); extern void setup_diff_pager(struct diff_options *); #endif /* DIFF_H */ diff --git a/submodule.c b/submodule.c index e072036e79..d53181ce7a 100644 --- a/submodule.c +++ b/submodule.c @@ -478,9 +478,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, return prepare_revision_walk(rev); } -static void print_submodule_summary(struct rev_info *rev, FILE *f, - const char *line_prefix, - const char *del, const char *add, const char *reset) +static void print_submodule_summary(struct rev_info *rev, struct diff_options *o) { static const char format[] = " %m %s"; struct strbuf sb = STRBUF_INIT; @@ -491,18 +489,12 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, ctx.date_mode = rev->date_mode; ctx.output_encoding = get_log_output_encoding(); strbuf_setlen(&sb, 0); - strbuf_addstr(&sb, line_prefix); - if (commit->object.flags & SYMMETRIC_LEFT) { - if (del) - strbuf_addstr(&sb, del); - } - else if (add) - strbuf_addstr(&sb, add); format_commit_message(commit, format, &sb, &ctx); - if (reset) - strbuf_addstr(&sb, reset); strbuf_addch(&sb, '\n'); - fprintf(f, "%s", sb.buf); + if (commit->object.flags & SYMMETRIC_LEFT) + diff_emit_submodule_del(o, sb.buf); + else + diff_emit_submodule_add(o, sb.buf); } strbuf_release(&sb); } @@ -529,11 +521,9 @@ void prepare_submodule_repo_env(struct argv_array *out) * attempt to lookup both the left and right commits and put them into the * left and right pointers. */ -static void show_submodule_header(FILE *f, const char *path, - const char *line_prefix, +static void show_submodule_header(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *reset, + unsigned dirty_submodule, struct commit **left, struct commit **right, struct commit_list **merge_bases) { @@ -542,11 +532,10 @@ static void show_submodule_header(FILE *f, const char *path, int fast_forward = 0, fast_backward = 0; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - fprintf(f, "%sSubmodule %s contains untracked content\n", - line_prefix, path); + diff_emit_submodule_untracked(o, path); + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - fprintf(f, "%sSubmodule %s contains modified content\n", - line_prefix, path); + diff_emit_submodule_modified(o, path); if (is_null_oid(one)) message = "(new submodule)"; @@ -588,31 +577,29 @@ static void show_submodule_header(FILE *f, const char *path, } output_header: - strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path); + strbuf_addf(&sb, "Submodule %s ", path); strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV); strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "..."); strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV); if (message) - strbuf_addf(&sb, " %s%s\n", message, reset); + strbuf_addf(&sb, " %s\n", message); else - strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); - fwrite(sb.buf, sb.len, 1, f); + strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); + diff_emit_submodule_header(o, sb.buf); strbuf_release(&sb); } -void show_submodule_summary(FILE *f, const char *path, - const char *line_prefix, +void show_submodule_summary(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset) + unsigned dirty_submodule) { struct rev_info rev; struct commit *left = NULL, *right = NULL; struct commit_list *merge_bases = NULL; - show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, - meta, reset, &left, &right, &merge_bases); + show_submodule_header(o, path, one, two, dirty_submodule, + &left, &right, &merge_bases); /* * If we don't have both a left and a right pointer, there is no @@ -624,11 +611,11 @@ void show_submodule_summary(FILE *f, const char *path, /* Treat revision walker failure the same as missing commits */ if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) { - fprintf(f, "%s(revision walker failed)\n", line_prefix); + diff_emit_submodule_error(o, "(revision walker failed)\n"); goto out; } - print_submodule_summary(&rev, f, line_prefix, del, add, reset); + print_submodule_summary(&rev, o); out: if (merge_bases) @@ -637,21 +624,18 @@ out: clear_commit_marks(right, ~0); } -void show_submodule_inline_diff(FILE *f, const char *path, - const char *line_prefix, +void show_submodule_inline_diff(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset, - const struct diff_options *o) + unsigned dirty_submodule) { const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid; struct commit *left = NULL, *right = NULL; struct commit_list *merge_bases = NULL; - struct strbuf submodule_dir = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; - show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, - meta, reset, &left, &right, &merge_bases); + show_submodule_header(o, path, one, two, dirty_submodule, + &left, &right, &merge_bases); /* We need a valid left and right commit to display a difference */ if (!(left || is_null_oid(one)) || @@ -663,16 +647,16 @@ void show_submodule_inline_diff(FILE *f, const char *path, if (right) new = two; - fflush(f); cp.git_cmd = 1; cp.dir = path; - cp.out = dup(fileno(f)); + cp.out = -1; cp.no_stdin = 1; /* TODO: other options may need to be passed here. */ argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL); + argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ? + "always" : "never"); - argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix); if (DIFF_OPT_TST(o, REVERSE_DIFF)) { argv_array_pushf(&cp.args, "--src-prefix=%s%s/", o->b_prefix, path); @@ -695,11 +679,17 @@ void show_submodule_inline_diff(FILE *f, const char *path, argv_array_push(&cp.args, oid_to_hex(new)); prepare_submodule_repo_env(&cp.env_array); - if (run_command(&cp)) - fprintf(f, "(diff failed)\n"); + if (start_command(&cp)) + diff_emit_submodule_error(o, "(diff failed)\n"); + + while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF) + diff_emit_submodule_pipethrough(o, sb.buf, sb.len); + + if (finish_command(&cp)) + diff_emit_submodule_error(o, "(diff failed)\n"); done: - strbuf_release(&submodule_dir); + strbuf_release(&sb); if (merge_bases) free_commit_list(merge_bases); if (left) diff --git a/submodule.h b/submodule.h index e402b004ff..5f26b54049 100644 --- a/submodule.h +++ b/submodule.h @@ -66,17 +66,12 @@ extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); extern void handle_ignore_submodules_arg(struct diff_options *, const char *); -extern void show_submodule_summary(FILE *f, const char *path, - const char *line_prefix, +extern void show_submodule_summary(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset); -extern void show_submodule_inline_diff(FILE *f, const char *path, - const char *line_prefix, + unsigned dirty_submodule); +extern void show_submodule_inline_diff(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset, - const struct diff_options *opt); + unsigned dirty_submodule); /* Check if we want to update any submodule.*/ extern int should_update_submodules(void); /* diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 289806d0c7..c3b697411a 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -972,4 +972,438 @@ test_expect_success 'option overrides diff.wsErrorHighlight' ' ' +test_expect_success 'detect moved code, complete file' ' + git reset --hard && + cat <<-\EOF >test.c && + #include<stdio.h> + main() + { + printf("Hello World"); + } + EOF + git add test.c && + git commit -m "add main function" && + git mv test.c main.c && + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + git diff HEAD --color-moved=zebra --no-renames | test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>new file mode 100644<RESET> + <BOLD>index 0000000..a986c57<RESET> + <BOLD>--- /dev/null<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -0,0 +1,5 @@<RESET> + <BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET> + <BGREEN>+<RESET><BGREEN>main()<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>printf("Hello World");<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>deleted file mode 100644<RESET> + <BOLD>index a986c57..0000000<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ /dev/null<RESET> + <CYAN>@@ -1,5 +0,0 @@<RESET> + <BRED>-#include<stdio.h><RESET> + <BRED>-main()<RESET> + <BRED>-{<RESET> + <BRED>-printf("Hello World");<RESET> + <BRED>-}<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect malicious moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + git reset --hard && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int secure_foo(struct user *u) + { + if (!u->is_allowed_foo) + return; + foo(u); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int another_function() + { + bar(); + } + EOF + git add main.c test.c && + git commit -m "add main and test file" && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int secure_foo(struct user *u) + { + foo(u); + if (!u->is_allowed_foo) + return; + } + + int another_function() + { + bar(); + } + EOF + git diff HEAD --no-renames --color-moved=zebra| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BLUE>-if (!u->is_allowed_foo)<RESET> + <BLUE>-return;<RESET> + <BRED>-foo(u);<RESET> + <BLUE>-}<RESET> + <BLUE>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <YELLOW>+<RESET><YELLOW>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <YELLOW>+<RESET><YELLOW>}<RESET> + <YELLOW>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'plain moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + # needs previous test as setup + git diff HEAD --no-renames --color-moved=plain| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BRED>-if (!u->is_allowed_foo)<RESET> + <BRED>-return;<RESET> + <BRED>-foo(u);<RESET> + <BRED>-}<RESET> + <BRED>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BGREEN>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect permutations inside moved code -- dimmed_zebra' ' + git reset --hard && + cat <<-\EOF >lines.txt && + line 1 + line 2 + line 3 + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + line 10 + line 11 + line 12 + line 13 + line 14 + line 15 + line 16 + EOF + git add lines.txt && + git commit -m "add poetry" && + cat <<-\EOF >lines.txt && + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + line 1 + line 2 + line 3 + line 14 + line 15 + line 16 + line 10 + line 11 + line 12 + line 13 + EOF + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + git diff HEAD --no-renames --color-moved=dimmed_zebra| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>index 47ea9c3..ba96a38 100644<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <BMAGENTA>-line 1<RESET> + <BMAGENTA>-line 2<RESET> + <BMAGENTA>-line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <BCYAN>+<RESET><BCYAN>line 1<RESET> + <BCYAN>+<RESET><BCYAN>line 2<RESET> + <CYAN>+<RESET><CYAN>line 3<RESET> + <YELLOW>+<RESET><YELLOW>line 14<RESET> + <BYELLOW>+<RESET><BYELLOW>line 15<RESET> + <BYELLOW>+<RESET><BYELLOW>line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <BMAGENTA>-line 14<RESET> + <BMAGENTA>-line 15<RESET> + <BMAGENTA>-line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'cmd option assumes configured colored-moved' ' + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + test_config diff.colorMoved zebra && + git diff HEAD --no-renames --color-moved| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>index 47ea9c3..ba96a38 100644<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <MAGENTA>-line 1<RESET> + <MAGENTA>-line 2<RESET> + <MAGENTA>-line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <CYAN>+<RESET><CYAN>line 1<RESET> + <CYAN>+<RESET><CYAN>line 2<RESET> + <CYAN>+<RESET><CYAN>line 3<RESET> + <YELLOW>+<RESET><YELLOW>line 14<RESET> + <YELLOW>+<RESET><YELLOW>line 15<RESET> + <YELLOW>+<RESET><YELLOW>line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <MAGENTA>-line 14<RESET> + <MAGENTA>-line 15<RESET> + <MAGENTA>-line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'no effect from --color-moved with --word-diff' ' + cat <<-\EOF >text.txt && + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + EOF + git add text.txt && + git commit -a -m "clean state" && + cat <<-\EOF >text.txt && + simply Lorem Ipsum dummy is text of the typesetting and printing industry. + EOF + git diff --color-moved --word-diff >actual && + git diff --word-diff >expect && + test_cmp expect actual +' + +test_expect_success 'move detection ignoring whitespace ' ' + git reset --hard && + cat <<\EOF >lines.txt && +line 1 +line 2 +line 3 +line 4 +line 5 +line 6 +line 7 +EOF + git add lines.txt && + git commit -m "add poetry" && + cat <<\EOF >lines.txt && + line 5 + line 6 + line 7 +line 1 +line 2 +line 3 +line 4 +EOF + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + git diff HEAD --no-renames --color-moved| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>index 734156d..eb89ead 100644<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,7 +1,7 @@<RESET> + <GREEN>+<RESET> <GREEN>line 5<RESET> + <GREEN>+<RESET> <GREEN>line 6<RESET> + <GREEN>+<RESET> <GREEN>line 7<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + <RED>-line 5<RESET> + <RED>-line 6<RESET> + <RED>-line 7<RESET> + EOF + test_cmp expected actual && + + git diff HEAD --no-renames -w --color-moved| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>index 734156d..eb89ead 100644<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,7 +1,7 @@<RESET> + <CYAN>+<RESET> <CYAN>line 5<RESET> + <CYAN>+<RESET> <CYAN>line 6<RESET> + <CYAN>+<RESET> <CYAN>line 7<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + <MAGENTA>-line 5<RESET> + <MAGENTA>-line 6<RESET> + <MAGENTA>-line 7<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'move detection with submodules' ' + test_create_repo bananas && + echo ripe >bananas/recipe && + git -C bananas add recipe && + test_commit fruit && + test_commit -C bananas recipe && + git submodule add ./bananas && + git add bananas && + git commit -a -m "bananas are like a heavy library?" && + echo foul >bananas/recipe && + echo ripe >fruit.t && + + git diff --submodule=diff --color-moved >actual && + + # no move detection as the moved line is across repository boundaries. + test_decode_color <actual >decoded_actual && + ! grep BGREEN decoded_actual && + ! grep BRED decoded_actual && + + # nor did we mess with it another way + git diff --submodule=diff | test_decode_color >expect && + test_cmp expect decoded_actual +' + test_done |