@ -37,6 +37,7 @@ static int diff_rename_limit_default = 400;
@@ -37,6 +37,7 @@ static int diff_rename_limit_default = 400;
static int diff_suppress_blank_empty;
static int diff_use_color_default = -1;
static int diff_color_moved_default;
static int diff_color_moved_ws_default;
static int diff_context_default = 3;
static int diff_interhunk_context_default;
static const char *diff_word_regex_cfg;
@ -264,6 +265,8 @@ static int parse_color_moved(const char *arg)
@@ -264,6 +265,8 @@ static int parse_color_moved(const char *arg)
return COLOR_MOVED_NO;
else if (!strcmp(arg, "plain"))
return COLOR_MOVED_PLAIN;
else if (!strcmp(arg, "blocks"))
return COLOR_MOVED_BLOCKS;
else if (!strcmp(arg, "zebra"))
return COLOR_MOVED_ZEBRA;
else if (!strcmp(arg, "default"))
@ -271,7 +274,43 @@ static int parse_color_moved(const char *arg)
@@ -271,7 +274,43 @@ static int parse_color_moved(const char *arg)
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'"));
return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed_zebra', 'plain'"));
}
static int parse_color_moved_ws(const char *arg)
{
int ret = 0;
struct string_list l = STRING_LIST_INIT_DUP;
struct string_list_item *i;
string_list_split(&l, arg, ',', -1);
for_each_string_list_item(i, &l) {
struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, i->string);
strbuf_trim(&sb);
if (!strcmp(sb.buf, "ignore-space-change"))
ret |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(sb.buf, "ignore-space-at-eol"))
ret |= XDF_IGNORE_WHITESPACE_AT_EOL;
else if (!strcmp(sb.buf, "ignore-all-space"))
ret |= XDF_IGNORE_WHITESPACE;
else if (!strcmp(sb.buf, "allow-indentation-change"))
ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;
else
error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf);
strbuf_release(&sb);
}
if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&
(ret & XDF_WHITESPACE_FLAGS))
die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes"));
string_list_clear(&l, 0);
return ret;
}
int git_diff_ui_config(const char *var, const char *value, void *cb)
@ -287,6 +326,13 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
@@ -287,6 +326,13 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
diff_color_moved_default = cm;
return 0;
}
if (!strcmp(var, "diff.colormovedws")) {
int cm = parse_color_moved_ws(value);
if (cm < 0)
return -1;
diff_color_moved_ws_default = cm;
return 0;
}
if (!strcmp(var, "diff.context")) {
diff_context_default = git_config_int(var, value);
if (diff_context_default < 0)
@ -698,16 +744,116 @@ struct moved_entry {
@@ -698,16 +744,116 @@ struct moved_entry {
struct hashmap_entry ent;
const struct emitted_diff_symbol *es;
struct moved_entry *next_line;
struct ws_delta *wsd;
};
/**
* The struct ws_delta holds white space differences between moved lines, i.e.
* between '+' and '-' lines that have been detected to be a move.
* The string contains the difference in leading white spaces, before the
* rest of the line is compared using the white space config for move
* coloring. The current_longer indicates if the first string in the
* comparision is longer than the second.
*/
struct ws_delta {
char *string;
unsigned int current_longer : 1;
};
#define WS_DELTA_INIT { NULL, 0 }
static int compute_ws_delta(const struct emitted_diff_symbol *a,
const struct emitted_diff_symbol *b,
struct ws_delta *out)
{
const struct emitted_diff_symbol *longer = a->len > b->len ? a : b;
const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a;
int d = longer->len - shorter->len;
static int moved_entry_cmp(const struct diff_options *diffopt,
const struct moved_entry *a,
const struct moved_entry *b,
out->string = xmemdupz(longer->line, d);
out->current_longer = (a == longer);
return !strncmp(longer->line + d, shorter->line, shorter->len);
}
static int cmp_in_block_with_wsd(const struct diff_options *o,
const struct moved_entry *cur,
const struct moved_entry *match,
struct moved_entry *pmb,
int n)
{
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
int al = cur->es->len, cl = l->len;
const char *a = cur->es->line,
*b = match->es->line,
*c = l->line;
int wslen;
/*
* We need to check if 'cur' is equal to 'match'.
* As those are from the same (+/-) side, we do not need to adjust for
* indent changes. However these were found using fuzzy matching
* so we do have to check if they are equal.
*/
if (strcmp(a, b))
return 1;
if (!pmb->wsd)
/*
* No white space delta was carried forward? This can happen
* when we exit early in this function and do not carry
* forward ws.
*/
return 1;
/*
* The indent changes of the block are known and carried forward in
* pmb->wsd; however we need to check if the indent changes of the
* current line are still the same as before.
*
* To do so we need to compare 'l' to 'cur', adjusting the
* one of them for the white spaces, depending which was longer.
*/
wslen = strlen(pmb->wsd->string);
if (pmb->wsd->current_longer) {
c += wslen;
cl -= wslen;
} else {
a += wslen;
al -= wslen;
}
if (strcmp(a, c))
return 1;
return 0;
}
static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
const void *entry,
const void *entry_or_key,
const void *keydata)
{
const struct diff_options *diffopt = hashmap_cmp_fn_data;
const struct moved_entry *a = entry;
const struct moved_entry *b = entry_or_key;
unsigned flags = diffopt->color_moved_ws_handling
& XDF_WHITESPACE_FLAGS;
if (diffopt->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
/*
* As there is not specific white space config given,
* we'd need to check for a new block, so ignore all
* white space. The setup of the white space
* configuration for the next block is done else where
*/
flags |= XDF_IGNORE_WHITESPACE;
return !xdiff_compare_lines(a->es->line, a->es->len,
b->es->line, b->es->len,
diffopt->xdl_opts);
flags);
}
static struct moved_entry *prepare_entry(struct diff_options *o,
@ -715,10 +861,12 @@ static struct moved_entry *prepare_entry(struct diff_options *o,
@@ -715,10 +861,12 @@ static struct moved_entry *prepare_entry(struct diff_options *o,
{
struct moved_entry *ret = xmalloc(sizeof(*ret));
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS;
ret->ent.hash = xdiff_hash_string(l->line, l->len, o->xdl_opts);
ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
ret->es = l;
ret->next_line = NULL;
ret->wsd = NULL;
return ret;
}
@ -755,6 +903,56 @@ static void add_lines_to_move_detection(struct diff_options *o,
@@ -755,6 +903,56 @@ static void add_lines_to_move_detection(struct diff_options *o,
}
}
static void pmb_advance_or_null(struct diff_options *o,
struct moved_entry *match,
struct hashmap *hm,
struct moved_entry **pmb,
int pmb_nr)
{
int i;
for (i = 0; i < pmb_nr; i++) {
struct moved_entry *prev = pmb[i];
struct moved_entry *cur = (prev && prev->next_line) ?
prev->next_line : NULL;
if (cur && !hm->cmpfn(o, cur, match, NULL)) {
pmb[i] = cur;
} else {
pmb[i] = NULL;
}
}
}
static void pmb_advance_or_null_multi_match(struct diff_options *o,
struct moved_entry *match,
struct hashmap *hm,
struct moved_entry **pmb,
int pmb_nr, int n)
{
int i;
char *got_match = xcalloc(1, pmb_nr);
for (; match; match = hashmap_get_next(hm, match)) {
for (i = 0; i < pmb_nr; i++) {
struct moved_entry *prev = pmb[i];
struct moved_entry *cur = (prev && prev->next_line) ?
prev->next_line : NULL;
if (!cur)
continue;
if (!cmp_in_block_with_wsd(o, cur, match, pmb[i], n))
got_match[i] |= 1;
}
}
for (i = 0; i < pmb_nr; i++) {
if (got_match[i]) {
/* Carry the white space delta forward */
pmb[i]->next_line->wsd = pmb[i]->wsd;
pmb[i] = pmb[i]->next_line;
} else
pmb[i] = NULL;
}
}
static int shrink_potential_moved_blocks(struct moved_entry **pmb,
int pmb_nr)
{
@ -772,6 +970,10 @@ static int shrink_potential_moved_blocks(struct moved_entry **pmb,
@@ -772,6 +970,10 @@ static int shrink_potential_moved_blocks(struct moved_entry **pmb,
if (lp < pmb_nr && rp > -1 && lp < rp) {
pmb[lp] = pmb[rp];
if (pmb[rp]->wsd) {
free(pmb[rp]->wsd->string);
FREE_AND_NULL(pmb[rp]->wsd);
}
pmb[rp] = NULL;
rp--;
lp++;
@ -829,19 +1031,18 @@ static void mark_color_as_moved(struct diff_options *o,
@@ -829,19 +1031,18 @@ static void mark_color_as_moved(struct diff_options *o,
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);
match = hashmap_get(hm, key, NULL);
free(key);
break;
case DIFF_SYMBOL_MINUS:
hm = add_lines;
key = prepare_entry(o, n);
match = hashmap_get(hm, key, o);
match = hashmap_get(hm, key, NULL);
free(key);
break;
default:
@ -860,17 +1061,11 @@ static void mark_color_as_moved(struct diff_options *o,
@@ -860,17 +1061,11 @@ static void mark_color_as_moved(struct diff_options *o,
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;
}
}
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n);
else
pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
@ -881,8 +1076,18 @@ static void mark_color_as_moved(struct diff_options *o,
@@ -881,8 +1076,18 @@ static void mark_color_as_moved(struct diff_options *o,
*/
for (; match; match = hashmap_get_next(hm, match)) {
ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
struct ws_delta *wsd = xmalloc(sizeof(*match->wsd));
if (compute_ws_delta(l, match->es, wsd)) {
match->wsd = wsd;
pmb[pmb_nr++] = match;
} else
free(wsd);
} else {
pmb[pmb_nr++] = match;
}
}
flipped_block = (flipped_block + 1) % 2;
@ -892,7 +1097,7 @@ static void mark_color_as_moved(struct diff_options *o,
@@ -892,7 +1097,7 @@ static void mark_color_as_moved(struct diff_options *o,
block_length++;
if (flipped_block)
if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
}
adjust_last_block(o, n, block_length);
@ -4125,6 +4330,7 @@ void diff_setup(struct diff_options *options)
@@ -4125,6 +4330,7 @@ void diff_setup(struct diff_options *options)
}
options->color_moved = diff_color_moved_default;
options->color_moved_ws_handling = diff_color_moved_ws_default;
}
void diff_setup_done(struct diff_options *options)
@ -4704,6 +4910,8 @@ int diff_opt_parse(struct diff_options *options,
@@ -4704,6 +4910,8 @@ int diff_opt_parse(struct diff_options *options,
if (cm < 0)
die("bad --color-moved argument: %s", arg);
options->color_moved = cm;
} else if (skip_prefix(arg, "--color-moved-ws=", &arg)) {
options->color_moved_ws_handling = parse_color_moved_ws(arg);
} else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
options->use_color = 1;
options->word_diff = DIFF_WORDS_COLOR;
@ -5534,10 +5742,12 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
@@ -5534,10 +5742,12 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
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);
if (o->color_moved_ws_handling &
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
o->color_moved_ws_handling |= XDF_IGNORE_WHITESPACE;
hashmap_init(&del_lines, moved_entry_cmp, o, 0);
hashmap_init(&add_lines, moved_entry_cmp, o, 0);
add_lines_to_move_detection(o, &add_lines, &del_lines);
mark_color_as_moved(o, &add_lines, &del_lines);