This marks moved code still as blocks when their indentation level
changes uniformly.

Signed-off-by: Stefan Beller <sbel...@google.com>
---
 Documentation/diff-options.txt |  4 ++
 diff.c                         | 83 +++++++++++++++++++++++++++++++---
 diff.h                         |  2 +
 t/t4015-diff-whitespace.sh     | 54 ++++++++++++++++++++++
 4 files changed, 137 insertions(+), 6 deletions(-)

diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 7b2527b9a19..facdbc8f95f 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -304,6 +304,10 @@ dimmed_zebra::
        Ignore whitespace when comparing lines when performing the move
        detection for --color-moved.  This ignores differences even if
        one line has whitespace where the other line has none.
+--color-moved-[no-]ignore-space-prefix-delta::
+       Ignores whitespace when comparing lines when performing the move
+       detection for --color-moved. This ignores uniform differences
+       of white space at the beginning lines in moved blocks.
 
 --word-diff[=<mode>]::
        Show a word diff, using the <mode> to delimit changed words.
diff --git a/diff.c b/diff.c
index b5819dd538f..1227a4d2a83 100644
--- a/diff.c
+++ b/diff.c
@@ -709,6 +709,31 @@ struct moved_entry {
        struct moved_entry *next_line;
 };
 
+struct ws_delta {
+       char *string; /* The prefix delta, which is the same in the block */
+       int direction; /* adding or removing the line? */
+       int missmatch; /* in the remainder */
+};
+#define WS_DELTA_INIT { NULL, 0, 0 }
+
+static void 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;
+
+       out->missmatch = !memcmp(longer->line + d, shorter->line, shorter->len);
+       out->string = xmemdupz(longer->line, d);
+       out->direction = (a == longer);
+}
+
+static int compare_ws_delta(const struct ws_delta *a, const struct ws_delta *b)
+{
+       return a->direction == b->direction && !strcmp(a->string, b->string);
+}
+
 static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
                           const void *entry,
                           const void *entry_or_key,
@@ -720,6 +745,15 @@ static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
        unsigned flags = diffopt->color_moved_ws_handling
                         & XDF_WHITESPACE_FLAGS;
 
+       if (diffopt->color_moved_ws_handling & COLOR_MOVED_DELTA_WHITESPACES)
+               /*
+                * 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);
@@ -772,7 +806,8 @@ static void add_lines_to_move_detection(struct diff_options 
*o,
 }
 
 static int shrink_potential_moved_blocks(struct moved_entry **pmb,
-                                        int pmb_nr)
+                                        int pmb_nr,
+                                        struct ws_delta **wsd)
 {
        int lp, rp;
 
@@ -788,6 +823,10 @@ static int shrink_potential_moved_blocks(struct 
moved_entry **pmb,
 
                if (lp < pmb_nr && rp > -1 && lp < rp) {
                        pmb[lp] = pmb[rp];
+                       if (*wsd) {
+                               free((*wsd)[lp].string);
+                               (*wsd)[lp] = (*wsd)[rp];
+                       }
                        pmb[rp] = NULL;
                        rp--;
                        lp++;
@@ -837,8 +876,11 @@ static void mark_color_as_moved(struct diff_options *o,
 {
        struct moved_entry **pmb = NULL; /* potentially moved blocks */
        int pmb_nr = 0, pmb_alloc = 0;
-       int n, flipped_block = 1, block_length = 0;
 
+       struct ws_delta *wsd = NULL; /* white space deltas between pmb */
+       int wsd_alloc = 0;
+
+       int n, flipped_block = 1, block_length = 0;
 
        for (n = 0; n < o->emitted_symbols->nr; n++) {
                struct hashmap *hm = NULL;
@@ -881,14 +923,31 @@ static void mark_color_as_moved(struct diff_options *o,
                        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;
+
+                       if (o->color_moved_ws_handling & 
COLOR_MOVED_DELTA_WHITESPACES) {
+                               struct ws_delta out = WS_DELTA_INIT;
+
+                               if (pnext)
+                                       compute_ws_delta(l, pnext->es, &out);
+                               if (pnext &&
+                                   !hm->cmpfn(o, pnext, match, NULL) &&
+                                   compare_ws_delta(&out, &wsd[i])) {
+                                       pmb[i] = p->next_line;
+                                       /* wsd[i] is the same */
+                               } else {
+                                       pmb[i] = NULL;
+                               }
+                               free(out.string);
                        } else {
-                               pmb[i] = 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);
+               pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr, &wsd);
 
                if (pmb_nr == 0) {
                        /*
@@ -897,6 +956,10 @@ 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_DELTA_WHITESPACES) {
+                                       ALLOC_GROW(wsd, pmb_nr + 1, wsd_alloc);
+                                       compute_ws_delta(l, match->es, 
&wsd[pmb_nr]);
+                               }
                                pmb[pmb_nr++] = match;
                        }
 
@@ -914,6 +977,7 @@ static void mark_color_as_moved(struct diff_options *o,
        adjust_last_block(o, n, block_length);
 
        free(pmb);
+       free(wsd);
 }
 
 #define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
@@ -4647,12 +4711,16 @@ int diff_opt_parse(struct diff_options *options,
                options->color_moved_ws_handling &= 
~XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(arg, "--color-moved-no-ignore-space-at-eol"))
                options->color_moved_ws_handling &= 
~XDF_IGNORE_WHITESPACE_AT_EOL;
+       else if (!strcmp(arg, "--color-moved-no-ignore-space-prefix-delta"))
+               options->color_moved_ws_handling &= 
~COLOR_MOVED_DELTA_WHITESPACES;
        else if (!strcmp(arg, "--color-moved-ignore-all-space"))
                options->color_moved_ws_handling |= XDF_IGNORE_WHITESPACE;
        else if (!strcmp(arg, "--color-moved-ignore-space-change"))
                options->color_moved_ws_handling |= 
XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(arg, "--color-moved-ignore-space-at-eol"))
                options->color_moved_ws_handling |= 
XDF_IGNORE_WHITESPACE_AT_EOL;
+       else if (!strcmp(arg, "--color-moved-ignore-space-prefix-delta"))
+               options->color_moved_ws_handling |= 
COLOR_MOVED_DELTA_WHITESPACES;
        else if (!strcmp(arg, "--indent-heuristic"))
                DIFF_XDL_SET(options, INDENT_HEURISTIC);
        else if (!strcmp(arg, "--no-indent-heuristic"))
@@ -5558,6 +5626,9 @@ static void diff_flush_patch_all_file_pairs(struct 
diff_options *o)
                        hashmap_init(&del_lines, moved_entry_cmp, o, 0);
                        hashmap_init(&add_lines, moved_entry_cmp, o, 0);
 
+                       if (o->color_moved_ws_handling & 
COLOR_MOVED_DELTA_WHITESPACES)
+                               o->color_moved_ws_handling |= 
XDF_IGNORE_WHITESPACE;
+
                        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)
diff --git a/diff.h b/diff.h
index de5dc680051..b00ea76c083 100644
--- a/diff.h
+++ b/diff.h
@@ -214,6 +214,8 @@ struct diff_options {
        } color_moved;
        #define COLOR_MOVED_DEFAULT COLOR_MOVED_ZEBRA
        #define COLOR_MOVED_MIN_ALNUM_COUNT 20
+       /* XDF_WHITESPACE_FLAGS regarding block detection are set at 2, 3, 4 */
+       #define COLOR_MOVED_DELTA_WHITESPACES   (1 << 22)
        int color_moved_ws_handling;
 };
 
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 751fc478dde..37ff528822f 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -1847,4 +1847,58 @@ test_expect_success 'only move detection ignores white 
spaces' '
        test_cmp expected actual
 '
 
+test_expect_success 'compare whitespace delta across moved blocks' '
+
+       git reset --hard &&
+       q_to_tab <<-\EOF >text.txt &&
+       QIndented
+       QText across
+       Qthree lines
+       QBut! <- this stands out
+       Qthis one
+       QQline did
+       Qnot adjust
+       EOF
+
+       git add text.txt &&
+       git commit -m "add text.txt" &&
+
+       q_to_tab <<-\EOF >text.txt &&
+       QQIndented
+       QQText across
+       QQthree lines
+       QQQBut! <- this stands out
+       this one
+       Qline did
+       not adjust
+       EOF
+
+       git diff --color --color-moved --color-moved-ignore-space-prefix-delta |
+               grep -v "index" |
+               test_decode_color >actual &&
+
+       q_to_tab <<-\EOF >expected &&
+               <BOLD>diff --git a/text.txt b/text.txt<RESET>
+               <BOLD>--- a/text.txt<RESET>
+               <BOLD>+++ b/text.txt<RESET>
+               <CYAN>@@ -1,7 +1,7 @@<RESET>
+               <BOLD;MAGENTA>-QIndented<RESET>
+               <BOLD;MAGENTA>-QText across<RESET>
+               <BOLD;MAGENTA>-Qthree lines<RESET>
+               <RED>-QBut! <- this stands out<RESET>
+               <BOLD;MAGENTA>-Qthis one<RESET>
+               <BOLD;MAGENTA>-QQline did<RESET>
+               <BOLD;MAGENTA>-Qnot adjust<RESET>
+               <BOLD;YELLOW>+<RESET>QQ<BOLD;YELLOW>Indented<RESET>
+               <BOLD;YELLOW>+<RESET>QQ<BOLD;YELLOW>Text across<RESET>
+               <BOLD;YELLOW>+<RESET>QQ<BOLD;YELLOW>three lines<RESET>
+               <GREEN>+<RESET>QQQ<GREEN>But! <- this stands out<RESET>
+               <BOLD;YELLOW>+<RESET><BOLD;YELLOW>this one<RESET>
+               <BOLD;YELLOW>+<RESET>Q<BOLD;YELLOW>line did<RESET>
+               <BOLD;YELLOW>+<RESET><BOLD;YELLOW>not adjust<RESET>
+       EOF
+
+       test_cmp expected actual
+'
+
 test_done
-- 
2.17.0.582.gccdcbd54c44.dirty

Reply via email to