diff.c: add white space mode to move detection that allows indent changes
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index 4963819e53076b6e21784368fbd0664ded1cc735..7810a4733f8f230af54d1d8b8d96484ebea3a2bc 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -302,12 +302,18 @@ static int parse_color_moved_ws(const char *arg)
                        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;
@@ -737,7 +743,91 @@ 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;
+
+       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,
@@ -750,6 +840,16 @@ 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_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);
@@ -765,6 +865,7 @@ static struct moved_entry *prepare_entry(struct diff_options *o,
        ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
        ret->es = l;
        ret->next_line = NULL;
+       ret->wsd = NULL;
 
        return ret;
 }
@@ -820,6 +921,37 @@ static void pmb_advance_or_null(struct diff_options *o,
        }
 }
 
+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)
 {
@@ -837,6 +969,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++;
@@ -924,7 +1060,11 @@ static void mark_color_as_moved(struct diff_options *o,
                if (o->color_moved == COLOR_MOVED_PLAIN)
                        continue;
 
-               pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
+               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);
 
@@ -935,7 +1075,17 @@ 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);
-                               pmb[pmb_nr++] = match;
+                               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;
@@ -5583,6 +5733,10 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
                if (o->color_moved) {
                        struct hashmap add_lines, del_lines;
 
+                       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);