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>maint
							parent
							
								
									f800b65bea
								
							
						
					
					
						commit
						427fc5b888
					
				
							
								
								
									
										1
									
								
								color.h
								
								
								
								
							
							
						
						
									
										1
									
								
								color.h
								
								
								
								
							|  | @ -11,6 +11,7 @@ | |||
| #define GIT_COLOR_GREEN		"\033[32m" | ||||
| #define GIT_COLOR_YELLOW	"\033[33m" | ||||
| #define GIT_COLOR_BLUE		"\033[34m" | ||||
| #define GIT_COLOR_MAGENTA	"\033[35m" | ||||
| #define GIT_COLOR_CYAN		"\033[36m" | ||||
| #define GIT_COLOR_BG_RED	"\033[41m" | ||||
|  | ||||
|  |  | |||
							
								
								
									
										246
									
								
								graph.c
								
								
								
								
							
							
						
						
									
										246
									
								
								graph.c
								
								
								
								
							|  | @ -1,5 +1,6 @@ | |||
| #include "cache.h" | ||||
| #include "commit.h" | ||||
| #include "color.h" | ||||
| #include "graph.h" | ||||
| #include "diff.h" | ||||
| #include "revision.h" | ||||
|  | @ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb); | |||
|  | ||||
| /* | ||||
|  * TODO: | ||||
|  * - Add colors to the graph. | ||||
|  *   Pick a color for each column, and print all characters | ||||
|  *   in that column with the specified color. | ||||
|  * | ||||
|  * - Limit the number of columns, similar to the way gitk does. | ||||
|  *   If we reach more than a specified number of columns, omit | ||||
|  *   sections of some columns. | ||||
|  | @ -72,9 +69,10 @@ struct column { | |||
| 	 */ | ||||
| 	struct commit *commit; | ||||
| 	/* | ||||
| 	 * XXX: Once we add support for colors, struct column could also | ||||
| 	 * contain the color of its branch line. | ||||
| 	 * The color to (optionally) print this column in.  This is an | ||||
| 	 * index into column_colors. | ||||
| 	 */ | ||||
| 	unsigned short color; | ||||
| }; | ||||
|  | ||||
| enum graph_state { | ||||
|  | @ -86,6 +84,41 @@ enum graph_state { | |||
| 	GRAPH_COLLAPSING | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * The list of available column colors. | ||||
|  */ | ||||
| static char column_colors[][COLOR_MAXLEN] = { | ||||
| 	GIT_COLOR_RED, | ||||
| 	GIT_COLOR_GREEN, | ||||
| 	GIT_COLOR_YELLOW, | ||||
| 	GIT_COLOR_BLUE, | ||||
| 	GIT_COLOR_MAGENTA, | ||||
| 	GIT_COLOR_CYAN, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_RED, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_GREEN, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_YELLOW, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_BLUE, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_MAGENTA, | ||||
| 	GIT_COLOR_BOLD GIT_COLOR_CYAN, | ||||
| }; | ||||
|  | ||||
| #define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) | ||||
|  | ||||
| static const char *column_get_color_code(const struct column *c) | ||||
| { | ||||
| 	return column_colors[c->color]; | ||||
| } | ||||
|  | ||||
| static void strbuf_write_column(struct strbuf *sb, const struct column *c, | ||||
| 				char col_char) | ||||
| { | ||||
| 	if (c->color < COLUMN_COLORS_MAX) | ||||
| 		strbuf_addstr(sb, column_get_color_code(c)); | ||||
| 	strbuf_addch(sb, col_char); | ||||
| 	if (c->color < COLUMN_COLORS_MAX) | ||||
| 		strbuf_addstr(sb, GIT_COLOR_RESET); | ||||
| } | ||||
|  | ||||
| struct git_graph { | ||||
| 	/* | ||||
| 	 * The commit currently being processed | ||||
|  | @ -185,6 +218,11 @@ struct git_graph { | |||
| 	 * temporary array each time we have to output a collapsing line. | ||||
| 	 */ | ||||
| 	int *new_mapping; | ||||
| 	/* | ||||
| 	 * The current default column color being used.  This is | ||||
| 	 * stored as an index into the array column_colors. | ||||
| 	 */ | ||||
| 	unsigned short default_column_color; | ||||
| }; | ||||
|  | ||||
| struct git_graph *graph_init(struct rev_info *opt) | ||||
|  | @ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt) | |||
| 	graph->num_columns = 0; | ||||
| 	graph->num_new_columns = 0; | ||||
| 	graph->mapping_size = 0; | ||||
| 	graph->default_column_color = 0; | ||||
|  | ||||
| 	/* | ||||
| 	 * Allocate a reasonably large default number of columns | ||||
|  | @ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph) | |||
| 	return next_interesting_parent(graph, parents); | ||||
| } | ||||
|  | ||||
| static unsigned short graph_get_current_column_color(const struct git_graph *graph) | ||||
| { | ||||
| 	if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF)) | ||||
| 		return COLUMN_COLORS_MAX; | ||||
| 	return graph->default_column_color; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Update the graph's default column color. | ||||
|  */ | ||||
| static void graph_increment_column_color(struct git_graph *graph) | ||||
| { | ||||
| 	graph->default_column_color = (graph->default_column_color + 1) % | ||||
| 		COLUMN_COLORS_MAX; | ||||
| } | ||||
|  | ||||
| static unsigned short graph_find_commit_color(const struct git_graph *graph, | ||||
| 					      const struct commit *commit) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < graph->num_columns; i++) { | ||||
| 		if (graph->columns[i].commit == commit) | ||||
| 			return graph->columns[i].color; | ||||
| 	} | ||||
| 	return graph_get_current_column_color(graph); | ||||
| } | ||||
|  | ||||
| static void graph_insert_into_new_columns(struct git_graph *graph, | ||||
| 					  struct commit *commit, | ||||
| 					  int *mapping_index) | ||||
|  | @ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph, | |||
| 	 * This commit isn't already in new_columns.  Add it. | ||||
| 	 */ | ||||
| 	graph->new_columns[graph->num_new_columns].commit = commit; | ||||
| 	graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit); | ||||
| 	graph->mapping[*mapping_index] = graph->num_new_columns; | ||||
| 	*mapping_index += 2; | ||||
| 	graph->num_new_columns++; | ||||
|  | @ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph) | |||
| 			for (parent = first_interesting_parent(graph); | ||||
| 			     parent; | ||||
| 			     parent = next_interesting_parent(graph, parent)) { | ||||
| 				/* | ||||
| 				 * If this is a merge increment the current | ||||
| 				 * color. | ||||
| 				 */ | ||||
| 				if (graph->num_parents > 1) | ||||
| 					graph_increment_column_color(graph); | ||||
| 				graph_insert_into_new_columns(graph, | ||||
| 							      parent->item, | ||||
| 							      &mapping_idx); | ||||
|  | @ -560,7 +633,8 @@ static int graph_is_mapping_correct(struct git_graph *graph) | |||
| 	return 1; | ||||
| } | ||||
|  | ||||
| static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb) | ||||
| static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, | ||||
| 				   int chars_written) | ||||
| { | ||||
| 	/* | ||||
| 	 * 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) | |||
| 	 * aligned for the entire commit. | ||||
| 	 */ | ||||
| 	int extra; | ||||
| 	if (sb->len >= graph->width) | ||||
| 	if (chars_written >= graph->width) | ||||
| 		return; | ||||
|  | ||||
| 	extra = graph->width - sb->len; | ||||
| 	extra = graph->width - chars_written; | ||||
| 	strbuf_addf(sb, "%*s", (int) extra, ""); | ||||
| } | ||||
|  | ||||
|  | @ -596,10 +670,11 @@ static void graph_output_padding_line(struct git_graph *graph, | |||
| 	 * Output a padding row, that leaves all branch lines unchanged | ||||
| 	 */ | ||||
| 	for (i = 0; i < graph->num_new_columns; i++) { | ||||
| 		strbuf_addstr(sb, "| "); | ||||
| 		strbuf_write_column(sb, &graph->new_columns[i], '|'); | ||||
| 		strbuf_addch(sb, ' '); | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, graph->num_new_columns * 2); | ||||
| } | ||||
|  | ||||
| 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) | |||
| 	 * of the graph is missing. | ||||
| 	 */ | ||||
| 	strbuf_addstr(sb, "..."); | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, 3); | ||||
|  | ||||
| 	if (graph->num_parents >= 3 && | ||||
| 	    graph->commit_index < (graph->num_columns - 1)) | ||||
|  | @ -623,6 +698,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph, | |||
| { | ||||
| 	int num_expansion_rows; | ||||
| 	int i, seen_this; | ||||
| 	int chars_written; | ||||
|  | ||||
| 	/* | ||||
| 	 * 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, | |||
| 	 * Output the row | ||||
| 	 */ | ||||
| 	seen_this = 0; | ||||
| 	chars_written = 0; | ||||
| 	for (i = 0; i < graph->num_columns; i++) { | ||||
| 		struct column *col = &graph->columns[i]; | ||||
| 		if (col->commit == graph->commit) { | ||||
| 			seen_this = 1; | ||||
| 			strbuf_addf(sb, "| %*s", graph->expansion_row, ""); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
| 			strbuf_addf(sb, " %*s", graph->expansion_row, ""); | ||||
| 			chars_written += 2 + graph->expansion_row; | ||||
| 		} else if (seen_this && (graph->expansion_row == 0)) { | ||||
| 			/* | ||||
| 			 * 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, | |||
| 			 */ | ||||
| 			if (graph->prev_state == GRAPH_POST_MERGE && | ||||
| 			    graph->prev_commit_index < i) | ||||
| 				strbuf_addstr(sb, "\\ "); | ||||
| 				strbuf_write_column(sb, col, '\\'); | ||||
| 			else | ||||
| 				strbuf_addstr(sb, "| "); | ||||
| 				strbuf_write_column(sb, col, '|'); | ||||
| 			chars_written++; | ||||
| 		} else if (seen_this && (graph->expansion_row > 0)) { | ||||
| 			strbuf_addstr(sb, "\\ "); | ||||
| 			strbuf_write_column(sb, col, '\\'); | ||||
| 			chars_written++; | ||||
| 		} else { | ||||
| 			strbuf_addstr(sb, "| "); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
| 			chars_written++; | ||||
| 		} | ||||
| 		strbuf_addch(sb, ' '); | ||||
| 		chars_written++; | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, chars_written); | ||||
|  | ||||
| 	/* | ||||
| 	 * Increment graph->expansion_row, | ||||
|  | @ -714,10 +798,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) | |||
| 	strbuf_addch(sb, '*'); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Draw an octopus merge and return the number of characters written. | ||||
|  */ | ||||
| static int graph_draw_octopus_merge(struct git_graph *graph, | ||||
| 				    struct strbuf *sb) | ||||
| { | ||||
| 	/* | ||||
| 	 * Here dashless_commits represents the number of parents | ||||
| 	 * which don't need to have dashes (because their edges fit | ||||
| 	 * neatly under the commit). | ||||
| 	 */ | ||||
| 	const int dashless_commits = 2; | ||||
| 	int col_num, i; | ||||
| 	int num_dashes = | ||||
| 		((graph->num_parents - dashless_commits) * 2) - 1; | ||||
| 	for (i = 0; i < num_dashes; i++) { | ||||
| 		col_num = (i / 2) + dashless_commits; | ||||
| 		strbuf_write_column(sb, &graph->new_columns[col_num], '-'); | ||||
| 	} | ||||
| 	col_num = (i / 2) + dashless_commits; | ||||
| 	strbuf_write_column(sb, &graph->new_columns[col_num], '.'); | ||||
| 	return num_dashes + 1; | ||||
| } | ||||
|  | ||||
| static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) | ||||
| { | ||||
| 	int seen_this = 0; | ||||
| 	int i, j; | ||||
| 	int i, chars_written; | ||||
|  | ||||
| 	/* | ||||
| 	 * Output the row containing this commit | ||||
|  | @ -727,7 +835,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) | |||
| 	 * children that we have already processed.) | ||||
| 	 */ | ||||
| 	seen_this = 0; | ||||
| 	chars_written = 0; | ||||
| 	for (i = 0; i <= graph->num_columns; i++) { | ||||
| 		struct column *col = &graph->columns[i]; | ||||
| 		struct commit *col_commit; | ||||
| 		if (i == graph->num_columns) { | ||||
| 			if (seen_this) | ||||
|  | @ -740,18 +850,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) | |||
| 		if (col_commit == graph->commit) { | ||||
| 			seen_this = 1; | ||||
| 			graph_output_commit_char(graph, sb); | ||||
| 			chars_written++; | ||||
|  | ||||
| 			if (graph->num_parents < 3) | ||||
| 				strbuf_addch(sb, ' '); | ||||
| 			else { | ||||
| 				int num_dashes = | ||||
| 					((graph->num_parents - 2) * 2) - 1; | ||||
| 				for (j = 0; j < num_dashes; j++) | ||||
| 					strbuf_addch(sb, '-'); | ||||
| 				strbuf_addstr(sb, ". "); | ||||
| 			} | ||||
| 			if (graph->num_parents > 3) | ||||
| 				chars_written += graph_draw_octopus_merge(graph, | ||||
| 									  sb); | ||||
| 		} else if (seen_this && (graph->num_parents > 2)) { | ||||
| 			strbuf_addstr(sb, "\\ "); | ||||
| 			strbuf_write_column(sb, col, '\\'); | ||||
| 			chars_written++; | ||||
| 		} else if (seen_this && (graph->num_parents == 2)) { | ||||
| 			/* | ||||
| 			 * This is a 2-way merge commit. | ||||
|  | @ -768,15 +874,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) | |||
| 			 */ | ||||
| 			if (graph->prev_state == GRAPH_POST_MERGE && | ||||
| 			    graph->prev_commit_index < i) | ||||
| 				strbuf_addstr(sb, "\\ "); | ||||
| 				strbuf_write_column(sb, col, '\\'); | ||||
| 			else | ||||
| 				strbuf_addstr(sb, "| "); | ||||
| 				strbuf_write_column(sb, col, '|'); | ||||
| 			chars_written++; | ||||
| 		} else { | ||||
| 			strbuf_addstr(sb, "| "); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
| 			chars_written++; | ||||
| 		} | ||||
| 		strbuf_addch(sb, ' '); | ||||
| 		chars_written++; | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, chars_written); | ||||
|  | ||||
| 	/* | ||||
| 	 * Update graph->state | ||||
|  | @ -789,37 +899,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) | |||
| 		graph_update_state(graph, GRAPH_COLLAPSING); | ||||
| } | ||||
|  | ||||
| static struct column *find_new_column_by_commit(struct git_graph *graph, | ||||
| 						struct commit *commit) | ||||
| { | ||||
| 	int i; | ||||
| 	for (i = 0; i < graph->num_new_columns; i++) { | ||||
| 		if (graph->new_columns[i].commit == commit) | ||||
| 			return &graph->new_columns[i]; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb) | ||||
| { | ||||
| 	int seen_this = 0; | ||||
| 	int i, j; | ||||
| 	int i, j, chars_written; | ||||
|  | ||||
| 	/* | ||||
| 	 * Output the post-merge row | ||||
| 	 */ | ||||
| 	chars_written = 0; | ||||
| 	for (i = 0; i <= graph->num_columns; i++) { | ||||
| 		struct column *col = &graph->columns[i]; | ||||
| 		struct commit *col_commit; | ||||
| 		if (i == graph->num_columns) { | ||||
| 			if (seen_this) | ||||
| 				break; | ||||
| 			col_commit = graph->commit; | ||||
| 		} else { | ||||
| 			col_commit = graph->columns[i].commit; | ||||
| 			col_commit = col->commit; | ||||
| 		} | ||||
|  | ||||
| 		if (col_commit == graph->commit) { | ||||
| 			/* | ||||
| 			 * Since the current commit is a merge find | ||||
| 			 * the columns for the parent commits in | ||||
| 			 * new_columns and use those to format the | ||||
| 			 * edges. | ||||
| 			 */ | ||||
| 			struct commit_list *parents = NULL; | ||||
| 			struct column *par_column; | ||||
| 			seen_this = 1; | ||||
| 			strbuf_addch(sb, '|'); | ||||
| 			for (j = 0; j < graph->num_parents - 1; j++) | ||||
| 				strbuf_addstr(sb, "\\ "); | ||||
| 			parents = first_interesting_parent(graph); | ||||
| 			assert(parents); | ||||
| 			par_column = find_new_column_by_commit(graph, parents->item); | ||||
| 			assert(par_column); | ||||
|  | ||||
| 			strbuf_write_column(sb, par_column, '|'); | ||||
| 			chars_written++; | ||||
| 			for (j = 0; j < graph->num_parents - 1; j++) { | ||||
| 				parents = next_interesting_parent(graph, parents); | ||||
| 				assert(parents); | ||||
| 				par_column = find_new_column_by_commit(graph, parents->item); | ||||
| 				assert(par_column); | ||||
| 				strbuf_write_column(sb, par_column, '\\'); | ||||
| 				strbuf_addch(sb, ' '); | ||||
| 			} | ||||
| 			chars_written += j * 2; | ||||
| 		} else if (seen_this) { | ||||
| 			strbuf_addstr(sb, "\\ "); | ||||
| 			strbuf_write_column(sb, col, '\\'); | ||||
| 			strbuf_addch(sb, ' '); | ||||
| 			chars_written += 2; | ||||
| 		} else { | ||||
| 			strbuf_addstr(sb, "| "); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
| 			strbuf_addch(sb, ' '); | ||||
| 			chars_written += 2; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, chars_written); | ||||
|  | ||||
| 	/* | ||||
| 	 * Update graph->state | ||||
|  | @ -912,12 +1060,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf | |||
| 		if (target < 0) | ||||
| 			strbuf_addch(sb, ' '); | ||||
| 		else if (target * 2 == i) | ||||
| 			strbuf_addch(sb, '|'); | ||||
| 			strbuf_write_column(sb, &graph->new_columns[target], '|'); | ||||
| 		else | ||||
| 			strbuf_addch(sb, '/'); | ||||
| 			strbuf_write_column(sb, &graph->new_columns[target], '/'); | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, graph->mapping_size); | ||||
|  | ||||
| 	/* | ||||
| 	 * Swap mapping and new_mapping | ||||
|  | @ -979,9 +1127,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) | |||
| 	 * children that we have already processed.) | ||||
| 	 */ | ||||
| 	for (i = 0; i < graph->num_columns; i++) { | ||||
| 		struct commit *col_commit = graph->columns[i].commit; | ||||
| 		struct column *col = &graph->columns[i]; | ||||
| 		struct commit *col_commit = col->commit; | ||||
| 		if (col_commit == graph->commit) { | ||||
| 			strbuf_addch(sb, '|'); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
|  | ||||
| 			if (graph->num_parents < 3) | ||||
| 				strbuf_addch(sb, ' '); | ||||
|  | @ -991,11 +1140,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) | |||
| 					strbuf_addch(sb, ' '); | ||||
| 			} | ||||
| 		} else { | ||||
| 			strbuf_addstr(sb, "| "); | ||||
| 			strbuf_write_column(sb, col, '|'); | ||||
| 			strbuf_addch(sb, ' '); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	graph_pad_horizontally(graph, sb); | ||||
| 	graph_pad_horizontally(graph, sb, graph->num_columns); | ||||
|  | ||||
| 	/* | ||||
| 	 * Update graph->prev_state since we have output a padding line | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Allan Caffee
						Allan Caffee