#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"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
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;
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)
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;
}
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)
return 0;
}
- if (git_color_config(var, value, cb) < 0)
- return -1;
-
return git_diff_basic_config(var, value, 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;
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;
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;
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,
* 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_CONTENT_BLANK_LINE_EOF (1<<16)
+#define DIFF_SYMBOL_MOVED_LINE (1<<17)
+#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18)
+#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19)
#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
+/*
+ * This struct is used when we need to buffer the output of the diff output.
+ *
+ * NEEDSWORK: Instead of storing a copy of the line, add an offset pointer
+ * into the pre/post image file. This pointer could be a union with the
+ * line pointer. By storing an offset into the file instead of the literal line,
+ * we can decrease the memory footprint for the buffered output. At first we
+ * may want to only have indirection for the content lines, but we could also
+ * enhance the state for emitting prefabricated lines, e.g. the similarity
+ * score line or hunk/file headers would only need to store a number or path
+ * and then the output can be constructed later on depending on state.
+ */
+struct emitted_diff_symbol {
+ const char *line;
+ int len;
+ int flags;
+ enum diff_symbol s;
+};
+#define EMITTED_DIFF_SYMBOL_INIT {NULL}
+
+struct emitted_diff_symbols {
+ struct emitted_diff_symbol *buf;
+ int nr, alloc;
+};
+#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}
+
+static void append_emitted_diff_symbol(struct diff_options *o,
+ struct emitted_diff_symbol *e)
+{
+ struct emitted_diff_symbol *f;
+
+ ALLOC_GROW(o->emitted_symbols->buf,
+ o->emitted_symbols->nr + 1,
+ o->emitted_symbols->alloc);
+ f = &o->emitted_symbols->buf[o->emitted_symbols->nr++];
+
+ memcpy(f, e, sizeof(struct emitted_diff_symbol));
+ f->line = e->line ? xmemdupz(e->line, e->len) : NULL;
+}
+
+struct moved_entry {
+ struct hashmap_entry ent;
+ const struct emitted_diff_symbol *es;
+ struct moved_entry *next_line;
+};
+
+static int next_byte(const char **cp, const char **endp,
+ const struct diff_options *diffopt)
+{
+ int retval;
+
+ if (*cp > *endp)
+ return -1;
+
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) {
+ while (*cp < *endp && isspace(**cp))
+ (*cp)++;
+ /*
+ * After skipping a couple of whitespaces, we still have to
+ * account for one space.
+ */
+ return (int)' ';
+ }
+
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) {
+ while (*cp < *endp && isspace(**cp))
+ (*cp)++;
+ /* return the first non-ws character via the usual below */
+ }
+
+ retval = (unsigned char)(**cp);
+ (*cp)++;
+ return retval;
+}
+
+static int moved_entry_cmp(const struct diff_options *diffopt,
+ const struct moved_entry *a,
+ const struct moved_entry *b,
+ const void *keydata)
+{
+ const char *ap = a->es->line, *ae = a->es->line + a->es->len;
+ const char *bp = b->es->line, *be = b->es->line + b->es->len;
+
+ if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS))
+ return a->es->len != b->es->len || memcmp(ap, bp, a->es->len);
+
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) {
+ while (ae > ap && isspace(*ae))
+ ae--;
+ while (be > bp && isspace(*be))
+ be--;
+ }
+
+ while (1) {
+ int ca, cb;
+ ca = next_byte(&ap, &ae, diffopt);
+ cb = next_byte(&bp, &be, diffopt);
+ if (ca != cb)
+ return 1;
+ if (ca < 0)
+ return 0;
+ }
+}
+
+static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o)
+{
+ if (o->xdl_opts & XDF_WHITESPACE_FLAGS) {
+ static struct strbuf sb = STRBUF_INIT;
+ const char *ap = es->line, *ae = es->line + es->len;
+ int c;
+
+ strbuf_reset(&sb);
+ while (ae > ap && isspace(*ae))
+ ae--;
+ while ((c = next_byte(&ap, &ae, o)) > 0)
+ strbuf_addch(&sb, c);
+
+ return memhash(sb.buf, sb.len);
+ } else {
+ return memhash(es->line, es->len);
+ }
+}
+
+static struct moved_entry *prepare_entry(struct diff_options *o,
+ int line_no)
+{
+ struct moved_entry *ret = xmalloc(sizeof(*ret));
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
+
+ ret->ent.hash = get_string_hash(l, o);
+ ret->es = l;
+ ret->next_line = NULL;
+
+ return ret;
+}
+
+static void add_lines_to_move_detection(struct diff_options *o,
+ struct hashmap *add_lines,
+ struct hashmap *del_lines)
+{
+ struct moved_entry *prev_line = NULL;
+
+ int n;
+ for (n = 0; n < o->emitted_symbols->nr; n++) {
+ struct hashmap *hm;
+ struct moved_entry *key;
+
+ switch (o->emitted_symbols->buf[n].s) {
+ case DIFF_SYMBOL_PLUS:
+ hm = add_lines;
+ break;
+ case DIFF_SYMBOL_MINUS:
+ hm = del_lines;
+ break;
+ default:
+ prev_line = NULL;
+ continue;
+ }
+
+ key = prepare_entry(o, n);
+ if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
+ prev_line->next_line = key;
+
+ hashmap_add(hm, key);
+ prev_line = key;
+ }
+}
+
+static int shrink_potential_moved_blocks(struct moved_entry **pmb,
+ int pmb_nr)
+{
+ int lp, rp;
+
+ /* Shrink the set of potential block to the remaining running */
+ for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
+ while (lp < pmb_nr && pmb[lp])
+ lp++;
+ /* lp points at the first NULL now */
+
+ while (rp > -1 && !pmb[rp])
+ rp--;
+ /* rp points at the last non-NULL */
+
+ if (lp < pmb_nr && rp > -1 && lp < rp) {
+ pmb[lp] = pmb[rp];
+ pmb[rp] = NULL;
+ rp--;
+ lp++;
+ }
+ }
+
+ /* Remember the number of running sets */
+ return rp + 1;
+}
+
+/*
+ * If o->color_moved is COLOR_MOVED_PLAIN, this function does nothing.
+ *
+ * Otherwise, if the last block has fewer alphanumeric characters than
+ * COLOR_MOVED_MIN_ALNUM_COUNT, unset DIFF_SYMBOL_MOVED_LINE on all lines in
+ * that block.
+ *
+ * The last block consists of the (n - block_length)'th line up to but not
+ * including the nth line.
+ *
+ * NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
+ * Think of a way to unify them.
+ */
+static void adjust_last_block(struct diff_options *o, int n, int block_length)
+{
+ int i, alnum_count = 0;
+ if (o->color_moved == COLOR_MOVED_PLAIN)
+ return;
+ for (i = 1; i < block_length + 1; i++) {
+ const char *c = o->emitted_symbols->buf[n - i].line;
+ for (; *c; c++) {
+ if (!isalnum(*c))
+ continue;
+ alnum_count++;
+ if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT)
+ return;
+ }
+ }
+ for (i = 1; i < block_length + 1; i++)
+ o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
+}
+
+/* Find blocks of moved code, delegate actual coloring decision to helper */
+static void mark_color_as_moved(struct diff_options *o,
+ struct hashmap *add_lines,
+ struct hashmap *del_lines)
+{
+ struct moved_entry **pmb = NULL; /* potentially moved blocks */
+ int pmb_nr = 0, pmb_alloc = 0;
+ int n, flipped_block = 1, block_length = 0;
+
+
+ for (n = 0; n < o->emitted_symbols->nr; n++) {
+ struct hashmap *hm = NULL;
+ struct moved_entry *key;
+ struct moved_entry *match = NULL;
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
+ int i;
+
+ switch (l->s) {
+ case DIFF_SYMBOL_PLUS:
+ hm = del_lines;
+ key = prepare_entry(o, n);
+ match = hashmap_get(hm, key, o);
+ free(key);
+ break;
+ case DIFF_SYMBOL_MINUS:
+ hm = add_lines;
+ key = prepare_entry(o, n);
+ match = hashmap_get(hm, key, o);
+ free(key);
+ break;
+ default:
+ flipped_block = 1;
+ }
+
+ if (!match) {
+ adjust_last_block(o, n, block_length);
+ pmb_nr = 0;
+ block_length = 0;
+ continue;
+ }
+
+ l->flags |= DIFF_SYMBOL_MOVED_LINE;
+
+ if (o->color_moved == COLOR_MOVED_PLAIN)
+ continue;
+
+ /* Check any potential block runs, advance each or nullify */
+ for (i = 0; i < pmb_nr; i++) {
+ struct moved_entry *p = pmb[i];
+ struct moved_entry *pnext = (p && p->next_line) ?
+ p->next_line : NULL;
+ if (pnext && !hm->cmpfn(o, pnext, match, NULL)) {
+ pmb[i] = p->next_line;
+ } else {
+ pmb[i] = NULL;
+ }
+ }
+
+ pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
+
+ if (pmb_nr == 0) {
+ /*
+ * The current line is the start of a new block.
+ * Setup the set of potential blocks.
+ */
+ for (; match; match = hashmap_get_next(hm, match)) {
+ ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
+ pmb[pmb_nr++] = match;
+ }
+
+ flipped_block = (flipped_block + 1) % 2;
+
+ adjust_last_block(o, n, block_length);
+ block_length = 0;
+ }
+
+ block_length++;
+
+ if (flipped_block)
+ l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
+ }
+ adjust_last_block(o, n, block_length);
+
+ free(pmb);
+}
+
+#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
+ (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
+static void dim_moved_lines(struct diff_options *o)
+{
+ int n;
+ for (n = 0; n < o->emitted_symbols->nr; n++) {
+ struct emitted_diff_symbol *prev = (n != 0) ?
+ &o->emitted_symbols->buf[n - 1] : NULL;
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
+ struct emitted_diff_symbol *next =
+ (n < o->emitted_symbols->nr - 1) ?
+ &o->emitted_symbols->buf[n + 1] : NULL;
+
+ /* Not a plus or minus line? */
+ if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS)
+ continue;
+
+ /* Not a moved line? */
+ if (!(l->flags & DIFF_SYMBOL_MOVED_LINE))
+ continue;
+
+ /*
+ * If prev or next are not a plus or minus line,
+ * pretend they don't exist
+ */
+ if (prev && prev->s != DIFF_SYMBOL_PLUS &&
+ prev->s != DIFF_SYMBOL_MINUS)
+ prev = NULL;
+ if (next && next->s != DIFF_SYMBOL_PLUS &&
+ next->s != DIFF_SYMBOL_MINUS)
+ next = NULL;
+
+ /* Inside a block? */
+ if ((prev &&
+ (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
+ (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) &&
+ (next &&
+ (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
+ (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) {
+ l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
+ continue;
+ }
+
+ /* Check if we are at an interesting bound: */
+ if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) &&
+ (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
+ (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
+ continue;
+ if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) &&
+ (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
+ (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
+ continue;
+
+ /*
+ * The boundary to prev and next are not interesting,
+ * so this line is not interesting as a whole
+ */
+ l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
+ }
+}
+
static void emit_line_ws_markup(struct diff_options *o,
const char *set, const char *reset,
const char *line, int len, char sign,
}
}
-static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s,
- const char *line, int len, unsigned flags)
+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);
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:
flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
break;
case DIFF_SYMBOL_PLUS:
- set = diff_get_color_opt(o, DIFF_FILE_NEW);
+ 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:
- set = diff_get_color_opt(o, DIFF_FILE_OLD);
+ 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);
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);
/* 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)
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)
{
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);
unsigned long allot;
size_t l = len;
- if (ecb->truncate)
- return ecb->truncate(line, len);
cp = line;
allot = l;
while (0 < l) {
}
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)
* bytes per "line".
* This is stupid and ugly, but very cheap...
*/
- damage = (damage + 63) / 64;
+ damage = DIV_ROUND_UP(damage, 64);
ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
dir.files[dir.nr].name = file->name;
dir.files[dir.nr].changed = damage;
}
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(&one->oid, one->path, &st, 0))
die("cannot hash %s", one->path);
}
}
options->a_prefix = "a/";
options->b_prefix = "b/";
}
+
+ options->color_moved = diff_color_moved_default;
}
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)
}
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;
}
}
}
-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);
+ 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;
}
}
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)
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))
if (output_format & DIFF_FORMAT_PATCH) {
if (separator) {
emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0);
- if (options->stat_sep) {
+ 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);
}
diff_flush_patch_all_file_pairs(options);