mirror of
https://github.com/git/git.git
synced 2024-11-28 04:23:30 +08:00
Merge branch 'pw/diff-color-moved-fix'
Correctness and performance update to "diff --color-moved" feature. * pw/diff-color-moved-fix: diff --color-moved: intern strings diff: use designated initializers for emitted_diff_symbol diff --color-moved-ws=allow-indentation-change: improve hash lookups diff --color-moved: stop clearing potential moved blocks diff --color-moved: shrink potential moved blocks as we go diff --color-moved: unify moved block growth functions diff --color-moved: call comparison function directly diff --color-moved-ws=allow-indentation-change: simplify and optimize diff: simplify allow-indentation-change delta calculation diff --color-moved: avoid false short line matches and bad zebra coloring diff --color-moved=zebra: fix alternate coloring diff --color-moved: rewind when discarding pmb diff --color-moved: factor out function diff --color-moved: clear all flags on blocks that are too short diff --color-moved: add perf tests
This commit is contained in:
commit
2b755b3371
433
diff.c
433
diff.c
@ -18,6 +18,7 @@
|
||||
#include "submodule-config.h"
|
||||
#include "submodule.h"
|
||||
#include "hashmap.h"
|
||||
#include "mem-pool.h"
|
||||
#include "ll-merge.h"
|
||||
#include "string-list.h"
|
||||
#include "strvec.h"
|
||||
@ -773,6 +774,7 @@ struct emitted_diff_symbol {
|
||||
int flags;
|
||||
int indent_off; /* Offset to first non-whitespace character */
|
||||
int indent_width; /* The visual width of the indentation */
|
||||
unsigned id;
|
||||
enum diff_symbol s;
|
||||
};
|
||||
#define EMITTED_DIFF_SYMBOL_INIT { 0 }
|
||||
@ -798,9 +800,9 @@ static void append_emitted_diff_symbol(struct diff_options *o,
|
||||
}
|
||||
|
||||
struct moved_entry {
|
||||
struct hashmap_entry ent;
|
||||
const struct emitted_diff_symbol *es;
|
||||
struct moved_entry *next_line;
|
||||
struct moved_entry *next_match;
|
||||
};
|
||||
|
||||
struct moved_block {
|
||||
@ -808,11 +810,6 @@ struct moved_block {
|
||||
int wsd; /* The whitespace delta of this block */
|
||||
};
|
||||
|
||||
static void moved_block_clear(struct moved_block *b)
|
||||
{
|
||||
memset(b, 0, sizeof(*b));
|
||||
}
|
||||
|
||||
#define INDENT_BLANKLINE INT_MIN
|
||||
|
||||
static void fill_es_indent_data(struct emitted_diff_symbol *es)
|
||||
@ -856,79 +853,41 @@ static void fill_es_indent_data(struct emitted_diff_symbol *es)
|
||||
}
|
||||
|
||||
static int compute_ws_delta(const struct emitted_diff_symbol *a,
|
||||
const struct emitted_diff_symbol *b,
|
||||
int *out)
|
||||
const struct emitted_diff_symbol *b)
|
||||
{
|
||||
int a_len = a->len,
|
||||
b_len = b->len,
|
||||
a_off = a->indent_off,
|
||||
a_width = a->indent_width,
|
||||
b_off = b->indent_off,
|
||||
int a_width = a->indent_width,
|
||||
b_width = b->indent_width;
|
||||
int delta;
|
||||
|
||||
if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE) {
|
||||
*out = INDENT_BLANKLINE;
|
||||
return 1;
|
||||
}
|
||||
if (a_width == INDENT_BLANKLINE && b_width == INDENT_BLANKLINE)
|
||||
return INDENT_BLANKLINE;
|
||||
|
||||
if (a->s == DIFF_SYMBOL_PLUS)
|
||||
delta = a_width - b_width;
|
||||
else
|
||||
delta = b_width - a_width;
|
||||
|
||||
if (a_len - a_off != b_len - b_off ||
|
||||
memcmp(a->line + a_off, b->line + b_off, a_len - a_off))
|
||||
return 0;
|
||||
|
||||
*out = delta;
|
||||
|
||||
return 1;
|
||||
return a_width - b_width;
|
||||
}
|
||||
|
||||
static int cmp_in_block_with_wsd(const struct diff_options *o,
|
||||
const struct moved_entry *cur,
|
||||
const struct moved_entry *match,
|
||||
struct moved_block *pmb,
|
||||
int n)
|
||||
static int cmp_in_block_with_wsd(const struct moved_entry *cur,
|
||||
const struct emitted_diff_symbol *l,
|
||||
struct moved_block *pmb)
|
||||
{
|
||||
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
|
||||
int al = cur->es->len, bl = match->es->len, cl = l->len;
|
||||
const char *a = cur->es->line,
|
||||
*b = match->es->line,
|
||||
*c = l->line;
|
||||
int a_off = cur->es->indent_off,
|
||||
a_width = cur->es->indent_width,
|
||||
c_off = l->indent_off,
|
||||
c_width = l->indent_width;
|
||||
int a_width = cur->es->indent_width, b_width = l->indent_width;
|
||||
int delta;
|
||||
|
||||
/*
|
||||
* 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. Here we
|
||||
* just check the lengths. We delay calling memcmp() to check
|
||||
* the contents until later as if the length comparison for a
|
||||
* and c fails we can avoid the call all together.
|
||||
*/
|
||||
if (al != bl)
|
||||
/* The text of each line must match */
|
||||
if (cur->es->id != l->id)
|
||||
return 1;
|
||||
|
||||
/* If 'l' and 'cur' are both blank then they match. */
|
||||
if (a_width == INDENT_BLANKLINE && c_width == INDENT_BLANKLINE)
|
||||
/*
|
||||
* If 'l' and 'cur' are both blank then we don't need to check the
|
||||
* indent. We only need to check cur as we know the strings match.
|
||||
* */
|
||||
if (a_width == INDENT_BLANKLINE)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The indent changes of the block are known and stored in pmb->wsd;
|
||||
* however we need to check if the indent changes of the current line
|
||||
* match those of the current block and that the text of 'l' and 'cur'
|
||||
* after the indentation match.
|
||||
* match those of the current block.
|
||||
*/
|
||||
if (cur->es->s == DIFF_SYMBOL_PLUS)
|
||||
delta = a_width - c_width;
|
||||
else
|
||||
delta = c_width - a_width;
|
||||
delta = b_width - a_width;
|
||||
|
||||
/*
|
||||
* If the previous lines of this block were all blank then set its
|
||||
@ -937,166 +896,165 @@ static int cmp_in_block_with_wsd(const struct diff_options *o,
|
||||
if (pmb->wsd == INDENT_BLANKLINE)
|
||||
pmb->wsd = delta;
|
||||
|
||||
return !(delta == pmb->wsd && al - a_off == cl - c_off &&
|
||||
!memcmp(a, b, al) && !
|
||||
memcmp(a + a_off, c + c_off, al - a_off));
|
||||
return delta != pmb->wsd;
|
||||
}
|
||||
|
||||
static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
|
||||
const struct hashmap_entry *eptr,
|
||||
const struct hashmap_entry *entry_or_key,
|
||||
const void *keydata)
|
||||
struct interned_diff_symbol {
|
||||
struct hashmap_entry ent;
|
||||
struct emitted_diff_symbol *es;
|
||||
};
|
||||
|
||||
static int interned_diff_symbol_cmp(const void *hashmap_cmp_fn_data,
|
||||
const struct hashmap_entry *eptr,
|
||||
const struct hashmap_entry *entry_or_key,
|
||||
const void *keydata)
|
||||
{
|
||||
const struct diff_options *diffopt = hashmap_cmp_fn_data;
|
||||
const struct moved_entry *a, *b;
|
||||
const struct emitted_diff_symbol *a, *b;
|
||||
unsigned flags = diffopt->color_moved_ws_handling
|
||||
& XDF_WHITESPACE_FLAGS;
|
||||
|
||||
a = container_of(eptr, const struct moved_entry, ent);
|
||||
b = container_of(entry_or_key, const struct moved_entry, ent);
|
||||
a = container_of(eptr, const struct interned_diff_symbol, ent)->es;
|
||||
b = container_of(entry_or_key, const struct interned_diff_symbol, ent)->es;
|
||||
|
||||
if (diffopt->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
|
||||
/*
|
||||
* As there is not specific white space config given,
|
||||
* we'd need to check for a new block, so ignore all
|
||||
* white space. The setup of the white space
|
||||
* configuration for the next block is done else where
|
||||
*/
|
||||
flags |= XDF_IGNORE_WHITESPACE;
|
||||
|
||||
return !xdiff_compare_lines(a->es->line, a->es->len,
|
||||
b->es->line, b->es->len,
|
||||
flags);
|
||||
return !xdiff_compare_lines(a->line + a->indent_off,
|
||||
a->len - a->indent_off,
|
||||
b->line + b->indent_off,
|
||||
b->len - b->indent_off, flags);
|
||||
}
|
||||
|
||||
static struct moved_entry *prepare_entry(struct diff_options *o,
|
||||
int line_no)
|
||||
static void prepare_entry(struct diff_options *o, struct emitted_diff_symbol *l,
|
||||
struct interned_diff_symbol *s)
|
||||
{
|
||||
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;
|
||||
unsigned int hash = xdiff_hash_string(l->line, l->len, flags);
|
||||
unsigned int hash = xdiff_hash_string(l->line + l->indent_off,
|
||||
l->len - l->indent_off, flags);
|
||||
|
||||
hashmap_entry_init(&ret->ent, hash);
|
||||
ret->es = l;
|
||||
ret->next_line = NULL;
|
||||
|
||||
return ret;
|
||||
hashmap_entry_init(&s->ent, hash);
|
||||
s->es = l;
|
||||
}
|
||||
|
||||
static void add_lines_to_move_detection(struct diff_options *o,
|
||||
struct hashmap *add_lines,
|
||||
struct hashmap *del_lines)
|
||||
struct moved_entry_list {
|
||||
struct moved_entry *add, *del;
|
||||
};
|
||||
|
||||
static struct moved_entry_list *add_lines_to_move_detection(struct diff_options *o,
|
||||
struct mem_pool *entry_mem_pool)
|
||||
{
|
||||
struct moved_entry *prev_line = NULL;
|
||||
|
||||
struct mem_pool interned_pool;
|
||||
struct hashmap interned_map;
|
||||
struct moved_entry_list *entry_list = NULL;
|
||||
size_t entry_list_alloc = 0;
|
||||
unsigned id = 0;
|
||||
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:
|
||||
hashmap_init(&interned_map, interned_diff_symbol_cmp, o, 8096);
|
||||
mem_pool_init(&interned_pool, 1024 * 1024);
|
||||
|
||||
for (n = 0; n < o->emitted_symbols->nr; n++) {
|
||||
struct interned_diff_symbol key;
|
||||
struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
|
||||
struct interned_diff_symbol *s;
|
||||
struct moved_entry *entry;
|
||||
|
||||
if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS) {
|
||||
prev_line = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
|
||||
fill_es_indent_data(&o->emitted_symbols->buf[n]);
|
||||
key = prepare_entry(o, n);
|
||||
if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
|
||||
prev_line->next_line = key;
|
||||
fill_es_indent_data(l);
|
||||
|
||||
hashmap_add(hm, &key->ent);
|
||||
prev_line = key;
|
||||
prepare_entry(o, l, &key);
|
||||
s = hashmap_get_entry(&interned_map, &key, ent, &key.ent);
|
||||
if (s) {
|
||||
l->id = s->es->id;
|
||||
} else {
|
||||
l->id = id;
|
||||
ALLOC_GROW_BY(entry_list, id, 1, entry_list_alloc);
|
||||
hashmap_add(&interned_map,
|
||||
memcpy(mem_pool_alloc(&interned_pool,
|
||||
sizeof(key)),
|
||||
&key, sizeof(key)));
|
||||
}
|
||||
entry = mem_pool_alloc(entry_mem_pool, sizeof(*entry));
|
||||
entry->es = l;
|
||||
entry->next_line = NULL;
|
||||
if (prev_line && prev_line->es->s == l->s)
|
||||
prev_line->next_line = entry;
|
||||
prev_line = entry;
|
||||
if (l->s == DIFF_SYMBOL_PLUS) {
|
||||
entry->next_match = entry_list[l->id].add;
|
||||
entry_list[l->id].add = entry;
|
||||
} else {
|
||||
entry->next_match = entry_list[l->id].del;
|
||||
entry_list[l->id].del = entry;
|
||||
}
|
||||
}
|
||||
|
||||
hashmap_clear(&interned_map);
|
||||
mem_pool_discard(&interned_pool, 0);
|
||||
|
||||
return entry_list;
|
||||
}
|
||||
|
||||
static void pmb_advance_or_null(struct diff_options *o,
|
||||
struct moved_entry *match,
|
||||
struct hashmap *hm,
|
||||
struct emitted_diff_symbol *l,
|
||||
struct moved_block *pmb,
|
||||
int pmb_nr)
|
||||
int *pmb_nr)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < pmb_nr; i++) {
|
||||
int i, j;
|
||||
|
||||
for (i = 0, j = 0; i < *pmb_nr; i++) {
|
||||
int match;
|
||||
struct moved_entry *prev = pmb[i].match;
|
||||
struct moved_entry *cur = (prev && prev->next_line) ?
|
||||
prev->next_line : NULL;
|
||||
if (cur && !hm->cmpfn(o, &cur->ent, &match->ent, NULL)) {
|
||||
pmb[i].match = cur;
|
||||
} else {
|
||||
pmb[i].match = NULL;
|
||||
|
||||
if (o->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
|
||||
match = cur &&
|
||||
!cmp_in_block_with_wsd(cur, l, &pmb[i]);
|
||||
else
|
||||
match = cur && cur->es->id == l->id;
|
||||
|
||||
if (match) {
|
||||
pmb[j] = pmb[i];
|
||||
pmb[j++].match = cur;
|
||||
}
|
||||
}
|
||||
*pmb_nr = j;
|
||||
}
|
||||
|
||||
static void pmb_advance_or_null_multi_match(struct diff_options *o,
|
||||
struct moved_entry *match,
|
||||
struct hashmap *hm,
|
||||
struct moved_block *pmb,
|
||||
int pmb_nr, int n)
|
||||
static void fill_potential_moved_blocks(struct diff_options *o,
|
||||
struct moved_entry *match,
|
||||
struct emitted_diff_symbol *l,
|
||||
struct moved_block **pmb_p,
|
||||
int *pmb_alloc_p, int *pmb_nr_p)
|
||||
|
||||
{
|
||||
int i;
|
||||
char *got_match = xcalloc(1, pmb_nr);
|
||||
struct moved_block *pmb = *pmb_p;
|
||||
int pmb_alloc = *pmb_alloc_p, pmb_nr = *pmb_nr_p;
|
||||
|
||||
hashmap_for_each_entry_from(hm, match, ent) {
|
||||
for (i = 0; i < pmb_nr; i++) {
|
||||
struct moved_entry *prev = pmb[i].match;
|
||||
struct moved_entry *cur = (prev && prev->next_line) ?
|
||||
prev->next_line : NULL;
|
||||
if (!cur)
|
||||
continue;
|
||||
if (!cmp_in_block_with_wsd(o, cur, match, &pmb[i], n))
|
||||
got_match[i] |= 1;
|
||||
}
|
||||
/*
|
||||
* The current line is the start of a new block.
|
||||
* Setup the set of potential blocks.
|
||||
*/
|
||||
for (; match; match = match->next_match) {
|
||||
ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
|
||||
if (o->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
|
||||
pmb[pmb_nr].wsd = compute_ws_delta(l, match->es);
|
||||
else
|
||||
pmb[pmb_nr].wsd = 0;
|
||||
pmb[pmb_nr++].match = match;
|
||||
}
|
||||
|
||||
for (i = 0; i < pmb_nr; i++) {
|
||||
if (got_match[i]) {
|
||||
/* Advance to the next line */
|
||||
pmb[i].match = pmb[i].match->next_line;
|
||||
} else {
|
||||
moved_block_clear(&pmb[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(got_match);
|
||||
}
|
||||
|
||||
static int shrink_potential_moved_blocks(struct moved_block *pmb,
|
||||
int pmb_nr)
|
||||
{
|
||||
int lp, rp;
|
||||
|
||||
/* Shrink the set of potential block to the remaining running */
|
||||
for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
|
||||
while (lp < pmb_nr && pmb[lp].match)
|
||||
lp++;
|
||||
/* lp points at the first NULL now */
|
||||
|
||||
while (rp > -1 && !pmb[rp].match)
|
||||
rp--;
|
||||
/* rp points at the last non-NULL */
|
||||
|
||||
if (lp < pmb_nr && rp > -1 && lp < rp) {
|
||||
pmb[lp] = pmb[rp];
|
||||
memset(&pmb[rp], 0, sizeof(pmb[rp]));
|
||||
rp--;
|
||||
lp++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember the number of running sets */
|
||||
return rp + 1;
|
||||
*pmb_p = pmb;
|
||||
*pmb_alloc_p = pmb_alloc;
|
||||
*pmb_nr_p = pmb_nr;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1115,6 +1073,8 @@ static int shrink_potential_moved_blocks(struct moved_block *pmb,
|
||||
* NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
|
||||
* Think of a way to unify them.
|
||||
*/
|
||||
#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
|
||||
(DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
|
||||
static int adjust_last_block(struct diff_options *o, int n, int block_length)
|
||||
{
|
||||
int i, alnum_count = 0;
|
||||
@ -1131,95 +1091,85 @@ static int adjust_last_block(struct diff_options *o, int n, int block_length)
|
||||
}
|
||||
}
|
||||
for (i = 1; i < block_length + 1; i++)
|
||||
o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
|
||||
o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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_list *entry_list)
|
||||
{
|
||||
struct moved_block *pmb = NULL; /* potentially moved blocks */
|
||||
int pmb_nr = 0, pmb_alloc = 0;
|
||||
int n, flipped_block = 0, block_length = 0;
|
||||
enum diff_symbol moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
|
||||
|
||||
|
||||
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];
|
||||
enum diff_symbol last_symbol = 0;
|
||||
|
||||
switch (l->s) {
|
||||
case DIFF_SYMBOL_PLUS:
|
||||
hm = del_lines;
|
||||
key = prepare_entry(o, n);
|
||||
match = hashmap_get_entry(hm, key, ent, NULL);
|
||||
free(key);
|
||||
match = entry_list[l->id].del;
|
||||
break;
|
||||
case DIFF_SYMBOL_MINUS:
|
||||
hm = add_lines;
|
||||
key = prepare_entry(o, n);
|
||||
match = hashmap_get_entry(hm, key, ent, NULL);
|
||||
free(key);
|
||||
match = entry_list[l->id].add;
|
||||
break;
|
||||
default:
|
||||
flipped_block = 0;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
int i;
|
||||
|
||||
adjust_last_block(o, n, block_length);
|
||||
for(i = 0; i < pmb_nr; i++)
|
||||
moved_block_clear(&pmb[i]);
|
||||
if (pmb_nr && (!match || l->s != moved_symbol)) {
|
||||
if (!adjust_last_block(o, n, block_length) &&
|
||||
block_length > 1) {
|
||||
/*
|
||||
* Rewind in case there is another match
|
||||
* starting at the second line of the block
|
||||
*/
|
||||
match = NULL;
|
||||
n -= block_length;
|
||||
}
|
||||
pmb_nr = 0;
|
||||
block_length = 0;
|
||||
flipped_block = 0;
|
||||
last_symbol = l->s;
|
||||
}
|
||||
if (!match) {
|
||||
moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o->color_moved == COLOR_MOVED_PLAIN) {
|
||||
last_symbol = l->s;
|
||||
l->flags |= DIFF_SYMBOL_MOVED_LINE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
|
||||
pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n);
|
||||
else
|
||||
pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
|
||||
|
||||
pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
|
||||
pmb_advance_or_null(o, l, pmb, &pmb_nr);
|
||||
|
||||
if (pmb_nr == 0) {
|
||||
/*
|
||||
* The current line is the start of a new block.
|
||||
* Setup the set of potential blocks.
|
||||
*/
|
||||
hashmap_for_each_entry_from(hm, match, ent) {
|
||||
ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
|
||||
if (o->color_moved_ws_handling &
|
||||
COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
|
||||
if (compute_ws_delta(l, match->es,
|
||||
&pmb[pmb_nr].wsd))
|
||||
pmb[pmb_nr++].match = match;
|
||||
} else {
|
||||
pmb[pmb_nr].wsd = 0;
|
||||
pmb[pmb_nr++].match = match;
|
||||
}
|
||||
}
|
||||
int contiguous = adjust_last_block(o, n, block_length);
|
||||
|
||||
if (adjust_last_block(o, n, block_length) &&
|
||||
pmb_nr && last_symbol != l->s)
|
||||
if (!contiguous && block_length > 1)
|
||||
/*
|
||||
* Rewind in case there is another match
|
||||
* starting at the second line of the block
|
||||
*/
|
||||
n -= block_length;
|
||||
else
|
||||
fill_potential_moved_blocks(o, match, l,
|
||||
&pmb, &pmb_alloc,
|
||||
&pmb_nr);
|
||||
|
||||
if (contiguous && pmb_nr && moved_symbol == l->s)
|
||||
flipped_block = (flipped_block + 1) % 2;
|
||||
else
|
||||
flipped_block = 0;
|
||||
|
||||
if (pmb_nr)
|
||||
moved_symbol = l->s;
|
||||
else
|
||||
moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
|
||||
|
||||
block_length = 0;
|
||||
}
|
||||
|
||||
@ -1229,17 +1179,12 @@ static void mark_color_as_moved(struct diff_options *o,
|
||||
if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
|
||||
l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
|
||||
}
|
||||
last_symbol = l->s;
|
||||
}
|
||||
adjust_last_block(o, n, block_length);
|
||||
|
||||
for(n = 0; n < pmb_nr; n++)
|
||||
moved_block_clear(&pmb[n]);
|
||||
free(pmb);
|
||||
}
|
||||
|
||||
#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
|
||||
(DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
|
||||
static void dim_moved_lines(struct diff_options *o)
|
||||
{
|
||||
int n;
|
||||
@ -1573,7 +1518,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
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, 0, 0, s};
|
||||
struct emitted_diff_symbol e = {
|
||||
.line = line, .len = len, .flags = flags, .s = s
|
||||
};
|
||||
|
||||
if (o->emitted_symbols)
|
||||
append_emitted_diff_symbol(o, &e);
|
||||
@ -6345,24 +6292,18 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
|
||||
|
||||
if (o->emitted_symbols) {
|
||||
if (o->color_moved) {
|
||||
struct hashmap add_lines, del_lines;
|
||||
struct mem_pool entry_pool;
|
||||
struct moved_entry_list *entry_list;
|
||||
|
||||
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);
|
||||
mem_pool_init(&entry_pool, 1024 * 1024);
|
||||
entry_list = add_lines_to_move_detection(o,
|
||||
&entry_pool);
|
||||
mark_color_as_moved(o, entry_list);
|
||||
if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
|
||||
dim_moved_lines(o);
|
||||
|
||||
hashmap_clear_and_free(&add_lines, struct moved_entry,
|
||||
ent);
|
||||
hashmap_clear_and_free(&del_lines, struct moved_entry,
|
||||
ent);
|
||||
mem_pool_discard(&entry_pool, 0);
|
||||
free(entry_list);
|
||||
}
|
||||
|
||||
for (i = 0; i < esm.nr; i++)
|
||||
|
57
t/perf/p4002-diff-color-moved.sh
Executable file
57
t/perf/p4002-diff-color-moved.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='Tests diff --color-moved performance'
|
||||
. ./perf-lib.sh
|
||||
|
||||
test_perf_default_repo
|
||||
|
||||
# The endpoints of the diff can be customized by setting TEST_REV_A
|
||||
# and TEST_REV_B in the environment when running this test.
|
||||
|
||||
rev="${TEST_REV_A:-v2.28.0}"
|
||||
if ! rev_a="$(git rev-parse --quiet --verify "$rev")"
|
||||
then
|
||||
skip_all="skipping because '$rev' was not found. \
|
||||
Use TEST_REV_A and TEST_REV_B to set the revs to use"
|
||||
test_done
|
||||
fi
|
||||
rev="${TEST_REV_B:-v2.29.0}"
|
||||
if ! rev_b="$(git rev-parse --quiet --verify "$rev")"
|
||||
then
|
||||
skip_all="skipping because '$rev' was not found. \
|
||||
Use TEST_REV_A and TEST_REV_B to set the revs to use"
|
||||
test_done
|
||||
fi
|
||||
|
||||
GIT_PAGER_IN_USE=1
|
||||
test_export GIT_PAGER_IN_USE rev_a rev_b
|
||||
|
||||
test_perf 'diff --no-color-moved --no-color-moved-ws large change' '
|
||||
git diff --no-color-moved --no-color-moved-ws $rev_a $rev_b
|
||||
'
|
||||
|
||||
test_perf 'diff --color-moved --no-color-moved-ws large change' '
|
||||
git diff --color-moved=zebra --no-color-moved-ws $rev_a $rev_b
|
||||
'
|
||||
|
||||
test_perf 'diff --color-moved-ws=allow-indentation-change large change' '
|
||||
git diff --color-moved=zebra --color-moved-ws=allow-indentation-change \
|
||||
$rev_a $rev_b
|
||||
'
|
||||
|
||||
test_perf 'log --no-color-moved --no-color-moved-ws' '
|
||||
git log --no-color-moved --no-color-moved-ws --no-merges --patch \
|
||||
-n1000 $rev_b
|
||||
'
|
||||
|
||||
test_perf 'log --color-moved --no-color-moved-ws' '
|
||||
git log --color-moved=zebra --no-color-moved-ws --no-merges --patch \
|
||||
-n1000 $rev_b
|
||||
'
|
||||
|
||||
test_perf 'log --color-moved-ws=allow-indentation-change' '
|
||||
git log --color-moved=zebra --color-moved-ws=allow-indentation-change \
|
||||
--no-merges --patch -n1000 $rev_b
|
||||
'
|
||||
|
||||
test_done
|
@ -1442,6 +1442,143 @@ test_expect_success 'detect permutations inside moved code -- dimmed-zebra' '
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'zebra alternate color is only used when necessary' '
|
||||
cat >old.txt <<-\EOF &&
|
||||
line 1A should be marked as oldMoved newMovedAlternate
|
||||
line 1B should be marked as oldMoved newMovedAlternate
|
||||
unchanged
|
||||
line 2A should be marked as oldMoved newMovedAlternate
|
||||
line 2B should be marked as oldMoved newMovedAlternate
|
||||
line 3A should be marked as oldMovedAlternate newMoved
|
||||
line 3B should be marked as oldMovedAlternate newMoved
|
||||
unchanged
|
||||
line 4A should be marked as oldMoved newMovedAlternate
|
||||
line 4B should be marked as oldMoved newMovedAlternate
|
||||
line 5A should be marked as oldMovedAlternate newMoved
|
||||
line 5B should be marked as oldMovedAlternate newMoved
|
||||
line 6A should be marked as oldMoved newMoved
|
||||
line 6B should be marked as oldMoved newMoved
|
||||
EOF
|
||||
cat >new.txt <<-\EOF &&
|
||||
line 1A should be marked as oldMoved newMovedAlternate
|
||||
line 1B should be marked as oldMoved newMovedAlternate
|
||||
unchanged
|
||||
line 3A should be marked as oldMovedAlternate newMoved
|
||||
line 3B should be marked as oldMovedAlternate newMoved
|
||||
line 2A should be marked as oldMoved newMovedAlternate
|
||||
line 2B should be marked as oldMoved newMovedAlternate
|
||||
unchanged
|
||||
line 6A should be marked as oldMoved newMoved
|
||||
line 6B should be marked as oldMoved newMoved
|
||||
line 4A should be marked as oldMoved newMovedAlternate
|
||||
line 4B should be marked as oldMoved newMovedAlternate
|
||||
line 5A should be marked as oldMovedAlternate newMoved
|
||||
line 5B should be marked as oldMovedAlternate newMoved
|
||||
EOF
|
||||
test_expect_code 1 git diff --no-index --color --color-moved=zebra \
|
||||
--color-moved-ws=allow-indentation-change \
|
||||
old.txt new.txt >output &&
|
||||
grep -v index output | test_decode_color >actual &&
|
||||
cat >expected <<-\EOF &&
|
||||
<BOLD>diff --git a/old.txt b/new.txt<RESET>
|
||||
<BOLD>--- a/old.txt<RESET>
|
||||
<BOLD>+++ b/new.txt<RESET>
|
||||
<CYAN>@@ -1,14 +1,14 @@<RESET>
|
||||
<BOLD;MAGENTA>-line 1A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;MAGENTA>-line 1B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 1A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 1B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
unchanged<RESET>
|
||||
<BOLD;MAGENTA>-line 2A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;MAGENTA>-line 2B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;BLUE>-line 3A should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;BLUE>-line 3B should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 3A should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 3B should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW> line 2A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW> line 2B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
unchanged<RESET>
|
||||
<BOLD;MAGENTA>-line 4A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;MAGENTA>-line 4B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;BLUE>-line 5A should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;BLUE>-line 5B should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;MAGENTA>-line 6A should be marked as oldMoved newMoved<RESET>
|
||||
<BOLD;MAGENTA>-line 6B should be marked as oldMoved newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 6A should be marked as oldMoved newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 6B should be marked as oldMoved newMoved<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW> line 4A should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW> line 4B should be marked as oldMoved newMovedAlternate<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 5A should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN> line 5B should be marked as oldMovedAlternate newMoved<RESET>
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'short lines of opposite sign do not get marked as moved' '
|
||||
cat >old.txt <<-\EOF &&
|
||||
this line should be marked as moved
|
||||
unchanged
|
||||
unchanged
|
||||
unchanged
|
||||
unchanged
|
||||
too short
|
||||
this line should be marked as oldMoved newMoved
|
||||
this line should be marked as oldMovedAlternate newMoved
|
||||
unchanged 1
|
||||
unchanged 2
|
||||
unchanged 3
|
||||
unchanged 4
|
||||
this line should be marked as oldMoved newMoved/newMovedAlternate
|
||||
EOF
|
||||
cat >new.txt <<-\EOF &&
|
||||
too short
|
||||
unchanged
|
||||
unchanged
|
||||
this line should be marked as moved
|
||||
too short
|
||||
unchanged
|
||||
unchanged
|
||||
this line should be marked as oldMoved newMoved/newMovedAlternate
|
||||
unchanged 1
|
||||
unchanged 2
|
||||
this line should be marked as oldMovedAlternate newMoved
|
||||
this line should be marked as oldMoved newMoved/newMovedAlternate
|
||||
unchanged 3
|
||||
this line should be marked as oldMoved newMoved
|
||||
unchanged 4
|
||||
EOF
|
||||
test_expect_code 1 git diff --no-index --color --color-moved=zebra \
|
||||
old.txt new.txt >output && cat output &&
|
||||
grep -v index output | test_decode_color >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
<BOLD>diff --git a/old.txt b/new.txt<RESET>
|
||||
<BOLD>--- a/old.txt<RESET>
|
||||
<BOLD>+++ b/new.txt<RESET>
|
||||
<CYAN>@@ -1,13 +1,15 @@<RESET>
|
||||
<BOLD;MAGENTA>-this line should be marked as moved<RESET>
|
||||
<GREEN>+<RESET><GREEN>too short<RESET>
|
||||
unchanged<RESET>
|
||||
unchanged<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>this line should be marked as moved<RESET>
|
||||
<GREEN>+<RESET><GREEN>too short<RESET>
|
||||
unchanged<RESET>
|
||||
unchanged<RESET>
|
||||
<RED>-too short<RESET>
|
||||
<BOLD;MAGENTA>-this line should be marked as oldMoved newMoved<RESET>
|
||||
<BOLD;BLUE>-this line should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>this line should be marked as oldMoved newMoved/newMovedAlternate<RESET>
|
||||
unchanged 1<RESET>
|
||||
unchanged 2<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>this line should be marked as oldMovedAlternate newMoved<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW>this line should be marked as oldMoved newMoved/newMovedAlternate<RESET>
|
||||
unchanged 3<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>this line should be marked as oldMoved newMoved<RESET>
|
||||
unchanged 4<RESET>
|
||||
<BOLD;MAGENTA>-this line should be marked as oldMoved newMoved/newMovedAlternate<RESET>
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'cmd option assumes configured colored-moved' '
|
||||
test_config color.diff.oldMoved "magenta" &&
|
||||
test_config color.diff.newMoved "cyan" &&
|
||||
@ -1833,6 +1970,52 @@ test_expect_success '--color-moved treats adjacent blocks as separate for MIN_AL
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success '--color-moved rewinds for MIN_ALNUM_COUNT' '
|
||||
git reset --hard &&
|
||||
test_write_lines >file \
|
||||
A B C one two three four five six seven D E F G H I J &&
|
||||
git add file &&
|
||||
test_write_lines >file \
|
||||
one two A B C D E F G H I J two three four five six seven &&
|
||||
git diff --color-moved=zebra -- file &&
|
||||
|
||||
git diff --color-moved=zebra --color -- file >actual.raw &&
|
||||
grep -v "index" actual.raw | test_decode_color >actual &&
|
||||
cat >expected <<-\EOF &&
|
||||
<BOLD>diff --git a/file b/file<RESET>
|
||||
<BOLD>--- a/file<RESET>
|
||||
<BOLD>+++ b/file<RESET>
|
||||
<CYAN>@@ -1,13 +1,8 @@<RESET>
|
||||
<GREEN>+<RESET><GREEN>one<RESET>
|
||||
<GREEN>+<RESET><GREEN>two<RESET>
|
||||
A<RESET>
|
||||
B<RESET>
|
||||
C<RESET>
|
||||
<RED>-one<RESET>
|
||||
<BOLD;MAGENTA>-two<RESET>
|
||||
<BOLD;MAGENTA>-three<RESET>
|
||||
<BOLD;MAGENTA>-four<RESET>
|
||||
<BOLD;MAGENTA>-five<RESET>
|
||||
<BOLD;MAGENTA>-six<RESET>
|
||||
<BOLD;MAGENTA>-seven<RESET>
|
||||
D<RESET>
|
||||
E<RESET>
|
||||
F<RESET>
|
||||
<CYAN>@@ -15,3 +10,9 @@<RESET> <RESET>G<RESET>
|
||||
H<RESET>
|
||||
I<RESET>
|
||||
J<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>two<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>three<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>four<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>five<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>six<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>seven<RESET>
|
||||
EOF
|
||||
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'move detection with submodules' '
|
||||
test_create_repo bananas &&
|
||||
echo ripe >bananas/recipe &&
|
||||
@ -2023,10 +2206,10 @@ EMPTY=''
|
||||
test_expect_success 'compare mixed whitespace delta across moved blocks' '
|
||||
|
||||
git reset --hard &&
|
||||
tr Q_ "\t " <<-EOF >text.txt &&
|
||||
${EMPTY}
|
||||
____too short without
|
||||
${EMPTY}
|
||||
tr "^|Q_" "\f\v\t " <<-EOF >text.txt &&
|
||||
^__
|
||||
|____too short without
|
||||
^
|
||||
___being grouped across blank line
|
||||
${EMPTY}
|
||||
context
|
||||
@ -2045,7 +2228,7 @@ test_expect_success 'compare mixed whitespace delta across moved blocks' '
|
||||
git add text.txt &&
|
||||
git commit -m "add text.txt" &&
|
||||
|
||||
tr Q_ "\t " <<-EOF >text.txt &&
|
||||
tr "^|Q_" "\f\v\t " <<-EOF >text.txt &&
|
||||
context
|
||||
lines
|
||||
to
|
||||
@ -2056,7 +2239,7 @@ test_expect_success 'compare mixed whitespace delta across moved blocks' '
|
||||
${EMPTY}
|
||||
QQtoo short without
|
||||
${EMPTY}
|
||||
Q_______being grouped across blank line
|
||||
^Q_______being grouped across blank line
|
||||
${EMPTY}
|
||||
Q_QThese two lines have had their
|
||||
indentation reduced by four spaces
|
||||
@ -2068,16 +2251,16 @@ test_expect_success 'compare mixed whitespace delta across moved blocks' '
|
||||
-c core.whitespace=space-before-tab \
|
||||
diff --color --color-moved --ws-error-highlight=all \
|
||||
--color-moved-ws=allow-indentation-change >actual.raw &&
|
||||
grep -v "index" actual.raw | test_decode_color >actual &&
|
||||
grep -v "index" actual.raw | tr "\f\v" "^|" | test_decode_color >actual &&
|
||||
|
||||
cat <<-\EOF >expected &&
|
||||
<BOLD>diff --git a/text.txt b/text.txt<RESET>
|
||||
<BOLD>--- a/text.txt<RESET>
|
||||
<BOLD>+++ b/text.txt<RESET>
|
||||
<CYAN>@@ -1,16 +1,16 @@<RESET>
|
||||
<BOLD;MAGENTA>-<RESET>
|
||||
<BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> too short without<RESET>
|
||||
<BOLD;MAGENTA>-<RESET>
|
||||
<BOLD;MAGENTA>-<RESET><BOLD;MAGENTA>^<RESET><BRED> <RESET>
|
||||
<BOLD;MAGENTA>-<RESET><BOLD;MAGENTA>| too short without<RESET>
|
||||
<BOLD;MAGENTA>-<RESET><BOLD;MAGENTA>^<RESET>
|
||||
<BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> being grouped across blank line<RESET>
|
||||
<BOLD;MAGENTA>-<RESET>
|
||||
<RESET>context<RESET>
|
||||
@ -2097,7 +2280,7 @@ test_expect_success 'compare mixed whitespace delta across moved blocks' '
|
||||
<BOLD;YELLOW>+<RESET>
|
||||
<BOLD;YELLOW>+<RESET> <BOLD;YELLOW>too short without<RESET>
|
||||
<BOLD;YELLOW>+<RESET>
|
||||
<BOLD;YELLOW>+<RESET> <BOLD;YELLOW> being grouped across blank line<RESET>
|
||||
<BOLD;YELLOW>+<RESET><BOLD;YELLOW>^ being grouped across blank line<RESET>
|
||||
<BOLD;YELLOW>+<RESET>
|
||||
<BOLD;CYAN>+<RESET> <BRED> <RESET> <BOLD;CYAN>These two lines have had their<RESET>
|
||||
<BOLD;CYAN>+<RESET><BOLD;CYAN>indentation reduced by four spaces<RESET>
|
||||
|
Loading…
Reference in New Issue
Block a user