graph API: Added logic for colored edges
Modified the graph drawing logic to colorize edges based on parent-child relationships similiarly to gitk. Signed-off-by: Allan Caffee <allan.caffee@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Allan Caffee committed
Apr 13, 2009 at 15:53 UTC
427fc5b8885d269359c8eff187925e3d49f103b4
2 files changed
+199
-48
color.h
+1
@@ -11,6 +11,7 @@
11
#define GIT_COLOR_GREEN "\033[32m"
12
#define GIT_COLOR_YELLOW "\033[33m"
13
#define GIT_COLOR_BLUE "\033[34m"
14
+#define GIT_COLOR_MAGENTA "\033[35m"
15
#define GIT_COLOR_CYAN "\033[36m"
16
#define GIT_COLOR_BG_RED "\033[41m"
17
graph.c
+198
-48
@@ -1,5 +1,6 @@
1
#include "cache.h"
2
#include "commit.h"
3
+#include "color.h"
4
#include "graph.h"
5
#include "diff.h"
6
#include "revision.h"
@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
44
45
/*
46
* TODO:
46
- * - Add colors to the graph.
47
- * Pick a color for each column, and print all characters
48
- * in that column with the specified color.
49
- *
47
* - Limit the number of columns, similar to the way gitk does.
48
* If we reach more than a specified number of columns, omit
49
* sections of some columns.
@@ -72,9 +69,10 @@ struct column {
69
*/
70
struct commit *commit;
71
/*
75
- * XXX: Once we add support for colors, struct column could also
76
- * contain the color of its branch line.
72
+ * The color to (optionally) print this column in. This is an
73
+ * index into column_colors.
74
*/
75
+ unsigned short color;
76
};
77
78
enum graph_state {
@@ -86,6 +84,41 @@ enum graph_state {
84
GRAPH_COLLAPSING
85
};
86
87
+/*
88
+ * The list of available column colors.
89
+ */
90
+static char column_colors[][COLOR_MAXLEN] = {
91
+ GIT_COLOR_RED,
92
+ GIT_COLOR_GREEN,
93
+ GIT_COLOR_YELLOW,
94
+ GIT_COLOR_BLUE,
95
+ GIT_COLOR_MAGENTA,
96
+ GIT_COLOR_CYAN,
97
+ GIT_COLOR_BOLD GIT_COLOR_RED,
98
+ GIT_COLOR_BOLD GIT_COLOR_GREEN,
99
+ GIT_COLOR_BOLD GIT_COLOR_YELLOW,
100
+ GIT_COLOR_BOLD GIT_COLOR_BLUE,
101
+ GIT_COLOR_BOLD GIT_COLOR_MAGENTA,
102
+ GIT_COLOR_BOLD GIT_COLOR_CYAN,
103
+};
104
+
105
+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
106
+
107
+static const char *column_get_color_code(const struct column *c)
108
+{
109
+ return column_colors[c->color];
110
+}
111
+
112
+static void strbuf_write_column(struct strbuf *sb, const struct column *c,
113
+ char col_char)
114
+{
115
+ if (c->color < COLUMN_COLORS_MAX)
116
+ strbuf_addstr(sb, column_get_color_code(c));
117
+ strbuf_addch(sb, col_char);
118
+ if (c->color < COLUMN_COLORS_MAX)
119
+ strbuf_addstr(sb, GIT_COLOR_RESET);
120
+}
121
+
122
struct git_graph {
123
/*
124
* The commit currently being processed
@@ -185,6 +218,11 @@ struct git_graph {
218
* temporary array each time we have to output a collapsing line.
219
*/
220
int *new_mapping;
221
+ /*
222
+ * The current default column color being used. This is
223
+ * stored as an index into the array column_colors.
224
+ */
225
+ unsigned short default_column_color;
226
};
227
228
struct git_graph *graph_init(struct rev_info *opt)
@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)
239
graph->num_columns = 0;
240
graph->num_new_columns = 0;
241
graph->mapping_size = 0;
242
+ graph->default_column_color = 0;
243
244
/*
245
* Allocate a reasonably large default number of columns
@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
351
return next_interesting_parent(graph, parents);
352
}
353
354
+static unsigned short graph_get_current_column_color(const struct git_graph *graph)
355
+{
356
+ if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
357
+ return COLUMN_COLORS_MAX;
358
+ return graph->default_column_color;
359
+}
360
+
361
+/*
362
+ * Update the graph's default column color.
363
+ */
364
+static void graph_increment_column_color(struct git_graph *graph)
365
+{
366
+ graph->default_column_color = (graph->default_column_color + 1) %
367
+ COLUMN_COLORS_MAX;
368
+}
369
+
370
+static unsigned short graph_find_commit_color(const struct git_graph *graph,
371
+ const struct commit *commit)
372
+{
373
+ int i;
374
+ for (i = 0; i < graph->num_columns; i++) {
375
+ if (graph->columns[i].commit == commit)
376
+ return graph->columns[i].color;
377
+ }
378
+ return graph_get_current_column_color(graph);
379
+}
380
+
381
static void graph_insert_into_new_columns(struct git_graph *graph,
382
struct commit *commit,
383
int *mapping_index)
@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
400
* This commit isn't already in new_columns. Add it.
401
*/
402
graph->new_columns[graph->num_new_columns].commit = commit;
403
+ graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);
404
graph->mapping[*mapping_index] = graph->num_new_columns;
405
*mapping_index += 2;
406
graph->num_new_columns++;
@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)
512
for (parent = first_interesting_parent(graph);
513
parent;
514
parent = next_interesting_parent(graph, parent)) {
515
+ /*
516
+ * If this is a merge increment the current
517
+ * color.
518
+ */
519
+ if (graph->num_parents > 1)
520
+ graph_increment_column_color(graph);
521
graph_insert_into_new_columns(graph,
522
parent->item,
523
&mapping_idx);
@@ -560,7 +633,8 @@ static int graph_is_mapping_correct(struct git_graph *graph)
633
return 1;
634
}
635
563
-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
636
+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
637
+ int chars_written)
638
{
639
/*
640
* Add additional spaces to the end of the strbuf, so that all
@@ -570,10 +644,10 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
644
* aligned for the entire commit.
645
*/
646
int extra;
573
- if (sb->len >= graph->width)
647
+ if (chars_written >= graph->width)
648
return;
649
576
- extra = graph->width - sb->len;
650
+ extra = graph->width - chars_written;
651
strbuf_addf(sb, "%*s", (int) extra, "");
652
}
653
@@ -596,10 +670,11 @@ static void graph_output_padding_line(struct git_graph *graph,
670
* Output a padding row, that leaves all branch lines unchanged
671
*/
672
for (i = 0; i < graph->num_new_columns; i++) {
599
- strbuf_addstr(sb, "| ");
673
+ strbuf_write_column(sb, &graph->new_columns[i], '|');
674
+ strbuf_addch(sb, ' ');
675
}
676
602
- graph_pad_horizontally(graph, sb);
677
+ graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
678
}
679
680
static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
@@ -609,7 +684,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
684
* of the graph is missing.
685
*/
686
strbuf_addstr(sb, "...");
612
- graph_pad_horizontally(graph, sb);
687
+ graph_pad_horizontally(graph, sb, 3);
688
689
if (graph->num_parents >= 3 &&
690
graph->commit_index < (graph->num_columns - 1))
@@ -623,6 +698,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
698
{
699
int num_expansion_rows;
700
int i, seen_this;
701
+ int chars_written;
702
703
/*
704
* This function formats a row that increases the space around a commit
@@ -645,11 +721,14 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
721
* Output the row
722
*/
723
seen_this = 0;
724
+ chars_written = 0;
725
for (i = 0; i < graph->num_columns; i++) {
726
struct column *col = &graph->columns[i];
727
if (col->commit == graph->commit) {
728
seen_this = 1;
652
- strbuf_addf(sb, "| %*s", graph->expansion_row, "");
729
+ strbuf_write_column(sb, col, '|');
730
+ strbuf_addf(sb, " %*s", graph->expansion_row, "");
731
+ chars_written += 2 + graph->expansion_row;
732
} else if (seen_this && (graph->expansion_row == 0)) {
733
/*
734
* This is the first line of the pre-commit output.
@@ -662,17 +741,22 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
741
*/
742
if (graph->prev_state == GRAPH_POST_MERGE &&
743
graph->prev_commit_index < i)
665
- strbuf_addstr(sb, "\\ ");
744
+ strbuf_write_column(sb, col, '\\');
745
else
667
- strbuf_addstr(sb, "| ");
746
+ strbuf_write_column(sb, col, '|');
747
+ chars_written++;
748
} else if (seen_this && (graph->expansion_row > 0)) {
669
- strbuf_addstr(sb, "\\ ");
749
+ strbuf_write_column(sb, col, '\\');
750
+ chars_written++;
751
} else {
671
- strbuf_addstr(sb, "| ");
752
+ strbuf_write_column(sb, col, '|');
753
+ chars_written++;
754
}
755
+ strbuf_addch(sb, ' ');
756
+ chars_written++;
757
}
758
675
- graph_pad_horizontally(graph, sb);
759
+ graph_pad_horizontally(graph, sb, chars_written);
760
761
/*
762
* Increment graph->expansion_row,
@@ -714,10 +798,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
798
strbuf_addch(sb, '*');
799
}
800
801
+/*
802
+ * Draw an octopus merge and return the number of characters written.
803
+ */
804
+static int graph_draw_octopus_merge(struct git_graph *graph,
805
+ struct strbuf *sb)
806
+{
807
+ /*
808
+ * Here dashless_commits represents the number of parents
809
+ * which don't need to have dashes (because their edges fit
810
+ * neatly under the commit).
811
+ */
812
+ const int dashless_commits = 2;
813
+ int col_num, i;
814
+ int num_dashes =
815
+ ((graph->num_parents - dashless_commits) * 2) - 1;
816
+ for (i = 0; i < num_dashes; i++) {
817
+ col_num = (i / 2) + dashless_commits;
818
+ strbuf_write_column(sb, &graph->new_columns[col_num], '-');
819
+ }
820
+ col_num = (i / 2) + dashless_commits;
821
+ strbuf_write_column(sb, &graph->new_columns[col_num], '.');
822
+ return num_dashes + 1;
823
+}
824
+
825
static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
826
{
827
int seen_this = 0;
720
- int i, j;
828
+ int i, chars_written;
829
830
/*
831
* Output the row containing this commit
@@ -727,7 +835,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
835
* children that we have already processed.)
836
*/
837
seen_this = 0;
838
+ chars_written = 0;
839
for (i = 0; i <= graph->num_columns; i++) {
840
+ struct column *col = &graph->columns[i];
841
struct commit *col_commit;
842
if (i == graph->num_columns) {
843
if (seen_this)
@@ -740,18 +850,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
850
if (col_commit == graph->commit) {
851
seen_this = 1;
852
graph_output_commit_char(graph, sb);
853
+ chars_written++;
854
744
- if (graph->num_parents < 3)
745
- strbuf_addch(sb, ' ');
746
- else {
747
- int num_dashes =
748
- ((graph->num_parents - 2) * 2) - 1;
749
- for (j = 0; j < num_dashes; j++)
750
- strbuf_addch(sb, '-');
751
- strbuf_addstr(sb, ". ");
752
- }
855
+ if (graph->num_parents > 3)
856
+ chars_written += graph_draw_octopus_merge(graph,
857
+ sb);
858
} else if (seen_this && (graph->num_parents > 2)) {
754
- strbuf_addstr(sb, "\\ ");
859
+ strbuf_write_column(sb, col, '\\');
860
+ chars_written++;
861
} else if (seen_this && (graph->num_parents == 2)) {
862
/*
863
* This is a 2-way merge commit.
@@ -768,15 +874,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
874
*/
875
if (graph->prev_state == GRAPH_POST_MERGE &&
876
graph->prev_commit_index < i)
771
- strbuf_addstr(sb, "\\ ");
877
+ strbuf_write_column(sb, col, '\\');
878
else
773
- strbuf_addstr(sb, "| ");
879
+ strbuf_write_column(sb, col, '|');
880
+ chars_written++;
881
} else {
775
- strbuf_addstr(sb, "| ");
882
+ strbuf_write_column(sb, col, '|');
883
+ chars_written++;
884
}
885
+ strbuf_addch(sb, ' ');
886
+ chars_written++;
887
}
888
779
- graph_pad_horizontally(graph, sb);
889
+ graph_pad_horizontally(graph, sb, chars_written);
890
891
/*
892
* Update graph->state
@@ -789,37 +899,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
899
graph_update_state(graph, GRAPH_COLLAPSING);
900
}
901
902
+static struct column *find_new_column_by_commit(struct git_graph *graph,
903
+ struct commit *commit)
904
+{
905
+ int i;
906
+ for (i = 0; i < graph->num_new_columns; i++) {
907
+ if (graph->new_columns[i].commit == commit)
908
+ return &graph->new_columns[i];
909
+ }
910
+ return 0;
911
+}
912
+
913
static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
914
{
915
int seen_this = 0;
795
- int i, j;
916
+ int i, j, chars_written;
917
918
/*
919
* Output the post-merge row
920
*/
921
+ chars_written = 0;
922
for (i = 0; i <= graph->num_columns; i++) {
923
+ struct column *col = &graph->columns[i];
924
struct commit *col_commit;
925
if (i == graph->num_columns) {
926
if (seen_this)
927
break;
928
col_commit = graph->commit;
929
} else {
807
- col_commit = graph->columns[i].commit;
930
+ col_commit = col->commit;
931
}
932
933
if (col_commit == graph->commit) {
934
+ /*
935
+ * Since the current commit is a merge find
936
+ * the columns for the parent commits in
937
+ * new_columns and use those to format the
938
+ * edges.
939
+ */
940
+ struct commit_list *parents = NULL;
941
+ struct column *par_column;
942
seen_this = 1;
812
- strbuf_addch(sb, '|');
813
- for (j = 0; j < graph->num_parents - 1; j++)
814
- strbuf_addstr(sb, "\\ ");
943
+ parents = first_interesting_parent(graph);
944
+ assert(parents);
945
+ par_column = find_new_column_by_commit(graph, parents->item);
946
+ assert(par_column);
947
+
948
+ strbuf_write_column(sb, par_column, '|');
949
+ chars_written++;
950
+ for (j = 0; j < graph->num_parents - 1; j++) {
951
+ parents = next_interesting_parent(graph, parents);
952
+ assert(parents);
953
+ par_column = find_new_column_by_commit(graph, parents->item);
954
+ assert(par_column);
955
+ strbuf_write_column(sb, par_column, '\\');
956
+ strbuf_addch(sb, ' ');
957
+ }
958
+ chars_written += j * 2;
959
} else if (seen_this) {
816
- strbuf_addstr(sb, "\\ ");
960
+ strbuf_write_column(sb, col, '\\');
961
+ strbuf_addch(sb, ' ');
962
+ chars_written += 2;
963
} else {
818
- strbuf_addstr(sb, "| ");
964
+ strbuf_write_column(sb, col, '|');
965
+ strbuf_addch(sb, ' ');
966
+ chars_written += 2;
967
}
968
}
969
822
- graph_pad_horizontally(graph, sb);
970
+ graph_pad_horizontally(graph, sb, chars_written);
971
972
/*
973
* Update graph->state
@@ -912,12 +1060,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
1060
if (target < 0)
1061
strbuf_addch(sb, ' ');
1062
else if (target * 2 == i)
915
- strbuf_addch(sb, '|');
1063
+ strbuf_write_column(sb, &graph->new_columns[target], '|');
1064
else
917
- strbuf_addch(sb, '/');
1065
+ strbuf_write_column(sb, &graph->new_columns[target], '/');
1066
}
1067
920
- graph_pad_horizontally(graph, sb);
1068
+ graph_pad_horizontally(graph, sb, graph->mapping_size);
1069
1070
/*
1071
* Swap mapping and new_mapping
@@ -979,9 +1127,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
1127
* children that we have already processed.)
1128
*/
1129
for (i = 0; i < graph->num_columns; i++) {
982
- struct commit *col_commit = graph->columns[i].commit;
1130
+ struct column *col = &graph->columns[i];
1131
+ struct commit *col_commit = col->commit;
1132
if (col_commit == graph->commit) {
984
- strbuf_addch(sb, '|');
1133
+ strbuf_write_column(sb, col, '|');
1134
1135
if (graph->num_parents < 3)
1136
strbuf_addch(sb, ' ');
@@ -991,11 +1140,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
1140
strbuf_addch(sb, ' ');
1141
}
1142
} else {
994
- strbuf_addstr(sb, "| ");
1143
+ strbuf_write_column(sb, col, '|');
1144
+ strbuf_addch(sb, ' ');
1145
}
1146
}
1147
998
- graph_pad_horizontally(graph, sb);
1148
+ graph_pad_horizontally(graph, sb, graph->num_columns);
1149
1150
/*
1151
* Update graph->prev_state since we have output a padding line