16
#include "userdiff.h"
17
#include "submodule-config.h"
18
#include "submodule.h"
19
+#include "hashmap.h"
20
#include "ll-merge.h"
21
#include "string-list.h"
22
#include "argv-array.h"
33
static int diff_rename_limit_default = 400;
34
static int diff_suppress_blank_empty;
35
static int diff_use_color_default = -1;
36
+static int diff_color_moved_default;
37
static int diff_context_default = 3;
38
static int diff_interhunk_context_default;
39
static const char *diff_word_regex_cfg;
58
GIT_COLOR_YELLOW, /* COMMIT */
59
GIT_COLOR_BG_RED, /* WHITESPACE */
60
GIT_COLOR_NORMAL, /* FUNCINFO */
61
+ GIT_COLOR_MAGENTA, /* OLD_MOVED */
62
+ GIT_COLOR_BLUE, /* OLD_MOVED ALTERNATIVE */
63
+ GIT_COLOR_CYAN, /* NEW_MOVED */
64
+ GIT_COLOR_YELLOW, /* NEW_MOVED ALTERNATIVE */
65
};
66
67
static NORETURN void die_want_option(const char *option_name)
87
return DIFF_WHITESPACE;
88
if (!strcasecmp(var, "func"))
89
return DIFF_FUNCINFO;
90
+ if (!strcasecmp(var, "oldmoved"))
91
+ return DIFF_FILE_OLD_MOVED;
92
+ if (!strcasecmp(var, "oldmovedalternative"))
93
+ return DIFF_FILE_OLD_MOVED_ALT;
94
+ if (!strcasecmp(var, "newmoved"))
95
+ return DIFF_FILE_NEW_MOVED;
96
+ if (!strcasecmp(var, "newmovedalternative"))
97
+ return DIFF_FILE_NEW_MOVED_ALT;
98
return -1;
99
}
100
243
return 0;
244
}
245
246
+static int parse_color_moved(const char *arg)
247
+{
248
+ switch (git_parse_maybe_bool(arg)) {
249
+ case 0:
250
+ return COLOR_MOVED_NO;
251
+ case 1:
252
+ return COLOR_MOVED_DEFAULT;
253
+ default:
254
+ break;
255
+ }
256
+
257
+ if (!strcmp(arg, "no"))
258
+ return COLOR_MOVED_NO;
259
+ else if (!strcmp(arg, "zebra"))
260
+ return COLOR_MOVED_ZEBRA;
261
+ else if (!strcmp(arg, "default"))
262
+ return COLOR_MOVED_DEFAULT;
263
+ else
264
+ return error(_("color moved setting must be one of 'no', 'default', 'zebra'"));
265
+}
266
+
267
int git_diff_ui_config(const char *var, const char *value, void *cb)
268
{
269
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
270
diff_use_color_default = git_config_colorbool(var, value);
271
return 0;
272
}
273
+ if (!strcmp(var, "diff.colormoved")) {
274
+ int cm = parse_color_moved(value);
275
+ if (cm < 0)
276
+ return -1;
277
+ diff_color_moved_default = cm;
278
+ return 0;
279
+ }
280
if (!strcmp(var, "diff.context")) {
281
diff_context_default = git_config_int(var, value);
282
if (diff_context_default < 0)
644
* 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
645
* 16 is marking if the line is blank at EOF
646
*/
605
-#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16)
647
+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16)
648
+#define DIFF_SYMBOL_MOVED_LINE (1<<17)
649
+#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18)
650
#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
651
652
/*
689
f->line = e->line ? xmemdupz(e->line, e->len) : NULL;
690
}
691
692
+struct moved_entry {
693
+ struct hashmap_entry ent;
694
+ const struct emitted_diff_symbol *es;
695
+ struct moved_entry *next_line;
696
+};
697
+
698
+static int next_byte(const char **cp, const char **endp,
699
+ const struct diff_options *diffopt)
700
+{
701
+ int retval;
702
+
703
+ if (*cp > *endp)
704
+ return -1;
705
+
706
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) {
707
+ while (*cp < *endp && isspace(**cp))
708
+ (*cp)++;
709
+ /*
710
+ * After skipping a couple of whitespaces, we still have to
711
+ * account for one space.
712
+ */
713
+ return (int)' ';
714
+ }
715
+
716
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) {
717
+ while (*cp < *endp && isspace(**cp))
718
+ (*cp)++;
719
+ /* return the first non-ws character via the usual below */
720
+ }
721
+
722
+ retval = (unsigned char)(**cp);
723
+ (*cp)++;
724
+ return retval;
725
+}
726
+
727
+static int moved_entry_cmp(const struct diff_options *diffopt,
728
+ const struct moved_entry *a,
729
+ const struct moved_entry *b,
730
+ const void *keydata)
731
+{
732
+ const char *ap = a->es->line, *ae = a->es->line + a->es->len;
733
+ const char *bp = b->es->line, *be = b->es->line + b->es->len;
734
+
735
+ if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS))
736
+ return a->es->len != b->es->len || memcmp(ap, bp, a->es->len);
737
+
738
+ if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) {
739
+ while (ae > ap && isspace(*ae))
740
+ ae--;
741
+ while (be > bp && isspace(*be))
742
+ be--;
743
+ }
744
+
745
+ while (1) {
746
+ int ca, cb;
747
+ ca = next_byte(&ap, &ae, diffopt);
748
+ cb = next_byte(&bp, &be, diffopt);
749
+ if (ca != cb)
750
+ return 1;
751
+ if (ca < 0)
752
+ return 0;
753
+ }
754
+}
755
+
756
+static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o)
757
+{
758
+ if (o->xdl_opts & XDF_WHITESPACE_FLAGS) {
759
+ static struct strbuf sb = STRBUF_INIT;
760
+ const char *ap = es->line, *ae = es->line + es->len;
761
+ int c;
762
+
763
+ strbuf_reset(&sb);
764
+ while (ae > ap && isspace(*ae))
765
+ ae--;
766
+ while ((c = next_byte(&ap, &ae, o)) > 0)
767
+ strbuf_addch(&sb, c);
768
+
769
+ return memhash(sb.buf, sb.len);
770
+ } else {
771
+ return memhash(es->line, es->len);
772
+ }
773
+}
774
+
775
+static struct moved_entry *prepare_entry(struct diff_options *o,
776
+ int line_no)
777
+{
778
+ struct moved_entry *ret = xmalloc(sizeof(*ret));
779
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
780
+
781
+ ret->ent.hash = get_string_hash(l, o);
782
+ ret->es = l;
783
+ ret->next_line = NULL;
784
+
785
+ return ret;
786
+}
787
+
788
+static void add_lines_to_move_detection(struct diff_options *o,
789
+ struct hashmap *add_lines,
790
+ struct hashmap *del_lines)
791
+{
792
+ struct moved_entry *prev_line = NULL;
793
+
794
+ int n;
795
+ for (n = 0; n < o->emitted_symbols->nr; n++) {
796
+ struct hashmap *hm;
797
+ struct moved_entry *key;
798
+
799
+ switch (o->emitted_symbols->buf[n].s) {
800
+ case DIFF_SYMBOL_PLUS:
801
+ hm = add_lines;
802
+ break;
803
+ case DIFF_SYMBOL_MINUS:
804
+ hm = del_lines;
805
+ break;
806
+ default:
807
+ prev_line = NULL;
808
+ continue;
809
+ }
810
+
811
+ key = prepare_entry(o, n);
812
+ if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
813
+ prev_line->next_line = key;
814
+
815
+ hashmap_add(hm, key);
816
+ prev_line = key;
817
+ }
818
+}
819
+
820
+static int shrink_potential_moved_blocks(struct moved_entry **pmb,
821
+ int pmb_nr)
822
+{
823
+ int lp, rp;
824
+
825
+ /* Shrink the set of potential block to the remaining running */
826
+ for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
827
+ while (lp < pmb_nr && pmb[lp])
828
+ lp++;
829
+ /* lp points at the first NULL now */
830
+
831
+ while (rp > -1 && !pmb[rp])
832
+ rp--;
833
+ /* rp points at the last non-NULL */
834
+
835
+ if (lp < pmb_nr && rp > -1 && lp < rp) {
836
+ pmb[lp] = pmb[rp];
837
+ pmb[rp] = NULL;
838
+ rp--;
839
+ lp++;
840
+ }
841
+ }
842
+
843
+ /* Remember the number of running sets */
844
+ return rp + 1;
845
+}
846
+
847
+/* Find blocks of moved code, delegate actual coloring decision to helper */
848
+static void mark_color_as_moved(struct diff_options *o,
849
+ struct hashmap *add_lines,
850
+ struct hashmap *del_lines)
851
+{
852
+ struct moved_entry **pmb = NULL; /* potentially moved blocks */
853
+ int pmb_nr = 0, pmb_alloc = 0;
854
+ int n, flipped_block = 1, block_length = 0;
855
+
856
+
857
+ for (n = 0; n < o->emitted_symbols->nr; n++) {
858
+ struct hashmap *hm = NULL;
859
+ struct moved_entry *key;
860
+ struct moved_entry *match = NULL;
861
+ struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
862
+ int i;
863
+
864
+ switch (l->s) {
865
+ case DIFF_SYMBOL_PLUS:
866
+ hm = del_lines;
867
+ key = prepare_entry(o, n);
868
+ match = hashmap_get(hm, key, o);
869
+ free(key);
870
+ break;
871
+ case DIFF_SYMBOL_MINUS:
872
+ hm = add_lines;
873
+ key = prepare_entry(o, n);
874
+ match = hashmap_get(hm, key, o);
875
+ free(key);
876
+ break;
877
+ default:
878
+ flipped_block = 1;
879
+ }
880
+
881
+ if (!match) {
882
+ if (block_length < COLOR_MOVED_MIN_BLOCK_LENGTH) {
883
+ for (i = 0; i < block_length + 1; i++) {
884
+ l = &o->emitted_symbols->buf[n - i];
885
+ l->flags &= ~DIFF_SYMBOL_MOVED_LINE;
886
+ }
887
+ }
888
+ pmb_nr = 0;
889
+ block_length = 0;
890
+ continue;
891
+ }
892
+
893
+ l->flags |= DIFF_SYMBOL_MOVED_LINE;
894
+ block_length++;
895
+
896
+ /* Check any potential block runs, advance each or nullify */
897
+ for (i = 0; i < pmb_nr; i++) {
898
+ struct moved_entry *p = pmb[i];
899
+ struct moved_entry *pnext = (p && p->next_line) ?
900
+ p->next_line : NULL;
901
+ if (pnext && !hm->cmpfn(o, pnext, match, NULL)) {
902
+ pmb[i] = p->next_line;
903
+ } else {
904
+ pmb[i] = NULL;
905
+ }
906
+ }
907
+
908
+ pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
909
+
910
+ if (pmb_nr == 0) {
911
+ /*
912
+ * The current line is the start of a new block.
913
+ * Setup the set of potential blocks.
914
+ */
915
+ for (; match; match = hashmap_get_next(hm, match)) {
916
+ ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
917
+ pmb[pmb_nr++] = match;
918
+ }
919
+
920
+ flipped_block = (flipped_block + 1) % 2;
921
+ }
922
+
923
+ if (flipped_block)
924
+ l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
925
+ }
926
+
927
+ free(pmb);
928
+}
929
930
static void emit_line_ws_markup(struct diff_options *o,
931
const char *set, const char *reset,
1001
flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
1002
break;
1003
case DIFF_SYMBOL_PLUS:
723
- set = diff_get_color_opt(o, DIFF_FILE_NEW);
1004
+ if (flags & DIFF_SYMBOL_MOVED_LINE_ALT)
1005
+ set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT);
1006
+ else if (flags & DIFF_SYMBOL_MOVED_LINE)
1007
+ set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED);
1008
+ else
1009
+ set = diff_get_color_opt(o, DIFF_FILE_NEW);
1010
reset = diff_get_color_opt(o, DIFF_RESET);
1011
emit_line_ws_markup(o, set, reset, line, len, '+',
1012
flags & DIFF_SYMBOL_CONTENT_WS_MASK,
1013
flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
1014
break;
1015
case DIFF_SYMBOL_MINUS:
730
- set = diff_get_color_opt(o, DIFF_FILE_OLD);
1016
+ if (flags & DIFF_SYMBOL_MOVED_LINE_ALT)
1017
+ set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT);
1018
+ else if (flags & DIFF_SYMBOL_MOVED_LINE)
1019
+ set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED);
1020
+ else
1021
+ set = diff_get_color_opt(o, DIFF_FILE_OLD);
1022
reset = diff_get_color_opt(o, DIFF_RESET);
1023
emit_line_ws_markup(o, set, reset, line, len, '-',
1024
flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
4031
options->a_prefix = "a/";
4032
options->b_prefix = "b/";
4033
}
4034
+
4035
+ options->color_moved = diff_color_moved_default;
4036
}
4037
4038
void diff_setup_done(struct diff_options *options)
4142
4143
if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1)
4144
die(_("--follow requires exactly one pathspec"));
4145
+
4146
+ if (!options->use_color || external_diff())
4147
+ options->color_moved = 0;
4148
}
4149
4150
static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
4569
}
4570
else if (!strcmp(arg, "--no-color"))
4571
options->use_color = 0;
4276
- else if (!strcmp(arg, "--color-words")) {
4572
+ else if (!strcmp(arg, "--color-moved")) {
4573
+ if (diff_color_moved_default)
4574
+ options->color_moved = diff_color_moved_default;
4575
+ if (options->color_moved == COLOR_MOVED_NO)
4576
+ options->color_moved = COLOR_MOVED_DEFAULT;
4577
+ } else if (!strcmp(arg, "--no-color-moved"))
4578
+ options->color_moved = COLOR_MOVED_NO;
4579
+ else if (skip_prefix(arg, "--color-moved=", &arg)) {
4580
+ int cm = parse_color_moved(arg);
4581
+ if (cm < 0)
4582
+ die("bad --color-moved argument: %s", arg);
4583
+ options->color_moved = cm;
4584
+ } else if (!strcmp(arg, "--color-words")) {
4585
options->use_color = 1;
4586
options->word_diff = DIFF_WORDS_COLOR;
4587
}
5394
if (WSEH_NEW & WS_RULE_MASK)
5395
die("BUG: WS rules bit mask overlaps with diff symbol flags");
5396
5089
- /*
5090
- * For testing purposes we want to make sure the diff machinery
5091
- * works completely with the buffer. If there is anything emitted
5092
- * outside the emit_string, then the order is screwed
5093
- * up and the tests will fail.
5094
- *
5095
- * TODO (later in this series):
5096
- * We'll unset this pointer in a later patch.
5097
- */
5098
- o->emitted_symbols = &esm;
5397
+ if (o->color_moved)
5398
+ o->emitted_symbols = &esm;
5399
5400
for (i = 0; i < q->nr; i++) {
5401
struct diff_filepair *p = q->queue[i];
5404
}
5405
5406
if (o->emitted_symbols) {
5407
+ if (o->color_moved) {
5408
+ struct hashmap add_lines, del_lines;
5409
+
5410
+ hashmap_init(&del_lines,
5411
+ (hashmap_cmp_fn)moved_entry_cmp, o, 0);
5412
+ hashmap_init(&add_lines,
5413
+ (hashmap_cmp_fn)moved_entry_cmp, o, 0);
5414
+
5415
+ add_lines_to_move_detection(o, &add_lines, &del_lines);
5416
+ mark_color_as_moved(o, &add_lines, &del_lines);
5417
+
5418
+ hashmap_free(&add_lines, 0);
5419
+ hashmap_free(&del_lines, 0);
5420
+ }
5421
+
5422
for (i = 0; i < esm.nr; i++)
5423
emit_diff_symbol_from_struct(o, &esm.buf[i]);
5424
5500
fclose(options->file);
5501
options->file = xfopen("/dev/null", "w");
5502
options->close_file = 1;
5503
+ options->color_moved = 0;
5504
for (i = 0; i < q->nr; i++) {
5505
struct diff_filepair *p = q->queue[i];
5506
if (check_pair_status(p))