diff --color-moved: avoid false short line matches and bad zebra coloring

When marking moved lines it is possible for a block of potential
matched lines to extend past a change in sign when there is a sequence
of added lines whose text matches the text of a sequence of deleted
and added lines. Most of the time either `match` will be NULL or
`pmb_advance_or_null()` will fail when the loop encounters a change of
sign but there are corner cases where `match` is non-NULL and
`pmb_advance_or_null()` successfully advances the moved block despite
the change in sign.

One consequence of this is highlighting a short line as moved when it
should not be. For example

-moved line  # Correctly highlighted as moved
+short line  # Wrongly highlighted as moved
 context
+moved line  # Correctly highlighted as moved
+short line
 context
-short line

The other consequence is coloring a moved addition following a moved
deletion in the wrong color. In the example below the first "+moved
line 3" should be highlighted as newMoved not newMovedAlternate.

-moved line 1 # Correctly highlighted as oldMoved
-moved line 2 # Correctly highlighted as oldMovedAlternate
+moved line 3 # Wrongly highlighted as newMovedAlternate
 context      # Everything else is highlighted correctly
+moved line 2
+moved line 3
 context
+moved line 1
-moved line 3

These false matches are more likely when using --color-moved-ws with
the exception of --color-moved-ws=allow-indentation-change which ties
the sign of the current whitespace delta to the sign of the line to
avoid this problem. The fix is to check that the sign of the new line
being matched is the same as the sign of the line that started the
block of potential matches.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Phillip Wood 2021-12-09 10:30:00 +00:00 committed by Junio C Hamano
parent eb315457f6
commit eb89352504
2 changed files with 76 additions and 6 deletions

17
diff.c
View File

@ -1176,7 +1176,7 @@ static void mark_color_as_moved(struct diff_options *o,
struct moved_block *pmb = NULL; /* potentially moved blocks */ struct moved_block *pmb = NULL; /* potentially moved blocks */
int pmb_nr = 0, pmb_alloc = 0; int pmb_nr = 0, pmb_alloc = 0;
int n, flipped_block = 0, block_length = 0; int n, flipped_block = 0, block_length = 0;
enum diff_symbol last_symbol = 0; enum diff_symbol moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
for (n = 0; n < o->emitted_symbols->nr; n++) { for (n = 0; n < o->emitted_symbols->nr; n++) {
@ -1202,7 +1202,7 @@ static void mark_color_as_moved(struct diff_options *o,
flipped_block = 0; flipped_block = 0;
} }
if (!match) { if (pmb_nr && (!match || l->s != moved_symbol)) {
int i; int i;
if (!adjust_last_block(o, n, block_length) && if (!adjust_last_block(o, n, block_length) &&
@ -1219,12 +1219,13 @@ static void mark_color_as_moved(struct diff_options *o,
pmb_nr = 0; pmb_nr = 0;
block_length = 0; block_length = 0;
flipped_block = 0; flipped_block = 0;
last_symbol = l->s; }
if (!match) {
moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
continue; continue;
} }
if (o->color_moved == COLOR_MOVED_PLAIN) { if (o->color_moved == COLOR_MOVED_PLAIN) {
last_symbol = l->s;
l->flags |= DIFF_SYMBOL_MOVED_LINE; l->flags |= DIFF_SYMBOL_MOVED_LINE;
continue; continue;
} }
@ -1251,11 +1252,16 @@ static void mark_color_as_moved(struct diff_options *o,
&pmb, &pmb_alloc, &pmb, &pmb_alloc,
&pmb_nr); &pmb_nr);
if (contiguous && pmb_nr && last_symbol == l->s) if (contiguous && pmb_nr && moved_symbol == l->s)
flipped_block = (flipped_block + 1) % 2; flipped_block = (flipped_block + 1) % 2;
else else
flipped_block = 0; flipped_block = 0;
if (pmb_nr)
moved_symbol = l->s;
else
moved_symbol = DIFF_SYMBOL_BINARY_DIFF_HEADER;
block_length = 0; block_length = 0;
} }
@ -1265,7 +1271,6 @@ static void mark_color_as_moved(struct diff_options *o,
if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS) if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
} }
last_symbol = l->s;
} }
adjust_last_block(o, n, block_length); adjust_last_block(o, n, block_length);

View File

@ -1514,6 +1514,71 @@ test_expect_success 'zebra alternate color is only used when necessary' '
test_cmp expected actual 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_expect_success 'cmd option assumes configured colored-moved' '
test_config color.diff.oldMoved "magenta" && test_config color.diff.oldMoved "magenta" &&
test_config color.diff.newMoved "cyan" && test_config color.diff.newMoved "cyan" &&