{"thread":{"id":"18768","subject":"[PATCH] graph API: Added logic for colored edges","startedAt":"2009-04-07T18:57:24Z","lastAt":"2009-04-13T19:53:41Z","messageCount":13,"participants":["Allan Caffee","Junio C Hamano","Teemu Likonen"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"110726","messageId":"20090407185724.GA9996@linux.vnet","threadId":"18768","inReplyTo":"20090331235922.GA7411@linux.vnet","subject":"[PATCH] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-07T18:57:24Z","receivedAt":"2009-04-07T18:57:24Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"Modified the graph drawing logic to colorize edges based on parent-child\nrelationships similiarly to gitk.\n\nSigned-off-by: Allan Caffee <allan.caffee@gmail.com>\n---\nIt seemed like it was time for another iteration so here it is.\nChanges this round include: (Only the first two were actually posted\nto the list as full patches.)\n\n* graph.c: avoid compile warnings (Thanks Johannes)\n\n* --graph: respect --no-color (Thanks Johannes)\n\n* Remove some outdated TODO comments\n\n* Avoid using forward declarations\n\n* Remove commented out debugging code\n\n* Avoid an unnecessary function call\n\n* Refactor/rename some local functions\nI replaced get_next_column_column color with graph_increment_column_color since\nthe return value is never actually used.  I also renamed\nget_current_column_color to graph_get_current_column_color.\n\n* Handle column colors as const pointers\nThe color codes used should never be modified once we begin graphing,\nso columns should store/handle them as const char*.\n\n* graph API: Add handling for merges with 3+ parents\nI accidentally left this one out of the first round.\n\n~Allan\n\n color.h |    1 +\n graph.c |  175 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------\n 2 files changed, 145 insertions(+), 31 deletions(-)\n\ndiff --git a/color.h b/color.h\nindex 6846be1..18abeb7 100644\n--- a/color.h\n+++ b/color.h\n@@ -11,6 +11,7 @@\n #define GIT_COLOR_GREEN\t\t\"\\033[32m\"\n #define GIT_COLOR_YELLOW\t\"\\033[33m\"\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n+#define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n \ndiff --git a/graph.c b/graph.c\nindex 162a516..0e112d7 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"color.h\"\n #include \"graph.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);\n \n /*\n  * TODO:\n- * - Add colors to the graph.\n- *   Pick a color for each column, and print all characters\n- *   in that column with the specified color.\n- *\n  * - Limit the number of columns, similar to the way gitk does.\n  *   If we reach more than a specified number of columns, omit\n  *   sections of some columns.\n@@ -72,11 +69,21 @@ struct column {\n \t */\n \tstruct commit *commit;\n \t/*\n-\t * XXX: Once we add support for colors, struct column could also\n-\t * contain the color of its branch line.\n+\t * The color to (optionally) print this column in.\n \t */\n+\tconst char *color;\n };\n \n+static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n+\t\tconst char *s)\n+{\n+\tif (c->color)\n+\t\tstrbuf_addstr(sb, c->color);\n+\tstrbuf_addstr(sb, s);\n+\tif (c->color)\n+\t\tstrbuf_addstr(sb, GIT_COLOR_RESET);\n+}\n+\n enum graph_state {\n \tGRAPH_PADDING,\n \tGRAPH_SKIP,\n@@ -86,6 +93,24 @@ enum graph_state {\n \tGRAPH_COLLAPSING\n };\n \n+/*\n+ * The list of available column colors.\n+ */\n+static char column_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RED,\n+\tGIT_COLOR_GREEN,\n+\tGIT_COLOR_YELLOW,\n+\tGIT_COLOR_BLUE,\n+\tGIT_COLOR_MAGENTA,\n+\tGIT_COLOR_CYAN,\n+\tGIT_COLOR_BOLD GIT_COLOR_RED,\n+\tGIT_COLOR_BOLD GIT_COLOR_GREEN,\n+\tGIT_COLOR_BOLD GIT_COLOR_YELLOW,\n+\tGIT_COLOR_BOLD GIT_COLOR_BLUE,\n+\tGIT_COLOR_BOLD GIT_COLOR_MAGENTA,\n+\tGIT_COLOR_BOLD GIT_COLOR_CYAN,\n+};\n+\n struct git_graph {\n \t/*\n \t * The commit currently being processed\n@@ -185,6 +210,11 @@ struct git_graph {\n \t * temporary array each time we have to output a collapsing line.\n \t */\n \tint *new_mapping;\n+\t/*\n+\t * The current default column color being used.  This is\n+\t * stored as an index into the array column_colors.\n+\t */\n+\tshort default_column_color;\n };\n \n struct git_graph *graph_init(struct rev_info *opt)\n@@ -201,6 +231,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n+\tgraph->default_column_color = 0;\n \n \t/*\n \t * Allocate a reasonably large default number of columns\n@@ -312,6 +343,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n \treturn next_interesting_parent(graph, parents);\n }\n \n+static const char* graph_get_current_column_color(const struct git_graph* graph)\n+{\n+\tif (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))\n+\t\treturn NULL;\n+\treturn column_colors[graph->default_column_color];\n+}\n+\n+/*\n+ * Update the graph's default column color.\n+ */\n+static void graph_increment_column_color(struct git_graph* graph)\n+{\n+\tgraph->default_column_color = (graph->default_column_color + 1) %\n+\t\tARRAY_SIZE(column_colors);\n+}\n+\n+static const char * graph_find_commit_color(const struct git_graph *graph,\n+\t\t\t\t      const struct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_columns; i++) {\n+\t\tif (graph->columns[i].commit == commit)\n+\t\t\treturn graph->columns[i].color;\n+\t}\n+\treturn graph_get_current_column_color(graph);\n+}\n+\n static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\t\t\t\t  struct commit *commit,\n \t\t\t\t\t  int *mapping_index)\n@@ -334,6 +392,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t * This commit isn't already in new_columns.  Add it.\n \t */\n \tgraph->new_columns[graph->num_new_columns].commit = commit;\n+\tgraph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);\n \tgraph->mapping[*mapping_index] = graph->num_new_columns;\n \t*mapping_index += 2;\n \tgraph->num_new_columns++;\n@@ -445,6 +504,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tfor (parent = first_interesting_parent(graph);\n \t\t\t     parent;\n \t\t\t     parent = next_interesting_parent(graph, parent)) {\n+\t\t\t\t/*\n+\t\t\t\t * If this is a merge increment the current\n+\t\t\t\t * color.\n+\t\t\t\t */\n+\t\t\t\tif (graph->num_parents > 1)\n+\t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\tgraph_insert_into_new_columns(graph,\n \t\t\t\t\t\t\t      parent->item,\n \t\t\t\t\t\t\t      &mapping_idx);\n@@ -596,7 +661,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n \tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tstrbuf_addstr(sb, \"| \");\n+\t\tstrbuf_write_column(sb, &graph->new_columns[i], \"| \");\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -648,8 +713,11 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \t\tif (col->commit == graph->commit) {\n+\t\t\tstruct strbuf tmp = STRBUF_INIT;\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addf(sb, \"| %*s\", graph->expansion_row, \"\");\n+\t\t\tstrbuf_addf(&tmp, \"| %*s\", graph->expansion_row, \"\");\n+\t\t\tstrbuf_write_column(sb, col, tmp.buf);\n+\t\t\tstrbuf_release(&tmp);\n \t\t} else if (seen_this && (graph->expansion_row == 0)) {\n \t\t\t/*\n \t\t\t * This is the first line of the pre-commit output.\n@@ -662,13 +730,13 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t}\n \t}\n \n@@ -728,6 +796,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tseen_this = 0;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n@@ -744,14 +813,25 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\tif (graph->num_parents < 3)\n \t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\telse {\n+\t\t\t\t/*\n+\t\t\t\t * Here dashless_commits represents the\n+\t\t\t\t * number of parents which don't need\n+\t\t\t\t * to have dashes (because their edges\n+\t\t\t\t * fit neatly under the commit).\n+\t\t\t\t */\n+\t\t\t\tconst int dashless_commits = 2;\n \t\t\t\tint num_dashes =\n-\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n+\t\t\t\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n \t\t\t\tfor (j = 0; j < num_dashes; j++)\n-\t\t\t\t\tstrbuf_addch(sb, '-');\n-\t\t\t\tstrbuf_addstr(sb, \". \");\n+\t\t\t\t\tstrbuf_write_column(sb,\n+\t\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n+\t\t\t\t\t\t\t    \"-\");\n+\t\t\t\tstrbuf_write_column(sb,\n+\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n+\t\t\t\t\t\t    \". \");\n \t\t\t}\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n \t\t} else if (seen_this && (graph->num_parents == 2)) {\n \t\t\t/*\n \t\t\t * This is a 2-way merge commit.\n@@ -768,11 +848,11 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t}\n \t}\n \n@@ -789,6 +869,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+inline struct column* find_new_column_by_commit(struct git_graph *graph,\n+\t\t\t\t\t\tstruct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_new_columns; i++) {\n+\t\tif (graph->new_columns[i].commit == commit)\n+\t\t\treturn &graph->new_columns[i];\n+\t}\n+\treturn 0;\n+}\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n@@ -798,24 +889,43 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t * Output the post-merge row\n \t */\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n \t\t\t\tbreak;\n \t\t\tcol_commit = graph->commit;\n \t\t} else {\n-\t\t\tcol_commit = graph->columns[i].commit;\n+\t\t\tcol_commit = col->commit;\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n+\t\t\t/*\n+\t\t\t * Since the current commit is a merge find\n+\t\t\t * the columns for the parent commits in\n+\t\t\t * new_columns and use those to format the\n+\t\t\t * edges.\n+\t\t\t */\n+\t\t\tstruct commit_list *parents = NULL;\n+\t\t\tstruct column *par_column;\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\t\tfor (j = 0; j < graph->num_parents - 1; j++)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tparents = first_interesting_parent(graph);\n+\t\t\tassert(parents);\n+\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\tassert(par_column);\n+\n+\t\t\tstrbuf_write_column(sb, par_column, \"|\");\n+\t\t\tfor (j = 0; j < graph->num_parents - 1; j++) {\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n+\t\t\t\tassert(parents);\n+\t\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\t\tassert(par_column);\n+\t\t\t\tstrbuf_write_column(sb, par_column, \"\\\\ \");\n+\t\t\t}\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t}\n \t}\n \n@@ -912,9 +1022,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\tif (target < 0)\n \t\t\tstrbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\telse\n-\t\t\tstrbuf_addch(sb, '/');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], \"|\");\n+\t\telse {\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], \"/\");\n+\n+\t\t}\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -979,9 +1091,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t * children that we have already processed.)\n \t */\n \tfor (i = 0; i < graph->num_columns; i++) {\n-\t\tstruct commit *col_commit = graph->columns[i].commit;\n+\t\tstruct column *col = &graph->columns[i];\n+\t\tstruct commit *col_commit = col->commit;\n \t\tif (col_commit == graph->commit) {\n-\t\t\tstrbuf_addch(sb, '|');\n+\t\t\tstrbuf_write_column(sb, col, \"|\");\n \n \t\t\tif (graph->num_parents < 3)\n \t\t\t\tstrbuf_addch(sb, ' ');\n@@ -991,7 +1104,7 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\t}\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, \"| \");\n \t\t}\n \t}\n \n-- \n1.5.6.3\n"},{"id":"110807","messageId":"7vd4bnpodt.fsf@gitster.siamese.dyndns.org","threadId":"18768","inReplyTo":"20090407185724.GA9996@linux.vnet","subject":"Re: [PATCH] graph API: Added logic for colored edges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-08T07:59:10Z","receivedAt":"2009-04-08T07:59:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Allan Caffee <allan.caffee@gmail.com> writes:\n\n> @@ -72,11 +69,21 @@ struct column {\n>  \t */\n>  \tstruct commit *commit;\n>  \t/*\n> -\t * XXX: Once we add support for colors, struct column could also\n> -\t * contain the color of its branch line.\n> +\t * The color to (optionally) print this column in.\n>  \t */\n> +\tconst char *color;\n\nYou already use short for git_graph.default_column_color and I suspect in\nthe longer term you want to make this one an index into column_colors[]\narray the same way.  We may someday decide to support non-ANSI color\nscheme using ncurses or something, and at that point the \"hardware color\"\nconstants like GIT_COLOR_RED and friends may change from strings to small\nintegers we use to call our (yet to be written) curses interface layer\nwith.\n\n> @@ -312,6 +343,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n>  \treturn next_interesting_parent(graph, parents);\n>  }\n>  \n> +static const char* graph_get_current_column_color(const struct git_graph* graph)\n\nStyle.  Asterisk comes next to identifiers, not types (the parameter to\ngraph_increment_column_color has the same issue).\n\n\tstatic const char *graph_...color(const struct git_graph *graph)\n\n> @@ -596,7 +661,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n>  \t * Output a padding row, that leaves all branch lines unchanged\n>  \t */\n>  \tfor (i = 0; i < graph->num_new_columns; i++) {\n> -\t\tstrbuf_addstr(sb, \"| \");\n> +\t\tstrbuf_write_column(sb, &graph->new_columns[i], \"| \");\n\nHmmm, this forbids us to use reverse color in the color palette because\nthat would highlight the trailing whitespace.  Is that something we care\nabout, or a reversed \"|\", \"/\" and \"\\\" are already too ugly that we won't\nwant to support them?\n\n> @@ -648,8 +713,11 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n>  \tfor (i = 0; i < graph->num_columns; i++) {\n>  \t\tstruct column *col = &graph->columns[i];\n>  \t\tif (col->commit == graph->commit) {\n> +\t\t\tstruct strbuf tmp = STRBUF_INIT;\n>  \t\t\tseen_this = 1;\n> -\t\t\tstrbuf_addf(sb, \"| %*s\", graph->expansion_row, \"\");\n> +\t\t\tstrbuf_addf(&tmp, \"| %*s\", graph->expansion_row, \"\");\n> +\t\t\tstrbuf_write_column(sb, col, tmp.buf);\n> +\t\t\tstrbuf_release(&tmp);\n\nSame here.\n\n> @@ -662,13 +730,13 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n>  \t\t\t */\n>  \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n>  \t\t\t    graph->prev_commit_index < i)\n> -\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n> +\t\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n>  \t\t\telse\n> -\t\t\t\tstrbuf_addstr(sb, \"| \");\n> +\t\t\t\tstrbuf_write_column(sb, col, \"| \");\n>  \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n> -\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n> +\t\t\tstrbuf_write_column(sb, col, \"\\\\ \");\n>  \t\t} else {\n> -\t\t\tstrbuf_addstr(sb, \"| \");\n> +\t\t\tstrbuf_write_column(sb, col, \"| \");\n>  \t\t}\n>  \t}\n\nLikewise.\n\n> @@ -744,14 +813,25 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \t\t\tif (graph->num_parents < 3)\n>  \t\t\t\tstrbuf_addch(sb, ' ');\n>  \t\t\telse {\n> +\t\t\t\t/*\n> +\t\t\t\t * Here dashless_commits represents the\n> +\t\t\t\t * number of parents which don't need\n> +\t\t\t\t * to have dashes (because their edges\n> +\t\t\t\t * fit neatly under the commit).\n> +\t\t\t\t */\n> +\t\t\t\tconst int dashless_commits = 2;\n>  \t\t\t\tint num_dashes =\n> -\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n> +\t\t\t\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n>  \t\t\t\tfor (j = 0; j < num_dashes; j++)\n> -\t\t\t\t\tstrbuf_addch(sb, '-');\n> -\t\t\t\tstrbuf_addstr(sb, \". \");\n> +\t\t\t\t\tstrbuf_write_column(sb,\n> +\t\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n> +\t\t\t\t\t\t\t    \"-\");\n> +\t\t\t\tstrbuf_write_column(sb,\n> +\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n> +\t\t\t\t\t\t    \". \");\n\nThe nesting seems to be becoming too deep and the body of the for loop is\ngetting too long.  Time to make it a helper function that handles only one\ncolumn, perhaps?\n"},{"id":"110875","messageId":"20090408214129.GA20764@linux.vnet","threadId":"18768","inReplyTo":"7vd4bnpodt.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-08T21:41:29Z","receivedAt":"2009-04-08T21:41:29Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"On Wed, 08 Apr 2009, Junio C Hamano wrote:\n> Allan Caffee <allan.caffee@gmail.com> writes:\n> \n> > @@ -72,11 +69,21 @@ struct column {\n> >  \t */\n> >  \tstruct commit *commit;\n> >  \t/*\n> > -\t * XXX: Once we add support for colors, struct column could also\n> > -\t * contain the color of its branch line.\n> > +\t * The color to (optionally) print this column in.\n> >  \t */\n> > +\tconst char *color;\n> \n> You already use short for git_graph.default_column_color and I suspect in\n> the longer term you want to make this one an index into column_colors[]\n> array the same way.  We may someday decide to support non-ANSI color\n> scheme using ncurses or something, and at that point the \"hardware color\"\n> constants like GIT_COLOR_RED and friends may change from strings to small\n> integers we use to call our (yet to be written) curses interface layer\n> with.\n\nThe problem with making it an index into the column_colors array is that\nwe don't have a convenient place to test whether the user actually wants\ncolor.  We can't do it in strbuf_write_column because AFAIK there's no\nway to get the rev-info to test the options.  I suppose we could define\nGIT_NOT_A_COLOR to -1 and just set the color to that when we don't\nintend to use color.  (Either way I should probably change that to an\nunsigned short.)  What do you think?\n\n> > @@ -312,6 +343,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n> >  \treturn next_interesting_parent(graph, parents);\n> >  }\n> >  \n> > +static const char* graph_get_current_column_color(const struct git_graph* graph)\n> \n> Style.  Asterisk comes next to identifiers, not types (the parameter to\n> graph_increment_column_color has the same issue).\n\nOkay.\n\n> \tstatic const char *graph_...color(const struct git_graph *graph)\n> \n> > @@ -596,7 +661,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n> >  \t * Output a padding row, that leaves all branch lines unchanged\n> >  \t */\n> >  \tfor (i = 0; i < graph->num_new_columns; i++) {\n> > -\t\tstrbuf_addstr(sb, \"| \");\n> > +\t\tstrbuf_write_column(sb, &graph->new_columns[i], \"| \");\n> \n> Hmmm, this forbids us to use reverse color in the color palette because\n> that would highlight the trailing whitespace.  Is that something we care\n> about, or a reversed \"|\", \"/\" and \"\\\" are already too ugly that we won't\n> want to support them?\n\nPersonally I don't think it's a good idea to limit the colors just\nbecause they would \"look funny\".  It seems to me that if the user wanted\nthe background colored they would expect to see just the line segment\ncolored and not the whitespace.\n\nThe simplest way to fix this AFAIKS is to change strbuf_write_column to\ntake a single character and change the existing code to add spaces in\nseperately.\n\n/* For example */\nstrbuf_write_column(sb, &graph->new_columns[i], \"| \");\n/* Becomes */\nstrbuf_write_column(sb, &graph->new_columns[i], '|');\nstrbuf_addch(sb, ' ');\n\nThis would fix the problem at the minor expense of adding ~15 lines of\ncode.\n\n> [...]\n> \n> > @@ -744,14 +813,25 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n> >  \t\t\tif (graph->num_parents < 3)\n> >  \t\t\t\tstrbuf_addch(sb, ' ');\n> >  \t\t\telse {\n> > +\t\t\t\t/*\n> > +\t\t\t\t * Here dashless_commits represents the\n> > +\t\t\t\t * number of parents which don't need\n> > +\t\t\t\t * to have dashes (because their edges\n> > +\t\t\t\t * fit neatly under the commit).\n> > +\t\t\t\t */\n> > +\t\t\t\tconst int dashless_commits = 2;\n> >  \t\t\t\tint num_dashes =\n> > -\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n> > +\t\t\t\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n> >  \t\t\t\tfor (j = 0; j < num_dashes; j++)\n> > -\t\t\t\t\tstrbuf_addch(sb, '-');\n> > -\t\t\t\tstrbuf_addstr(sb, \". \");\n> > +\t\t\t\t\tstrbuf_write_column(sb,\n> > +\t\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n> > +\t\t\t\t\t\t\t    \"-\");\n> > +\t\t\t\tstrbuf_write_column(sb,\n> > +\t\t\t\t\t\t    &graph->new_columns[(j / 2) + dashless_commits],\n> > +\t\t\t\t\t\t    \". \");\n> \n> The nesting seems to be becoming too deep and the body of the for loop is\n> getting too long.  Time to make it a helper function that handles only one\n> column, perhaps?\n\nIf I understand this correctly in order to write a per column function\nwould be like:\n\n/*\n * Draw one peice of the commit line and return the new value of\n * seen_this.\n */\nstatic int graph_commit_line_draw_column(struct git_graph *graph, \n                                         struct strbuf *sb,\n                                         int i, int seen_this)\n\nWhich moves the entire body of the for loop into a function and only\nreduces the indentation by one level.  Am I missing something?\n"},{"id":"110883","messageId":"7vocv6is9g.fsf@gitster.siamese.dyndns.org","threadId":"18768","inReplyTo":"20090408214129.GA20764@linux.vnet","subject":"Re: [PATCH] graph API: Added logic for colored edges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-09T00:29:31Z","receivedAt":"2009-04-09T00:29:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Allan Caffee <allan.caffee@gmail.com> writes:\n\n> The problem with making it an index into the column_colors array is that\n> we don't have a convenient place to test whether the user actually wants\n> color.  We can't do it in strbuf_write_column because AFAIK there's no\n> way to get the rev-info to test the options.  I suppose we could define\n> GIT_NOT_A_COLOR to -1 and just set the color to that when we don't\n> intend to use color.  (Either way I should probably change that to an\n> unsigned short.)  What do you think?\n\nSounds sensible.\n"},{"id":"110941","messageId":"87hc0x7lpg.fsf@iki.fi","threadId":"18768","inReplyTo":"20090407185724.GA9996@linux.vnet","subject":"Re: [PATCH] graph API: Added logic for colored edges","fromName":"Teemu Likonen","fromEmail":"tlikonen@iki.fi","sentAt":"2009-04-09T17:58:51Z","receivedAt":"2009-04-09T17:58:51Z","isPatch":true,"sender":{"key":"tlikonen@iki.fi","avatar":null},"body":"On 2009-04-07 14:57 (-0400), Allan Caffee wrote:\n\n> Modified the graph drawing logic to colorize edges based on\n> parent-child relationships similiarly to gitk.\n\nI like the colored graph very much, thanks. Unfortunately there are some\nproblems with aligning of log messages and headers. For example, try\nthis in git.git repository:\n\n\n$ git log -1 --graph 796b137\n\n*   commit 796b13781aecce551b8f92049a66646e60f31dce\n|\\ Merge: 6da14ee db12d97\n| | Author: Junio C Hamano <gitster@pobox.com>\n| | Date:   2009-04-08 23:41:27 -0700\n\n\nWithout colors or without your patch the alignment is correct:\n\n$ git log -1 --graph --no-color 796b137\n\n*   commit 796b13781aecce551b8f92049a66646e60f31dce\n|\\  Merge: 6da14ee db12d97\n| | Author: Junio C Hamano <gitster@pobox.com>\n| | Date:   2009-04-08 23:41:27 -0700\n\n\n(Perhaps the \"Merge:\" header could have two spaces befor the data, but\nthis is  unrelated to --graph.)\n"},{"id":"110959","messageId":"20090409220816.GA8234@linux.vnet","threadId":"18768","inReplyTo":"87hc0x7lpg.fsf@iki.fi","subject":"Re: [PATCH] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-09T22:08:16Z","receivedAt":"2009-04-09T22:08:16Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"On Thu, 09 Apr 2009, Teemu Likonen wrote:\n> On 2009-04-07 14:57 (-0400), Allan Caffee wrote:\n> \n> > Modified the graph drawing logic to colorize edges based on\n> > parent-child relationships similiarly to gitk.\n> \n> I like the colored graph very much, thanks. Unfortunately there are some\n> problems with aligning of log messages and headers. For example, try\n> this in git.git repository:\n> \n> \n> $ git log -1 --graph 796b137\n> \n> *   commit 796b13781aecce551b8f92049a66646e60f31dce\n> |\\ Merge: 6da14ee db12d97\n> | | Author: Junio C Hamano <gitster@pobox.com>\n> | | Date:   2009-04-08 23:41:27 -0700\n> \n> \n> Without colors or without your patch the alignment is correct:\n> \n> $ git log -1 --graph --no-color 796b137\n> \n> *   commit 796b13781aecce551b8f92049a66646e60f31dce\n> |\\  Merge: 6da14ee db12d97\n> | | Author: Junio C Hamano <gitster@pobox.com>\n> | | Date:   2009-04-08 23:41:27 -0700\n> \n> \n> (Perhaps the \"Merge:\" header could have two spaces befor the data, but\n> this is  unrelated to --graph.)\n\nThanks for the feedback!  The problem here is that\ngraph_horizontal_padding was counting the number of characters in the\nstrbuf in order to decide how much whitespace to pad the row with.  The\nANSI escape sequences for colors and attributes are not printable\ncharacters but do contribute to the length of the strbuf.  This can be\nfixed by adding a new function that counts only printing\ncharacters/spaces.  I'll include this in my next round.  \n"},{"id":"110963","messageId":"20090409222238.GA16573@linux.vnet","threadId":"18768","inReplyTo":"7vocv6is9g.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v3] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-09T22:22:38Z","receivedAt":"2009-04-09T22:22:38Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"Modified the graph drawing logic to colorize edges based on parent-child\nrelationships similiarly to gitk.\n\nSigned-off-by: Allan Caffee <allan.caffee@gmail.com>\n---\n\nHere's a list of the changes this round (I can send individual patches\nif it's easier to review):\n\n* graph API: Fix graph_pad_horizontal to work properly with colors\n    The horizontal padding was counting the number of characters in the\nstrbuf in order to decide how much whitespace to pad the row with.\nThe ANSI escape sequences for colors and attributes are not printable\ncharacters but do contribute to the length of the strbuf.  This fix\nteaches strbuf's to count printing characters (i.e. characters that\nconsume space).\n* graph.c: Add graph_draw_octopus_merge() to simplify graph_output_commit_line()\n* --graph: Fix whitespace discoloration\n    Background colors now work as expected (but boy are they ugly ;).\n* graph.c: Store colors as unsigned short\n* graph: Fixed up some stylistic issues with pointers\n\n~Allan\n\n color.h  |    1 +\n graph.c  |  212 +++++++++++++++++++++++++++++++++++++++++++++++++------------\n strbuf.c |   39 +++++++++++\n strbuf.h |    1 +\n 4 files changed, 211 insertions(+), 42 deletions(-)\n\ndiff --git a/color.h b/color.h\nindex 6846be1..18abeb7 100644\n--- a/color.h\n+++ b/color.h\n@@ -11,6 +11,7 @@\n #define GIT_COLOR_GREEN\t\t\"\\033[32m\"\n #define GIT_COLOR_YELLOW\t\"\\033[33m\"\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n+#define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n \ndiff --git a/graph.c b/graph.c\nindex 162a516..beb622a 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"color.h\"\n #include \"graph.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);\n \n /*\n  * TODO:\n- * - Add colors to the graph.\n- *   Pick a color for each column, and print all characters\n- *   in that column with the specified color.\n- *\n  * - Limit the number of columns, similar to the way gitk does.\n  *   If we reach more than a specified number of columns, omit\n  *   sections of some columns.\n@@ -72,11 +69,14 @@ struct column {\n \t */\n \tstruct commit *commit;\n \t/*\n-\t * XXX: Once we add support for colors, struct column could also\n-\t * contain the color of its branch line.\n+\t * The color to (optionally) print this column in.  This is an\n+\t * index into column_colors.\n \t */\n+\tunsigned short color;\n };\n \n+const unsigned short GIT_NOT_A_COLOR = -1;\n+\n enum graph_state {\n \tGRAPH_PADDING,\n \tGRAPH_SKIP,\n@@ -86,6 +86,39 @@ enum graph_state {\n \tGRAPH_COLLAPSING\n };\n \n+/*\n+ * The list of available column colors.\n+ */\n+static char column_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RED,\n+\tGIT_COLOR_GREEN,\n+\tGIT_COLOR_YELLOW,\n+\tGIT_COLOR_BLUE,\n+\tGIT_COLOR_MAGENTA,\n+\tGIT_COLOR_CYAN,\n+\tGIT_COLOR_BOLD GIT_COLOR_RED,\n+\tGIT_COLOR_BOLD GIT_COLOR_GREEN,\n+\tGIT_COLOR_BOLD GIT_COLOR_YELLOW,\n+\tGIT_COLOR_BOLD GIT_COLOR_BLUE,\n+\tGIT_COLOR_BOLD GIT_COLOR_MAGENTA,\n+\tGIT_COLOR_BOLD GIT_COLOR_CYAN,\n+};\n+\n+static const char *column_get_color_code(const struct column *c)\n+{\n+\treturn column_colors[c->color];\n+}\n+\n+static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n+\t\t\t\tchar col_char)\n+{\n+\tif (c->color != GIT_NOT_A_COLOR)\n+\t\tstrbuf_addstr(sb, column_get_color_code(c));\n+\tstrbuf_addch(sb, col_char);\n+\tif (c->color != GIT_NOT_A_COLOR)\n+\t\tstrbuf_addstr(sb, GIT_COLOR_RESET);\n+}\n+\n struct git_graph {\n \t/*\n \t * The commit currently being processed\n@@ -185,6 +218,11 @@ struct git_graph {\n \t * temporary array each time we have to output a collapsing line.\n \t */\n \tint *new_mapping;\n+\t/*\n+\t * The current default column color being used.  This is\n+\t * stored as an index into the array column_colors.\n+\t */\n+\tunsigned short default_column_color;\n };\n \n struct git_graph *graph_init(struct rev_info *opt)\n@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n+\tgraph->default_column_color = 0;\n \n \t/*\n \t * Allocate a reasonably large default number of columns\n@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n \treturn next_interesting_parent(graph, parents);\n }\n \n+inline unsigned short graph_get_current_column_color(const struct git_graph *graph)\n+{\n+\tif (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))\n+\t\treturn GIT_NOT_A_COLOR;\n+\treturn graph->default_column_color;\n+}\n+\n+/*\n+ * Update the graph's default column color.\n+ */\n+static void graph_increment_column_color(struct git_graph *graph)\n+{\n+\tgraph->default_column_color = (graph->default_column_color + 1) %\n+\t\tARRAY_SIZE(column_colors);\n+}\n+\n+inline unsigned short graph_find_commit_color(const struct git_graph *graph,\n+\t\t\t\t\t      const struct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_columns; i++) {\n+\t\tif (graph->columns[i].commit == commit)\n+\t\t\treturn graph->columns[i].color;\n+\t}\n+\treturn graph_get_current_column_color(graph);\n+}\n+\n static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\t\t\t\t  struct commit *commit,\n \t\t\t\t\t  int *mapping_index)\n@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t * This commit isn't already in new_columns.  Add it.\n \t */\n \tgraph->new_columns[graph->num_new_columns].commit = commit;\n+\tgraph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);\n \tgraph->mapping[*mapping_index] = graph->num_new_columns;\n \t*mapping_index += 2;\n \tgraph->num_new_columns++;\n@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tfor (parent = first_interesting_parent(graph);\n \t\t\t     parent;\n \t\t\t     parent = next_interesting_parent(graph, parent)) {\n+\t\t\t\t/*\n+\t\t\t\t * If this is a merge increment the current\n+\t\t\t\t * color.\n+\t\t\t\t */\n+\t\t\t\tif (graph->num_parents > 1)\n+\t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\tgraph_insert_into_new_columns(graph,\n \t\t\t\t\t\t\t      parent->item,\n \t\t\t\t\t\t\t      &mapping_idx);\n@@ -569,11 +642,11 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n \t * This way, fields printed to the right of the graph will remain\n \t * aligned for the entire commit.\n \t */\n-\tint extra;\n-\tif (sb->len >= graph->width)\n+\tint extra, printing;\n+\tprinting = strbuf_count_printing_chars(sb);\n+\tif (printing >= graph->width)\n \t\treturn;\n-\n-\textra = graph->width - sb->len;\n+\textra = graph->width - printing;\n \tstrbuf_addf(sb, \"%*s\", (int) extra, \"\");\n }\n \n@@ -596,7 +669,8 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n \tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tstrbuf_addstr(sb, \"| \");\n+\t\tstrbuf_write_column(sb, &graph->new_columns[i], '|');\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -649,7 +723,8 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tstruct column *col = &graph->columns[i];\n \t\tif (col->commit == graph->commit) {\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addf(sb, \"| %*s\", graph->expansion_row, \"\");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addf(sb, \" %*s\", graph->expansion_row, \"\");\n \t\t} else if (seen_this && (graph->expansion_row == 0)) {\n \t\t\t/*\n \t\t\t * This is the first line of the pre-commit output.\n@@ -662,14 +737,15 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -714,10 +790,30 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \tstrbuf_addch(sb, '*');\n }\n \n+inline void graph_draw_octopus_merge(const struct git_graph *graph,\n+\t\t\t\t     struct strbuf *sb)\n+{\n+\t/*\n+\t * Here dashless_commits represents the number of parents\n+\t * which don't need to have dashes (because their edges fit\n+\t * neatly under the commit).\n+\t */\n+\tconst int dashless_commits = 2;\n+\tint col_num, i;\n+\tint num_dashes =\n+\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n+\tfor (i = 0; i < num_dashes; i++) {\n+\t\tcol_num = (i / 2) + dashless_commits;\n+\t\tstrbuf_write_column(sb, &graph->new_columns[col_num], '-');\n+\t}\n+\tcol_num = (i / 2) + dashless_commits;\n+\tstrbuf_write_column(sb, &graph->new_columns[col_num], '.');\n+}\n+\n static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -728,6 +824,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tseen_this = 0;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n@@ -741,17 +838,10 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\tseen_this = 1;\n \t\t\tgraph_output_commit_char(graph, sb);\n \n-\t\t\tif (graph->num_parents < 3)\n-\t\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\telse {\n-\t\t\t\tint num_dashes =\n-\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n-\t\t\t\tfor (j = 0; j < num_dashes; j++)\n-\t\t\t\t\tstrbuf_addch(sb, '-');\n-\t\t\t\tstrbuf_addstr(sb, \". \");\n-\t\t\t}\n+\t\t\tif (graph->num_parents > 3)\n+\t\t\t\tgraph_draw_octopus_merge(graph, sb);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t} else if (seen_this && (graph->num_parents == 2)) {\n \t\t\t/*\n \t\t\t * This is a 2-way merge commit.\n@@ -768,12 +858,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -789,6 +880,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+inline struct column *find_new_column_by_commit(struct git_graph *graph,\n+\t\t\t\t\t\tstruct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_new_columns; i++) {\n+\t\tif (graph->new_columns[i].commit == commit)\n+\t\t\treturn &graph->new_columns[i];\n+\t}\n+\treturn 0;\n+}\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n@@ -798,24 +900,46 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t * Output the post-merge row\n \t */\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n \t\t\t\tbreak;\n \t\t\tcol_commit = graph->commit;\n \t\t} else {\n-\t\t\tcol_commit = graph->columns[i].commit;\n+\t\t\tcol_commit = col->commit;\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n+\t\t\t/*\n+\t\t\t * Since the current commit is a merge find\n+\t\t\t * the columns for the parent commits in\n+\t\t\t * new_columns and use those to format the\n+\t\t\t * edges.\n+\t\t\t */\n+\t\t\tstruct commit_list *parents = NULL;\n+\t\t\tstruct column *par_column;\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\t\tfor (j = 0; j < graph->num_parents - 1; j++)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tparents = first_interesting_parent(graph);\n+\t\t\tassert(parents);\n+\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\tassert(par_column);\n+\n+\t\t\tstrbuf_write_column(sb, par_column, '|');\n+\t\t\tfor (j = 0; j < graph->num_parents - 1; j++) {\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n+\t\t\t\tassert(parents);\n+\t\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\t\tassert(par_column);\n+\t\t\t\tstrbuf_write_column(sb, par_column, '\\\\');\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\t}\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t}\n \t}\n \n@@ -912,9 +1036,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\tif (target < 0)\n \t\t\tstrbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\telse\n-\t\t\tstrbuf_addch(sb, '/');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\telse {\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\n+\t\t}\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -979,9 +1105,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t * children that we have already processed.)\n \t */\n \tfor (i = 0; i < graph->num_columns; i++) {\n-\t\tstruct commit *col_commit = graph->columns[i].commit;\n+\t\tstruct column *col = &graph->columns[i];\n+\t\tstruct commit *col_commit = col->commit;\n \t\tif (col_commit == graph->commit) {\n-\t\t\tstrbuf_addch(sb, '|');\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \n \t\t\tif (graph->num_parents < 3)\n \t\t\t\tstrbuf_addch(sb, ' ');\n@@ -991,7 +1118,8 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\t}\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t}\n \t}\n \ndiff --git a/strbuf.c b/strbuf.c\nindex a884960..f525d51 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -1,3 +1,4 @@\n+#include <ctype.h>\n #include \"cache.h\"\n #include \"refs.h\"\n \n@@ -138,6 +139,44 @@ void strbuf_list_free(struct strbuf **sbs)\n \tfree(sbs);\n }\n \n+/*\n+ * Return the length of the escape sequence in a string buffer\n+ * starting at index i.  If there is no escape sequence starting at\n+ * return 0.\n+ */\n+inline size_t strbuf_esc_sequence_length(const struct strbuf *sb, size_t i)\n+{\n+\tsize_t start = i;\n+\tif (sb->buf[i] != '\\033')\n+\t\treturn 0;\n+\t++i;\n+\n+\tif (i >= sb->len || sb->buf[i] != '[')\n+\t\treturn 0;\n+\t++i;\n+\twhile (i < sb->len && isdigit(sb->buf[i]))\n+\t\t++i;\n+\n+\tif (i >= sb->len || sb->buf[i] != 'm')\n+\t\treturn 0;\n+\treturn i - start;\n+}\n+\n+size_t strbuf_count_printing_chars(const struct strbuf *sb)\n+{\n+\tint i;\n+\tsize_t n = 0;\n+\tsize_t esc_len;\n+\tfor (i = 0; i < sb->len; i++) {\n+\t\tesc_len = strbuf_esc_sequence_length(sb, i);\n+\t\tif (esc_len)\n+\t\t\ti += esc_len;\n+\t\telse if (isgraph(sb->buf[i]) || sb->buf[i] == ' ')\n+\t\t\t++n;\n+\t}\n+\treturn n;\n+}\n+\n int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)\n {\n \tint len = a->len < b->len ? a->len: b->len;\ndiff --git a/strbuf.h b/strbuf.h\nindex 9ee908a..3851ddf 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -86,6 +86,7 @@ extern void strbuf_tolower(struct strbuf *);\n extern struct strbuf **strbuf_split(const struct strbuf *, int delim);\n extern void strbuf_list_free(struct strbuf **);\n \n+extern size_t strbuf_count_printing_chars(const struct strbuf *sb);\n /*----- add data in your buffer -----*/\n static inline void strbuf_addch(struct strbuf *sb, int c) {\n \tstrbuf_grow(sb, 1);\n-- \n1.5.6.3\n"},{"id":"111123","messageId":"7vhc0u9s86.fsf@gitster.siamese.dyndns.org","threadId":"18768","inReplyTo":"20090409222238.GA16573@linux.vnet","subject":"Re: [PATCH v3] graph API: Added logic for colored edges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-12T08:44:09Z","receivedAt":"2009-04-12T08:44:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Allan Caffee <allan.caffee@gmail.com> writes:\n\n> diff --git a/graph.c b/graph.c\n> index 162a516..beb622a 100644\n> --- a/graph.c\n> +++ b/graph.c\n> @@ -1,5 +1,6 @@\n>  #include \"cache.h\"\n>  #include \"commit.h\"\n> +#include \"color.h\"\n>  #include \"graph.h\"\n>  #include \"diff.h\"\n>  #include \"revision.h\"\n> @@ -72,11 +69,14 @@ struct column {\n>  \t */\n>  \tstruct commit *commit;\n>  \t/*\n> -\t * XXX: Once we add support for colors, struct column could also\n> -\t * contain the color of its branch line.\n> +\t * The color to (optionally) print this column in.  This is an\n> +\t * index into column_colors.\n>  \t */\n> +\tunsigned short color;\n>  };\n>  \n> +const unsigned short GIT_NOT_A_COLOR = -1;\n\nThat (-1) is an unusual value for an *unsigned* short variable.\n\n> @@ -714,10 +790,30 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n>  \tstrbuf_addch(sb, '*');\n>  }\n>  \n> +inline void graph_draw_octopus_merge(const struct git_graph *graph,\n> +\t\t\t\t     struct strbuf *sb)\n> +{\n> +\t/*\n> +\t * Here dashless_commits represents the number of parents\n> +\t * which don't need to have dashes (because their edges fit\n> +\t * neatly under the commit).\n> +\t */\n> +\tconst int dashless_commits = 2;\n> +\tint col_num, i;\n> +\tint num_dashes =\n> +\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n> +\tfor (i = 0; i < num_dashes; i++) {\n> +\t\tcol_num = (i / 2) + dashless_commits;\n> +\t\tstrbuf_write_column(sb, &graph->new_columns[col_num], '-');\n\ngraph.c: In function 'graph_draw_octopus_merge':\ngraph.c:807: error: 'strbuf_write_column' is static but used in inline function 'graph_draw_octopus_merge' which is not static\ngraph.c:810: error: 'strbuf_write_column' is static but used in inline function 'graph_draw_octopus_merge' which is not static\nmake: *** [graph.o] Error 1\n\nIn general, I'd prefer people not to say \"inline\" unless (1) they know\nwhat they are doing, and (2) the code is really performance critical.\n\nAt least I do not think the colored commit graph is performance critical,\nespecially a function that only deals with octopus merges.\n"},{"id":"111133","messageId":"b2e43f8f0904121043o425ac311ib32c18625fac2c0e@mail.gmail.com","threadId":"18768","inReplyTo":"7vhc0u9s86.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH v3] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-12T17:43:19Z","receivedAt":"2009-04-12T17:43:19Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"On Sun, 12 Apr 2009, Junio C Hamano wrote:\n\n> Allan Caffee <allan.caffee@gmail.com> writes:\n>\n> > diff --git a/graph.c b/graph.c\n> > index 162a516..beb622a 100644\n> > --- a/graph.c\n> > +++ b/graph.c\n> > @@ -1,5 +1,6 @@\n> >  #include \"cache.h\"\n> >  #include \"commit.h\"\n> > +#include \"color.h\"\n> >  #include \"graph.h\"\n> >  #include \"diff.h\"\n> >  #include \"revision.h\"\n> > @@ -72,11 +69,14 @@ struct column {\n> >  \t */\n> >  \tstruct commit *commit;\n> >  \t/*\n> > -\t * XXX: Once we add support for colors, struct column could also\n> > -\t * contain the color of its branch line.\n> > +\t * The color to (optionally) print this column in.  This is an\n> > +\t * index into column_colors.\n> >  \t */\n> > +\tunsigned short color;\n> >  };\n> >\n> > +const unsigned short GIT_NOT_A_COLOR = -1;\n>\n> That (-1) is an unusual value for an *unsigned* short variable.\n\nPerhaps you would prefer USHRT_MAX?  I noticed that none of the existing\ncode #includes limits.h.  Is it safe to assume this header is present?\n\n> > @@ -714,10 +790,30 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n> >  \tstrbuf_addch(sb, '*');\n> >  }\n> >\n> > +inline void graph_draw_octopus_merge(const struct git_graph *graph,\n> > +\t\t\t\t     struct strbuf *sb)\n> > +{\n> > +\t/*\n> > +\t * Here dashless_commits represents the number of parents\n> > +\t * which don't need to have dashes (because their edges fit\n> > +\t * neatly under the commit).\n> > +\t */\n> > +\tconst int dashless_commits = 2;\n> > +\tint col_num, i;\n> > +\tint num_dashes =\n> > +\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n> > +\tfor (i = 0; i < num_dashes; i++) {\n> > +\t\tcol_num = (i / 2) + dashless_commits;\n> > +\t\tstrbuf_write_column(sb, &graph->new_columns[col_num], '-');\n>\n> graph.c: In function 'graph_draw_octopus_merge':\n> graph.c:807: error: 'strbuf_write_column' is static but used in inline function 'graph_draw_octopus_merge' which is not static\n> graph.c:810: error: 'strbuf_write_column' is static but used in inline function 'graph_draw_octopus_merge' which is not static\n> make: *** [graph.o] Error 1\n>\n> In general, I'd prefer people not to say \"inline\" unless (1) they know\n> what they are doing, and (2) the code is really performance critical.\n>\n> At least I do not think the colored commit graph is performance critical,\n> especially a function that only deals with octopus merges.\n\nOkay.  I'll remove the inline specifier from all the functions I added.\n"},{"id":"111135","messageId":"7vk55p7lu3.fsf@gitster.siamese.dyndns.org","threadId":"18768","inReplyTo":"b2e43f8f0904121043o425ac311ib32c18625fac2c0e@mail.gmail.com","subject":"Re: [PATCH v3] graph API: Added logic for colored edges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-12T18:45:08Z","receivedAt":"2009-04-12T18:45:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Allan Caffee <allan.caffee@gmail.com> writes:\n\n>> > +const unsigned short GIT_NOT_A_COLOR = -1;\n>>\n>> That (-1) is an unusual value for an *unsigned* short variable.\n>\n> Perhaps you would prefer USHRT_MAX?  I noticed that none of the existing\n> code #includes limits.h.  Is it safe to assume this header is present?\n\nI expected to see something like \n\n\t#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)+1)\n\n\twrite_with_color(...) {\n                if (c->color < COLUMN_COLORS_MAX)\n                        add color prefix;\n                add string\n                if (c->color < COLUMN_COLORS_MAX)\n                        add color suffix;\n\t}\n\ninstead, actually, and was a bit surprised with (-1).\n"},{"id":"111141","messageId":"20090412202709.GA20549@linux.vnet","threadId":"18768","inReplyTo":"7vk55p7lu3.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v4] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-12T20:27:09Z","receivedAt":"2009-04-12T20:27:09Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"Modified the graph drawing logic to colorize edges based on parent-child\nrelationships similiarly to gitk.\n\nSigned-off-by: Allan Caffee <allan.caffee@gmail.com>\n---\n\nOn Sun, 12 Apr 2009, Junio C Hamano wrote:\n\n> Allan Caffee <allan.caffee@gmail.com> writes:\n>\n> >> > +const unsigned short GIT_NOT_A_COLOR = -1;\n> >>\n> >> That (-1) is an unusual value for an *unsigned* short variable.\n> >\n> > Perhaps you would prefer USHRT_MAX?  I noticed that none of the\n> > existing\n> > code #includes limits.h.  Is it safe to assume this header is\n> > present?\n>\n> I expected to see something like\n>\n>       #define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)+1)\n>\n>       write_with_color(...) {\n>                 if (c->color < COLUMN_COLORS_MAX)\n>                         add color prefix;\n>                 add string\n>                 if (c->color < COLUMN_COLORS_MAX)\n>                         add color suffix;\n>       }\n>\n> instead, actually, and was a bit surprised with (-1).\n\nJunio,\n\nI assumed that the +1 in your example was a typo since AFAIKS ARRAY_SIZE\nshould give us one past the last index.  If that's not correct let me\nknow and I'll fix it.\n\nAlso if git is to be expanded allow the use of non-ANSI color codes (or\nalready does so) the strbuf_escape_sequence_length needs to be updated\nto accept the relevant escape codes.\n\nThanks for all the help,\nAllan\n\n color.h  |    1 +\n graph.c  |  212 +++++++++++++++++++++++++++++++++++++++++++++++++------------\n strbuf.c |   39 +++++++++++\n strbuf.h |    1 +\n 4 files changed, 211 insertions(+), 42 deletions(-)\n\ndiff --git a/color.h b/color.h\nindex 6846be1..18abeb7 100644\n--- a/color.h\n+++ b/color.h\n@@ -11,6 +11,7 @@\n #define GIT_COLOR_GREEN\t\t\"\\033[32m\"\n #define GIT_COLOR_YELLOW\t\"\\033[33m\"\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n+#define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n \ndiff --git a/graph.c b/graph.c\nindex 162a516..1ae7a9c 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"color.h\"\n #include \"graph.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);\n \n /*\n  * TODO:\n- * - Add colors to the graph.\n- *   Pick a color for each column, and print all characters\n- *   in that column with the specified color.\n- *\n  * - Limit the number of columns, similar to the way gitk does.\n  *   If we reach more than a specified number of columns, omit\n  *   sections of some columns.\n@@ -72,11 +69,14 @@ struct column {\n \t */\n \tstruct commit *commit;\n \t/*\n-\t * XXX: Once we add support for colors, struct column could also\n-\t * contain the color of its branch line.\n+\t * The color to (optionally) print this column in.  This is an\n+\t * index into column_colors.\n \t */\n+\tunsigned short color;\n };\n \n+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))\n+\n enum graph_state {\n \tGRAPH_PADDING,\n \tGRAPH_SKIP,\n@@ -86,6 +86,39 @@ enum graph_state {\n \tGRAPH_COLLAPSING\n };\n \n+/*\n+ * The list of available column colors.\n+ */\n+static char column_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RED,\n+\tGIT_COLOR_GREEN,\n+\tGIT_COLOR_YELLOW,\n+\tGIT_COLOR_BLUE,\n+\tGIT_COLOR_MAGENTA,\n+\tGIT_COLOR_CYAN,\n+\tGIT_COLOR_BOLD GIT_COLOR_RED,\n+\tGIT_COLOR_BOLD GIT_COLOR_GREEN,\n+\tGIT_COLOR_BOLD GIT_COLOR_YELLOW,\n+\tGIT_COLOR_BOLD GIT_COLOR_BLUE,\n+\tGIT_COLOR_BOLD GIT_COLOR_MAGENTA,\n+\tGIT_COLOR_BOLD GIT_COLOR_CYAN,\n+};\n+\n+static const char *column_get_color_code(const struct column *c)\n+{\n+\treturn column_colors[c->color];\n+}\n+\n+static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n+\t\t\t\tchar col_char)\n+{\n+\tif (c->color < COLUMN_COLORS_MAX)\n+\t\tstrbuf_addstr(sb, column_get_color_code(c));\n+\tstrbuf_addch(sb, col_char);\n+\tif (c->color < COLUMN_COLORS_MAX)\n+\t\tstrbuf_addstr(sb, GIT_COLOR_RESET);\n+}\n+\n struct git_graph {\n \t/*\n \t * The commit currently being processed\n@@ -185,6 +218,11 @@ struct git_graph {\n \t * temporary array each time we have to output a collapsing line.\n \t */\n \tint *new_mapping;\n+\t/*\n+\t * The current default column color being used.  This is\n+\t * stored as an index into the array column_colors.\n+\t */\n+\tunsigned short default_column_color;\n };\n \n struct git_graph *graph_init(struct rev_info *opt)\n@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n+\tgraph->default_column_color = 0;\n \n \t/*\n \t * Allocate a reasonably large default number of columns\n@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n \treturn next_interesting_parent(graph, parents);\n }\n \n+unsigned short graph_get_current_column_color(const struct git_graph *graph)\n+{\n+\tif (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))\n+\t\treturn COLUMN_COLORS_MAX;\n+\treturn graph->default_column_color;\n+}\n+\n+/*\n+ * Update the graph's default column color.\n+ */\n+static void graph_increment_column_color(struct git_graph *graph)\n+{\n+\tgraph->default_column_color = (graph->default_column_color + 1) %\n+\t\tARRAY_SIZE(column_colors);\n+}\n+\n+unsigned short graph_find_commit_color(const struct git_graph *graph,\n+\t\t\t\t       const struct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_columns; i++) {\n+\t\tif (graph->columns[i].commit == commit)\n+\t\t\treturn graph->columns[i].color;\n+\t}\n+\treturn graph_get_current_column_color(graph);\n+}\n+\n static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\t\t\t\t  struct commit *commit,\n \t\t\t\t\t  int *mapping_index)\n@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t * This commit isn't already in new_columns.  Add it.\n \t */\n \tgraph->new_columns[graph->num_new_columns].commit = commit;\n+\tgraph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);\n \tgraph->mapping[*mapping_index] = graph->num_new_columns;\n \t*mapping_index += 2;\n \tgraph->num_new_columns++;\n@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tfor (parent = first_interesting_parent(graph);\n \t\t\t     parent;\n \t\t\t     parent = next_interesting_parent(graph, parent)) {\n+\t\t\t\t/*\n+\t\t\t\t * If this is a merge increment the current\n+\t\t\t\t * color.\n+\t\t\t\t */\n+\t\t\t\tif (graph->num_parents > 1)\n+\t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\tgraph_insert_into_new_columns(graph,\n \t\t\t\t\t\t\t      parent->item,\n \t\t\t\t\t\t\t      &mapping_idx);\n@@ -569,11 +642,11 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n \t * This way, fields printed to the right of the graph will remain\n \t * aligned for the entire commit.\n \t */\n-\tint extra;\n-\tif (sb->len >= graph->width)\n+\tint extra, printing;\n+\tprinting = strbuf_count_printing_chars(sb);\n+\tif (printing >= graph->width)\n \t\treturn;\n-\n-\textra = graph->width - sb->len;\n+\textra = graph->width - printing;\n \tstrbuf_addf(sb, \"%*s\", (int) extra, \"\");\n }\n \n@@ -596,7 +669,8 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n \tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tstrbuf_addstr(sb, \"| \");\n+\t\tstrbuf_write_column(sb, &graph->new_columns[i], '|');\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -649,7 +723,8 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tstruct column *col = &graph->columns[i];\n \t\tif (col->commit == graph->commit) {\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addf(sb, \"| %*s\", graph->expansion_row, \"\");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addf(sb, \" %*s\", graph->expansion_row, \"\");\n \t\t} else if (seen_this && (graph->expansion_row == 0)) {\n \t\t\t/*\n \t\t\t * This is the first line of the pre-commit output.\n@@ -662,14 +737,15 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -714,10 +790,30 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \tstrbuf_addch(sb, '*');\n }\n \n+void graph_draw_octopus_merge(const struct git_graph *graph,\n+\t\t\t      struct strbuf *sb)\n+{\n+\t/*\n+\t * Here dashless_commits represents the number of parents\n+\t * which don't need to have dashes (because their edges fit\n+\t * neatly under the commit).\n+\t */\n+\tconst int dashless_commits = 2;\n+\tint col_num, i;\n+\tint num_dashes =\n+\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n+\tfor (i = 0; i < num_dashes; i++) {\n+\t\tcol_num = (i / 2) + dashless_commits;\n+\t\tstrbuf_write_column(sb, &graph->new_columns[col_num], '-');\n+\t}\n+\tcol_num = (i / 2) + dashless_commits;\n+\tstrbuf_write_column(sb, &graph->new_columns[col_num], '.');\n+}\n+\n static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -728,6 +824,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tseen_this = 0;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n@@ -741,17 +838,10 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\tseen_this = 1;\n \t\t\tgraph_output_commit_char(graph, sb);\n \n-\t\t\tif (graph->num_parents < 3)\n-\t\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\telse {\n-\t\t\t\tint num_dashes =\n-\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n-\t\t\t\tfor (j = 0; j < num_dashes; j++)\n-\t\t\t\t\tstrbuf_addch(sb, '-');\n-\t\t\t\tstrbuf_addstr(sb, \". \");\n-\t\t\t}\n+\t\t\tif (graph->num_parents > 3)\n+\t\t\t\tgraph_draw_octopus_merge(graph, sb);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t} else if (seen_this && (graph->num_parents == 2)) {\n \t\t\t/*\n \t\t\t * This is a 2-way merge commit.\n@@ -768,12 +858,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -789,6 +880,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+struct column *find_new_column_by_commit(struct git_graph *graph,\n+\t\t\t\t\t struct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_new_columns; i++) {\n+\t\tif (graph->new_columns[i].commit == commit)\n+\t\t\treturn &graph->new_columns[i];\n+\t}\n+\treturn 0;\n+}\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n@@ -798,24 +900,46 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t * Output the post-merge row\n \t */\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n \t\t\t\tbreak;\n \t\t\tcol_commit = graph->commit;\n \t\t} else {\n-\t\t\tcol_commit = graph->columns[i].commit;\n+\t\t\tcol_commit = col->commit;\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n+\t\t\t/*\n+\t\t\t * Since the current commit is a merge find\n+\t\t\t * the columns for the parent commits in\n+\t\t\t * new_columns and use those to format the\n+\t\t\t * edges.\n+\t\t\t */\n+\t\t\tstruct commit_list *parents = NULL;\n+\t\t\tstruct column *par_column;\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\t\tfor (j = 0; j < graph->num_parents - 1; j++)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tparents = first_interesting_parent(graph);\n+\t\t\tassert(parents);\n+\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\tassert(par_column);\n+\n+\t\t\tstrbuf_write_column(sb, par_column, '|');\n+\t\t\tfor (j = 0; j < graph->num_parents - 1; j++) {\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n+\t\t\t\tassert(parents);\n+\t\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\t\tassert(par_column);\n+\t\t\t\tstrbuf_write_column(sb, par_column, '\\\\');\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\t}\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t}\n \t}\n \n@@ -912,9 +1036,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\tif (target < 0)\n \t\t\tstrbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\telse\n-\t\t\tstrbuf_addch(sb, '/');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\telse {\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\n+\t\t}\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\n@@ -979,9 +1105,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t * children that we have already processed.)\n \t */\n \tfor (i = 0; i < graph->num_columns; i++) {\n-\t\tstruct commit *col_commit = graph->columns[i].commit;\n+\t\tstruct column *col = &graph->columns[i];\n+\t\tstruct commit *col_commit = col->commit;\n \t\tif (col_commit == graph->commit) {\n-\t\t\tstrbuf_addch(sb, '|');\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \n \t\t\tif (graph->num_parents < 3)\n \t\t\t\tstrbuf_addch(sb, ' ');\n@@ -991,7 +1118,8 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\t}\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t}\n \t}\n \ndiff --git a/strbuf.c b/strbuf.c\nindex a884960..666460d 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -1,3 +1,4 @@\n+#include <ctype.h>\n #include \"cache.h\"\n #include \"refs.h\"\n \n@@ -138,6 +139,44 @@ void strbuf_list_free(struct strbuf **sbs)\n \tfree(sbs);\n }\n \n+/*\n+ * Return the length of the escape sequence in a string buffer\n+ * starting at index i.  If there is no escape sequence starting at\n+ * return 0.\n+ */\n+size_t strbuf_esc_sequence_length(const struct strbuf *sb, size_t i)\n+{\n+\tsize_t start = i;\n+\tif (sb->buf[i] != '\\033')\n+\t\treturn 0;\n+\t++i;\n+\n+\tif (i >= sb->len || sb->buf[i] != '[')\n+\t\treturn 0;\n+\t++i;\n+\twhile (i < sb->len && isdigit(sb->buf[i]))\n+\t\t++i;\n+\n+\tif (i >= sb->len || sb->buf[i] != 'm')\n+\t\treturn 0;\n+\treturn i - start;\n+}\n+\n+size_t strbuf_count_printing_chars(const struct strbuf *sb)\n+{\n+\tint i;\n+\tsize_t n = 0;\n+\tsize_t esc_len;\n+\tfor (i = 0; i < sb->len; i++) {\n+\t\tesc_len = strbuf_esc_sequence_length(sb, i);\n+\t\tif (esc_len)\n+\t\t\ti += esc_len;\n+\t\telse if (isgraph(sb->buf[i]) || sb->buf[i] == ' ')\n+\t\t\t++n;\n+\t}\n+\treturn n;\n+}\n+\n int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)\n {\n \tint len = a->len < b->len ? a->len: b->len;\ndiff --git a/strbuf.h b/strbuf.h\nindex 9ee908a..3851ddf 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -86,6 +86,7 @@ extern void strbuf_tolower(struct strbuf *);\n extern struct strbuf **strbuf_split(const struct strbuf *, int delim);\n extern void strbuf_list_free(struct strbuf **);\n \n+extern size_t strbuf_count_printing_chars(const struct strbuf *sb);\n /*----- add data in your buffer -----*/\n static inline void strbuf_addch(struct strbuf *sb, int c) {\n \tstrbuf_grow(sb, 1);\n-- \n1.5.6.3\n"},{"id":"111143","messageId":"7veivx354b.fsf@gitster.siamese.dyndns.org","threadId":"18768","inReplyTo":"20090412202709.GA20549@linux.vnet","subject":"Re: [PATCH v4] graph API: Added logic for colored edges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-12T21:59:48Z","receivedAt":"2009-04-12T21:59:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Allan Caffee <allan.caffee@gmail.com> writes:\n\n> I assumed that the +1 in your example was a typo since AFAIKS ARRAY_SIZE\n> should give us one past the last index.\n\nYou are correct.\n\n> Also if git is to be expanded allow the use of non-ANSI color codes (or\n> already does so) the strbuf_escape_sequence_length needs to be updated\n> to accept the relevant escape codes.\n\nActually, I am starting to hate this.\n\nJust step back a bit and imagine how you would do this, if you _were_\nwriting an application to do this kind of thing, generating output\ndirectly to the terminal.  You obviously would not seek back and count the\nwidth of what you sent out.  Instead,...?\n\nThat's right.  You just keep a running total of how much you sent, iow,\nwhat column you expect the current cursor should be.  Can't we do the same\nthing here?\n\n> @@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n>  \treturn next_interesting_parent(graph, parents);\n>  }\n>  \n> +unsigned short graph_get_current_column_color(const struct git_graph *graph)\n\nstatic?\n\n> +static void graph_increment_column_color(struct git_graph *graph)\n> +{\n> +\tgraph->default_column_color = (graph->default_column_color + 1) %\n> +\t\tARRAY_SIZE(column_colors);\n\nCOLUMN_COLORS_MAX?\n\n> +unsigned short graph_find_commit_color(const struct git_graph *graph,\n> +\t\t\t\t       const struct commit *commit)\n> +{\n\nstatic?\n\n> @@ -714,10 +790,30 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n>  \tstrbuf_addch(sb, '*');\n>  }\n>  \n> +void graph_draw_octopus_merge(const struct git_graph *graph,\n> +\t\t\t      struct strbuf *sb)\n\nstatic?\n\n> @@ -789,6 +880,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n>  }\n>  \n> +struct column *find_new_column_by_commit(struct git_graph *graph,\n> +\t\t\t\t\t struct commit *commit)\n> +{\n\nstatic?\n\n> diff --git a/strbuf.c b/strbuf.c\n> index a884960..666460d 100644\n> --- a/strbuf.c\n> +++ b/strbuf.c\n> @@ -1,3 +1,4 @@\n> +#include <ctype.h>\n>  #include \"cache.h\"\n>  #include \"refs.h\"\n\nBAD.  Do not directly include system headers.  If you need isgraph(),\nplease support it as part of sane_ctype.  But if you count what you\nemitted so far, you would not have to do this at all.\n\n> +/*\n> + * Return the length of the escape sequence in a string buffer\n> + * starting at index i.  If there is no escape sequence starting at\n> + * return 0.\n> + */\n> +size_t strbuf_esc_sequence_length(const struct strbuf *sb, size_t i)\n> +{\n> +\tsize_t start = i;\n> +\tif (sb->buf[i] != '\\033')\n> +\t\treturn 0;\n> +\t++i;\n> +\n> +\tif (i >= sb->len || sb->buf[i] != '[')\n> +\t\treturn 0;\n> +\t++i;\n> +\twhile (i < sb->len && isdigit(sb->buf[i]))\n> +\t\t++i;\n> +\tif (i >= sb->len || sb->buf[i] != 'm')\n> +\t\treturn 0;\n\nThese preincrements are extremely unreadable at least for me.\n\n\tif (sb->buf[i++] != '\\033')\n        \treturn 0;\n\tif (sb->len <= i || sb->buf[i++] != '[')\n        \treturn 0;\n\t...\n\nBut again the point is hopefully moot.\n"},{"id":"111229","messageId":"20090413195341.GA20532@linux.vnet","threadId":"18768","inReplyTo":"7veivx354b.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v5] graph API: Added logic for colored edges","fromName":"Allan Caffee","fromEmail":"allan.caffee@gmail.com","sentAt":"2009-04-13T19:53:41Z","receivedAt":"2009-04-13T19:53:41Z","isPatch":true,"sender":{"key":"allan.caffee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/114759?v=4"},"body":"Modified the graph drawing logic to colorize edges based on parent-child\nrelationships similiarly to gitk.\n\nSigned-off-by: Allan Caffee <allan.caffee@gmail.com>\n---\n color.h |    1 +\n graph.c |  246 ++++++++++++++++++++++++++++++++++++++++++++++++++------------\n 2 files changed, 199 insertions(+), 48 deletions(-)\n\ndiff --git a/color.h b/color.h\nindex 6846be1..18abeb7 100644\n--- a/color.h\n+++ b/color.h\n@@ -11,6 +11,7 @@\n #define GIT_COLOR_GREEN\t\t\"\\033[32m\"\n #define GIT_COLOR_YELLOW\t\"\\033[33m\"\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n+#define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n #define GIT_COLOR_BG_RED\t\"\\033[41m\"\n \ndiff --git a/graph.c b/graph.c\nindex 162a516..0fcb61a 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"color.h\"\n #include \"graph.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);\n \n /*\n  * TODO:\n- * - Add colors to the graph.\n- *   Pick a color for each column, and print all characters\n- *   in that column with the specified color.\n- *\n  * - Limit the number of columns, similar to the way gitk does.\n  *   If we reach more than a specified number of columns, omit\n  *   sections of some columns.\n@@ -72,9 +69,10 @@ struct column {\n \t */\n \tstruct commit *commit;\n \t/*\n-\t * XXX: Once we add support for colors, struct column could also\n-\t * contain the color of its branch line.\n+\t * The color to (optionally) print this column in.  This is an\n+\t * index into column_colors.\n \t */\n+\tunsigned short color;\n };\n \n enum graph_state {\n@@ -86,6 +84,41 @@ enum graph_state {\n \tGRAPH_COLLAPSING\n };\n \n+/*\n+ * The list of available column colors.\n+ */\n+static char column_colors[][COLOR_MAXLEN] = {\n+\tGIT_COLOR_RED,\n+\tGIT_COLOR_GREEN,\n+\tGIT_COLOR_YELLOW,\n+\tGIT_COLOR_BLUE,\n+\tGIT_COLOR_MAGENTA,\n+\tGIT_COLOR_CYAN,\n+\tGIT_COLOR_BOLD GIT_COLOR_RED,\n+\tGIT_COLOR_BOLD GIT_COLOR_GREEN,\n+\tGIT_COLOR_BOLD GIT_COLOR_YELLOW,\n+\tGIT_COLOR_BOLD GIT_COLOR_BLUE,\n+\tGIT_COLOR_BOLD GIT_COLOR_MAGENTA,\n+\tGIT_COLOR_BOLD GIT_COLOR_CYAN,\n+};\n+\n+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))\n+\n+static const char *column_get_color_code(const struct column *c)\n+{\n+\treturn column_colors[c->color];\n+}\n+\n+static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n+\t\t\t\tchar col_char)\n+{\n+\tif (c->color < COLUMN_COLORS_MAX)\n+\t\tstrbuf_addstr(sb, column_get_color_code(c));\n+\tstrbuf_addch(sb, col_char);\n+\tif (c->color < COLUMN_COLORS_MAX)\n+\t\tstrbuf_addstr(sb, GIT_COLOR_RESET);\n+}\n+\n struct git_graph {\n \t/*\n \t * The commit currently being processed\n@@ -185,6 +218,11 @@ struct git_graph {\n \t * temporary array each time we have to output a collapsing line.\n \t */\n \tint *new_mapping;\n+\t/*\n+\t * The current default column color being used.  This is\n+\t * stored as an index into the array column_colors.\n+\t */\n+\tunsigned short default_column_color;\n };\n \n struct git_graph *graph_init(struct rev_info *opt)\n@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n+\tgraph->default_column_color = 0;\n \n \t/*\n \t * Allocate a reasonably large default number of columns\n@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)\n \treturn next_interesting_parent(graph, parents);\n }\n \n+static unsigned short graph_get_current_column_color(const struct git_graph *graph)\n+{\n+\tif (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))\n+\t\treturn COLUMN_COLORS_MAX;\n+\treturn graph->default_column_color;\n+}\n+\n+/*\n+ * Update the graph's default column color.\n+ */\n+static void graph_increment_column_color(struct git_graph *graph)\n+{\n+\tgraph->default_column_color = (graph->default_column_color + 1) %\n+\t\tCOLUMN_COLORS_MAX;\n+}\n+\n+static unsigned short graph_find_commit_color(const struct git_graph *graph,\n+\t\t\t\t\t      const struct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_columns; i++) {\n+\t\tif (graph->columns[i].commit == commit)\n+\t\t\treturn graph->columns[i].color;\n+\t}\n+\treturn graph_get_current_column_color(graph);\n+}\n+\n static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\t\t\t\t  struct commit *commit,\n \t\t\t\t\t  int *mapping_index)\n@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t * This commit isn't already in new_columns.  Add it.\n \t */\n \tgraph->new_columns[graph->num_new_columns].commit = commit;\n+\tgraph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);\n \tgraph->mapping[*mapping_index] = graph->num_new_columns;\n \t*mapping_index += 2;\n \tgraph->num_new_columns++;\n@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tfor (parent = first_interesting_parent(graph);\n \t\t\t     parent;\n \t\t\t     parent = next_interesting_parent(graph, parent)) {\n+\t\t\t\t/*\n+\t\t\t\t * If this is a merge increment the current\n+\t\t\t\t * color.\n+\t\t\t\t */\n+\t\t\t\tif (graph->num_parents > 1)\n+\t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\tgraph_insert_into_new_columns(graph,\n \t\t\t\t\t\t\t      parent->item,\n \t\t\t\t\t\t\t      &mapping_idx);\n@@ -560,7 +633,8 @@ static int graph_is_mapping_correct(struct git_graph *graph)\n \treturn 1;\n }\n \n-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,\n+\t\t\t\t   int chars_written)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -570,10 +644,10 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n \t * aligned for the entire commit.\n \t */\n \tint extra;\n-\tif (sb->len >= graph->width)\n+\tif (chars_written >= graph->width)\n \t\treturn;\n \n-\textra = graph->width - sb->len;\n+\textra = graph->width - chars_written;\n \tstrbuf_addf(sb, \"%*s\", (int) extra, \"\");\n }\n \n@@ -596,10 +670,11 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n \tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tstrbuf_addstr(sb, \"| \");\n+\t\tstrbuf_write_column(sb, &graph->new_columns[i], '|');\n+\t\tstrbuf_addch(sb, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n }\n \n static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n@@ -609,7 +684,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n \t * of the graph is missing.\n \t */\n \tstrbuf_addstr(sb, \"...\");\n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, 3);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -623,6 +698,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n {\n \tint num_expansion_rows;\n \tint i, seen_this;\n+\tint chars_written;\n \n \t/*\n \t * This function formats a row that increases the space around a commit\n@@ -645,11 +721,14 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * Output the row\n \t */\n \tseen_this = 0;\n+\tchars_written = 0;\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \t\tif (col->commit == graph->commit) {\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addf(sb, \"| %*s\", graph->expansion_row, \"\");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addf(sb, \" %*s\", graph->expansion_row, \"\");\n+\t\t\tchars_written += 2 + graph->expansion_row;\n \t\t} else if (seen_this && (graph->expansion_row == 0)) {\n \t\t\t/*\n \t\t\t * This is the first line of the pre-commit output.\n@@ -662,17 +741,22 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tchars_written++;\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tchars_written++;\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tchars_written++;\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n+\t\tchars_written++;\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, chars_written);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -714,10 +798,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \tstrbuf_addch(sb, '*');\n }\n \n+/*\n+ * Draw an octopus merge and return the number of characters written.\n+ */\n+static int graph_draw_octopus_merge(struct git_graph *graph,\n+\t\t\t\t    struct strbuf *sb)\n+{\n+\t/*\n+\t * Here dashless_commits represents the number of parents\n+\t * which don't need to have dashes (because their edges fit\n+\t * neatly under the commit).\n+\t */\n+\tconst int dashless_commits = 2;\n+\tint col_num, i;\n+\tint num_dashes =\n+\t\t((graph->num_parents - dashless_commits) * 2) - 1;\n+\tfor (i = 0; i < num_dashes; i++) {\n+\t\tcol_num = (i / 2) + dashless_commits;\n+\t\tstrbuf_write_column(sb, &graph->new_columns[col_num], '-');\n+\t}\n+\tcol_num = (i / 2) + dashless_commits;\n+\tstrbuf_write_column(sb, &graph->new_columns[col_num], '.');\n+\treturn num_dashes + 1;\n+}\n+\n static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i, chars_written;\n \n \t/*\n \t * Output the row containing this commit\n@@ -727,7 +835,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t * children that we have already processed.)\n \t */\n \tseen_this = 0;\n+\tchars_written = 0;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n@@ -740,18 +850,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tif (col_commit == graph->commit) {\n \t\t\tseen_this = 1;\n \t\t\tgraph_output_commit_char(graph, sb);\n+\t\t\tchars_written++;\n \n-\t\t\tif (graph->num_parents < 3)\n-\t\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\telse {\n-\t\t\t\tint num_dashes =\n-\t\t\t\t\t((graph->num_parents - 2) * 2) - 1;\n-\t\t\t\tfor (j = 0; j < num_dashes; j++)\n-\t\t\t\t\tstrbuf_addch(sb, '-');\n-\t\t\t\tstrbuf_addstr(sb, \". \");\n-\t\t\t}\n+\t\t\tif (graph->num_parents > 3)\n+\t\t\t\tchars_written += graph_draw_octopus_merge(graph,\n+\t\t\t\t\t\t\t\t\t  sb);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tchars_written++;\n \t\t} else if (seen_this && (graph->num_parents == 2)) {\n \t\t\t/*\n \t\t\t * This is a 2-way merge commit.\n@@ -768,15 +874,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n \t\t\t    graph->prev_commit_index < i)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tchars_written++;\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tchars_written++;\n \t\t}\n+\t\tstrbuf_addch(sb, ' ');\n+\t\tchars_written++;\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, chars_written);\n \n \t/*\n \t * Update graph->state\n@@ -789,37 +899,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+static struct column *find_new_column_by_commit(struct git_graph *graph,\n+\t\t\t\t\t\tstruct commit *commit)\n+{\n+\tint i;\n+\tfor (i = 0; i < graph->num_new_columns; i++) {\n+\t\tif (graph->new_columns[i].commit == commit)\n+\t\t\treturn &graph->new_columns[i];\n+\t}\n+\treturn 0;\n+}\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i, j, chars_written;\n \n \t/*\n \t * Output the post-merge row\n \t */\n+\tchars_written = 0;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n+\t\tstruct column *col = &graph->columns[i];\n \t\tstruct commit *col_commit;\n \t\tif (i == graph->num_columns) {\n \t\t\tif (seen_this)\n \t\t\t\tbreak;\n \t\t\tcol_commit = graph->commit;\n \t\t} else {\n-\t\t\tcol_commit = graph->columns[i].commit;\n+\t\t\tcol_commit = col->commit;\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n+\t\t\t/*\n+\t\t\t * Since the current commit is a merge find\n+\t\t\t * the columns for the parent commits in\n+\t\t\t * new_columns and use those to format the\n+\t\t\t * edges.\n+\t\t\t */\n+\t\t\tstruct commit_list *parents = NULL;\n+\t\t\tstruct column *par_column;\n \t\t\tseen_this = 1;\n-\t\t\tstrbuf_addch(sb, '|');\n-\t\t\tfor (j = 0; j < graph->num_parents - 1; j++)\n-\t\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tparents = first_interesting_parent(graph);\n+\t\t\tassert(parents);\n+\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\tassert(par_column);\n+\n+\t\t\tstrbuf_write_column(sb, par_column, '|');\n+\t\t\tchars_written++;\n+\t\t\tfor (j = 0; j < graph->num_parents - 1; j++) {\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n+\t\t\t\tassert(parents);\n+\t\t\t\tpar_column = find_new_column_by_commit(graph,parents->item);\n+\t\t\t\tassert(par_column);\n+\t\t\t\tstrbuf_write_column(sb, par_column, '\\\\');\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\t}\n+\t\t\tchars_written += j * 2;\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_addstr(sb, \"\\\\ \");\n+\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tchars_written += 2;\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tchars_written += 2;\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, chars_written);\n \n \t/*\n \t * Update graph->state\n@@ -912,12 +1060,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\tif (target < 0)\n \t\t\tstrbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_addch(sb, '|');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n \t\telse\n-\t\t\tstrbuf_addch(sb, '/');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -979,9 +1127,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t * children that we have already processed.)\n \t */\n \tfor (i = 0; i < graph->num_columns; i++) {\n-\t\tstruct commit *col_commit = graph->columns[i].commit;\n+\t\tstruct column *col = &graph->columns[i];\n+\t\tstruct commit *col_commit = col->commit;\n \t\tif (col_commit == graph->commit) {\n-\t\t\tstrbuf_addch(sb, '|');\n+\t\t\tstrbuf_write_column(sb, col, '|');\n \n \t\t\tif (graph->num_parents < 3)\n \t\t\t\tstrbuf_addch(sb, ' ');\n@@ -991,11 +1140,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\t}\n \t\t} else {\n-\t\t\tstrbuf_addstr(sb, \"| \");\n+\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t\tstrbuf_addch(sb, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb);\n+\tgraph_pad_horizontally(graph, sb, graph->num_columns);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\n-- \n1.5.6.3\n"}]}