{"thread":{"id":"52011","subject":"[PATCH 00/11] Improve the readability of log --graph output","startedAt":"2019-10-10T16:13:47Z","lastAt":"2019-11-13T02:05:52Z","messageCount":83,"participants":["James Coglan via GitGitGadget","Derrick Stolee","Denton Liu","Johannes Schindelin","Junio C Hamano","James Coglan","Jeff King","SZEDER Gábor"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"383804","messageId":"pull.383.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":null,"subject":"[PATCH 00/11] Improve the readability of log --graph output","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:42Z","receivedAt":"2019-10-10T16:13:47Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"This series of patches are designed to improve the output of the log --graph\ncommand; their effect can be summed up in the following diagram:\n\n    Before                    After\n    ------                    -----\n\n    *\n    |\\\n    | *                       *\n    | |\\                      |\\\n    | | *                     | *\n    | | |                     | |\\\n    | |  \\                    | | *\n    | *-. \\                   | * |\n    | |\\ \\ \\                  |/|\\|\n    |/ / / /                  | | *\n    | | | /                   | * |\n    | | |/                    | |/\n    | | *                     * /\n    | * |                     |/\n    | |/                      *\n    * |\n    |/\n    *\n\nThese changes aim to make the edges in graph diagrams easier to read, by\nstraightening lines and making certain kinds of topologies display more\ncompactly. Three distinct changes are included.\n\nFirst, if the first parent of a merge fuses with an edge to the left of the\ncommit, then we display that by making the edges fuse immediately rather\nthan by drawing a line straight down and then having it track to the left.\nThat is, where we currently display these graphs:\n\n    | *             | | | *\n    | |\\            | | | |\\\n    |/ /            | |_|/ /\n    | |             |/| | |\n\nWe will now display these merges as follows:\n\n    | *             | | | *\n    |/|             | |_|/|\n    | |             |/| | |\n\nThis transformation is applied to merges with any number of parents, for\nexample we currently display 3-parent merges like this:\n\n    | *-.           | | | *-.\n    | |\\ \\          | | | |\\ \\\n    |/ / /          | |_|/ / /\n    | | |           |/| | | |\n\nAnd we will now display them like this:\n\n    | *             | | | *\n    |/|\\            | |_|/|\\\n    | | |           |/| | | |\n\nIf the edge the first parent fuses with is separated from the commit by\nmultiple columns, a horizontal edge is drawn just as we currently do in the\n'collapsing' state. This change also affects the display of commit and\npost-merge lines in subtle ways that are more thoroughly described in the\nrelevant commits.\n\nThe second change is that if the final parent of a merge fuses with the edge\nto the right of the commit, then we can remove the zig-zag effect that\ncurrently results. We currently display these merges like this:\n\n    * |\n    |\\ \\\n    | |/\n    | *\n\nAfter these changes, this merge will now be displayed like so:\n\n    * |\n    |\\|\n    | *\n\nIf the final parent fuses with an edge that's further to the right, its\ndisplay is unchanged and it will still display like this:\n\n    * | | |\n    |\\ \\ \\ \\\n    | | |_|/\n    | |/| |\n    | * | |\n\nThe final structural change smooths out lines that are collapsing through\ncommit lines. For example, consider the following history:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * | |\n    |/ /\n    * |\n    |/\n    *\n\nThis is now rendered so that commit lines display an edge using / instead of\n|, if that edge is tracking to the left both above and below the commit\nline. That results in this improved display:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * / /\n    |/ /\n    * /\n    |/\n    *\n\nTaken together, these changes produce the change shown in the first diagram\nabove, with the current rendering on the left and the new rendering on the\nright.\n\nA final addition to that set of changes fixes the coloring of dashes that\nare drawn next to octopus merges, in a manner compatible with all these\nchanges. The early commits in this set are refactorings that make the\nfunctional changes easier to introduce.\n\nJames Coglan (11):\n  graph: automatically track visible width of `strbuf`\n  graph: reuse `find_new_column_by_commit()`\n  graph: reduce duplication in `graph_insert_into_new_columns()`\n  graph: remove `mapping_idx` and `graph_update_width()`\n  graph: extract logic for moving to GRAPH_PRE_COMMIT state\n  graph: tidy up display of left-skewed merges\n  graph: commit and post-merge lines for left-skewed merges\n  graph: rename `new_mapping` to `old_mapping`\n  graph: smooth appearance of collapsing edges on commit lines\n  graph: flatten edges that join to their right neighbor\n  graph: fix coloring of octopus dashes\n\n graph.c                                    | 499 ++++++++++++---------\n strbuf.h                                   |   8 +-\n t/t3430-rebase-merges.sh                   |   2 +-\n t/t4202-log.sh                             |   2 +-\n t/t4214-log-graph-octopus.sh               |  85 +++-\n t/t4215-log-skewed-merges.sh               | 267 +++++++++++\n t/t6016-rev-list-graph-simplify-history.sh |  30 +-\n 7 files changed, 649 insertions(+), 244 deletions(-)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\n\nbase-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-383/jcoglan/jc/simplify-graph-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/383\n-- \ngitgitgadget\n"},{"id":"383805","messageId":"4bc0a0596164212aa9d29d6dd0d7a0d8ab1b9dd0.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:43Z","receivedAt":"2019-10-10T16:13:48Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nAll the output functions in `graph.c` currently keep track of how many\nprintable chars they've written to the buffer, before calling\n`graph_pad_horizontally()` to pad the line with spaces. Some functions\ndo this by incrementing a counter whenever they write to the buffer, and\nothers do it by encoding an assumption about how many chars are written,\nas in:\n\n    graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n\nThis adds a fair amount of noise to the functions' logic and is easily\nbroken if one forgets to increment the right counter or update the\ncalculations used for padding.\n\nTo make this easier to use, I'm adding a `width` field to `strbuf` that\ntracks the number of printing characters added after the line prefix.\nIt's set to 0 at the start of `graph_next_line()`, and then various\n`strbuf` functions update it as follows:\n\n- `strbuf_write_column()` increments `width` by 1\n\n- `strbuf_setlen()` changes `width` by the amount added to `len` if\n  `len` is increased, or makes `width` and `len` the same if it's\n  decreased\n\n- `strbuf_addch()` increments `width` by 1\n\nThis is enough to ensure that the functions used by `graph.c` update\n`strbuf->width` correctly, and `graph_pad_horizontally()` can then use\nthis field instead of taking `chars_written` as a parameter.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c  | 68 ++++++++++++++++++++++----------------------------------\n strbuf.h |  8 ++++++-\n 2 files changed, 33 insertions(+), 43 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex f53135485f..c56fdec1fc 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -115,11 +115,20 @@ static const char *column_get_color_code(unsigned short color)\n static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n \t\t\t\tchar col_char)\n {\n+\t/*\n+\t * Remember the buffer's width as we're about to add non-printing\n+\t * content to it, and we want to avoid counting the byte length\n+\t * of this content towards the buffer's visible width\n+\t */\n+\tsize_t prev_width = sb->width;\n+\n \tif (c->color < column_colors_max)\n \t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n \tstrbuf_addch(sb, col_char);\n \tif (c->color < column_colors_max)\n \t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n+\n+\tsb->width = prev_width + 1;\n }\n \n struct git_graph {\n@@ -686,8 +695,7 @@ 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-\t\t\t\t   int chars_written)\n+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -696,8 +704,8 @@ 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-\tif (chars_written < graph->width)\n-\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n+\tif (sb->width < graph->width)\n+\t\tstrbuf_addchars(sb, ' ', graph->width - sb->width);\n }\n \n static void graph_output_padding_line(struct git_graph *graph,\n@@ -723,7 +731,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t\tstrbuf_addch(sb, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n+\tgraph_pad_horizontally(graph, sb);\n }\n \n \n@@ -740,7 +748,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, 3);\n+\tgraph_pad_horizontally(graph, sb);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -754,7 +762,6 @@ 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@@ -777,14 +784,12 @@ 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_write_column(sb, col, '|');\n \t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n-\t\t\tchars_written += 1 + 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@@ -800,19 +805,15 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\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_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n \t\t} else {\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, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -842,11 +843,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n }\n \n /*\n- * Draw the horizontal dashes of an octopus merge and return the number of\n- * characters written.\n+ * Draw the horizontal dashes of an octopus merge.\n  */\n-static int graph_draw_octopus_merge(struct git_graph *graph,\n-\t\t\t\t    struct strbuf *sb)\n+static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n {\n \t/*\n \t * Here dashless_parents represents the number of parents which don't\n@@ -890,13 +889,12 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n \t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n \t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n \t}\n-\treturn 2 * dashful_parents;\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, chars_written;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -906,7 +904,6 @@ 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@@ -921,14 +918,11 @@ 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 > 2)\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\t\tgraph_draw_octopus_merge(graph, sb);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\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@@ -948,16 +942,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n \t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n \t\t} else {\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, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Update graph->state\n@@ -984,12 +975,11 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\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, chars_written;\n+\tint i, j;\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@@ -1017,7 +1007,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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@@ -1026,19 +1015,16 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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_write_column(sb, col, '\\\\');\n \t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n \t\t} else {\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, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Update graph->state\n@@ -1181,7 +1167,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -1199,6 +1185,8 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tsb->width = 0;\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n \t\tgraph_output_padding_line(graph, sb);\n@@ -1227,7 +1215,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint i;\n-\tint chars_written = 0;\n \n \tif (graph->state != GRAPH_COMMIT) {\n \t\tgraph_next_line(graph, sb);\n@@ -1245,19 +1232,16 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \t\tstruct column *col = &graph->columns[i];\n \n \t\tstrbuf_write_column(sb, col, '|');\n-\t\tchars_written++;\n \n \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n \t\t\tint len = (graph->num_parents - 2) * 2;\n \t\t\tstrbuf_addchars(sb, ' ', len);\n-\t\t\tchars_written += len;\n \t\t} else {\n \t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written++;\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\ndiff --git a/strbuf.h b/strbuf.h\nindex f62278a0be..3a98147321 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -66,11 +66,12 @@ struct string_list;\n struct strbuf {\n \tsize_t alloc;\n \tsize_t len;\n+\tsize_t width;\n \tchar *buf;\n };\n \n extern char strbuf_slopbuf[];\n-#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }\n+#define STRBUF_INIT  { .alloc = 0, .len = 0, .width = 0, .buf = strbuf_slopbuf }\n \n /*\n  * Predeclare this here, since cache.h includes this file before it defines the\n@@ -161,6 +162,10 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len)\n {\n \tif (len > (sb->alloc ? sb->alloc - 1 : 0))\n \t\tdie(\"BUG: strbuf_setlen() beyond buffer\");\n+\tif (len > sb->len)\n+\t\tsb->width += len - sb->len;\n+\telse\n+\t\tsb->width = len;\n \tsb->len = len;\n \tif (sb->buf != strbuf_slopbuf)\n \t\tsb->buf[len] = '\\0';\n@@ -231,6 +236,7 @@ static inline void strbuf_addch(struct strbuf *sb, int c)\n \t\tstrbuf_grow(sb, 1);\n \tsb->buf[sb->len++] = c;\n \tsb->buf[sb->len] = '\\0';\n+\tsb->width++;\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"383806","messageId":"21a36efd7bc405ea01be8ecfd9775ebdf68946ad.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 03/11] graph: reduce duplication in `graph_insert_into_new_columns()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:45Z","receivedAt":"2019-10-10T16:13:49Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to this function and so am trying\nto simplify it. It currently contains some duplicated logic; both\nbranches the function can take assign the commit's column index into\nthe `mapping` array and increment `mapping_index`.\n\nHere I change the function so that the only conditional behaviour is\nthat it appends the commit to `new_columns` if it's not present. All\nmanipulation of `mapping` now happens on a single code path.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex a890c9d8bb..1cd30f80e6 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -459,23 +459,17 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n-\t * If the commit is already in the new_columns list, we don't need to\n-\t * add it.  Just update the mapping correctly.\n+\t * If the commit is not already in the new_columns array, then add it\n+\t * and record it as being in the final column.\n \t */\n-\tif (i >= 0) {\n-\t\tgraph->mapping[*mapping_index] = i;\n-\t\t*mapping_index += 2;\n-\t\treturn;\n+\tif (i < 0) {\n+\t\ti = graph->num_new_columns++;\n+\t\tgraph->new_columns[i].commit = commit;\n+\t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\t/*\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+\tgraph->mapping[*mapping_index] = i;\n \t*mapping_index += 2;\n-\tgraph->num_new_columns++;\n }\n \n static void graph_update_width(struct git_graph *graph,\n-- \ngitgitgadget\n\n"},{"id":"383807","messageId":"a1305741916f03f0cacf4f617ac8a76fd92b9240.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 02/11] graph: reuse `find_new_column_by_commit()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:44Z","receivedAt":"2019-10-10T16:13:50Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to\n`graph_insert_into_new_columns()` and so am trying to simplify it. One\npossible simplification is that we can extract the loop for finding the\nelement in `new_columns` containing the given commit.\n\n`find_new_column_by_commit()` contains a very similar loop but it\nreturns a `struct column *` rather than an `int` offset into the array.\nHere I'm introducing a version that returns `int` and using that in\n`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 48 +++++++++++++++++++++++-------------------------\n 1 file changed, 23 insertions(+), 25 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex c56fdec1fc..a890c9d8bb 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -441,22 +441,31 @@ static unsigned short graph_find_commit_color(const struct git_graph *graph,\n \treturn graph_get_current_column_color(graph);\n }\n \n+static int graph_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 i;\n+\t}\n+\treturn -1;\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 {\n-\tint i;\n+\tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n \t * If the commit is already in the new_columns list, we don't need to\n \t * add it.  Just update the mapping correctly.\n \t */\n-\tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tif (graph->new_columns[i].commit == commit) {\n-\t\t\tgraph->mapping[*mapping_index] = i;\n-\t\t\t*mapping_index += 2;\n-\t\t\treturn;\n-\t\t}\n+\tif (i >= 0) {\n+\t\tgraph->mapping[*mapping_index] = i;\n+\t\t*mapping_index += 2;\n+\t\treturn;\n \t}\n \n \t/*\n@@ -961,17 +970,6 @@ 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 NULL;\n-}\n-\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n@@ -999,20 +997,20 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tint par_column;\n \t\t\tseen_this = 1;\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+\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\tassert(par_column >= 0);\n \n-\t\t\tstrbuf_write_column(sb, par_column, '|');\n+\t\t\tstrbuf_write_column(sb, &graph->new_columns[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\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\t\tassert(par_column >= 0);\n+\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '\\\\');\n \t\t\t\tstrbuf_addch(sb, ' ');\n \t\t\t}\n \t\t} else if (seen_this) {\n-- \ngitgitgadget\n\n"},{"id":"383808","messageId":"674b992371b791daa67a98aa157e5166b4e68037.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 04/11] graph: remove `mapping_idx` and `graph_update_width()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:46Z","receivedAt":"2019-10-10T16:13:51Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThere's a duplication of logic between `graph_insert_into_new_columns()`\nand `graph_update_width()`. `graph_insert_into_new_columns()` is called\nrepeatedly by `graph_update_columns()` with an `int *` that tracks the\noffset into the `mapping` array where we should write the next value.\nEach call to `graph_insert_into_new_columns()` effectively pushes one\ncolumn index and one \"null\" value (-1) onto the `mapping` array and\ntherefore increments `mapping_idx` by 2.\n\n`graph_update_width()` duplicates this process: the `width` of the graph\nis essentially the initial width of the `mapping` array before edges\nbegin collapsing. The `graph_update_width()` function's logic\neffectively works out how many times `graph_insert_into_new_columns()`\nwas called based on the relationship of the current commit to the rest\nof the graph.\n\nI'm about to make some changes that make the assignment of values into\nthe `mapping` array more complicated. Rather than make\n`graph_update_width()` more complicated at the same time, we can simply\nremove this function and use `graph->width` to track the offset into the\n`mapping` array as we're building it. This removes the duplication and\nmakes sure that `graph->width` is the same as the visual width of the\n`mapping` array once `graph_update_columns()` is complete.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 65 +++++++++------------------------------------------------\n 1 file changed, 10 insertions(+), 55 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 1cd30f80e6..a71ae5cd61 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -453,8 +453,7 @@ static int graph_find_new_column_by_commit(struct git_graph *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+\t\t\t\t\t  struct commit *commit)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n@@ -468,50 +467,14 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[*mapping_index] = i;\n-\t*mapping_index += 2;\n-}\n-\n-static void graph_update_width(struct git_graph *graph,\n-\t\t\t       int is_commit_in_existing_columns)\n-{\n-\t/*\n-\t * Compute the width needed to display the graph for this commit.\n-\t * This is the maximum width needed for any row.  All other rows\n-\t * will be padded to this width.\n-\t *\n-\t * Compute the number of columns in the widest row:\n-\t * Count each existing column (graph->num_columns), and each new\n-\t * column added by this commit.\n-\t */\n-\tint max_cols = graph->num_columns + graph->num_parents;\n-\n-\t/*\n-\t * Even if the current commit has no parents to be printed, it\n-\t * still takes up a column for itself.\n-\t */\n-\tif (graph->num_parents < 1)\n-\t\tmax_cols++;\n-\n-\t/*\n-\t * We added a column for the current commit as part of\n-\t * graph->num_parents.  If the current commit was already in\n-\t * graph->columns, then we have double counted it.\n-\t */\n-\tif (is_commit_in_existing_columns)\n-\t\tmax_cols--;\n-\n-\t/*\n-\t * Each column takes up 2 spaces\n-\t */\n-\tgraph->width = max_cols * 2;\n+\tgraph->mapping[graph->width] = i;\n+\tgraph->width += 2;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n {\n \tstruct commit_list *parent;\n \tint max_new_columns;\n-\tint mapping_idx;\n \tint i, seen_this, is_commit_in_columns;\n \n \t/*\n@@ -544,6 +507,8 @@ static void graph_update_columns(struct git_graph *graph)\n \tfor (i = 0; i < graph->mapping_size; i++)\n \t\tgraph->mapping[i] = -1;\n \n+\tgraph->width = 0;\n+\n \t/*\n \t * Populate graph->new_columns and graph->mapping\n \t *\n@@ -554,7 +519,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t * supposed to end up after the collapsing is performed.\n \t */\n \tseen_this = 0;\n-\tmapping_idx = 0;\n \tis_commit_in_columns = 1;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n \t\tstruct commit *col_commit;\n@@ -568,7 +532,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n-\t\t\tint old_mapping_idx = mapping_idx;\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n \t\t\tfor (parent = first_interesting_parent(graph);\n@@ -583,21 +546,18 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\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+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n \t\t\t}\n \t\t\t/*\n-\t\t\t * We always need to increment mapping_idx by at\n+\t\t\t * We always need to increment graph->width by at\n \t\t\t * least 2, even if it has no interesting parents.\n \t\t\t * The current commit always takes up at least 2\n \t\t\t * spaces.\n \t\t\t */\n-\t\t\tif (mapping_idx == old_mapping_idx)\n-\t\t\t\tmapping_idx += 2;\n+\t\t\tif (graph->num_parents == 0)\n+\t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit,\n-\t\t\t\t\t\t      &mapping_idx);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n \t\t}\n \t}\n \n@@ -607,11 +567,6 @@ static void graph_update_columns(struct git_graph *graph)\n \twhile (graph->mapping_size > 1 &&\n \t       graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n-\n-\t/*\n-\t * Compute graph->width for this commit\n-\t */\n-\tgraph_update_width(graph, is_commit_in_columns);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n-- \ngitgitgadget\n\n"},{"id":"383809","messageId":"d5d60ca9a280bb02c09c1e8e13b5b9c185cc09f9.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 05/11] graph: extract logic for moving to GRAPH_PRE_COMMIT state","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:47Z","receivedAt":"2019-10-10T16:13:53Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThis computation is repeated in a couple of places and I need to add\nanother condition to it to implement a further improvement to the graph\nrendering, so I'm extracting this into a function.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex a71ae5cd61..98e8777db4 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -569,6 +569,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_needs_pre_commit_line(struct git_graph *graph)\n+{\n+\treturn graph->num_parents >= 3 &&\n+\t       graph->commit_index < (graph->num_columns - 1);\n+}\n+\n void graph_update(struct git_graph *graph, struct commit *commit)\n {\n \tstruct commit_list *parent;\n@@ -624,8 +630,7 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tif (graph->state != GRAPH_PADDING)\n \t\tgraph->state = GRAPH_SKIP;\n-\telse if (graph->num_parents >= 3 &&\n-\t\t graph->commit_index < (graph->num_columns - 1))\n+\telse if (graph_needs_pre_commit_line(graph))\n \t\tgraph->state = GRAPH_PRE_COMMIT;\n \telse\n \t\tgraph->state = GRAPH_COMMIT;\n@@ -708,8 +713,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n \tstrbuf_addstr(sb, \"...\");\n \tgraph_pad_horizontally(graph, sb);\n \n-\tif (graph->num_parents >= 3 &&\n-\t    graph->commit_index < (graph->num_columns - 1))\n+\tif (graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_PRE_COMMIT);\n \telse\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n-- \ngitgitgadget\n\n"},{"id":"383810","messageId":"f9ced9090d0b5fb46e4ffbd31ec57a5eefeaf5e4.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 09/11] graph: smooth appearance of collapsing edges on commit lines","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:50Z","receivedAt":"2019-10-10T16:13:57Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a graph contains edges that are in the process of collapsing to the\nleft, but those edges cross a commit line, the effect is that the edges\nhave a jagged appearance:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * | |\n        |/ /\n        * |\n        |/\n        *\n\nWe already takes steps to smooth edges like this when they're expanding;\nwhen an edge appears to the right of a merge commit marker on a\nGRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we\nrender it as a `\\`:\n\n        * \\\n        |\\ \\\n        | * \\\n        | |\\ \\\n\nWe can make a similar improvement to collapsing edges, making them\neasier to follow and giving the overall graph a feeling of increased\nsymmetry:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * / /\n        |/ /\n        * /\n        |/\n        *\n\nTo do this, we introduce a new special case for edges on GRAPH_COMMIT\nlines that immediately follow a GRAPH_COLLAPSING line. By retaining a\ncopy of the `mapping` array used to render the GRAPH_COLLAPSING line in\nthe `old_mapping` array, we can determine that an edge is collapsing\nthrough the GRAPH_COMMIT line and should be smoothed.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 17 +++++++++++++----\n t/t3430-rebase-merges.sh                   |  2 +-\n t/t4202-log.sh                             |  2 +-\n t/t4214-log-graph-octopus.sh               | 16 ++++++++--------\n t/t4215-log-skewed-merges.sh               |  2 +-\n t/t6016-rev-list-graph-simplify-history.sh |  4 ++--\n 6 files changed, 26 insertions(+), 17 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex b0e31e23ca..6391e393ec 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -278,10 +278,10 @@ struct git_graph {\n \t */\n \tint *mapping;\n \t/*\n-\t * A temporary array for computing the next mapping state\n-\t * while we are outputting a mapping line.  This is stored as part\n-\t * of the git_graph simply so we don't have to allocate a new\n-\t * temporary array each time we have to output a collapsing line.\n+\t * A copy of the contents of the mapping array from the last commit,\n+\t * which we use to improve the display of columns that are tracking\n+\t * from right to left through a commit line.  We also use this to\n+\t * avoid allocating a fresh array when we compute the next mapping.\n \t */\n \tint *old_mapping;\n \t/*\n@@ -1011,6 +1011,10 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n \t\t\t\tstrbuf_write_column(sb, col, '|');\n+\t\t} else if (graph->prev_state == GRAPH_COLLAPSING &&\n+\t\t\t   graph->old_mapping[2 * i + 1] == i &&\n+\t\t\t   graph->mapping[2 * i] < i) {\n+\t\t\tstrbuf_write_column(sb, col, '/');\n \t\t} else {\n \t\t\tstrbuf_write_column(sb, col, '|');\n \t\t}\n@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t}\n \t}\n \n+\t/*\n+\t * Copy the current mapping array into old_mapping\n+\t */\n+\tCOPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);\n+\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 7b6c4847ad..051351ecdf 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -402,7 +402,7 @@ test_expect_success 'octopus merges' '\n \t| | * three\n \t| * | two\n \t| |/\n-\t* | one\n+\t* / one\n \t|/\n \to before-octopus\n \tEOF\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex c20209324c..c7aa0532c1 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -667,7 +667,7 @@ cat > expect <<\\EOF\n * | | fifth\n * | | fourth\n |/ /\n-* | third\n+* / third\n |/\n * second\n * initial\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 40f420877b..9ada687628 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -12,9 +12,9 @@ test_expect_success 'set up merge history' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -25,9 +25,9 @@ test_expect_success 'set up merge history' '\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<RED>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<RED>|<RESET> * <MAGENTA>/<RESET> 2\n \t<RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -68,9 +68,9 @@ test_expect_success 'log --graph with normal octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -86,9 +86,9 @@ test_expect_success 'log --graph with normal octopus merge with colors' '\n \t<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4\n \t<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3\n \t<RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t<RED>|<RESET> * <BLUE>|<RESET> 2\n+\t<RED>|<RESET> * <BLUE>/<RESET> 2\n \t<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t* <BLUE>|<RESET> 1\n+\t* <BLUE>/<RESET> 1\n \t<BLUE>|<RESET><BLUE>/<RESET>\n \t* initial\n \tEOF\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex e479d6e676..b739268e5e 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -62,7 +62,7 @@ cat > expect <<\\EOF\n | * | 1_D\n * | | 1_C\n |/ /\n-* | 1_B\n+* / 1_B\n |/\n * 1_A\n EOF\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex f7181d1d6a..ca1682f29b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -154,7 +154,7 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"* |   $A4\" >> expected &&\n \techo \"|\\\\ \\\\  \" >> expected &&\n \techo \"| |/  \" >> expected &&\n-\techo \"* | $A3\" >> expected &&\n+\techo \"* / $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n@@ -255,7 +255,7 @@ test_expect_success '--graph --boundary ^C3' '\n \techo \"* | | | $A3\" >> expected &&\n \techo \"o | | | $A2\" >> expected &&\n \techo \"|/ / /  \" >> expected &&\n-\techo \"o | | $A1\" >> expected &&\n+\techo \"o / / $A1\" >> expected &&\n \techo \" / /  \" >> expected &&\n \techo \"| o $C3\" >> expected &&\n \techo \"|/  \" >> expected &&\n-- \ngitgitgadget\n\n"},{"id":"383811","messageId":"23f13bbefaaccde4b914813a0ab7965ab1f83c04.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 08/11] graph: rename `new_mapping` to `old_mapping`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:49Z","receivedAt":"2019-10-10T16:13:57Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThe change I'm about to make requires being able to inspect the mapping\narray that was used to render the last GRAPH_COLLAPSING line while\nrendering a GRAPH_COMMIT line. The `new_mapping` array currently exists\nas a pre-allocated space for computing the next `mapping` array during\n`graph_output_collapsing_line()`, but we can repurpose it to let us see\nthe previous `mapping` state.\n\nTo support this use it will make more sense if this array is named\n`old_mapping`, as it will contain the mapping data for the previous line\nwe rendered, at the point we're rendering a commit line.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 54 +++++++++++++++++++++++++++---------------------------\n 1 file changed, 27 insertions(+), 27 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex fb2e42850f..b0e31e23ca 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -240,7 +240,7 @@ struct git_graph {\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n-\t * that can be stored in the mapping and new_mapping arrays.\n+\t * that can be stored in the mapping and old_mapping arrays.\n \t */\n \tint column_capacity;\n \t/*\n@@ -283,7 +283,7 @@ struct git_graph {\n \t * of the git_graph simply so we don't have to allocate a new\n \t * temporary array each time we have to output a collapsing line.\n \t */\n-\tint *new_mapping;\n+\tint *old_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@@ -369,7 +369,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);\n-\tALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);\n+\tALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);\n \n \t/*\n \t * The diff output prefix callback, with this we can make\n@@ -399,7 +399,7 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)\n \tREALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);\n-\tREALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);\n+\tREALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);\n }\n \n /*\n@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \tint horizontal_edge_target = -1;\n \n \t/*\n-\t * Clear out the new_mapping array\n+\t * Swap the mapping and old_mapping arrays\n+\t */\n+\tSWAP(graph->mapping, graph->old_mapping);\n+\n+\t/*\n+\t * Clear out the mapping array\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++)\n-\t\tgraph->new_mapping[i] = -1;\n+\t\tgraph->mapping[i] = -1;\n \n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->mapping[i];\n+\t\tint target = graph->old_mapping[i];\n \t\tif (target < 0)\n \t\t\tcontinue;\n \n@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t * This column is already in the\n \t\t\t * correct place\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i] == -1);\n-\t\t\tgraph->new_mapping[i] = target;\n-\t\t} else if (graph->new_mapping[i - 1] < 0) {\n+\t\t\tassert(graph->mapping[i] == -1);\n+\t\t\tgraph->mapping[i] = target;\n+\t\t} else if (graph->mapping[i - 1] < 0) {\n \t\t\t/*\n \t\t\t * Nothing is to the left.\n \t\t\t * Move to the left by one\n \t\t\t */\n-\t\t\tgraph->new_mapping[i - 1] = target;\n+\t\t\tgraph->mapping[i - 1] = target;\n \t\t\t/*\n \t\t\t * If there isn't already an edge moving horizontally\n \t\t\t * select this one.\n@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\t * line.\n \t\t\t\t */\n \t\t\t\tfor (j = (target * 2)+3; j < (i - 2); j += 2)\n-\t\t\t\t\tgraph->new_mapping[j] = target;\n+\t\t\t\t\tgraph->mapping[j] = target;\n \t\t\t}\n-\t\t} else if (graph->new_mapping[i - 1] == target) {\n+\t\t} else if (graph->mapping[i - 1] == target) {\n \t\t\t/*\n \t\t\t * There is a branch line to our left\n \t\t\t * already, and it is our target.  We\n@@ -1176,7 +1181,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t * the same parent commit.\n \t\t\t *\n \t\t\t * We don't have to add anything to the\n-\t\t\t * output or new_mapping, since the\n+\t\t\t * output or mapping, since the\n \t\t\t * existing branch line has already taken\n \t\t\t * care of it.\n \t\t\t */\n@@ -1192,10 +1197,10 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t * The branch to the left of that space\n \t\t\t * should be our eventual target.\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i - 1] > target);\n-\t\t\tassert(graph->new_mapping[i - 2] < 0);\n-\t\t\tassert(graph->new_mapping[i - 3] == target);\n-\t\t\tgraph->new_mapping[i - 2] = target;\n+\t\t\tassert(graph->mapping[i - 1] > target);\n+\t\t\tassert(graph->mapping[i - 2] < 0);\n+\t\t\tassert(graph->mapping[i - 3] == target);\n+\t\t\tgraph->mapping[i - 2] = target;\n \t\t\t/*\n \t\t\t * Mark this branch as the horizontal edge to\n \t\t\t * prevent any other edges from moving\n@@ -1209,14 +1214,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\n-\tif (graph->new_mapping[graph->mapping_size - 1] < 0)\n+\tif (graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n \n \t/*\n \t * Output out a line based on the new mapping info\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->new_mapping[i];\n+\t\tint target = graph->mapping[i];\n \t\tif (target < 0)\n \t\t\tstrbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n@@ -1229,12 +1234,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\t * won't continue into the next line.\n \t\t\t\t */\n \t\t\t\tif (i != (target * 2)+3)\n-\t\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\t\tgraph->mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n \t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n-\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\tgraph->mapping[i] = -1;\n \t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n \n \t\t}\n@@ -1242,11 +1247,6 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n \tgraph_pad_horizontally(graph, sb);\n \n-\t/*\n-\t * Swap mapping and new_mapping\n-\t */\n-\tSWAP(graph->mapping, graph->new_mapping);\n-\n \t/*\n \t * If graph->mapping indicates that all of the branch lines\n \t * are already in the correct positions, we are done.\n-- \ngitgitgadget\n\n"},{"id":"383812","messageId":"ea0df1d94aa6c42eb00d59b268a90a8724322452.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 11/11] graph: fix coloring of octopus dashes","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:52Z","receivedAt":"2019-10-10T16:13:58Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nIn 04005834ed (\"log: fix coloring of certain octopus merge shapes\",\n2018-09-01) there is a fix for the coloring of dashes following an\noctopus merge. It makes a distinction between the case where all parents\nintroduce a new column, versus the case where the first parent collapses\ninto an existing column:\n\n        | *-.           | *-.\n        | |\\ \\          | |\\ \\\n        | | | |         |/ / /\n\nThe latter case means that the columns for the merge parents begin one\nplace to the left in the `new_columns` array compared to the former\ncase.\n\nHowever, the implementation only works if the commit's parents are kept\nin order as they map onto the visual columns, as we get the colors by\niterating over `new_columns` as we print the dashes. In general, the\ncommit's parents can arbitrarily merge with existing columns, and change\ntheir ordering in the process.\n\nFor example, in the following diagram, the number of each column\nindicates which commit parent appears in each column.\n\n        | | *---.\n        | | |\\ \\ \\\n        | | |/ / /\n        | |/| | /\n        | |_|_|/\n        |/| | |\n        3 1 0 2\n\nIf the columns are colored (red, green, yellow, blue), then the dashes\nwill currently be colored yellow and blue, whereas they should be blue\nand red.\n\nTo fix this, we need to look up each column in the `mapping` array,\nwhich before the `GRAPH_COLLAPSING` state indicates which logical column\nis displayed in each visual column. This implementation is simpler as it\ndoesn't have any edge cases, and it also handles how left-skewed first\nparents are now displayed:\n\n        | *-.\n        |/|\\ \\\n        | | | |\n        0 1 2 3\n\nThe color of the first dashes is always the color found in `mapping` two\ncolumns to the right of the commit symbol. Because commits are displayed\nafter all edges have been collapsed together and the visual columns\nmatch the logical ones, we can find the visual offset of the commit\nsymbol using `commit_index`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 71 +++++++++++++++++++-----------------\n t/t4214-log-graph-octopus.sh | 59 ++++++++++++++++++++++++++++--\n 2 files changed, 92 insertions(+), 38 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 7dd2fab625..908f257018 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -665,6 +665,11 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_dashed_parents(struct git_graph *graph)\n+{\n+\treturn graph->num_parents + graph->merge_layout - 3;\n+}\n+\n static int graph_num_expansion_rows(struct git_graph *graph)\n {\n \t/*\n@@ -687,7 +692,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)\n \t * \t\t| * \\\n \t * \t\t|/|\\ \\\n \t */\n-\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+\treturn graph_num_dashed_parents(graph) * 2;\n }\n \n static int graph_needs_pre_commit_line(struct git_graph *graph)\n@@ -930,47 +935,45 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n {\n \t/*\n-\t * Here dashless_parents represents the number of parents which don't\n-\t * need to have dashes (the edges labeled \"0\" and \"1\").  And\n-\t * dashful_parents are the remaining ones.\n+\t * The parents of a merge commit can be arbitrarily reordered as they\n+\t * are mapped onto display columns, for example this is a valid merge:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * | | | | |\n-\t * x 0 1 2 3\n+\t *\t| | *---.\n+\t *\t| | |\\ \\ \\\n+\t *\t| | |/ / /\n+\t *\t| |/| | /\n+\t *\t| |_|_|/\n+\t *\t|/| | |\n+\t *\t3 1 0 2\n \t *\n-\t */\n-\tconst int dashless_parents = 3 - graph->merge_layout;\n-\tint dashful_parents = graph->num_parents - dashless_parents;\n-\n-\t/*\n-\t * Usually, we add one new column for each parent (like the diagram\n-\t * above) but sometimes the first parent goes into an existing column,\n-\t * like this:\n+\t * The numbers denote which parent of the merge each visual column\n+\t * corresponds to; we can't assume that the parents will initially\n+\t * display in the order given by new_columns.\n \t *\n-\t * | *-.\n-\t * |/|\\ \\\n-\t * | | | |\n-\t * x 0 1 2\n+\t * To find the right color for each dash, we need to consult the\n+\t * mapping array, starting from the column 2 places to the right of the\n+\t * merge commit, and use that to find out which logical column each\n+\t * edge will collapse to.\n \t *\n-\t * In which case the number of parents will be one greater than the\n-\t * number of added columns.\n+\t * Commits are rendered once all edges have collapsed to their correct\n+\t * logcial column, so commit_index gives us the right visual offset for\n+\t * the merge commit.\n \t */\n-\tint added_cols = (graph->num_new_columns - graph->num_columns);\n-\tint parent_in_old_cols = graph->num_parents - added_cols;\n \n-\t/*\n-\t * In both cases, commit_index corresponds to the edge labeled \"0\".\n-\t */\n-\tint first_col = graph->commit_index + dashless_parents\n-\t    - parent_in_old_cols;\n+\tint i, j;\n+\tstruct column *col;\n \n-\tint i;\n-\tfor (i = 0; i < dashful_parents; i++) {\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n+\tint dashed_parents = graph_num_dashed_parents(graph);\n+\n+\tfor (i = 0; i < dashed_parents; i++) {\n+\t\tj = graph->mapping[(graph->commit_index + i + 2) * 2];\n+\t\tcol = &graph->new_columns[j];\n+\n+\t\tstrbuf_write_column(sb, col, '-');\n+\t\tstrbuf_write_column(sb, col, (i == dashed_parents - 1) ? '.' : '-');\n \t}\n+\n+\treturn;\n }\n \n static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 9ada687628..fbd485d83a 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -42,23 +42,74 @@ test_expect_success 'set up merge history' '\n \ttest_tick &&\n \tgit merge -m octopus-merge 1 2 3 4 &&\n \tgit checkout 1 -b L &&\n-\ttest_commit left\n+\ttest_commit left &&\n+\tgit checkout 4 -b R &&\n+\ttest_commit right\n '\n \n test_expect_success 'log --graph with tricky octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n-\tgit log --color=always --graph --date-order --pretty=tformat:%s --all >actual.colors.raw &&\n+\tgit log --color=always --graph --date-order --pretty=tformat:%s L merge >actual.colors.raw &&\n \ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n \ttest_cmp expect.colors actual.colors\n '\n \n test_expect_success 'log --graph with tricky octopus merge, no color' '\n-\tgit log --color=never --graph --date-order --pretty=tformat:%s --all >actual.raw &&\n+\tgit log --color=never --graph --date-order --pretty=tformat:%s L merge >actual.raw &&\n \tsed \"s/ *\\$//\" actual.raw >actual &&\n \ttest_cmp expect.uncolored actual\n '\n \n-# Repeat the previous two tests with \"normal\" octopus merge (i.e.,\n+# Repeat the previous two tests with an octopus merge whose final parent skews left\n+\n+test_expect_success 'log --graph with left-skewed final parent, no color' '\n+\tcat >expect.uncolored <<-\\EOF &&\n+\t* right\n+\t| *---.   octopus-merge\n+\t| |\\ \\ \\\n+\t| |_|_|/\n+\t|/| | |\n+\t* | | | 4\n+\t| | | * 3\n+\t| |_|/\n+\t|/| |\n+\t| | * 2\n+\t| |/\n+\t|/|\n+\t| * 1\n+\t|/\n+\t* initial\n+\tEOF\n+\tgit log --color=never --graph --date-order --pretty=tformat:%s R merge >actual.raw &&\n+\tsed \"s/ *\\$//\" actual.raw >actual &&\n+\ttest_cmp expect.uncolored actual\n+'\n+\n+test_expect_success 'log --graph with left-skewed final parent with colors' '\n+\tcat >expect.colors <<-\\EOF &&\n+\t* right\n+\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><RED>-<RESET><RED>.<RESET>   octopus-merge\n+\t<RED>|<RESET> <GREEN>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <RED>\\<RESET>\n+\t<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>\n+\t<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET>\n+\t* <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> 4\n+\t<MAGENTA>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 3\n+\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>_<RESET><YELLOW>|<RESET><MAGENTA>/<RESET>\n+\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET>\n+\t<MAGENTA>|<RESET> <GREEN>|<RESET> * 2\n+\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>/<RESET>\n+\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET>\n+\t<MAGENTA>|<RESET> * 1\n+\t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n+\t* initial\n+\tEOF\n+\ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n+\tgit log --color=always --graph --date-order --pretty=tformat:%s R merge >actual.colors.raw &&\n+\ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n+\ttest_cmp expect.colors actual.colors\n+'\n+\n+# Repeat the first two tests with \"normal\" octopus merge (i.e.,\n # without the first parent skewing to the \"left\" branch column).\n \n test_expect_success 'log --graph with normal octopus merge, no color' '\n-- \ngitgitgadget\n"},{"id":"383813","messageId":"6c173663aac37f1d314db8637cf4a243066b8078.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:48Z","receivedAt":"2019-10-10T16:13:59Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nFollowing the introduction of \"left-skewed\" merges, which are merges\nwhose first parent fuses with another edge to its left, we have some\nmore edge cases to deal with in the display of commit and post-merge\nlines.\n\nThe current graph code handles the following cases for edges appearing\nto the right of the commit (*) on commit lines. A 2-way merge is usually\nfollowed by vertical lines:\n\n        | | |\n        | * |\n        | |\\ \\\n\nAn octopus merge (more than two parents) is always followed by edges\nsloping to the right:\n\n        | |  \\          | |    \\\n        | *-. \\         | *---. \\\n        | |\\ \\ \\        | |\\ \\ \\ \\\n\nA 2-way merge is followed by a right-sloping edge if the commit line\nimmediately follows a post-merge line for a commit that appears in the\nsame column as the current commit, or any column to the left of that:\n\n        | *             | * |\n        | |\\            | |\\ \\\n        | * \\           | | * \\\n        | |\\ \\          | | |\\ \\\n\nThis commit introduces the following new cases for commit lines. If a\n2-way merge skews to the left, then the edges to its right are always\nvertical lines, even if the commit follows a post-merge line:\n\n        | | |           | |\\\n        | * |           | * |\n        |/| |           |/| |\n\nA commit with 3 parents that skews left is followed by vertical edges:\n\n        | | |\n        | * |\n        |/|\\ \\\n\nIf a 3-way left-skewed merge commit appears immediately after a\npost-merge line, then it may be followed the right-sloping edges, just\nlike a 2-way merge that is not skewed.\n\n        | |\\\n        | * \\\n        |/|\\ \\\n\nOctopus merges with 4 or more parents that skew to the left will always\nbe followed by right-sloping edges, because the existing columns need to\nexpand around the merge.\n\n        | |  \\\n        | *-. \\\n        |/|\\ \\ \\\n\nOn post-merge lines, usually all edges following the current commit\nslope to the right:\n\n        | * | |\n        | |\\ \\ \\\n\nHowever, if the commit is a left-skewed 2-way merge, the edges to its\nright remain vertical. We also need to display a space after the\nvertical line descending from the commit marker, whereas this line would\nnormally be followed by a backslash.\n\n        | * | |\n        |/| | |\n\nIf a left-skewed merge has more than 2 parents, then the edges to its\nright are still sloped as they bend around the edges introduced by the\nmerge.\n\n        | * | |\n        |/|\\ \\ \\\n\nTo handle these new cases, we need to know not just how many parents\neach commit has, but how many new columns it adds to the display; this\nquantity is recorded in the `edges_added` field for the current commit,\nand `prev_edges_added` field for the previous commit.\n\nHere, \"column\" refers to visual columns, not the logical columns of the\n`columns` array. This is because even if all the commit's parents end up\nfusing with existing edges, they initially introduce distinct edges in\nthe commit and post-merge lines before those edges collapse. For\nexample, a 3-way merge whose 2nd and 3rd parents fuse with existing\nedges still introduces 2 visual columns that affect the display of edges\nto their right.\n\n        | | |  \\\n        | | *-. \\\n        | | |\\ \\ \\\n        | |_|/ / /\n        |/| | / /\n        | | |/ /\n        | |/| |\n        | | | |\n\nThis merge does not introduce any *logical* columns; there are 4 edges\nbefore and after this commit once all edges have collapsed. But it does\ninitially introduce 2 new edges that need to be accommodated by the\nedges to their right.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      |  63 +++++++++++++--\n t/t4215-log-skewed-merges.sh | 151 +++++++++++++++++++++++++++++++++++\n 2 files changed, 209 insertions(+), 5 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 9136173e03..fb2e42850f 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -197,6 +197,46 @@ struct git_graph {\n \t * \t\t|/| | | | |\t\t| | | | | *\n \t */\n \tint merge_layout;\n+\t/*\n+\t * The number of columns added to the graph by the current commit. For\n+\t * 2-way and octopus merges, this is is usually one less than the\n+\t * number of parents:\n+\t *\n+\t * \t\t| | |\t\t\t| |    \\\n+\t *\t\t| * |\t\t\t| *---. \\\n+\t *\t\t| |\\ \\\t\t\t| |\\ \\ \\ \\\n+\t *\t\t| | | |         \t| | | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 1\t\tedges_added: 3\n+\t *\n+\t * For left-skewed merges, the first parent fuses with its neighbor and\n+\t * so one less column is added:\n+\t *\n+\t *\t\t| | |\t\t\t| |  \\\n+\t *\t\t| * |\t\t\t| *-. \\\n+\t *\t\t|/| |\t\t\t|/|\\ \\ \\\n+\t *\t\t| | |\t\t\t| | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 0\t\tedges_added: 2\n+\t *\n+\t * This number determines how edges to the right of the merge are\n+\t * displayed in commit and post-merge lines; if no columns have been\n+\t * added then a vertical line should be used where a right-tracking\n+\t * line would otherwise be used.\n+\t *\n+\t *\t\t| * \\\t\t\t| * |\n+\t *\t\t| |\\ \\\t\t\t|/| |\n+\t *\t\t| | * \\\t\t\t| * |\n+\t */\n+\tint edges_added;\n+\t/*\n+\t * The number of columns added by the previous commit, which is used to\n+\t * smooth edges appearing to the right of a commit in a commit line\n+\t * following a post-merge line.\n+\t */\n+\tint prev_edges_added;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -309,6 +349,8 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n \tgraph->merge_layout = 0;\n+\tgraph->edges_added = 0;\n+\tgraph->prev_edges_added = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -670,6 +712,9 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n+\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -943,12 +988,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \n \t\t\tif (graph->num_parents > 2)\n \t\t\t\tgraph_draw_octopus_merge(graph, sb);\n-\t\t} else if (seen_this && (graph->num_parents > 2)) {\n+\t\t} else if (seen_this && (graph->edges_added > 1)) {\n \t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t} else if (seen_this && (graph->num_parents == 2)) {\n+\t\t} else if (seen_this && (graph->edges_added == 1)) {\n \t\t\t/*\n-\t\t\t * This is a 2-way merge commit.\n-\t\t\t * There is no GRAPH_PRE_COMMIT stage for 2-way\n+\t\t\t * This is either a right-skewed 2-way merge\n+\t\t\t * commit, or a left-skewed 3-way merge.\n+\t\t\t * There is no GRAPH_PRE_COMMIT stage for such\n \t\t\t * merges, so this is the first line of output\n \t\t\t * for this commit.  Check to see what the previous\n \t\t\t * line of output was.\n@@ -960,6 +1006,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\t\t * makes the output look nicer.\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n+\t\t\t    graph->prev_edges_added > 0 &&\n \t\t\t    graph->prev_commit_index < i)\n \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n@@ -1031,8 +1078,14 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\t\t\telse\n \t\t\t\t\tidx++;\n \t\t\t}\n+\t\t\tif (graph->edges_added == 0)\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\tif (graph->edges_added > 0)\n+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n+\t\t\telse\n+\t\t\t\tstrbuf_write_column(sb, col, '|');\n \t\t\tstrbuf_addch(sb, ' ');\n \t\t} else {\n \t\t\tstrbuf_write_column(sb, col, '|');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex cfaba40f97..e479d6e676 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -39,4 +39,155 @@ test_expect_success 'log --graph with left-skewed merge' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup nested left-skewed merge' '\n+\tgit checkout --orphan 1_p &&\n+\ttest_commit 1_A &&\n+\ttest_commit 1_B &&\n+\ttest_commit 1_C &&\n+\tgit checkout -b 1_q @^ && test_commit 1_D &&\n+\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n+\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n+\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n+\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H\n+'\n+\n+cat > expect <<\\EOF\n+*   1_H\n+|\\\n+| *   1_G\n+| |\\\n+| | * 1_F\n+| * | 1_E\n+|/| |\n+| * | 1_D\n+* | | 1_C\n+|/ /\n+* | 1_B\n+|/\n+* 1_A\n+EOF\n+\n+test_expect_success 'log --graph with nested left-skewed merge' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'setup nested left-skewed merge following normal merge' '\n+\tgit checkout --orphan 2_p &&\n+\ttest_commit 2_A &&\n+\ttest_commit 2_B &&\n+\ttest_commit 2_C &&\n+\tgit checkout -b 2_q @^^ &&\n+\ttest_commit 2_D &&\n+\ttest_commit 2_E &&\n+\tgit checkout -b 2_r @^ && test_commit 2_F &&\n+\tgit checkout 2_q &&\n+\tgit merge --no-ff 2_r -m 2_G &&\n+\tgit merge --no-ff 2_p^ -m 2_H &&\n+\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n+\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K\n+'\n+\n+cat > expect <<\\EOF\n+*   2_K\n+|\\\n+| *   2_J\n+| |\\\n+| | *   2_H\n+| | |\\\n+| | * | 2_G\n+| |/| |\n+| | * | 2_F\n+| * | | 2_E\n+| |/ /\n+| * | 2_D\n+* | | 2_C\n+| |/\n+|/|\n+* | 2_B\n+|/\n+* 2_A\n+EOF\n+\n+test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'setup nested right-skewed merge following left-skewed merge' '\n+\tgit checkout --orphan 3_p &&\n+\ttest_commit 3_A &&\n+\tgit checkout -b 3_q &&\n+\ttest_commit 3_B &&\n+\ttest_commit 3_C &&\n+\tgit checkout -b 3_r @^ &&\n+\ttest_commit 3_D &&\n+\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n+\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n+\tgit checkout 3_r && test_commit 3_G &&\n+\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n+\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J\n+'\n+\n+cat > expect <<\\EOF\n+*   3_J\n+|\\\n+| *   3_H\n+| |\\\n+| | * 3_G\n+| * | 3_F\n+|/| |\n+| * |   3_E\n+| |\\ \\\n+| | |/\n+| | * 3_D\n+| * | 3_C\n+| |/\n+| * 3_B\n+|/\n+* 3_A\n+EOF\n+\n+test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'setup right-skewed merge following a left-skewed one' '\n+\tgit checkout --orphan 4_p &&\n+\ttest_commit 4_A &&\n+\ttest_commit 4_B &&\n+\ttest_commit 4_C &&\n+\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n+\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n+\tgit checkout -b 4_s 4_p^^ &&\n+\tgit merge --no-ff 4_r -m 4_F &&\n+\tgit merge --no-ff 4_p -m 4_G &&\n+\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H\n+'\n+\n+cat > expect <<\\EOF\n+*   4_H\n+|\\\n+| *   4_G\n+| |\\\n+| * | 4_F\n+|/| |\n+| * |   4_E\n+| |\\ \\\n+| | * | 4_D\n+| |/ /\n+|/| |\n+| | * 4_C\n+| |/\n+| * 4_B\n+|/\n+* 4_A\n+EOF\n+\n+test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n+\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"383814","messageId":"12c0916cb1ef033f917dc065cc1f18c0477296b8.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 06/11] graph: tidy up display of left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:47Z","receivedAt":"2019-10-10T16:14:00Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nCurrently, when we display a merge whose first parent is already present\nin a column to the left of the merge commit, we display the first parent\nas a veritcal pipe `|` in the GRAPH_POST_MERGE line and then immediately\nenter the GRAPH_COLLAPSING state. The first-parent line tracks to the\nleft and all the other parent lines follow it; this creates a \"kink\" in\nthose lines:\n\n        | *---.\n        | |\\ \\ \\\n        |/ / / /\n        | | | *\n\nThis change tidies the display of such commits such that if the first\nparent appears to the left of the merge, we render it as a `/` and the\nsecond parent as a `|`. This reduces the horizontal and vertical space\nneeded to render the merge, and makes the resulting lines easier to\nread.\n\n        | *-.\n        |/|\\ \\\n        | | | *\n\nIf the first parent is separated from the merge by several columns, a\nhorizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING\nstate displays the line.\n\n        | | | *-.\n        | |_|/|\\ \\\n        |/| | | | *\n\nThis effect is applied to both \"normal\" two-parent merges, and to\noctopus merges. It also reduces the vertical space needed for pre-commit\nlines, as the merge occupies one less column than usual.\n\n        Before:         After:\n\n        | *             | *\n        | |\\            | |\\\n        | | \\           | * \\\n        | |  \\          |/|\\ \\\n        | *-. \\\n        | |\\ \\ \\\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 125 +++++++++++++++++++++++++++--------\n t/t4214-log-graph-octopus.sh |  10 ++-\n t/t4215-log-skewed-merges.sh |  42 ++++++++++++\n 3 files changed, 143 insertions(+), 34 deletions(-)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\ndiff --git a/graph.c b/graph.c\nindex 98e8777db4..9136173e03 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -183,6 +183,20 @@ struct git_graph {\n \t * previous commit.\n \t */\n \tint prev_commit_index;\n+\t/*\n+\t * Which layout variant to use to display merge commits. If the\n+\t * commit's first parent is known to be in a column to the left of the\n+\t * merge, then this value is 0 and we use the layout on the left.\n+\t * Otherwise, the value is 1 and the layout on the right is used. This\n+\t * field tells us how many columns the first parent occupies.\n+\t *\n+\t * \t\t0)\t\t\t1)\n+\t *\n+\t * \t\t| | | *-.\t\t| | *---.\n+\t * \t\t| |_|/|\\ \\\t\t| | |\\ \\ \\\n+\t * \t\t|/| | | | |\t\t| | | | | *\n+\t */\n+\tint merge_layout;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -294,6 +308,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->prev_state = GRAPH_PADDING;\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n+\tgraph->merge_layout = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -453,9 +468,11 @@ static int graph_find_new_column_by_commit(struct git_graph *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  struct commit *commit,\n+\t\t\t\t\t  int idx)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n+\tint mapping_idx;\n \n \t/*\n \t * If the commit is not already in the new_columns array, then add it\n@@ -467,8 +484,26 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[graph->width] = i;\n-\tgraph->width += 2;\n+\tif (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {\n+\t\t/*\n+\t\t * If this is the first parent of a merge, choose a layout for\n+\t\t * the merge line based on whether the parent appears in a\n+\t\t * column to the left of the merge\n+\t\t */\n+\t\tint dist, shift;\n+\n+\t\tdist = idx - i;\n+\t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n+\n+\t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n+\t\tgraph->width += 2 * graph->merge_layout;\n+\t} else {\n+\t\tmapping_idx = graph->width;\n+\t\tgraph->width += 2;\n+\t}\n+\n+\tgraph->mapping[mapping_idx] = i;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n@@ -534,6 +569,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tif (col_commit == graph->commit) {\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n+\t\t\tgraph->merge_layout = -1;\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@@ -546,7 +582,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\n-\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item, i);\n \t\t\t}\n \t\t\t/*\n \t\t\t * We always need to increment graph->width by at\n@@ -557,7 +593,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tif (graph->num_parents == 0)\n \t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit, -1);\n \t\t}\n \t}\n \n@@ -569,10 +605,36 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_expansion_rows(struct git_graph *graph)\n+{\n+\t/*\n+\t * Normally, we need two expansion rows for each dashed parent line from\n+\t * an octopus merge:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| | \\\n+\t * \t\t| |  \\\n+\t * \t\t| *-. \\\n+\t * \t\t| |\\ \\ \\\n+\t *\n+\t * If the merge is skewed to the left, then its parents occupy one less\n+\t * column, and we don't need as many expansion rows to route around it;\n+\t * in some cases that means we don't need any expansion rows at all:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| * \\\n+\t * \t\t|/|\\ \\\n+\t */\n+\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+}\n+\n static int graph_needs_pre_commit_line(struct git_graph *graph)\n {\n \treturn graph->num_parents >= 3 &&\n-\t       graph->commit_index < (graph->num_columns - 1);\n+\t       graph->commit_index < (graph->num_columns - 1) &&\n+\t       graph->expansion_row < graph_num_expansion_rows(graph);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n@@ -722,7 +784,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t\t\t struct strbuf *sb)\n {\n-\tint num_expansion_rows;\n \tint i, seen_this;\n \n \t/*\n@@ -733,14 +794,13 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * We need 2 extra rows for every parent over 2.\n \t */\n \tassert(graph->num_parents >= 3);\n-\tnum_expansion_rows = (graph->num_parents - 2) * 2;\n \n \t/*\n \t * graph->expansion_row tracks the current expansion row we are on.\n \t * It should be in the range [0, num_expansion_rows - 1]\n \t */\n \tassert(0 <= graph->expansion_row &&\n-\t       graph->expansion_row < num_expansion_rows);\n+\t       graph->expansion_row < graph_num_expansion_rows(graph));\n \n \t/*\n \t * Output the row\n@@ -782,7 +842,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * and move to state GRAPH_COMMIT if necessary\n \t */\n \tgraph->expansion_row++;\n-\tif (graph->expansion_row >= num_expansion_rows)\n+\tif (!graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n@@ -820,7 +880,7 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n \t * x 0 1 2 3\n \t *\n \t */\n-\tconst int dashless_parents = 2;\n+\tconst int dashless_parents = 3 - graph->merge_layout;\n \tint dashful_parents = graph->num_parents - dashless_parents;\n \n \t/*\n@@ -828,9 +888,9 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n \t * above) but sometimes the first parent goes into an existing column,\n \t * like this:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * |/ / / /\n+\t * | *-.\n+\t * |/|\\ \\\n+\t * | | | |\n \t * x 0 1 2\n \t *\n \t * In which case the number of parents will be one greater than the\n@@ -923,10 +983,15 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+const char merge_chars[] = {'/', '|', '\\\\'};\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;\n+\n+\tstruct commit_list *first_parent = first_interesting_parent(graph);\n+\tint seen_parent = 0;\n \n \t/*\n \t * Output the post-merge row\n@@ -949,30 +1014,34 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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 commit_list *parents = first_parent;\n \t\t\tint par_column;\n+\t\t\tint idx = graph->merge_layout;\n+\t\t\tchar c;\n \t\t\tseen_this = 1;\n-\t\t\tparents = first_interesting_parent(graph);\n-\t\t\tassert(parents);\n-\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n-\t\t\tassert(par_column >= 0);\n-\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[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+\n+\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n-\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '\\\\');\n-\t\t\t\tstrbuf_addch(sb, ' ');\n+\n+\t\t\t\tc = merge_chars[idx];\n+\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], c);\n+\t\t\t\tif (idx == 2)\n+\t\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\t\telse\n+\t\t\t\t\tidx++;\n \t\t\t}\n \t\t} else if (seen_this) {\n \t\t\tstrbuf_write_column(sb, col, '\\\\');\n \t\t\tstrbuf_addch(sb, ' ');\n \t\t} else {\n \t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tif (graph->merge_layout != 0 || i != graph->commit_index - 1)\n+\t\t\t\tstrbuf_addch(sb, seen_parent ? '_' : ' ');\n \t\t}\n+\n+\t\tif (col_commit == first_parent->item)\n+\t\t\tseen_parent = 1;\n \t}\n \n \tgraph_pad_horizontally(graph, sb);\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex dab96c89aa..40f420877b 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -7,9 +7,8 @@ test_description='git log --graph of skewed left octopus merge.'\n test_expect_success 'set up merge history' '\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n@@ -21,9 +20,8 @@ test_expect_success 'set up merge history' '\n \tEOF\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n-\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <MAGENTA>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>\n+\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET>\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nnew file mode 100755\nindex 0000000000..cfaba40f97\n--- /dev/null\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -0,0 +1,42 @@\n+#!/bin/sh\n+\n+test_description='git log --graph of skewed merges'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup left-skewed merge' '\n+\tgit checkout --orphan _a && test_commit A &&\n+\tgit branch _b &&\n+\tgit branch _c &&\n+\tgit branch _d &&\n+\tgit branch _e &&\n+\tgit checkout _b && test_commit B &&\n+\tgit checkout _c && test_commit C &&\n+\tgit checkout _d && test_commit D &&\n+\tgit checkout _e && git merge --no-ff _d -m E &&\n+\tgit checkout _a && git merge --no-ff _b _c _e -m F\n+'\n+\n+cat > expect <<\\EOF\n+*---.   F\n+|\\ \\ \\\n+| | | * E\n+| |_|/|\n+|/| | |\n+| | | * D\n+| |_|/\n+|/| |\n+| | * C\n+| |/\n+|/|\n+| * B\n+|/\n+* A\n+EOF\n+\n+test_expect_success 'log --graph with left-skewed merge' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"383815","messageId":"50756edcf7075b27b1ac0b0fb7af8688787bcd00.1570724021.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH 10/11] graph: flatten edges that join to their right neighbor","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-10T16:13:51Z","receivedAt":"2019-10-10T16:14:01Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a merge commit is printed and its final parent is the same commit\nthat occupies the column to the right of the merge, this results in a\nkink in the displayed edges:\n\n        * |\n        |\\ \\\n        | |/\n        | *\n\nGraphs containing these shapes can be hard to read, as the expansion to\nthe right followed immediately by collapsing back to the left creates a\nlot of zig-zagging edges, especially when many columns are present.\n\nWe can improve this by eliminating the zig-zag and having the merge's\nfinal parent edge fuse immediately with its neighbor:\n\n        * |\n        |\\|\n        | *\n\nThis reduces the horizontal width for the current commit by 2, and\nrequires one less row, making the graph display more compact. Taken in\ncombination with other graph-smoothing enhancements, it greatly\ncompresses the space needed to display certain histories:\n\n        *\n        |\\\n        | *                       *\n        | |\\                      |\\\n        | | *                     | *\n        | | |                     | |\\\n        | |  \\                    | | *\n        | *-. \\                   | * |\n        | |\\ \\ \\        =>        |/|\\|\n        |/ / / /                  | | *\n        | | | /                   | * |\n        | | |/                    | |/\n        | | *                     * /\n        | * |                     |/\n        | |/                      *\n        * |\n        |/\n        *\n\nOne of the test cases here cannot be correctly rendered in Git v2.23.0;\nit produces this output following commit E:\n\n        | | *-. \\   5_E\n        | | |\\ \\ \\\n        | |/ / / /\n        | | | / _\n        | |_|/\n        |/| |\n\nThe new implementation makes sure that the rightmost edge in this\nhistory is not left dangling as above.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 34 ++++++---\n t/t4215-log-skewed-merges.sh               | 80 +++++++++++++++++++++-\n t/t6016-rev-list-graph-simplify-history.sh | 30 ++++----\n 3 files changed, 116 insertions(+), 28 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 6391e393ec..7dd2fab625 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -538,8 +538,24 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n \n \t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tgraph->edges_added = graph->num_parents + graph->merge_layout  - 2;\n+\n \t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n \t\tgraph->width += 2 * graph->merge_layout;\n+\n+\t} else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 2]) {\n+\t\t/*\n+\t\t * If some columns have been added by a merge, but this commit\n+\t\t * was found in the last existing column, then adjust the\n+\t\t * numbers so that the two edges immediately join, i.e.:\n+\t\t *\n+\t\t *\t\t* |\t\t* |\n+\t\t *\t\t|\\ \\\t=>\t|\\|\n+\t\t *\t\t| |/\t\t| *\n+\t\t *\t\t| *\n+\t\t */\n+\t\tmapping_idx = graph->width - 2;\n+\t\tgraph->edges_added = -1;\n \t} else {\n \t\tmapping_idx = graph->width;\n \t\tgraph->width += 2;\n@@ -585,6 +601,8 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping[i] = -1;\n \n \tgraph->width = 0;\n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = 0;\n \n \t/*\n \t * Populate graph->new_columns and graph->mapping\n@@ -712,9 +730,6 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n-\tgraph->prev_edges_added = graph->edges_added;\n-\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n-\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -1039,7 +1054,7 @@ const char merge_chars[] = {'/', '|', '\\\\'};\n static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i;\n+\tint i, j;\n \n \tstruct commit_list *first_parent = first_interesting_parent(graph);\n \tint seen_parent = 0;\n@@ -1071,16 +1086,19 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\t\tchar c;\n \t\t\tseen_this = 1;\n \n-\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n+\t\t\tfor (j = 0; j < graph->num_parents; j++) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n \n \t\t\t\tc = merge_chars[idx];\n \t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], c);\n-\t\t\t\tif (idx == 2)\n-\t\t\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\t\telse\n+\t\t\t\tif (idx == 2) {\n+\t\t\t\t\tif (graph->edges_added > 0 || j < graph->num_parents - 1)\n+\t\t\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\t\t} else {\n \t\t\t\t\tidx++;\n+\t\t\t\t}\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n \t\t\t}\n \t\t\tif (graph->edges_added == 0)\n \t\t\t\tstrbuf_addch(sb, ' ');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex b739268e5e..1481e6fd80 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -137,9 +137,8 @@ cat > expect <<\\EOF\n | | * 3_G\n | * | 3_F\n |/| |\n-| * |   3_E\n-| |\\ \\\n-| | |/\n+| * | 3_E\n+| |\\|\n | | * 3_D\n | * | 3_C\n | |/\n@@ -190,4 +189,79 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup octopus merge with column joining its penultimate parent' '\n+\tgit checkout --orphan 5_p &&\n+\ttest_commit 5_A &&\n+\tgit branch 5_q &&\n+\tgit branch 5_r &&\n+\ttest_commit 5_B &&\n+\tgit checkout 5_q && test_commit 5_C &&\n+\tgit checkout 5_r && test_commit 5_D &&\n+\tgit checkout 5_p &&\n+\tgit merge --no-ff 5_q 5_r -m 5_E &&\n+\tgit checkout 5_q && test_commit 5_F &&\n+\tgit checkout -b 5_s 5_p^ &&\n+\tgit merge --no-ff 5_p 5_q -m 5_G &&\n+\tgit checkout 5_r &&\n+\tgit merge --no-ff 5_s -m 5_H\n+'\n+\n+cat > expect <<\\EOF\n+*   5_H\n+|\\\n+| *-.   5_G\n+| |\\ \\\n+| | | * 5_F\n+| | * |   5_E\n+| |/|\\ \\\n+| |_|/ /\n+|/| | /\n+| | |/\n+* | | 5_D\n+| | * 5_C\n+| |/\n+|/|\n+| * 5_B\n+|/\n+* 5_A\n+EOF\n+\n+test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'setup merge fusing with its left and right neighbors' '\n+\tgit checkout --orphan 6_p &&\n+\ttest_commit 6_A &&\n+\ttest_commit 6_B &&\n+\tgit checkout -b 6_q @^ && test_commit 6_C &&\n+\tgit checkout -b 6_r @^ && test_commit 6_D &&\n+\tgit checkout 6_p && git merge --no-ff 6_q 6_r -m 6_E &&\n+\tgit checkout 6_r && test_commit 6_F &&\n+\tgit checkout 6_p && git merge --no-ff 6_r -m 6_G &&\n+\tgit checkout @^^ && git merge --no-ff 6_p -m 6_H\n+'\n+\n+cat > expect <<\\EOF\n+*   6_H\n+|\\\n+| *   6_G\n+| |\\\n+| | * 6_F\n+| * | 6_E\n+|/|\\|\n+| | * 6_D\n+| * | 6_C\n+| |/\n+* / 6_B\n+|/\n+* 6_A\n+EOF\n+\n+test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex ca1682f29b..f5e6e92f5b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -67,11 +67,10 @@ test_expect_success '--graph --all' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -97,11 +96,10 @@ test_expect_success '--graph --simplify-by-decoration' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -131,9 +129,8 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n \techo \"| * $C2\" >> expected &&\n \techo \"| * $C1\" >> expected &&\n \techo \"* | $A3\" >> expected &&\n@@ -151,10 +148,9 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"|\\\\  \" >> expected &&\n \techo \"| * $C4\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n-\techo \"* / $A3\" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n+\techo \"* | $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n-- \ngitgitgadget\n\n"},{"id":"383820","messageId":"5c688030-6351-93a3-89bd-e666d02d12d9@gmail.com","threadId":"52011","inReplyTo":"12c0916cb1ef033f917dc065cc1f18c0477296b8.1570724021.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/11] graph: tidy up display of left-skewed merges","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-10T17:19:53Z","receivedAt":"2019-10-10T17:19:57Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n> From: James Coglan <jcoglan@gmail.com>\n> \n> Currently, when we display a merge whose first parent is already present\n> in a column to the left of the merge commit, we display the first parent\n> as a veritcal pipe `|` in the GRAPH_POST_MERGE line and then immediately\n> enter the GRAPH_COLLAPSING state. The first-parent line tracks to the\n> left and all the other parent lines follow it; this creates a \"kink\" in\n> those lines:\n> \n>         | *---.\n>         | |\\ \\ \\\n>         |/ / / /\n>         | | | *\n> \n> This change tidies the display of such commits such that if the first\n> parent appears to the left of the merge, we render it as a `/` and the\n> second parent as a `|`. This reduces the horizontal and vertical space\n> needed to render the merge, and makes the resulting lines easier to\n> read.\n> \n>         | *-.\n>         |/|\\ \\\n>         | | | *\n> \n> If the first parent is separated from the merge by several columns, a\n> horizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING\n> state displays the line.\n> \n>         | | | *-.\n>         | |_|/|\\ \\\n>         |/| | | | *\n> \n> This effect is applied to both \"normal\" two-parent merges, and to\n> octopus merges. It also reduces the vertical space needed for pre-commit\n> lines, as the merge occupies one less column than usual.\n> \n>         Before:         After:\n> \n>         | *             | *\n>         | |\\            | |\\\n>         | | \\           | * \\\n>         | |  \\          |/|\\ \\\n>         | *-. \\\n>         | |\\ \\ \\\n> \n\nThank you for adding these careful diagrams both to the message\nand the code. These concepts are hard to track without a visual\naid.\n\n[snip]\n\n> +++ b/t/t4215-log-skewed-merges.sh\n> @@ -0,0 +1,42 @@\n> +#!/bin/sh\n> +\n> +test_description='git log --graph of skewed merges'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup left-skewed merge' '\n\n\nCould you skew this example to include a left-skewed octopus merge\n(and use fewer Git processes) with the following:\n\n\tgit checkout --orphan _a && test_commit A &&\n\tgit switch -c _b _a && test_commit B &&\n\tgit switch -c _c _a && test_commit C &&\n\tgit switch -c _d _a && test_commit D &&\tgit switch -c _e _b && git merge --no-ff _c _d E &&\n\tgit switch -c _f _a && git merge --no-ff _d -m F &&\tgit checkout _a && git merge --no-ff _b _c _e _f -m G\nand I think the resulting output will be:\n\n*-----.   G\n|\\ \\ \\ \\\n| | | | * F\n| |_|_|/|\n|/| | | |\n| | | * | E\n| |_|/|\\|\n|/| | | |\n| | |/  * D\n| |_|__/\n|/| |\n| | * C\n| |/\n|/|\n| * B\n|/\n* A\n\n\n\n"},{"id":"383821","messageId":"9fe7f2d9-2108-5cf6-dcd7-06d91e74e98b@gmail.com","threadId":"52011","inReplyTo":"6c173663aac37f1d314db8637cf4a243066b8078.1570724021.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-10T17:49:20Z","receivedAt":"2019-10-10T17:49:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n> From: James Coglan <jcoglan@gmail.com>\n> \n> Following the introduction of \"left-skewed\" merges, which are merges\n> whose first parent fuses with another edge to its left, we have some\n> more edge cases to deal with in the display of commit and post-merge\n> lines.\n> \n> The current graph code handles the following cases for edges appearing\n> to the right of the commit (*) on commit lines. A 2-way merge is usually\n> followed by vertical lines:\n> \n>         | | |\n>         | * |\n>         | |\\ \\\n> \n> An octopus merge (more than two parents) is always followed by edges\n> sloping to the right:\n> \n>         | |  \\          | |    \\\n>         | *-. \\         | *---. \\\n>         | |\\ \\ \\        | |\\ \\ \\ \\\n> \n> A 2-way merge is followed by a right-sloping edge if the commit line\n> immediately follows a post-merge line for a commit that appears in the\n> same column as the current commit, or any column to the left of that:\n> \n>         | *             | * |\n>         | |\\            | |\\ \\\n>         | * \\           | | * \\\n>         | |\\ \\          | | |\\ \\\n> \n> This commit introduces the following new cases for commit lines. If a\n> 2-way merge skews to the left, then the edges to its right are always\n> vertical lines, even if the commit follows a post-merge line:\n> \n>         | | |           | |\\\n>         | * |           | * |\n>         |/| |           |/| |\n> \n> A commit with 3 parents that skews left is followed by vertical edges:\n> \n>         | | |\n>         | * |\n>         |/|\\ \\\n> \n> If a 3-way left-skewed merge commit appears immediately after a\n> post-merge line, then it may be followed the right-sloping edges, just\n> like a 2-way merge that is not skewed.\n> \n>         | |\\\n>         | * \\\n>         |/|\\ \\\n> \n> Octopus merges with 4 or more parents that skew to the left will always\n> be followed by right-sloping edges, because the existing columns need to\n> expand around the merge.\n> \n>         | |  \\\n>         | *-. \\\n>         |/|\\ \\ \\\n> \n> On post-merge lines, usually all edges following the current commit\n> slope to the right:\n> \n>         | * | |\n>         | |\\ \\ \\\n> \n> However, if the commit is a left-skewed 2-way merge, the edges to its\n> right remain vertical. We also need to display a space after the\n> vertical line descending from the commit marker, whereas this line would\n> normally be followed by a backslash.\n> \n>         | * | |\n>         |/| | |\n> \n> If a left-skewed merge has more than 2 parents, then the edges to its\n> right are still sloped as they bend around the edges introduced by the\n> merge.\n> \n>         | * | |\n>         |/|\\ \\ \\\n> \n> To handle these new cases, we need to know not just how many parents\n> each commit has, but how many new columns it adds to the display; this\n> quantity is recorded in the `edges_added` field for the current commit,\n> and `prev_edges_added` field for the previous commit.\n> \n> Here, \"column\" refers to visual columns, not the logical columns of the\n> `columns` array. This is because even if all the commit's parents end up\n> fusing with existing edges, they initially introduce distinct edges in\n> the commit and post-merge lines before those edges collapse. For\n> example, a 3-way merge whose 2nd and 3rd parents fuse with existing\n> edges still introduces 2 visual columns that affect the display of edges\n> to their right.\n> \n>         | | |  \\\n>         | | *-. \\\n>         | | |\\ \\ \\\n>         | |_|/ / /\n>         |/| | / /\n>         | | |/ /\n>         | |/| |\n>         | | | |\n> \n> This merge does not introduce any *logical* columns; there are 4 edges\n> before and after this commit once all edges have collapsed. But it does\n> initially introduce 2 new edges that need to be accommodated by the\n> edges to their right.\n> \n> Signed-off-by: James Coglan <jcoglan@gmail.com>\n> ---\n>  graph.c                      |  63 +++++++++++++--\n>  t/t4215-log-skewed-merges.sh | 151 +++++++++++++++++++++++++++++++++++\n>  2 files changed, 209 insertions(+), 5 deletions(-)\n> \n> diff --git a/graph.c b/graph.c\n> index 9136173e03..fb2e42850f 100644\n> --- a/graph.c\n> +++ b/graph.c\n> @@ -197,6 +197,46 @@ struct git_graph {\n>  \t * \t\t|/| | | | |\t\t| | | | | *\n>  \t */\n>  \tint merge_layout;\n> +\t/*\n> +\t * The number of columns added to the graph by the current commit. For\n> +\t * 2-way and octopus merges, this is is usually one less than the\n> +\t * number of parents:\n> +\t *\n> +\t * \t\t| | |\t\t\t| |    \\\n> +\t *\t\t| * |\t\t\t| *---. \\\n> +\t *\t\t| |\\ \\\t\t\t| |\\ \\ \\ \\\n> +\t *\t\t| | | |         \t| | | | | |\n> +\t *\n> +\t *\t\tnum_parents: 2\t\tnum_parents: 4\n> +\t *\t\tedges_added: 1\t\tedges_added: 3\n> +\t *\n> +\t * For left-skewed merges, the first parent fuses with its neighbor and\n> +\t * so one less column is added:\n> +\t *\n> +\t *\t\t| | |\t\t\t| |  \\\n> +\t *\t\t| * |\t\t\t| *-. \\\n> +\t *\t\t|/| |\t\t\t|/|\\ \\ \\\n> +\t *\t\t| | |\t\t\t| | | | |\n> +\t *\n> +\t *\t\tnum_parents: 2\t\tnum_parents: 4\n> +\t *\t\tedges_added: 0\t\tedges_added: 2\n> +\t *\n> +\t * This number determines how edges to the right of the merge are\n> +\t * displayed in commit and post-merge lines; if no columns have been\n> +\t * added then a vertical line should be used where a right-tracking\n> +\t * line would otherwise be used.\n> +\t *\n> +\t *\t\t| * \\\t\t\t| * |\n> +\t *\t\t| |\\ \\\t\t\t|/| |\n> +\t *\t\t| | * \\\t\t\t| * |\n> +\t */\n> +\tint edges_added;\n> +\t/*\n> +\t * The number of columns added by the previous commit, which is used to\n> +\t * smooth edges appearing to the right of a commit in a commit line\n> +\t * following a post-merge line.\n> +\t */\n> +\tint prev_edges_added;\n>  \t/*\n>  \t * The maximum number of columns that can be stored in the columns\n>  \t * and new_columns arrays.  This is also half the number of entries\n> @@ -309,6 +349,8 @@ struct git_graph *graph_init(struct rev_info *opt)\n>  \tgraph->commit_index = 0;\n>  \tgraph->prev_commit_index = 0;\n>  \tgraph->merge_layout = 0;\n> +\tgraph->edges_added = 0;\n> +\tgraph->prev_edges_added = 0;\n>  \tgraph->num_columns = 0;\n>  \tgraph->num_new_columns = 0;\n>  \tgraph->mapping_size = 0;\n> @@ -670,6 +712,9 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n>  \t */\n>  \tgraph_update_columns(graph);\n>  \n> +\tgraph->prev_edges_added = graph->edges_added;\n> +\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n> +\n>  \tgraph->expansion_row = 0;\n>  \n>  \t/*\n> @@ -943,12 +988,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \n>  \t\t\tif (graph->num_parents > 2)\n>  \t\t\t\tgraph_draw_octopus_merge(graph, sb);\n> -\t\t} else if (seen_this && (graph->num_parents > 2)) {\n> +\t\t} else if (seen_this && (graph->edges_added > 1)) {\n>  \t\t\tstrbuf_write_column(sb, col, '\\\\');\n> -\t\t} else if (seen_this && (graph->num_parents == 2)) {\n> +\t\t} else if (seen_this && (graph->edges_added == 1)) {\n>  \t\t\t/*\n> -\t\t\t * This is a 2-way merge commit.\n> -\t\t\t * There is no GRAPH_PRE_COMMIT stage for 2-way\n> +\t\t\t * This is either a right-skewed 2-way merge\n> +\t\t\t * commit, or a left-skewed 3-way merge.\n> +\t\t\t * There is no GRAPH_PRE_COMMIT stage for such\n>  \t\t\t * merges, so this is the first line of output\n>  \t\t\t * for this commit.  Check to see what the previous\n>  \t\t\t * line of output was.\n> @@ -960,6 +1006,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \t\t\t * makes the output look nicer.\n>  \t\t\t */\n>  \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n> +\t\t\t    graph->prev_edges_added > 0 &&\n>  \t\t\t    graph->prev_commit_index < i)\n>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>  \t\t\telse\n> @@ -1031,8 +1078,14 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n>  \t\t\t\telse\n>  \t\t\t\t\tidx++;\n>  \t\t\t}\n> +\t\t\tif (graph->edges_added == 0)\n> +\t\t\t\tstrbuf_addch(sb, ' ');\n> +\n>  \t\t} else if (seen_this) {\n> -\t\t\tstrbuf_write_column(sb, col, '\\\\');\n> +\t\t\tif (graph->edges_added > 0)\n> +\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n> +\t\t\telse\n> +\t\t\t\tstrbuf_write_column(sb, col, '|');\n>  \t\t\tstrbuf_addch(sb, ' ');\n>  \t\t} else {\n>  \t\t\tstrbuf_write_column(sb, col, '|');\n> diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n> index cfaba40f97..e479d6e676 100755\n> --- a/t/t4215-log-skewed-merges.sh\n> +++ b/t/t4215-log-skewed-merges.sh\n> @@ -39,4 +39,155 @@ test_expect_success 'log --graph with left-skewed merge' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'setup nested left-skewed merge' '\n> +\tgit checkout --orphan 1_p &&\n> +\ttest_commit 1_A &&\n> +\ttest_commit 1_B &&\n> +\ttest_commit 1_C &&\n> +\tgit checkout -b 1_q @^ && test_commit 1_D &&\n> +\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n> +\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n> +\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n> +\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H\n> +'\n> +\n> +cat > expect <<\\EOF\n> +*   1_H\n> +|\\\n> +| *   1_G\n> +| |\\\n> +| | * 1_F\n> +| * | 1_E\n> +|/| |\n> +| * | 1_D\n> +* | | 1_C\n> +|/ /\n> +* | 1_B\n> +|/\n> +* 1_A\n> +EOF\n> +\n> +test_expect_success 'log --graph with nested left-skewed merge' '\n> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n> +\ttest_cmp expect actual\n> +'\n\nI should have noticed in your earlier commits, but why don't you keep\nthe output inside the test suite? You can start with \"cat >expect <<-EOF\"\nto have it ignore leading whitespace. Sorry if there's something else about\nthis that is causing issues.\n\n> +\n> +test_expect_success 'setup nested left-skewed merge following normal merge' '\n> +\tgit checkout --orphan 2_p &&\n> +\ttest_commit 2_A &&\n> +\ttest_commit 2_B &&\n> +\ttest_commit 2_C &&\n> +\tgit checkout -b 2_q @^^ &&\n> +\ttest_commit 2_D &&\n> +\ttest_commit 2_E &&\n> +\tgit checkout -b 2_r @^ && test_commit 2_F &&\n> +\tgit checkout 2_q &&\n> +\tgit merge --no-ff 2_r -m 2_G &&\n> +\tgit merge --no-ff 2_p^ -m 2_H &&\n> +\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n> +\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K\n> +'\n> +\n> +cat > expect <<\\EOF\n> +*   2_K\n> +|\\\n> +| *   2_J\n> +| |\\\n> +| | *   2_H\n> +| | |\\\n> +| | * | 2_G\n> +| |/| |\n> +| | * | 2_F\n> +| * | | 2_E\n> +| |/ /\n> +| * | 2_D\n> +* | | 2_C\n> +| |/\n> +|/|\n> +* | 2_B\n> +|/\n> +* 2_A\n> +EOF\n> +\n> +test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'setup nested right-skewed merge following left-skewed merge' '\n> +\tgit checkout --orphan 3_p &&\n> +\ttest_commit 3_A &&\n> +\tgit checkout -b 3_q &&\n> +\ttest_commit 3_B &&\n> +\ttest_commit 3_C &&\n> +\tgit checkout -b 3_r @^ &&\n> +\ttest_commit 3_D &&\n> +\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n> +\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n> +\tgit checkout 3_r && test_commit 3_G &&\n> +\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n> +\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J\n> +'\n> +\n> +cat > expect <<\\EOF\n> +*   3_J\n> +|\\\n> +| *   3_H\n> +| |\\\n> +| | * 3_G\n> +| * | 3_F\n> +|/| |\n> +| * |   3_E\n> +| |\\ \\\n> +| | |/\n> +| | * 3_D\n> +| * | 3_C\n> +| |/\n> +| * 3_B\n> +|/\n> +* 3_A\n> +EOF\n> +\n> +test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'setup right-skewed merge following a left-skewed one' '\n> +\tgit checkout --orphan 4_p &&\n> +\ttest_commit 4_A &&\n> +\ttest_commit 4_B &&\n> +\ttest_commit 4_C &&\n> +\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n> +\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n> +\tgit checkout -b 4_s 4_p^^ &&\n> +\tgit merge --no-ff 4_r -m 4_F &&\n> +\tgit merge --no-ff 4_p -m 4_G &&\n> +\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H\n> +'\n> +\n> +cat > expect <<\\EOF\n> +*   4_H\n> +|\\\n> +| *   4_G\n> +| |\\\n> +| * | 4_F\n> +|/| |\n> +| * |   4_E\n> +| |\\ \\\n> +| | * | 4_D\n> +| |/ /\n> +|/| |\n> +| | * 4_C\n> +| |/\n> +| * 4_B\n> +|/\n> +* 4_A\n> +EOF\n> +\n> +test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n> +\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n> \n\n"},{"id":"383822","messageId":"0a35a944-cbf4-06c8-f327-26728f95fbed@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/11] Improve the readability of log --graph output","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-10T17:54:56Z","receivedAt":"2019-10-10T17:55:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n> This series of patches are designed to improve the output of the log --graph\n> command; their effect can be summed up in the following diagram:\n> \n>     Before                    After\n>     ------                    -----\n> \n>     *\n>     |\\\n>     | *                       *\n>     | |\\                      |\\\n>     | | *                     | *\n>     | | |                     | |\\\n>     | |  \\                    | | *\n>     | *-. \\                   | * |\n>     | |\\ \\ \\                  |/|\\|\n>     |/ / / /                  | | *\n>     | | | /                   | * |\n>     | | |/                    | |/\n>     | | *                     * /\n>     | * |                     |/\n>     | |/                      *\n>     * |\n>     |/\n>     *\n\nI took a brief look through your series, and I think this is a really\ncool improvement. I use \"git log --graph\" all the time and those kinks\nhave bothered me, too.\n\nI'd give you extra bonus points if your first patch added the case on\nthe left as an expected output and we watch it get simpler as you modify\nthe behavior in pieces.\n\nI do really like how you isolated different transformations into\ndifferent commits. I would have given more specific feedback if I\nwas more familiar with graph.c. I'll need to play with it more to\ngive more substantive feedback.\n\nThe only thing I could complain about in the first glance is some\ntest file formatting stuff, like how you split a test case into\n\"setup\" \"create expected output\" and \"test the output\". That would\nbe better as one test. Also you add a space between your redirect\ncharacter and your file (\"> expect\" instead of \">expect\").\n\nThose nits aside, I look forward to digging into the code more\nsoon.\n\nThanks,\n-Stolee\n"},{"id":"383823","messageId":"20191010181624.GA97861@generichostname","threadId":"52011","inReplyTo":"ea0df1d94aa6c42eb00d59b268a90a8724322452.1570724021.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 11/11] graph: fix coloring of octopus dashes","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-10-10T18:16:24Z","receivedAt":"2019-10-10T18:16:30Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi James,\n\nNicely done! This issue was bugging me for a while!\n\nOn Thu, Oct 10, 2019 at 09:13:52AM -0700, James Coglan via GitGitGadget wrote:\n\n[...]\n\n> diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\n> index 9ada687628..fbd485d83a 100755\n> --- a/t/t4214-log-graph-octopus.sh\n> +++ b/t/t4214-log-graph-octopus.sh\n> @@ -42,23 +42,74 @@ test_expect_success 'set up merge history' '\n>  \ttest_tick &&\n>  \tgit merge -m octopus-merge 1 2 3 4 &&\n>  \tgit checkout 1 -b L &&\n> -\ttest_commit left\n> +\ttest_commit left &&\n> +\tgit checkout 4 -b R &&\n> +\ttest_commit right\n>  '\n>  \n>  test_expect_success 'log --graph with tricky octopus merge with colors' '\n>  \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n> -\tgit log --color=always --graph --date-order --pretty=tformat:%s --all >actual.colors.raw &&\n> +\tgit log --color=always --graph --date-order --pretty=tformat:%s L merge >actual.colors.raw &&\n>  \ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n>  \ttest_cmp expect.colors actual.colors\n>  '\n>  \n>  test_expect_success 'log --graph with tricky octopus merge, no color' '\n> -\tgit log --color=never --graph --date-order --pretty=tformat:%s --all >actual.raw &&\n> +\tgit log --color=never --graph --date-order --pretty=tformat:%s L merge >actual.raw &&\n>  \tsed \"s/ *\\$//\" actual.raw >actual &&\n>  \ttest_cmp expect.uncolored actual\n>  '\n>  \n> -# Repeat the previous two tests with \"normal\" octopus merge (i.e.,\n> +# Repeat the previous two tests with an octopus merge whose final parent skews left\n> +\n> +test_expect_success 'log --graph with left-skewed final parent, no color' '\n> +\tcat >expect.uncolored <<-\\EOF &&\n> +\t* right\n> +\t| *---.   octopus-merge\n> +\t| |\\ \\ \\\n> +\t| |_|_|/\n> +\t|/| | |\n> +\t* | | | 4\n> +\t| | | * 3\n> +\t| |_|/\n> +\t|/| |\n> +\t| | * 2\n> +\t| |/\n> +\t|/|\n> +\t| * 1\n> +\t|/\n> +\t* initial\n> +\tEOF\n> +\tgit log --color=never --graph --date-order --pretty=tformat:%s R merge >actual.raw &&\n> +\tsed \"s/ *\\$//\" actual.raw >actual &&\n> +\ttest_cmp expect.uncolored actual\n> +'\n> +\n> +test_expect_success 'log --graph with left-skewed final parent with colors' '\n> +\tcat >expect.colors <<-\\EOF &&\n> +\t* right\n> +\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><RED>-<RESET><RED>.<RESET>   octopus-merge\n> +\t<RED>|<RESET> <GREEN>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <RED>\\<RESET>\n> +\t<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>\n> +\t<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET>\n> +\t* <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> 4\n> +\t<MAGENTA>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 3\n> +\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>_<RESET><YELLOW>|<RESET><MAGENTA>/<RESET>\n> +\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET>\n> +\t<MAGENTA>|<RESET> <GREEN>|<RESET> * 2\n> +\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>/<RESET>\n> +\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET>\n> +\t<MAGENTA>|<RESET> * 1\n> +\t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n> +\t* initial\n> +\tEOF\n> +\ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n> +\tgit log --color=always --graph --date-order --pretty=tformat:%s R merge >actual.colors.raw &&\n> +\ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n> +\ttest_cmp expect.colors actual.colors\n> +'\n> +\n> +# Repeat the first two tests with \"normal\" octopus merge (i.e.,\n>  # without the first parent skewing to the \"left\" branch column).\n>  \n>  test_expect_success 'log --graph with normal octopus merge, no color' '\n\nSo, I decided to merge 'dl/octopus-graph-bug' with your branch and I\ncouldn't be happier! I had to make a couple of minor tweaks to the\nexisting test cases but most of them only involved flipping\n`test_expect_failure` to `test_expect_success`.\n\nYou can see the results of this by doing:\n\n\t$ git fetch https://github.com/Denton-L/git.git testing/graph-output\n\t$ git diff FETCH_HEAD^2 t/t4214-log-graph-octopus.sh\n\nand the resulting diff is very pleasing imo.\n\nJunio, when you pick this topic up, that branch should contain the\ncorrect conflict resolution if that can help you out in any way.\n\nAnyway, thanks for the work, James!\n\nI'll give this patch my:\n\n\tTested-by: Denton Liu <liu.denton@gmail.com>\n\n> -- \n> gitgitgadget\n"},{"id":"383824","messageId":"20191010182847.GA1463@generichostname","threadId":"52011","inReplyTo":"20191010181624.GA97861@generichostname","subject":"Re: [PATCH 11/11] graph: fix coloring of octopus dashes","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-10-10T18:28:47Z","receivedAt":"2019-10-10T18:28:51Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Thu, Oct 10, 2019 at 11:16:24AM -0700, Denton Liu wrote:\n> You can see the results of this by doing:\n> \n> \t$ git fetch https://github.com/Denton-L/git.git testing/graph-output\n> \t$ git diff FETCH_HEAD^2 t/t4214-log-graph-octopus.sh\n> \n> and the resulting diff is very pleasing imo.\n\nI guess it'd probably be nice if the result of that diff were viewable\nwithout the extra work of fetching everything, so here it is:\n\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 3ae8e51e50..40d27db674 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -26,15 +26,14 @@ test_expect_success 'set up merge history' '\n test_expect_success 'log --graph with tricky octopus merge, no color' '\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -47,15 +46,14 @@ test_expect_success 'log --graph with tricky octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n-\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <MAGENTA>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>\n+\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET>\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<RED>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<RED>|<RESET> * <MAGENTA>/<RESET> 2\n \t<RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -74,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -92,9 +90,9 @@ test_expect_success 'log --graph with normal octopus merge with colors' '\n \t<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4\n \t<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3\n \t<RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t<RED>|<RESET> * <BLUE>|<RESET> 2\n+\t<RED>|<RESET> * <BLUE>/<RESET> 2\n \t<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t* <BLUE>|<RESET> 1\n+\t* <BLUE>/<RESET> 1\n \t<BLUE>|<RESET><BLUE>/<RESET>\n \t* initial\n \tEOF\n@@ -112,9 +110,9 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -123,7 +121,7 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with normal octopus and child merge with colors' '\n+test_expect_success 'log --graph with normal octopus and child merge with colors' '\n \tcat >expect.colors <<-\\EOF &&\n \t* after-merge\n \t*<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n@@ -131,9 +129,9 @@ test_expect_failure 'log --graph with normal octopus and child merge with colors\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<GREEN>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<GREEN>|<RESET> * <MAGENTA>/<RESET> 2\n \t<GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -147,15 +145,14 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n \t| * after-merge\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -164,20 +161,19 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with tricky octopus merge and its child with colors' '\n+test_expect_success 'log --graph with tricky octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n \t<RED>|<RESET> * after-merge\n-\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET> <CYAN>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> <CYAN>/<RESET>\n+\t<RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\\<RESET> <CYAN>\\<RESET>\n \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t<RED>|<RESET> * <CYAN>|<RESET> 2\n+\t<RED>|<RESET> * <CYAN>/<RESET> 2\n \t<RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t* <CYAN>|<RESET> 1\n+\t* <CYAN>/<RESET> 1\n \t<CYAN>|<RESET><CYAN>/<RESET>\n \t* initial\n \tEOF\n@@ -209,7 +205,7 @@ test_expect_success 'log --graph with crossover in octopus merge, no color' '\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -257,7 +253,7 @@ test_expect_success 'log --graph with crossover in octopus merge and its child,\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge and its child with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -353,7 +349,7 @@ test_expect_success 'log --graph with unrelated commit and octopus child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with unrelated commit and octopus child with colors' '\n+test_expect_success 'log --graph with unrelated commit and octopus child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-initial\n"},{"id":"383838","messageId":"nycvar.QRO.7.76.6.1910102303330.46@tvgsbejvaqbjf.bet","threadId":"52011","inReplyTo":"4bc0a0596164212aa9d29d6dd0d7a0d8ab1b9dd0.1570724021.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-10-10T21:07:35Z","receivedAt":"2019-10-10T21:08:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi James,\n\nOn Thu, 10 Oct 2019, James Coglan via GitGitGadget wrote:\n\n> From: James Coglan <jcoglan@gmail.com>\n>\n> All the output functions in `graph.c` currently keep track of how many\n> printable chars they've written to the buffer, before calling\n> `graph_pad_horizontally()` to pad the line with spaces. Some functions\n> do this by incrementing a counter whenever they write to the buffer, and\n> others do it by encoding an assumption about how many chars are written,\n> as in:\n>\n>     graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n>\n> This adds a fair amount of noise to the functions' logic and is easily\n> broken if one forgets to increment the right counter or update the\n> calculations used for padding.\n>\n> To make this easier to use, I'm adding a `width` field to `strbuf` that\n> tracks the number of printing characters added after the line prefix.\n\nThis is a big heavy-handed: adding a `width` field to `struct strbuf`\nand maintaining it _just_ for the purpose of `graph.c` puts an\nunnecssary load on every other `strbuf` user (of which there are a\n_lot_).\n\nSo my obvious question is: what makes `width` different from `len`?\nSince we exclusively use ASCII characters for the graph part, we should\nbe able to use the already-existing `len`, for free, no?\n\nI could imagine that the `strbuf` might receive more than one line, but\nthen we still would only need to remember the offset of the last newline\ncharacter in that `strbuf`, no?\n\nCiao,\nJohannes\n\n> It's set to 0 at the start of `graph_next_line()`, and then various\n> `strbuf` functions update it as follows:\n>\n> - `strbuf_write_column()` increments `width` by 1\n>\n> - `strbuf_setlen()` changes `width` by the amount added to `len` if\n>   `len` is increased, or makes `width` and `len` the same if it's\n>   decreased\n>\n> - `strbuf_addch()` increments `width` by 1\n>\n> This is enough to ensure that the functions used by `graph.c` update\n> `strbuf->width` correctly, and `graph_pad_horizontally()` can then use\n> this field instead of taking `chars_written` as a parameter.\n>\n> Signed-off-by: James Coglan <jcoglan@gmail.com>\n> ---\n>  graph.c  | 68 ++++++++++++++++++++++----------------------------------\n>  strbuf.h |  8 ++++++-\n>  2 files changed, 33 insertions(+), 43 deletions(-)\n>\n> diff --git a/graph.c b/graph.c\n> index f53135485f..c56fdec1fc 100644\n> --- a/graph.c\n> +++ b/graph.c\n> @@ -115,11 +115,20 @@ static const char *column_get_color_code(unsigned short color)\n>  static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n>  \t\t\t\tchar col_char)\n>  {\n> +\t/*\n> +\t * Remember the buffer's width as we're about to add non-printing\n> +\t * content to it, and we want to avoid counting the byte length\n> +\t * of this content towards the buffer's visible width\n> +\t */\n> +\tsize_t prev_width = sb->width;\n> +\n>  \tif (c->color < column_colors_max)\n>  \t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n>  \tstrbuf_addch(sb, col_char);\n>  \tif (c->color < column_colors_max)\n>  \t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n> +\n> +\tsb->width = prev_width + 1;\n>  }\n>\n>  struct git_graph {\n> @@ -686,8 +695,7 @@ 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> -\t\t\t\t   int chars_written)\n> +static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n>  {\n>  \t/*\n>  \t * Add additional spaces to the end of the strbuf, so that all\n> @@ -696,8 +704,8 @@ 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> -\tif (chars_written < graph->width)\n> -\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n> +\tif (sb->width < graph->width)\n> +\t\tstrbuf_addchars(sb, ' ', graph->width - sb->width);\n>  }\n>\n>  static void graph_output_padding_line(struct git_graph *graph,\n> @@ -723,7 +731,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n>  \t\tstrbuf_addch(sb, ' ');\n>  \t}\n>\n> -\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n> +\tgraph_pad_horizontally(graph, sb);\n>  }\n>\n>\n> @@ -740,7 +748,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, 3);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \tif (graph->num_parents >= 3 &&\n>  \t    graph->commit_index < (graph->num_columns - 1))\n> @@ -754,7 +762,6 @@ 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> @@ -777,14 +784,12 @@ 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_write_column(sb, col, '|');\n>  \t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n> -\t\t\tchars_written += 1 + 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> @@ -800,19 +805,15 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>  \t\t\telse\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_write_column(sb, col, '\\\\');\n> -\t\t\tchars_written++;\n>  \t\t} else {\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, chars_written);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \t/*\n>  \t * Increment graph->expansion_row,\n> @@ -842,11 +843,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n>  }\n>\n>  /*\n> - * Draw the horizontal dashes of an octopus merge and return the number of\n> - * characters written.\n> + * Draw the horizontal dashes of an octopus merge.\n>   */\n> -static int graph_draw_octopus_merge(struct git_graph *graph,\n> -\t\t\t\t    struct strbuf *sb)\n> +static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n>  {\n>  \t/*\n>  \t * Here dashless_parents represents the number of parents which don't\n> @@ -890,13 +889,12 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n>  \t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n>  \t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n>  \t}\n> -\treturn 2 * dashful_parents;\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, chars_written;\n> +\tint i;\n>\n>  \t/*\n>  \t * Output the row containing this commit\n> @@ -906,7 +904,6 @@ 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> @@ -921,14 +918,11 @@ 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 > 2)\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\t\tgraph_draw_octopus_merge(graph, sb);\n>  \t\t} else if (seen_this && (graph->num_parents > 2)) {\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> @@ -948,16 +942,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>  \t\t\telse\n>  \t\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tchars_written++;\n>  \t\t} else {\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, chars_written);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \t/*\n>  \t * Update graph->state\n> @@ -984,12 +975,11 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\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, chars_written;\n> +\tint i, j;\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> @@ -1017,7 +1007,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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> @@ -1026,19 +1015,16 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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_write_column(sb, col, '\\\\');\n>  \t\t\tstrbuf_addch(sb, ' ');\n> -\t\t\tchars_written += 2;\n>  \t\t} else {\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, chars_written);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \t/*\n>  \t * Update graph->state\n> @@ -1181,7 +1167,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>  \t\t}\n>  \t}\n>\n> -\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \t/*\n>  \t * Swap mapping and new_mapping\n> @@ -1199,6 +1185,8 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>\n>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>  {\n> +\tsb->width = 0;\n> +\n>  \tswitch (graph->state) {\n>  \tcase GRAPH_PADDING:\n>  \t\tgraph_output_padding_line(graph, sb);\n> @@ -1227,7 +1215,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>  static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>  {\n>  \tint i;\n> -\tint chars_written = 0;\n>\n>  \tif (graph->state != GRAPH_COMMIT) {\n>  \t\tgraph_next_line(graph, sb);\n> @@ -1245,19 +1232,16 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>  \t\tstruct column *col = &graph->columns[i];\n>\n>  \t\tstrbuf_write_column(sb, col, '|');\n> -\t\tchars_written++;\n>\n>  \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n>  \t\t\tint len = (graph->num_parents - 2) * 2;\n>  \t\t\tstrbuf_addchars(sb, ' ', len);\n> -\t\t\tchars_written += len;\n>  \t\t} else {\n>  \t\t\tstrbuf_addch(sb, ' ');\n> -\t\t\tchars_written++;\n>  \t\t}\n>  \t}\n>\n> -\tgraph_pad_horizontally(graph, sb, chars_written);\n> +\tgraph_pad_horizontally(graph, sb);\n>\n>  \t/*\n>  \t * Update graph->prev_state since we have output a padding line\n> diff --git a/strbuf.h b/strbuf.h\n> index f62278a0be..3a98147321 100644\n> --- a/strbuf.h\n> +++ b/strbuf.h\n> @@ -66,11 +66,12 @@ struct string_list;\n>  struct strbuf {\n>  \tsize_t alloc;\n>  \tsize_t len;\n> +\tsize_t width;\n>  \tchar *buf;\n>  };\n>\n>  extern char strbuf_slopbuf[];\n> -#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }\n> +#define STRBUF_INIT  { .alloc = 0, .len = 0, .width = 0, .buf = strbuf_slopbuf }\n>\n>  /*\n>   * Predeclare this here, since cache.h includes this file before it defines the\n> @@ -161,6 +162,10 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len)\n>  {\n>  \tif (len > (sb->alloc ? sb->alloc - 1 : 0))\n>  \t\tdie(\"BUG: strbuf_setlen() beyond buffer\");\n> +\tif (len > sb->len)\n> +\t\tsb->width += len - sb->len;\n> +\telse\n> +\t\tsb->width = len;\n>  \tsb->len = len;\n>  \tif (sb->buf != strbuf_slopbuf)\n>  \t\tsb->buf[len] = '\\0';\n> @@ -231,6 +236,7 @@ static inline void strbuf_addch(struct strbuf *sb, int c)\n>  \t\tstrbuf_grow(sb, 1);\n>  \tsb->buf[sb->len++] = c;\n>  \tsb->buf[sb->len] = '\\0';\n> +\tsb->width++;\n>  }\n>\n>  /**\n> --\n> gitgitgadget\n>\n>\n"},{"id":"383845","messageId":"20191010230550.GA42541@generichostname","threadId":"52011","inReplyTo":"nycvar.QRO.7.76.6.1910102303330.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-10-10T23:05:50Z","receivedAt":"2019-10-10T23:05:56Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Dscho,\n\nOn Thu, Oct 10, 2019 at 11:07:35PM +0200, Johannes Schindelin wrote:\n> Hi James,\n> \n> On Thu, 10 Oct 2019, James Coglan via GitGitGadget wrote:\n> \n> > From: James Coglan <jcoglan@gmail.com>\n> >\n> > All the output functions in `graph.c` currently keep track of how many\n> > printable chars they've written to the buffer, before calling\n> > `graph_pad_horizontally()` to pad the line with spaces. Some functions\n> > do this by incrementing a counter whenever they write to the buffer, and\n> > others do it by encoding an assumption about how many chars are written,\n> > as in:\n> >\n> >     graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n> >\n> > This adds a fair amount of noise to the functions' logic and is easily\n> > broken if one forgets to increment the right counter or update the\n> > calculations used for padding.\n> >\n> > To make this easier to use, I'm adding a `width` field to `strbuf` that\n> > tracks the number of printing characters added after the line prefix.\n> \n> This is a big heavy-handed: adding a `width` field to `struct strbuf`\n> and maintaining it _just_ for the purpose of `graph.c` puts an\n> unnecssary load on every other `strbuf` user (of which there are a\n> _lot_).\n> \n> So my obvious question is: what makes `width` different from `len`?\n> Since we exclusively use ASCII characters for the graph part, we should\n> be able to use the already-existing `len`, for free, no?\n\nFrom what I can gleam from looking at the code, `width` is different\nfrom `len` because when we're printing with colours, there'll be a bunch\nof termcodes that don't actually count for the width.\n\nI think that we should either leave the `chars_written` variable as is\nor maybe calculate it after the fact. Here's an untested and uncompiled\nimplementation of something that might do that:\n\n\tstatic int calculate_width(const struct strbuf *row)\n\t{\n\t\tint in_termcode = 0;\n\t\tint width = 0;\n\t\tint i;\n\n\t\tfor (i = 0; i < row.len; i++) {\n\t\t\tif (row.buf[i] == '\\033')\n\t\t\t\tin_termcode = 1;\n\n\t\t\tif (!in_termcode)\n\t\t\t\twidth++;\n\t\t\telse if (row.buf[i] == 'm')\n\t\t\t\tin_termcode = 0;\n\t\t}\n\t}\n\nIf we include this, I'm not sure what kind of performance hit we might\ntake if the graph we're generating is particularly big, though.\n\n> \n> I could imagine that the `strbuf` might receive more than one line, but\n> then we still would only need to remember the offset of the last newline\n> character in that `strbuf`, no?\n> \n> Ciao,\n> Johannes\n"},{"id":"383855","messageId":"f30ba88a-e9c1-b53e-e2e9-2d87b7531cee@gmail.com","threadId":"52011","inReplyTo":"20191010230550.GA42541@generichostname","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-11T00:49:17Z","receivedAt":"2019-10-11T00:49:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"\n\nOn 10/10/2019 7:05 PM, Denton Liu wrote:\n> Hi Dscho,\n> \n> On Thu, Oct 10, 2019 at 11:07:35PM +0200, Johannes Schindelin wrote:\n>> Hi James,\n>>\n>> On Thu, 10 Oct 2019, James Coglan via GitGitGadget wrote:\n>>\n>>> From: James Coglan <jcoglan@gmail.com>\n>>>\n>>> All the output functions in `graph.c` currently keep track of how many\n>>> printable chars they've written to the buffer, before calling\n>>> `graph_pad_horizontally()` to pad the line with spaces. Some functions\n>>> do this by incrementing a counter whenever they write to the buffer, and\n>>> others do it by encoding an assumption about how many chars are written,\n>>> as in:\n>>>\n>>>     graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n>>>\n>>> This adds a fair amount of noise to the functions' logic and is easily\n>>> broken if one forgets to increment the right counter or update the\n>>> calculations used for padding.\n>>>\n>>> To make this easier to use, I'm adding a `width` field to `strbuf` that\n>>> tracks the number of printing characters added after the line prefix.\n>>\n>> This is a big heavy-handed: adding a `width` field to `struct strbuf`\n>> and maintaining it _just_ for the purpose of `graph.c` puts an\n>> unnecssary load on every other `strbuf` user (of which there are a\n>> _lot_).\n\nI was concerned about this, too.\n\n>> So my obvious question is: what makes `width` different from `len`?\n>> Since we exclusively use ASCII characters for the graph part, we should\n>> be able to use the already-existing `len`, for free, no?\n> \n> From what I can gleam from looking at the code, `width` is different\n> from `len` because when we're printing with colours, there'll be a bunch\n> of termcodes that don't actually count for the width.\n> \n> I think that we should either leave the `chars_written` variable as is\n> or maybe calculate it after the fact. Here's an untested and uncompiled\n> implementation of something that might do that:\n> \n> \tstatic int calculate_width(const struct strbuf *row)\n> \t{\n> \t\tint in_termcode = 0;\n> \t\tint width = 0;\n> \t\tint i;\n> \n> \t\tfor (i = 0; i < row.len; i++) {\n> \t\t\tif (row.buf[i] == '\\033')\n> \t\t\t\tin_termcode = 1;\n> \n> \t\t\tif (!in_termcode)\n> \t\t\t\twidth++;\n> \t\t\telse if (row.buf[i] == 'm')\n> \t\t\t\tin_termcode = 0;\n> \t\t}\n> \t}\n> \n> If we include this, I'm not sure what kind of performance hit we might\n> take if the graph we're generating is particularly big, though.\n\nThis is worth trying. In terms of CPU time, this should be a mere\nblip compared to all of the commit walking that is going on. (Unless\nwe get to thousands of columns, but by then the output is useless.)\n\nOf course, we should measure the impact, but it is worth a try.\n\n-Stolee\n"},{"id":"383856","messageId":"xmqqbluojbzr.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"nycvar.QRO.7.76.6.1910102303330.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-11T01:40:24Z","receivedAt":"2019-10-11T01:40:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> This is a big heavy-handed: adding a `width` field to `struct strbuf`\n> and maintaining it _just_ for the purpose of `graph.c` puts an\n> unnecssary load on every other `strbuf` user (of which there are a\n> _lot_).\n>\n> So my obvious question is: what makes `width` different from `len`?\n> Since we exclusively use ASCII characters for the graph part, we should\n> be able to use the already-existing `len`, for free, no?\n\nA red-colored piece on the line consumes <RED><RESET> bytes more\nthan the payload.  Which is counted as part of \"len\".  These bytes\ndo not consume any display width.\n\nWhen the payload is a basic CJK char in UTF-8 it may typically use 3\nbyte, while consuming only two display columns.\n\nSo I can understand that this application may want to keep track of\n<byte sequence, byte sequence length, display width> tuple.\n\nI also understand that a programmer inexperienced/unfamiliar with\nour codebase may find it an easy way to satisfiy the need to add an\nextra field to strbuf.  But as you pointed out, that is a hack\nunacceptable in the larger picture.  Use of strbuf as \"auto resizing\nbyte array, represented as a <byte sequence, byte sequence length>\ntuple\" is everywhere and we do not want to bloat it.\n\nThanks for spotting and raising this unfortunate show-stopper issue.\nThe problem being solved is worth solving, but it needs to be done\nwithout butchering a basic data structure used elsewhere.\n\n\n\n\n\n\n\n"},{"id":"383857","messageId":"xmqq7e5cjbwj.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"20191010230550.GA42541@generichostname","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-11T01:42:20Z","receivedAt":"2019-10-11T01:42:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> \tstatic int calculate_width(const struct strbuf *row)\n> \t{\n> \t\tint in_termcode = 0;\n> \t\tint width = 0;\n> \t\tint i;\n>\n> \t\tfor (i = 0; i < row.len; i++) {\n> \t\t\tif (row.buf[i] == '\\033')\n> \t\t\t\tin_termcode = 1;\n>\n> \t\t\tif (!in_termcode)\n> \t\t\t\twidth++;\n> \t\t\telse if (row.buf[i] == 'm')\n> \t\t\t\tin_termcode = 0;\n> \t\t}\n> \t}\n\nNot every byte that is outside the escape sequence contributes to\none display columns.  You would want to take a look at utf8_width()\nfor inspiration.\n\n"},{"id":"383867","messageId":"20191011050111.GA94866@generichostname","threadId":"52011","inReplyTo":"xmqq7e5cjbwj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-10-11T05:01:11Z","receivedAt":"2019-10-11T05:01:18Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Fri, Oct 11, 2019 at 10:42:20AM +0900, Junio C Hamano wrote:\n> Denton Liu <liu.denton@gmail.com> writes:\n> \n> > \tstatic int calculate_width(const struct strbuf *row)\n> > \t{\n> > \t\tint in_termcode = 0;\n> > \t\tint width = 0;\n> > \t\tint i;\n> >\n> > \t\tfor (i = 0; i < row.len; i++) {\n> > \t\t\tif (row.buf[i] == '\\033')\n> > \t\t\t\tin_termcode = 1;\n> >\n> > \t\t\tif (!in_termcode)\n> > \t\t\t\twidth++;\n> > \t\t\telse if (row.buf[i] == 'm')\n> > \t\t\t\tin_termcode = 0;\n> > \t\t}\n> > \t}\n> \n> Not every byte that is outside the escape sequence contributes to\n> one display columns.  You would want to take a look at utf8_width()\n> for inspiration.\n> \n\nHeh, I guess you're right. Looking right below the definition of\nutf8_width, I realised we have the utf8_strnwidth function. We should be\nable to just call\n\n\tutf8_strnwidth(row.buf, row.len, 1);\n"},{"id":"383899","messageId":"nycvar.QRO.7.76.6.1910111800220.46@tvgsbejvaqbjf.bet","threadId":"52011","inReplyTo":"20191011050111.GA94866@generichostname","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-10-11T16:02:48Z","receivedAt":"2019-10-11T16:03:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 10 Oct 2019, Denton Liu wrote:\n\n> On Fri, Oct 11, 2019 at 10:42:20AM +0900, Junio C Hamano wrote:\n> > Denton Liu <liu.denton@gmail.com> writes:\n> >\n> > > \tstatic int calculate_width(const struct strbuf *row)\n> > > \t{\n> > > \t\tint in_termcode = 0;\n> > > \t\tint width = 0;\n> > > \t\tint i;\n> > >\n> > > \t\tfor (i = 0; i < row.len; i++) {\n> > > \t\t\tif (row.buf[i] == '\\033')\n> > > \t\t\t\tin_termcode = 1;\n> > >\n> > > \t\t\tif (!in_termcode)\n> > > \t\t\t\twidth++;\n> > > \t\t\telse if (row.buf[i] == 'm')\n> > > \t\t\t\tin_termcode = 0;\n> > > \t\t}\n> > > \t}\n> >\n> > Not every byte that is outside the escape sequence contributes to\n> > one display columns.  You would want to take a look at utf8_width()\n> > for inspiration.\n> >\n>\n> Heh, I guess you're right. Looking right below the definition of\n> utf8_width, I realised we have the utf8_strnwidth function. We should be\n> able to just call\n>\n> \tutf8_strnwidth(row.buf, row.len, 1);\n\nCorrect me if I'm wrong, but don't we want to keep track of the columns\n*only* in the part with the actual line graph, i.e. we're not at all\ninterested in the onelines' widths?\n\nIf so, I could imagine that a good idea would be to introduce\n\n\tstruct graphbuf {\n\t\tstruct strbuf buf;\n\t\tint width;\n\t};\n\nand then introduce wrappers for `_addch()` and whatever else is used in\n`graph.c`, these wrappers will increment the width together with the\n`buf.len` field, and one additional helper that adds color sequences to\nthat graphbuf that leaves `width` unchanged.\n\nCiao,\nDscho\n"},{"id":"383905","messageId":"494a92ba-00ab-b5d8-ee66-4007647f8483@gmail.com","threadId":"52011","inReplyTo":"5c688030-6351-93a3-89bd-e666d02d12d9@gmail.com","subject":"Re: [PATCH 06/11] graph: tidy up display of left-skewed merges","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-11T16:50:04Z","receivedAt":"2019-10-11T16:50:09Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 10/10/2019 18:19, Derrick Stolee wrote:\n> On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n>> +++ b/t/t4215-log-skewed-merges.sh\n>> @@ -0,0 +1,42 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='git log --graph of skewed merges'\n>> +\n>> +. ./test-lib.sh\n>> +\n>> +test_expect_success 'setup left-skewed merge' '\n> \n> \n> Could you skew this example to include a left-skewed octopus merge\n> (and use fewer Git processes) with the following:\n> \n> \tgit checkout --orphan _a && test_commit A &&\n> \tgit switch -c _b _a && test_commit B &&\n> \tgit switch -c _c _a && test_commit C &&\n> \tgit switch -c _d _a && test_commit D &&\tgit switch -c _e _b && git merge --no-ff _c _d E &&\n> \tgit switch -c _f _a && git merge --no-ff _d -m F &&\tgit checkout _a && git merge --no-ff _b _c _e _f -m G\n> and I think the resulting output will be:\n> \n> *-----.   G\n> |\\ \\ \\ \\\n> | | | | * F\n> | |_|_|/|\n> |/| | | |\n> | | | * | E\n> | |_|/|\\|\n> |/| | | |\n> | | |/  * D\n> | |_|__/\n> |/| |\n> | | * C\n> | |/\n> |/|\n> | * B\n> |/\n> * A\n\nAt this point in the history, commit E won't render like that -- this is before the change that flattens edges that fuse with the merge's last parent. I think the display of this history at this point will be:\n\n\t*-----.   G\n\t|\\ \\ \\ \\\n\t| | | | * F\n\t| |_|_|/|\n\t|/| | | |\n\t| | | * |   E\n\t| |_|/|\\ \\\n\t|/| |/ / /\n\t| | | | /\n\t| | | |/\n\t| | | * D\n\t| |_|/\n\t|/| |\n\t| | * C\n\t| |/\n\t|/|\n\t| * B\n\t|/\n\t* A\n\nIs there a particular reason for wanting to include this test case? What particular combination of states is it designed to test? (My guess is that it includes an octopus merge where the original test does not.) I'd be happy to add it at the appropriate point in the history if it's adding coverage not provided by the other tests.\n"},{"id":"383906","messageId":"204c7479-c78d-54ff-5ece-397b4c31804c@gmail.com","threadId":"52011","inReplyTo":"9fe7f2d9-2108-5cf6-dcd7-06d91e74e98b@gmail.com","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-11T17:04:21Z","receivedAt":"2019-10-11T17:04:28Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 10/10/2019 18:49, Derrick Stolee wrote:\n> On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n>> From: James Coglan <jcoglan@gmail.com>\n>>\n>> Following the introduction of \"left-skewed\" merges, which are merges\n>> whose first parent fuses with another edge to its left, we have some\n>> more edge cases to deal with in the display of commit and post-merge\n>> lines.\n>>\n>> The current graph code handles the following cases for edges appearing\n>> to the right of the commit (*) on commit lines. A 2-way merge is usually\n>> followed by vertical lines:\n>>\n>>         | | |\n>>         | * |\n>>         | |\\ \\\n>>\n>> An octopus merge (more than two parents) is always followed by edges\n>> sloping to the right:\n>>\n>>         | |  \\          | |    \\\n>>         | *-. \\         | *---. \\\n>>         | |\\ \\ \\        | |\\ \\ \\ \\\n>>\n>> A 2-way merge is followed by a right-sloping edge if the commit line\n>> immediately follows a post-merge line for a commit that appears in the\n>> same column as the current commit, or any column to the left of that:\n>>\n>>         | *             | * |\n>>         | |\\            | |\\ \\\n>>         | * \\           | | * \\\n>>         | |\\ \\          | | |\\ \\\n>>\n>> This commit introduces the following new cases for commit lines. If a\n>> 2-way merge skews to the left, then the edges to its right are always\n>> vertical lines, even if the commit follows a post-merge line:\n>>\n>>         | | |           | |\\\n>>         | * |           | * |\n>>         |/| |           |/| |\n>>\n>> A commit with 3 parents that skews left is followed by vertical edges:\n>>\n>>         | | |\n>>         | * |\n>>         |/|\\ \\\n>>\n>> If a 3-way left-skewed merge commit appears immediately after a\n>> post-merge line, then it may be followed the right-sloping edges, just\n>> like a 2-way merge that is not skewed.\n>>\n>>         | |\\\n>>         | * \\\n>>         |/|\\ \\\n>>\n>> Octopus merges with 4 or more parents that skew to the left will always\n>> be followed by right-sloping edges, because the existing columns need to\n>> expand around the merge.\n>>\n>>         | |  \\\n>>         | *-. \\\n>>         |/|\\ \\ \\\n>>\n>> On post-merge lines, usually all edges following the current commit\n>> slope to the right:\n>>\n>>         | * | |\n>>         | |\\ \\ \\\n>>\n>> However, if the commit is a left-skewed 2-way merge, the edges to its\n>> right remain vertical. We also need to display a space after the\n>> vertical line descending from the commit marker, whereas this line would\n>> normally be followed by a backslash.\n>>\n>>         | * | |\n>>         |/| | |\n>>\n>> If a left-skewed merge has more than 2 parents, then the edges to its\n>> right are still sloped as they bend around the edges introduced by the\n>> merge.\n>>\n>>         | * | |\n>>         |/|\\ \\ \\\n>>\n>> To handle these new cases, we need to know not just how many parents\n>> each commit has, but how many new columns it adds to the display; this\n>> quantity is recorded in the `edges_added` field for the current commit,\n>> and `prev_edges_added` field for the previous commit.\n>>\n>> Here, \"column\" refers to visual columns, not the logical columns of the\n>> `columns` array. This is because even if all the commit's parents end up\n>> fusing with existing edges, they initially introduce distinct edges in\n>> the commit and post-merge lines before those edges collapse. For\n>> example, a 3-way merge whose 2nd and 3rd parents fuse with existing\n>> edges still introduces 2 visual columns that affect the display of edges\n>> to their right.\n>>\n>>         | | |  \\\n>>         | | *-. \\\n>>         | | |\\ \\ \\\n>>         | |_|/ / /\n>>         |/| | / /\n>>         | | |/ /\n>>         | |/| |\n>>         | | | |\n>>\n>> This merge does not introduce any *logical* columns; there are 4 edges\n>> before and after this commit once all edges have collapsed. But it does\n>> initially introduce 2 new edges that need to be accommodated by the\n>> edges to their right.\n>>\n>> Signed-off-by: James Coglan <jcoglan@gmail.com>\n>> ---\n>>  graph.c                      |  63 +++++++++++++--\n>>  t/t4215-log-skewed-merges.sh | 151 +++++++++++++++++++++++++++++++++++\n>>  2 files changed, 209 insertions(+), 5 deletions(-)\n>>\n>> diff --git a/graph.c b/graph.c\n>> index 9136173e03..fb2e42850f 100644\n>> --- a/graph.c\n>> +++ b/graph.c\n>> @@ -197,6 +197,46 @@ struct git_graph {\n>>  \t * \t\t|/| | | | |\t\t| | | | | *\n>>  \t */\n>>  \tint merge_layout;\n>> +\t/*\n>> +\t * The number of columns added to the graph by the current commit. For\n>> +\t * 2-way and octopus merges, this is is usually one less than the\n>> +\t * number of parents:\n>> +\t *\n>> +\t * \t\t| | |\t\t\t| |    \\\n>> +\t *\t\t| * |\t\t\t| *---. \\\n>> +\t *\t\t| |\\ \\\t\t\t| |\\ \\ \\ \\\n>> +\t *\t\t| | | |         \t| | | | | |\n>> +\t *\n>> +\t *\t\tnum_parents: 2\t\tnum_parents: 4\n>> +\t *\t\tedges_added: 1\t\tedges_added: 3\n>> +\t *\n>> +\t * For left-skewed merges, the first parent fuses with its neighbor and\n>> +\t * so one less column is added:\n>> +\t *\n>> +\t *\t\t| | |\t\t\t| |  \\\n>> +\t *\t\t| * |\t\t\t| *-. \\\n>> +\t *\t\t|/| |\t\t\t|/|\\ \\ \\\n>> +\t *\t\t| | |\t\t\t| | | | |\n>> +\t *\n>> +\t *\t\tnum_parents: 2\t\tnum_parents: 4\n>> +\t *\t\tedges_added: 0\t\tedges_added: 2\n>> +\t *\n>> +\t * This number determines how edges to the right of the merge are\n>> +\t * displayed in commit and post-merge lines; if no columns have been\n>> +\t * added then a vertical line should be used where a right-tracking\n>> +\t * line would otherwise be used.\n>> +\t *\n>> +\t *\t\t| * \\\t\t\t| * |\n>> +\t *\t\t| |\\ \\\t\t\t|/| |\n>> +\t *\t\t| | * \\\t\t\t| * |\n>> +\t */\n>> +\tint edges_added;\n>> +\t/*\n>> +\t * The number of columns added by the previous commit, which is used to\n>> +\t * smooth edges appearing to the right of a commit in a commit line\n>> +\t * following a post-merge line.\n>> +\t */\n>> +\tint prev_edges_added;\n>>  \t/*\n>>  \t * The maximum number of columns that can be stored in the columns\n>>  \t * and new_columns arrays.  This is also half the number of entries\n>> @@ -309,6 +349,8 @@ struct git_graph *graph_init(struct rev_info *opt)\n>>  \tgraph->commit_index = 0;\n>>  \tgraph->prev_commit_index = 0;\n>>  \tgraph->merge_layout = 0;\n>> +\tgraph->edges_added = 0;\n>> +\tgraph->prev_edges_added = 0;\n>>  \tgraph->num_columns = 0;\n>>  \tgraph->num_new_columns = 0;\n>>  \tgraph->mapping_size = 0;\n>> @@ -670,6 +712,9 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n>>  \t */\n>>  \tgraph_update_columns(graph);\n>>  \n>> +\tgraph->prev_edges_added = graph->edges_added;\n>> +\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n>> +\n>>  \tgraph->expansion_row = 0;\n>>  \n>>  \t/*\n>> @@ -943,12 +988,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>>  \n>>  \t\t\tif (graph->num_parents > 2)\n>>  \t\t\t\tgraph_draw_octopus_merge(graph, sb);\n>> -\t\t} else if (seen_this && (graph->num_parents > 2)) {\n>> +\t\t} else if (seen_this && (graph->edges_added > 1)) {\n>>  \t\t\tstrbuf_write_column(sb, col, '\\\\');\n>> -\t\t} else if (seen_this && (graph->num_parents == 2)) {\n>> +\t\t} else if (seen_this && (graph->edges_added == 1)) {\n>>  \t\t\t/*\n>> -\t\t\t * This is a 2-way merge commit.\n>> -\t\t\t * There is no GRAPH_PRE_COMMIT stage for 2-way\n>> +\t\t\t * This is either a right-skewed 2-way merge\n>> +\t\t\t * commit, or a left-skewed 3-way merge.\n>> +\t\t\t * There is no GRAPH_PRE_COMMIT stage for such\n>>  \t\t\t * merges, so this is the first line of output\n>>  \t\t\t * for this commit.  Check to see what the previous\n>>  \t\t\t * line of output was.\n>> @@ -960,6 +1006,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>>  \t\t\t * makes the output look nicer.\n>>  \t\t\t */\n>>  \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n>> +\t\t\t    graph->prev_edges_added > 0 &&\n>>  \t\t\t    graph->prev_commit_index < i)\n>>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>>  \t\t\telse\n>> @@ -1031,8 +1078,14 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n>>  \t\t\t\telse\n>>  \t\t\t\t\tidx++;\n>>  \t\t\t}\n>> +\t\t\tif (graph->edges_added == 0)\n>> +\t\t\t\tstrbuf_addch(sb, ' ');\n>> +\n>>  \t\t} else if (seen_this) {\n>> -\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>> +\t\t\tif (graph->edges_added > 0)\n>> +\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>> +\t\t\telse\n>> +\t\t\t\tstrbuf_write_column(sb, col, '|');\n>>  \t\t\tstrbuf_addch(sb, ' ');\n>>  \t\t} else {\n>>  \t\t\tstrbuf_write_column(sb, col, '|');\n>> diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n>> index cfaba40f97..e479d6e676 100755\n>> --- a/t/t4215-log-skewed-merges.sh\n>> +++ b/t/t4215-log-skewed-merges.sh\n>> @@ -39,4 +39,155 @@ test_expect_success 'log --graph with left-skewed merge' '\n>>  \ttest_cmp expect actual\n>>  '\n>>  \n>> +test_expect_success 'setup nested left-skewed merge' '\n>> +\tgit checkout --orphan 1_p &&\n>> +\ttest_commit 1_A &&\n>> +\ttest_commit 1_B &&\n>> +\ttest_commit 1_C &&\n>> +\tgit checkout -b 1_q @^ && test_commit 1_D &&\n>> +\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n>> +\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n>> +\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n>> +\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H\n>> +'\n>> +\n>> +cat > expect <<\\EOF\n>> +*   1_H\n>> +|\\\n>> +| *   1_G\n>> +| |\\\n>> +| | * 1_F\n>> +| * | 1_E\n>> +|/| |\n>> +| * | 1_D\n>> +* | | 1_C\n>> +|/ /\n>> +* | 1_B\n>> +|/\n>> +* 1_A\n>> +EOF\n>> +\n>> +test_expect_success 'log --graph with nested left-skewed merge' '\n>> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n>> +\ttest_cmp expect actual\n>> +'\n> \n> I should have noticed in your earlier commits, but why don't you keep\n> the output inside the test suite? You can start with \"cat >expect <<-EOF\"\n> to have it ignore leading whitespace. Sorry if there's something else about\n> this that is causing issues.\n\nI was following a pattern used in t/t4202-log.sh. I believe it was easier to debug these tests with the setup and expectations split into separate blocks, but I wouldn't be opposed to merging them.\n\n>> +\n>> +test_expect_success 'setup nested left-skewed merge following normal merge' '\n>> +\tgit checkout --orphan 2_p &&\n>> +\ttest_commit 2_A &&\n>> +\ttest_commit 2_B &&\n>> +\ttest_commit 2_C &&\n>> +\tgit checkout -b 2_q @^^ &&\n>> +\ttest_commit 2_D &&\n>> +\ttest_commit 2_E &&\n>> +\tgit checkout -b 2_r @^ && test_commit 2_F &&\n>> +\tgit checkout 2_q &&\n>> +\tgit merge --no-ff 2_r -m 2_G &&\n>> +\tgit merge --no-ff 2_p^ -m 2_H &&\n>> +\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n>> +\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K\n>> +'\n>> +\n>> +cat > expect <<\\EOF\n>> +*   2_K\n>> +|\\\n>> +| *   2_J\n>> +| |\\\n>> +| | *   2_H\n>> +| | |\\\n>> +| | * | 2_G\n>> +| |/| |\n>> +| | * | 2_F\n>> +| * | | 2_E\n>> +| |/ /\n>> +| * | 2_D\n>> +* | | 2_C\n>> +| |/\n>> +|/|\n>> +* | 2_B\n>> +|/\n>> +* 2_A\n>> +EOF\n>> +\n>> +test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n>> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>> +test_expect_success 'setup nested right-skewed merge following left-skewed merge' '\n>> +\tgit checkout --orphan 3_p &&\n>> +\ttest_commit 3_A &&\n>> +\tgit checkout -b 3_q &&\n>> +\ttest_commit 3_B &&\n>> +\ttest_commit 3_C &&\n>> +\tgit checkout -b 3_r @^ &&\n>> +\ttest_commit 3_D &&\n>> +\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n>> +\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n>> +\tgit checkout 3_r && test_commit 3_G &&\n>> +\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n>> +\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J\n>> +'\n>> +\n>> +cat > expect <<\\EOF\n>> +*   3_J\n>> +|\\\n>> +| *   3_H\n>> +| |\\\n>> +| | * 3_G\n>> +| * | 3_F\n>> +|/| |\n>> +| * |   3_E\n>> +| |\\ \\\n>> +| | |/\n>> +| | * 3_D\n>> +| * | 3_C\n>> +| |/\n>> +| * 3_B\n>> +|/\n>> +* 3_A\n>> +EOF\n>> +\n>> +test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n>> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>> +test_expect_success 'setup right-skewed merge following a left-skewed one' '\n>> +\tgit checkout --orphan 4_p &&\n>> +\ttest_commit 4_A &&\n>> +\ttest_commit 4_B &&\n>> +\ttest_commit 4_C &&\n>> +\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n>> +\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n>> +\tgit checkout -b 4_s 4_p^^ &&\n>> +\tgit merge --no-ff 4_r -m 4_F &&\n>> +\tgit merge --no-ff 4_p -m 4_G &&\n>> +\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H\n>> +'\n>> +\n>> +cat > expect <<\\EOF\n>> +*   4_H\n>> +|\\\n>> +| *   4_G\n>> +| |\\\n>> +| * | 4_F\n>> +|/| |\n>> +| * |   4_E\n>> +| |\\ \\\n>> +| | * | 4_D\n>> +| |/ /\n>> +|/| |\n>> +| | * 4_C\n>> +| |/\n>> +| * 4_B\n>> +|/\n>> +* 4_A\n>> +EOF\n>> +\n>> +test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n>> +\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>>  test_done\n>>\n> \n"},{"id":"383907","messageId":"16547350-2997-d250-d48b-43c6ae49b3c3@gmail.com","threadId":"52011","inReplyTo":"nycvar.QRO.7.76.6.1910102303330.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-11T17:08:47Z","receivedAt":"2019-10-11T17:08:54Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"Hi Johannes,\n\nOn 10/10/2019 22:07, Johannes Schindelin wrote:\n> Hi James,\n> \n> On Thu, 10 Oct 2019, James Coglan via GitGitGadget wrote:\n> \n>> From: James Coglan <jcoglan@gmail.com>\n>>\n>> All the output functions in `graph.c` currently keep track of how many\n>> printable chars they've written to the buffer, before calling\n>> `graph_pad_horizontally()` to pad the line with spaces. Some functions\n>> do this by incrementing a counter whenever they write to the buffer, and\n>> others do it by encoding an assumption about how many chars are written,\n>> as in:\n>>\n>>     graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n>>\n>> This adds a fair amount of noise to the functions' logic and is easily\n>> broken if one forgets to increment the right counter or update the\n>> calculations used for padding.\n>>\n>> To make this easier to use, I'm adding a `width` field to `strbuf` that\n>> tracks the number of printing characters added after the line prefix.\n> \n> This is a big heavy-handed: adding a `width` field to `struct strbuf`\n> and maintaining it _just_ for the purpose of `graph.c` puts an\n> unnecssary load on every other `strbuf` user (of which there are a\n> _lot_).\n\nI was anticipating there might be objections to modifying a widely used struct. There are other idea I had for solving this that I can share -- I'll post an alternative to this patch shortly that does not involve changing `strbuf`.\n \n> So my obvious question is: what makes `width` different from `len`?\n> Since we exclusively use ASCII characters for the graph part, we should\n> be able to use the already-existing `len`, for free, no?\n\n`len` counts the number of bytes in the buffer, which may include non-printing bytes used to apply color formatting. For padding graph lines, we need to know the number of display columns the buffer will use, and that's what `width` was added for.\n\n> I could imagine that the `strbuf` might receive more than one line, but\n> then we still would only need to remember the offset of the last newline\n> character in that `strbuf`, no?\n\nAs far as I know, it does not contain multiple lines when used in graph.c, but I agree the implementation I've submitted here is obviously wrong if you wanted to use it to get the width of multi-line text. Putting code in strbuf that only works in graph.c and not more broadly was probably a mistake on my part.\n> \n>> It's set to 0 at the start of `graph_next_line()`, and then various\n>> `strbuf` functions update it as follows:\n>>\n>> - `strbuf_write_column()` increments `width` by 1\n>>\n>> - `strbuf_setlen()` changes `width` by the amount added to `len` if\n>>   `len` is increased, or makes `width` and `len` the same if it's\n>>   decreased\n>>\n>> - `strbuf_addch()` increments `width` by 1\n>>\n>> This is enough to ensure that the functions used by `graph.c` update\n>> `strbuf->width` correctly, and `graph_pad_horizontally()` can then use\n>> this field instead of taking `chars_written` as a parameter.\n>>\n>> Signed-off-by: James Coglan <jcoglan@gmail.com>\n>> ---\n>>  graph.c  | 68 ++++++++++++++++++++++----------------------------------\n>>  strbuf.h |  8 ++++++-\n>>  2 files changed, 33 insertions(+), 43 deletions(-)\n>>\n>> diff --git a/graph.c b/graph.c\n>> index f53135485f..c56fdec1fc 100644\n>> --- a/graph.c\n>> +++ b/graph.c\n>> @@ -115,11 +115,20 @@ static const char *column_get_color_code(unsigned short color)\n>>  static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n>>  \t\t\t\tchar col_char)\n>>  {\n>> +\t/*\n>> +\t * Remember the buffer's width as we're about to add non-printing\n>> +\t * content to it, and we want to avoid counting the byte length\n>> +\t * of this content towards the buffer's visible width\n>> +\t */\n>> +\tsize_t prev_width = sb->width;\n>> +\n>>  \tif (c->color < column_colors_max)\n>>  \t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n>>  \tstrbuf_addch(sb, col_char);\n>>  \tif (c->color < column_colors_max)\n>>  \t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n>> +\n>> +\tsb->width = prev_width + 1;\n>>  }\n>>\n>>  struct git_graph {\n>> @@ -686,8 +695,7 @@ 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>> -\t\t\t\t   int chars_written)\n>> +static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n>>  {\n>>  \t/*\n>>  \t * Add additional spaces to the end of the strbuf, so that all\n>> @@ -696,8 +704,8 @@ 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>> -\tif (chars_written < graph->width)\n>> -\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n>> +\tif (sb->width < graph->width)\n>> +\t\tstrbuf_addchars(sb, ' ', graph->width - sb->width);\n>>  }\n>>\n>>  static void graph_output_padding_line(struct git_graph *graph,\n>> @@ -723,7 +731,7 @@ static void graph_output_padding_line(struct git_graph *graph,\n>>  \t\tstrbuf_addch(sb, ' ');\n>>  \t}\n>>\n>> -\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>  }\n>>\n>>\n>> @@ -740,7 +748,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, 3);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \tif (graph->num_parents >= 3 &&\n>>  \t    graph->commit_index < (graph->num_columns - 1))\n>> @@ -754,7 +762,6 @@ 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>> @@ -777,14 +784,12 @@ 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_write_column(sb, col, '|');\n>>  \t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n>> -\t\t\tchars_written += 1 + 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>> @@ -800,19 +805,15 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n>>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>>  \t\t\telse\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_write_column(sb, col, '\\\\');\n>> -\t\t\tchars_written++;\n>>  \t\t} else {\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, chars_written);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \t/*\n>>  \t * Increment graph->expansion_row,\n>> @@ -842,11 +843,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n>>  }\n>>\n>>  /*\n>> - * Draw the horizontal dashes of an octopus merge and return the number of\n>> - * characters written.\n>> + * Draw the horizontal dashes of an octopus merge.\n>>   */\n>> -static int graph_draw_octopus_merge(struct git_graph *graph,\n>> -\t\t\t\t    struct strbuf *sb)\n>> +static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n>>  {\n>>  \t/*\n>>  \t * Here dashless_parents represents the number of parents which don't\n>> @@ -890,13 +889,12 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n>>  \t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n>>  \t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n>>  \t}\n>> -\treturn 2 * dashful_parents;\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, chars_written;\n>> +\tint i;\n>>\n>>  \t/*\n>>  \t * Output the row containing this commit\n>> @@ -906,7 +904,6 @@ 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>> @@ -921,14 +918,11 @@ 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 > 2)\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\t\tgraph_draw_octopus_merge(graph, sb);\n>>  \t\t} else if (seen_this && (graph->num_parents > 2)) {\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>> @@ -948,16 +942,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>>  \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n>>  \t\t\telse\n>>  \t\t\t\tstrbuf_write_column(sb, col, '|');\n>> -\t\t\tchars_written++;\n>>  \t\t} else {\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, chars_written);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \t/*\n>>  \t * Update graph->state\n>> @@ -984,12 +975,11 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\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, chars_written;\n>> +\tint i, j;\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>> @@ -1017,7 +1007,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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>> @@ -1026,19 +1015,16 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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_write_column(sb, col, '\\\\');\n>>  \t\t\tstrbuf_addch(sb, ' ');\n>> -\t\t\tchars_written += 2;\n>>  \t\t} else {\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, chars_written);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \t/*\n>>  \t * Update graph->state\n>> @@ -1181,7 +1167,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>>  \t\t}\n>>  \t}\n>>\n>> -\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \t/*\n>>  \t * Swap mapping and new_mapping\n>> @@ -1199,6 +1185,8 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>>\n>>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>>  {\n>> +\tsb->width = 0;\n>> +\n>>  \tswitch (graph->state) {\n>>  \tcase GRAPH_PADDING:\n>>  \t\tgraph_output_padding_line(graph, sb);\n>> @@ -1227,7 +1215,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>>  static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>>  {\n>>  \tint i;\n>> -\tint chars_written = 0;\n>>\n>>  \tif (graph->state != GRAPH_COMMIT) {\n>>  \t\tgraph_next_line(graph, sb);\n>> @@ -1245,19 +1232,16 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>>  \t\tstruct column *col = &graph->columns[i];\n>>\n>>  \t\tstrbuf_write_column(sb, col, '|');\n>> -\t\tchars_written++;\n>>\n>>  \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n>>  \t\t\tint len = (graph->num_parents - 2) * 2;\n>>  \t\t\tstrbuf_addchars(sb, ' ', len);\n>> -\t\t\tchars_written += len;\n>>  \t\t} else {\n>>  \t\t\tstrbuf_addch(sb, ' ');\n>> -\t\t\tchars_written++;\n>>  \t\t}\n>>  \t}\n>>\n>> -\tgraph_pad_horizontally(graph, sb, chars_written);\n>> +\tgraph_pad_horizontally(graph, sb);\n>>\n>>  \t/*\n>>  \t * Update graph->prev_state since we have output a padding line\n>> diff --git a/strbuf.h b/strbuf.h\n>> index f62278a0be..3a98147321 100644\n>> --- a/strbuf.h\n>> +++ b/strbuf.h\n>> @@ -66,11 +66,12 @@ struct string_list;\n>>  struct strbuf {\n>>  \tsize_t alloc;\n>>  \tsize_t len;\n>> +\tsize_t width;\n>>  \tchar *buf;\n>>  };\n>>\n>>  extern char strbuf_slopbuf[];\n>> -#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }\n>> +#define STRBUF_INIT  { .alloc = 0, .len = 0, .width = 0, .buf = strbuf_slopbuf }\n>>\n>>  /*\n>>   * Predeclare this here, since cache.h includes this file before it defines the\n>> @@ -161,6 +162,10 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len)\n>>  {\n>>  \tif (len > (sb->alloc ? sb->alloc - 1 : 0))\n>>  \t\tdie(\"BUG: strbuf_setlen() beyond buffer\");\n>> +\tif (len > sb->len)\n>> +\t\tsb->width += len - sb->len;\n>> +\telse\n>> +\t\tsb->width = len;\n>>  \tsb->len = len;\n>>  \tif (sb->buf != strbuf_slopbuf)\n>>  \t\tsb->buf[len] = '\\0';\n>> @@ -231,6 +236,7 @@ static inline void strbuf_addch(struct strbuf *sb, int c)\n>>  \t\tstrbuf_grow(sb, 1);\n>>  \tsb->buf[sb->len++] = c;\n>>  \tsb->buf[sb->len] = '\\0';\n>> +\tsb->width++;\n>>  }\n>>\n>>  /**\n>> --\n>> gitgitgadget\n>>\n>>\n"},{"id":"383908","messageId":"79463ee5-a171-53a8-4903-84a825c4e8d8@gmail.com","threadId":"52011","inReplyTo":"nycvar.QRO.7.76.6.1910111800220.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-11T17:20:43Z","receivedAt":"2019-10-11T17:20:50Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 11/10/2019 17:02, Johannes Schindelin wrote:\n> Hi,\n> \n> On Thu, 10 Oct 2019, Denton Liu wrote:\n> \n>> On Fri, Oct 11, 2019 at 10:42:20AM +0900, Junio C Hamano wrote:\n>>> Denton Liu <liu.denton@gmail.com> writes:\n>>>\n>>>> \tstatic int calculate_width(const struct strbuf *row)\n>>>> \t{\n>>>> \t\tint in_termcode = 0;\n>>>> \t\tint width = 0;\n>>>> \t\tint i;\n>>>>\n>>>> \t\tfor (i = 0; i < row.len; i++) {\n>>>> \t\t\tif (row.buf[i] == '\\033')\n>>>> \t\t\t\tin_termcode = 1;\n>>>>\n>>>> \t\t\tif (!in_termcode)\n>>>> \t\t\t\twidth++;\n>>>> \t\t\telse if (row.buf[i] == 'm')\n>>>> \t\t\t\tin_termcode = 0;\n>>>> \t\t}\n>>>> \t}\n>>>\n>>> Not every byte that is outside the escape sequence contributes to\n>>> one display columns.  You would want to take a look at utf8_width()\n>>> for inspiration.\n>>>\n>>\n>> Heh, I guess you're right. Looking right below the definition of\n>> utf8_width, I realised we have the utf8_strnwidth function. We should be\n>> able to just call\n>>\n>> \tutf8_strnwidth(row.buf, row.len, 1);\n> \n> Correct me if I'm wrong, but don't we want to keep track of the columns\n> *only* in the part with the actual line graph, i.e. we're not at all\n> interested in the onelines' widths?\n> \n> If so, I could imagine that a good idea would be to introduce\n> \n> \tstruct graphbuf {\n> \t\tstruct strbuf buf;\n> \t\tint width;\n> \t};\n> \n> and then introduce wrappers for `_addch()` and whatever else is used in\n> `graph.c`, these wrappers will increment the width together with the\n> `buf.len` field, and one additional helper that adds color sequences to\n> that graphbuf that leaves `width` unchanged.\n\nThat's exactly what I've spent this afternoon doing, in response to the feedback here. I took into consideration that:\n\n- We don't want a general solution to this problem for everything `strbuf` could be used for; it only needs to address the graph padding problem.\n\n- We only want to count printing characters, not color formatting sequences.\n\n- We only need to consider the width of a small set of characters: { | / \\ _ - . * }. We don't need to worry about Unicode, and the simple character counting in graph.c was working fine.\n\n- Attempting to count the characters in a `strbuf` after the fact is problematic, because sometimes the line prefix (`diff_options.line_prefix`) is included when `graph_pad_horizontally()` is called, and sometimes the prefix has already been sent to stdout and is not included in the `strbuf`. We need to know how many printing characters were added only during `graph_next_line()`.\n\nTo this end I've prepared a different implementation that introduces the indirection described above, and does not modify `strbuf`:\n\ndiff --git a/graph.c b/graph.c\nindex f53135485f..24bf1f4fe1 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -112,14 +112,38 @@ static const char *column_get_color_code(unsigned short color)\n \treturn column_colors[color];\n }\n \n-static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n-\t\t\t\tchar col_char)\n+struct graph_strbuf {\n+\tstruct strbuf *buf;\n+\tsize_t width;\n+};\n+\n+static inline void graph_strbuf_addch(struct graph_strbuf *sb, int c)\n+{\n+\tstrbuf_addch(sb->buf, c);\n+\tsb->width++;\n+}\n+\n+void graph_strbuf_addchars(struct graph_strbuf *sb, int c, size_t n)\n+{\n+\tstrbuf_addchars(sb->buf, c, n);\n+\tsb->width += n;\n+}\n+\n+static inline void graph_strbuf_addstr(struct graph_strbuf *sb, const char *s)\n+{\n+\tstrbuf_addstr(sb->buf, s);\n+\tsb->width += strlen(s);\n+}\n+\n+static void graph_strbuf_write_column(struct graph_strbuf *sb, const struct column *c,\n+\t\t\t\t      char col_char)\n {\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n-\tstrbuf_addch(sb, col_char);\n+\t\tstrbuf_addstr(sb->buf, column_get_color_code(c->color));\n+\tstrbuf_addch(sb->buf, col_char);\n+\tsb->width++;\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n+\t\tstrbuf_addstr(sb->buf, column_get_color_code(column_colors_max));\n }\n \n struct git_graph {\n@@ -686,8 +710,7 @@ 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-\t\t\t\t   int chars_written)\n+static void graph_pad_horizontally(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -696,12 +719,12 @@ 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-\tif (chars_written < graph->width)\n-\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n+\tif (sb->width < graph->width)\n+\t\tstrbuf_addchars(sb->buf, ' ', graph->width - sb->width);\n }\n \n static void graph_output_padding_line(struct git_graph *graph,\n-\t\t\t\t      struct strbuf *sb)\n+\t\t\t\t      struct graph_strbuf *sb)\n {\n \tint i;\n \n@@ -719,11 +742,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_write_column(sb, &graph->new_columns[i], '|');\n-\t\tstrbuf_addch(sb, ' ');\n+\t\tgraph_strbuf_write_column(sb, &graph->new_columns[i], '|');\n+\t\tgraph_strbuf_addch(sb, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n+\tgraph_pad_horizontally(graph, sb);\n }\n \n \n@@ -733,14 +756,14 @@ int graph_width(struct git_graph *graph)\n }\n \n \n-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_skip_line(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \t/*\n \t * Output an ellipsis to indicate that a portion\n \t * of the graph is missing.\n \t */\n-\tstrbuf_addstr(sb, \"...\");\n-\tgraph_pad_horizontally(graph, sb, 3);\n+\tgraph_strbuf_addstr(sb, \"...\");\n+\tgraph_pad_horizontally(graph, sb);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -750,11 +773,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n }\n \n static void graph_output_pre_commit_line(struct git_graph *graph,\n-\t\t\t\t\t struct strbuf *sb)\n+\t\t\t\t\t struct graph_strbuf *sb)\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@@ -777,14 +799,12 @@ 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_write_column(sb, col, '|');\n-\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n-\t\t\tchars_written += 1 + graph->expansion_row;\n+\t\t\tgraph_strbuf_write_column(sb, col, '|');\n+\t\t\tgraph_strbuf_addchars(sb, ' ', 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@@ -797,22 +817,18 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_strbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_strbuf_write_column(sb, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_strbuf_write_column(sb, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_strbuf_write_column(sb, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_strbuf_addch(sb, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -823,7 +839,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n-static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_char(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \t/*\n \t * For boundary commits, print 'o'\n@@ -831,22 +847,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tif (graph->commit->object.flags & BOUNDARY) {\n \t\tassert(graph->revs->boundary);\n-\t\tstrbuf_addch(sb, 'o');\n+\t\tgraph_strbuf_addch(sb, 'o');\n \t\treturn;\n \t}\n \n \t/*\n \t * get_revision_mark() handles all other cases without assert()\n \t */\n-\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n+\tgraph_strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n }\n \n /*\n- * Draw the horizontal dashes of an octopus merge and return the number of\n- * characters written.\n+ * Draw the horizontal dashes of an octopus merge.\n  */\n-static int graph_draw_octopus_merge(struct git_graph *graph,\n-\t\t\t\t    struct strbuf *sb)\n+static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \t/*\n \t * Here dashless_parents represents the number of parents which don't\n@@ -886,17 +900,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n \n \tint i;\n \tfor (i = 0; i < dashful_parents; i++) {\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n+\t\tgraph_strbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n+\t\tgraph_strbuf_write_column(sb, &graph->new_columns[i+first_col],\n+\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n \t}\n-\treturn 2 * dashful_parents;\n }\n \n-static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_line(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, chars_written;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -906,7 +919,6 @@ 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@@ -921,14 +933,11 @@ 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 > 2)\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\t\tgraph_draw_octopus_merge(graph, sb);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_strbuf_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@@ -945,19 +954,16 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_strbuf_write_column(sb, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_strbuf_write_column(sb, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_strbuf_write_column(sb, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_strbuf_addch(sb, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Update graph->state\n@@ -981,15 +987,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\n \treturn NULL;\n }\n \n-static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_post_merge_line(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \tint seen_this = 0;\n-\tint i, j, chars_written;\n+\tint i, j;\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@@ -1016,29 +1021,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tgraph_strbuf_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\tgraph_strbuf_write_column(sb, par_column, '\\\\');\n+\t\t\t\tgraph_strbuf_addch(sb, ' ');\n \t\t\t}\n-\t\t\tchars_written += j * 2;\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_strbuf_write_column(sb, col, '\\\\');\n+\t\t\tgraph_strbuf_addch(sb, ' ');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_strbuf_write_column(sb, col, '|');\n+\t\t\tgraph_strbuf_addch(sb, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Update graph->state\n@@ -1049,7 +1050,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n-static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_collapsing_line(struct git_graph *graph, struct graph_strbuf *sb)\n {\n \tint i;\n \tshort used_horizontal = 0;\n@@ -1159,9 +1160,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \tfor (i = 0; i < graph->mapping_size; i++) {\n \t\tint target = graph->new_mapping[i];\n \t\tif (target < 0)\n-\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tgraph_strbuf_addch(sb, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\t\tgraph_strbuf_write_column(sb, &graph->new_columns[target], '|');\n \t\telse if (target == horizontal_edge_target &&\n \t\t\t i != horizontal_edge - 1) {\n \t\t\t\t/*\n@@ -1172,16 +1173,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\tif (i != (target * 2)+3)\n \t\t\t\t\tgraph->new_mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n+\t\t\tgraph_strbuf_write_column(sb, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n \t\t\t\tgraph->new_mapping[i] = -1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\t\t\tgraph_strbuf_write_column(sb, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n+\tgraph_pad_horizontally(graph, sb);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -1199,24 +1200,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tstruct graph_strbuf gsb = { .buf = sb, .width = 0 };\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n-\t\tgraph_output_padding_line(graph, sb);\n+\t\tgraph_output_padding_line(graph, &gsb);\n \t\treturn 0;\n \tcase GRAPH_SKIP:\n-\t\tgraph_output_skip_line(graph, sb);\n+\t\tgraph_output_skip_line(graph, &gsb);\n \t\treturn 0;\n \tcase GRAPH_PRE_COMMIT:\n-\t\tgraph_output_pre_commit_line(graph, sb);\n+\t\tgraph_output_pre_commit_line(graph, &gsb);\n \t\treturn 0;\n \tcase GRAPH_COMMIT:\n-\t\tgraph_output_commit_line(graph, sb);\n+\t\tgraph_output_commit_line(graph, &gsb);\n \t\treturn 1;\n \tcase GRAPH_POST_MERGE:\n-\t\tgraph_output_post_merge_line(graph, sb);\n+\t\tgraph_output_post_merge_line(graph, &gsb);\n \t\treturn 0;\n \tcase GRAPH_COLLAPSING:\n-\t\tgraph_output_collapsing_line(graph, sb);\n+\t\tgraph_output_collapsing_line(graph, &gsb);\n \t\treturn 0;\n \t}\n \n@@ -1227,7 +1230,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint i;\n-\tint chars_written = 0;\n+\tstruct graph_strbuf gsb = { .buf = sb, .width = 0 };\n \n \tif (graph->state != GRAPH_COMMIT) {\n \t\tgraph_next_line(graph, sb);\n@@ -1244,20 +1247,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \n-\t\tstrbuf_write_column(sb, col, '|');\n-\t\tchars_written++;\n+\t\tgraph_strbuf_write_column(&gsb, col, '|');\n \n \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n \t\t\tint len = (graph->num_parents - 2) * 2;\n-\t\t\tstrbuf_addchars(sb, ' ', len);\n-\t\t\tchars_written += len;\n+\t\t\tgraph_strbuf_addchars(&gsb, ' ', len);\n \t\t} else {\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written++;\n+\t\t\tgraph_strbuf_addch(&gsb, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, &gsb);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\n\n\n"},{"id":"383953","messageId":"xmqqo8ymg64c.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"79463ee5-a171-53a8-4903-84a825c4e8d8@gmail.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-12T00:27:47Z","receivedAt":"2019-10-12T00:27:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"James Coglan <jcoglan@gmail.com> writes:\n\n> - We don't want a general solution to this problem for everything\n> `strbuf` could be used for; it only needs to address the graph\n> padding problem.\n\nOf course.  Somebody may use strbuf to hold rows of a table and you\ndo not want to contaminate strbuf with fields like width of each\ncolumn etc, that are very specific to the application.  IOW, strbuf\nis merely _one_ component of a larger solution to each specific\nproblem, and the latter may be things like \"struct graphbuf\" like\nDscho suggested, which might use strbuf as an underlying\n<byte-string, length> tuple mechanism, but that is an implementation\ndetail that should not be exposed to the users of the struct (and\nthat is why he did not call, and you should not call, the data\nstructure \"graph-strbuf\" or anything with \"strbuf\").\n\n> - We only want to count printing characters, not color formatting sequences.\n\nOK.  But I'd phrase \"count printing characters\" as \"measure display\nwidth\" for at least two reasons.  Whitespaces are typically counted\nas non-printing, but you do want to take them into account for this\napplication.  Also the graph may not be limited to ASCII graphics\nforever, and byte- or character-count may not match display width on\na fixed-width display.\n\n> - We only need to consider the width of a small set of characters:\n> { | / \\ _ - . * }. We don't need to worry about Unicode, and the\n> simple character counting in graph.c was working fine.\n\nI have to warn you that we saw attempts to step outside these ASCII\ngraphics and use Unicode characters for prettier output in the past.\nIf you can do so without too much complexity, I'd suggest you try\nnot to close the door to those people who follow your footsteps to\nfurther improve the system by pursuing the avenue further.\n\n> To this end I've prepared a different implementation that\n> introduces the indirection described above, and does not modify\n> `strbuf`:\n>\n> +struct graph_strbuf {\n> +\tstruct strbuf *buf;\n> +\tsize_t width;\n> +};\n\nIs there a reason why you need a pointer to a strbuf that is\nallocated separately?  E.g. would it make it harder to manage\nif the above were\n\n\tstruct graphbuf {\n\t\tstruct strbuf buf;\n\t\tint width; /* display width in columns */\n\t};\n\nwhich is essentially what Dscho suggested?\n\n> +static inline void graph_strbuf_addch(struct graph_strbuf *sb, int c)\n> +{\n> +\tstrbuf_addch(sb->buf, c);\n> +\tsb->width++;\n> +}\n> +\n> +void graph_strbuf_addchars(struct graph_strbuf *sb, int c, size_t n)\n> +{\n> +\tstrbuf_addchars(sb->buf, c, n);\n> +\tsb->width += n;\n> +}\n> +\n> +static inline void graph_strbuf_addstr(struct graph_strbuf *sb, const char *s)\n> +{\n> +\tstrbuf_addstr(sb->buf, s);\n> +\tsb->width += strlen(s);\n> +}\n\nI'd probably introduce another helper that takes color code and\ngraphbuf (also notice how I name the variables and types; calling\nsomething sb that is not a strbuf is misleading and inviting\nunnecessary bugs):\n\n    static inline void graphbuf_addcolor(struct graphbuf *gb, unsigned short color)\n    {\n            strbuf_addstr(gb->buf, column_get_color_code(color));\n    }\n\nif I were writing this code.  The goal is to make any strbuf_add*()\nthat is done directly on gb->buf outside these helpers a bug--that\nway, grepping for strbuf_add* in this file would become a very easy\nway to catch such a bug that bypasses the graphbuf_add*() layer and\npotentially screw up the column counting.\n\nOther than these three points (i.e. naming without \"strbuf\" that is\nan implementation detail, embedded vs pointer of strbuf in the\ngraphbuf, and making sure everything can be done via graphbuf_add*()\nwrappers to make it easier to spot bugs), it seems you are going in\nthe right direction.  Thanks for working on this.\n"},{"id":"383960","messageId":"ece84770-edcb-e6dd-488e-629cd7d8b698@gmail.com","threadId":"52011","inReplyTo":"494a92ba-00ab-b5d8-ee66-4007647f8483@gmail.com","subject":"Re: [PATCH 06/11] graph: tidy up display of left-skewed merges","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-12T01:36:46Z","receivedAt":"2019-10-12T01:36:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/11/2019 12:50 PM, James Coglan wrote:\n> On 10/10/2019 18:19, Derrick Stolee wrote:\n>> On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n>>> +++ b/t/t4215-log-skewed-merges.sh\n>>> @@ -0,0 +1,42 @@\n>>> +#!/bin/sh\n>>> +\n>>> +test_description='git log --graph of skewed merges'\n>>> +\n>>> +. ./test-lib.sh\n>>> +\n>>> +test_expect_success 'setup left-skewed merge' '\n>>\n>>\n>> Could you skew this example to include a left-skewed octopus merge\n>> (and use fewer Git processes) with the following:\n>>\n>> \tgit checkout --orphan _a && test_commit A &&\n>> \tgit switch -c _b _a && test_commit B &&\n>> \tgit switch -c _c _a && test_commit C &&\n>> \tgit switch -c _d _a && test_commit D &&\tgit switch -c _e _b && git merge --no-ff _c _d E &&\n>> \tgit switch -c _f _a && git merge --no-ff _d -m F &&\tgit checkout _a && git merge --no-ff _b _c _e _f -m G\n>> and I think the resulting output will be:\n>>\n>> *-----.   G\n>> |\\ \\ \\ \\\n>> | | | | * F\n>> | |_|_|/|\n>> |/| | | |\n>> | | | * | E\n>> | |_|/|\\|\n>> |/| | | |\n>> | | |/  * D\n>> | |_|__/\n>> |/| |\n>> | | * C\n>> | |/\n>> |/|\n>> | * B\n>> |/\n>> * A\n> \n> At this point in the history, commit E won't render like that -- this is before the change that flattens edges that fuse with the merge's last parent. I think the display of this history at this point will be:\n> \n> \t*-----.   G\n> \t|\\ \\ \\ \\\n> \t| | | | * F\n> \t| |_|_|/|\n> \t|/| | | |\n> \t| | | * |   E\n> \t| |_|/|\\ \\\n> \t|/| |/ / /\n> \t| | | | /\n> \t| | | |/\n> \t| | | * D\n> \t| |_|/\n> \t|/| |\n> \t| | * C\n> \t| |/\n> \t|/|\n> \t| * B\n> \t|/\n> \t* A\n> \n> Is there a particular reason for wanting to include this test case? What particular combination of states is it designed to test? (My guess is that it includes an octopus merge where the original test does not.) I'd be happy to add it at the appropriate point in the history if it's adding coverage not provided by the other tests.\n\nThanks for correcting my test case. It also helps that you would show the change in behavior in your later commits.\n\nMy reason to include this test is because it includes a regular merge and an octopus merge, both of which have a skewed render. Many times logic that applies to a normal merge breaks with octopus merges, so I try to include them whenever possible.\n\nThanks,\n-Stolee\n"},{"id":"383970","messageId":"nycvar.QRO.7.76.6.1910121817180.46@tvgsbejvaqbjf.bet","threadId":"52011","inReplyTo":"xmqqo8ymg64c.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-10-12T16:22:09Z","receivedAt":"2019-10-12T16:22:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nI just realized that I completely failed to offer my enthusiasm not only\nfor the idea of this patch series and the resulting graphs, but also at\nthe really high quality of the contribution. Thanks, James, for working\non this!\n\nAnd now just quickly...\n\nOn Sat, 12 Oct 2019, Junio C Hamano wrote:\n\n> James Coglan <jcoglan@gmail.com> writes:\n>\n> > - We only need to consider the width of a small set of characters:\n> > { | / \\ _ - . * }. We don't need to worry about Unicode, and the\n> > simple character counting in graph.c was working fine.\n>\n> I have to warn you that we saw attempts to step outside these ASCII\n> graphics and use Unicode characters for prettier output in the past.\n> If you can do so without too much complexity, I'd suggest you try\n> not to close the door to those people who follow your footsteps to\n> further improve the system by pursuing the avenue further.\n\nThat is a very valid point, I think. There are really pretty Unicode\ncharacters out there that I could totally see in a nice commit graph.\n\nAt that stage, we would have to use `int utf8_strnwidth(const char\n*string, int len, int skip_ansi)` anyway (because of the `skip_ansi`\nthat saves us _a lot_ of work). In this case, I doubt that it makes\nsense to keep a running tally of the width. It would be faster, or at\nleast similarly fast, to just run `utf8_strnwidth()` on the final string\nthat we want to measure.\n\nCiao,\nDscho\n"},{"id":"383986","messageId":"20191013065609.GA30601@sigill.intra.peff.net","threadId":"52011","inReplyTo":"204c7479-c78d-54ff-5ece-397b4c31804c@gmail.com","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-13T06:56:10Z","receivedAt":"2019-10-13T06:56:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 11, 2019 at 06:04:21PM +0100, James Coglan wrote:\n\n> > I should have noticed in your earlier commits, but why don't you keep\n> > the output inside the test suite? You can start with \"cat >expect <<-EOF\"\n> > to have it ignore leading whitespace. Sorry if there's something else about\n> > this that is causing issues.\n> \n> I was following a pattern used in t/t4202-log.sh. I believe it was\n> easier to debug these tests with the setup and expectations split into\n> separate blocks, but I wouldn't be opposed to merging them.\n\nSome of the older tests used that style, but we've been slowly\nmodernizing (I know, it's hard to pick up the style by example in such\ncases!). The usual style these days is making sure everything goes in a\ntest_expect_* block, with \"<<-\" to indent here-documents.\n\nAnother minor style nit that you picked up from t4202:\n\n> >> +cat > expect <<\\EOF\n\nWe'd omit the space after \">\" here.\n\n-Peff\n"},{"id":"383987","messageId":"20191013071500.GA30652@sigill.intra.peff.net","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/11] Improve the readability of log --graph output","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-13T07:15:01Z","receivedAt":"2019-10-13T07:18:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 10, 2019 at 09:13:42AM -0700, James Coglan via GitGitGadget wrote:\n\n> A final addition to that set of changes fixes the coloring of dashes that\n> are drawn next to octopus merges, in a manner compatible with all these\n> changes. The early commits in this set are refactorings that make the\n> functional changes easier to introduce.\n\nAs somebody who has pondered the octopus coloring code (for an\nembarrassingly long time considering that it still has some bugs!), let\nme just say thank you for taking this on. :)\n\nMoreover, I'll echo Dscho's comments elsewhere on the quality of this\nseries. It's a tricky topic to explain, and the way you've broken it up,\nalong with the commit messages, comments, and diagrams made it much\neasier to follow.\n\nOthers have already commented on things I saw while reading it, so I'll\njust add a few more thoughts.\n\n> This series of patches are designed to improve the output of the log --graph\n> command; their effect can be summed up in the following diagram:\n> \n>     Before                    After\n>     ------                    -----\n> \n>     *\n>     |\\\n>     | *                       *\n>     | |\\                      |\\\n>     | | *                     | *\n>     | | |                     | |\\\n>     | |  \\                    | | *\n>     | *-. \\                   | * |\n>     | |\\ \\ \\                  |/|\\|\n>     |/ / / /                  | | *\n>     | | | /                   | * |\n>     | | |/                    | |/\n>     | | *                     * /\n>     | * |                     |/\n>     | |/                      *\n>     * |\n>     |/\n>     *\n\nI wondered if anybody would prefer the sparseness of the \"before\"\ndiagram, and if that would merit having two modes that could selected at\nruntime. I'm not sure I'd want to carry the code for both types, though;\nit seems like a recipe for the non-default output format to accrue a\nbunch of bugs (since the graph code has proven itself to be a magnet for\noff-by-ones and other weirdness).\n\nDiffing the output of \"git diff --color --graph --oneline\" on git.git\nboth before and after your patch, the changes all look generally\npositive to me. The graph above is pretty dense, but that's not really\nwhat real-world graphs look like; it was designed to show off the\nchanges.\n\nThat plus the fact that you're fixing real bugs (like the octopus\ncoloring) makes me inclined to just move to your suggested output.\n\n-Peff\n"},{"id":"383988","messageId":"20191013072245.GB30652@sigill.intra.peff.net","threadId":"52011","inReplyTo":"20191010181624.GA97861@generichostname","subject":"Re: [PATCH 11/11] graph: fix coloring of octopus dashes","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-13T07:22:46Z","receivedAt":"2019-10-13T07:22:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 10, 2019 at 11:16:24AM -0700, Denton Liu wrote:\n\n> >  test_expect_success 'log --graph with normal octopus merge, no color' '\n> \n> So, I decided to merge 'dl/octopus-graph-bug' with your branch and I\n> couldn't be happier! I had to make a couple of minor tweaks to the\n> existing test cases but most of them only involved flipping\n> `test_expect_failure` to `test_expect_success`.\n\nThanks for doing that. I also checked the output on the git.git case\ndiscussed in:\n\n  https://public-inbox.org/git/20190926202326.GA16664@sigill.intra.peff.net/\n\nand it looks great. Probably because it's the same as your test cases --\nthe \"non-collapsing type\" I called it there (or maybe it's all just the\nout-of-order thing James brought up; I don't recall discussing that\nbefore, but certainly his test case exhibits the same off-by-one\ncoloring weirdness without the code change).\n\n-Peff\n"},{"id":"384015","messageId":"66b8ed45-4baa-37d9-c97f-72188ece32eb@gmail.com","threadId":"52011","inReplyTo":"xmqqo8ymg64c.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-14T12:55:39Z","receivedAt":"2019-10-14T12:55:49Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 12/10/2019 01:27, Junio C Hamano wrote:\n> James Coglan <jcoglan@gmail.com> writes:\n> \n>> - We don't want a general solution to this problem for everything\n>> `strbuf` could be used for; it only needs to address the graph\n>> padding problem.\n> \n> Of course.  Somebody may use strbuf to hold rows of a table and you\n> do not want to contaminate strbuf with fields like width of each\n> column etc, that are very specific to the application.  IOW, strbuf\n> is merely _one_ component of a larger solution to each specific\n> problem, and the latter may be things like \"struct graphbuf\" like\n> Dscho suggested, which might use strbuf as an underlying\n> <byte-string, length> tuple mechanism, but that is an implementation\n> detail that should not be exposed to the users of the struct (and\n> that is why he did not call, and you should not call, the data\n> structure \"graph-strbuf\" or anything with \"strbuf\").\n\nThank you for the clarification. I've now modified my patch to call the wrapper struct `graph_line` to better reflect its use. This was informed by uncertainty on this thread about whether the width calculation needs to take line breaks into account, so I wanted to name it to indicate it contains a single display line.\n\n>> - We only want to count printing characters, not color formatting sequences.\n> \n> OK.  But I'd phrase \"count printing characters\" as \"measure display\n> width\" for at least two reasons.  Whitespaces are typically counted\n> as non-printing, but you do want to take them into account for this\n> application.  Also the graph may not be limited to ASCII graphics\n> forever, and byte- or character-count may not match display width on\n> a fixed-width display.\n> \n>> - We only need to consider the width of a small set of characters:\n>> { | / \\ _ - . * }. We don't need to worry about Unicode, and the\n>> simple character counting in graph.c was working fine.\n> \n> I have to warn you that we saw attempts to step outside these ASCII\n> graphics and use Unicode characters for prettier output in the past.\n> If you can do so without too much complexity, I'd suggest you try\n> not to close the door to those people who follow your footsteps to\n> further improve the system by pursuing the avenue further.\n\nThat makes sense. All I've done for now is to put the calculations that were already being done behind an interface, consisting of just the operations graph.c actually uses, namely:\n\nvoid graph_line_addch(struct graph_line *line, int c);\n\nvoid graph_line_addchars(struct graph_line *line, int c, size_t n);\n\nvoid graph_line_addstr(struct graph_line *line, const char *s);\n\nvoid graph_line_write_column(struct graph_line *line, const struct column *c, char col_char);\n\nHaving this interface in place should not preclude extending this functionality to Unicode later, and may make it simpler as it puts all the disply width calculations in one place, rather than its current state where it's distributed across all the output functions and makes the assumption that all chars contribute one display column. It especially removes the need for statements like this that encode an assumption about what was printed:\n\n    graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n\n>> To this end I've prepared a different implementation that\n>> introduces the indirection described above, and does not modify\n>> `strbuf`:\n>>\n>> +struct graph_strbuf {\n>> +\tstruct strbuf *buf;\n>> +\tsize_t width;\n>> +};\n> \n> Is there a reason why you need a pointer to a strbuf that is\n> allocated separately?  E.g. would it make it harder to manage\n> if the above were\n> \n> \tstruct graphbuf {\n> \t\tstruct strbuf buf;\n> \t\tint width; /* display width in columns */\n> \t};\n> \n> which is essentially what Dscho suggested?\n\nI used a pointer here because I create the wrapper struct in `graph_next_line()`, which is an external interface that takes a `struct strbuf *`:\n\nint graph_next_line(struct git_graph *graph, struct strbuf *sb)\n{\n\tstruct graph_line line = { .buf = sb, .width = 0 };\n\t// ...\n}\n\nSo I'm not allocating the strbuf here, just wrapping a pointer to it. I would prefer to define `graph_line` as having a `strbuf` inline but I've not found a way to do that without breaking the other functions that call `graph_next_line()`. Although, as far as I know both this wrapper struct and every `strbuf` it points to are stack-allocated so I'm more concerned about the extra dereference rather than allocation cost.\n\nIf there's a way to do this I'm open to suggestions.\n\n>> +static inline void graph_strbuf_addch(struct graph_strbuf *sb, int c)\n>> +{\n>> +\tstrbuf_addch(sb->buf, c);\n>> +\tsb->width++;\n>> +}\n>> +\n>> +void graph_strbuf_addchars(struct graph_strbuf *sb, int c, size_t n)\n>> +{\n>> +\tstrbuf_addchars(sb->buf, c, n);\n>> +\tsb->width += n;\n>> +}\n>> +\n>> +static inline void graph_strbuf_addstr(struct graph_strbuf *sb, const char *s)\n>> +{\n>> +\tstrbuf_addstr(sb->buf, s);\n>> +\tsb->width += strlen(s);\n>> +}\n> \n> I'd probably introduce another helper that takes color code and\n> graphbuf (also notice how I name the variables and types; calling\n> something sb that is not a strbuf is misleading and inviting\n> unnecessary bugs):\n> \n>     static inline void graphbuf_addcolor(struct graphbuf *gb, unsigned short color)\n>     {\n>             strbuf_addstr(gb->buf, column_get_color_code(color));\n>     }\n> \n> if I were writing this code.  The goal is to make any strbuf_add*()\n> that is done directly on gb->buf outside these helpers a bug--that\n> way, grepping for strbuf_add* in this file would become a very easy\n> way to catch such a bug that bypasses the graphbuf_add*() layer and\n> potentially screw up the column counting.\n\nThat would be great, I'm just not sure how to fully decouple graph.c from `strbuf` so that I can properly block it from using the `strbuf` interface. What I have done is got a better separation between uses of `strbuf` and `graph_line`, so that the only functions that still use the `strbuf` interface are the `graph_line` interface, and functions that create a `strbuf` themselves (e.g. `graph_show_commit()`). Hopefully that separation makes the intent clearer and is a stepping stone to fully decoupling these interfaces.\n\n> Other than these three points (i.e. naming without \"strbuf\" that is\n> an implementation detail, embedded vs pointer of strbuf in the\n> graphbuf, and making sure everything can be done via graphbuf_add*()\n> wrappers to make it easier to spot bugs), it seems you are going in\n> the right direction.  Thanks for working on this.\n\nThanks! For completeness, here is the current state of this patch after I've been through the feedback up to this point:\n\nFrom 241180570fbaae04a2263c5ff1ab3b92f54f8004 Mon Sep 17 00:00:00 2001\nFrom: James Coglan <jcoglan@gmail.com>\nDate: Thu, 22 Aug 2019 09:30:08 +0100\nSubject: [PATCH] graph: automatically track display width of graph lines\n\nAll the output functions called by `graph_next_line()` currently keep\ntrack of how many printable chars they've written to the buffer, before\ncalling `graph_pad_horizontally()` to pad the line with spaces. Some\nfunctions do this by incrementing a counter whenever they write to the\nbuffer, and others do it by encoding an assumption about how many chars\nare written, as in:\n\n    graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n\nThis adds a fair amount of noise to the functions' logic and is easily\nbroken if one forgets to increment the right counter or update the\ncalculations used for padding.\n\nTo make this easier to use, I'm introducing a new struct called\n`graph_line` that wraps a `strbuf` and keeps count of its display width\nimplicitly. `graph_next_line()` wraps this around the `struct strbuf *`\nit's given and passes a `struct graph_line *` to the output functions,\nwhich use its interface.\n\nThe `graph_line` interface wraps the `strbuf_addch()`,\n`strbuf_addchars()` and `strbuf_addstr()` functions, and adds the\n`graph_line_write_column()` function for adding a single character with\ncolor formatting. The `graph_pad_horizontally()` function can then use\nthe `width` field from the struct rather than taking a character count\nas a parameter.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 194 +++++++++++++++++++++++++++++---------------------------\n 1 file changed, 99 insertions(+), 95 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex f53135485f..2f81a5d23d 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color)\n \treturn column_colors[color];\n }\n \n-static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n-\t\t\t\tchar col_char)\n+struct graph_line {\n+\tstruct strbuf *buf;\n+\tsize_t width;\n+};\n+\n+static inline void graph_line_addch(struct graph_line *line, int c)\n+{\n+\tstrbuf_addch(line->buf, c);\n+\tline->width++;\n+}\n+\n+static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n+{\n+\tstrbuf_addchars(line->buf, c, n);\n+\tline->width += n;\n+}\n+\n+static inline void graph_line_addstr(struct graph_line *line, const char *s)\n+{\n+\tstrbuf_addstr(line->buf, s);\n+\tline->width += strlen(s);\n+}\n+\n+static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n+{\n+\tstrbuf_addstr(line->buf, column_get_color_code(color));\n+}\n+\n+static void graph_line_write_column(struct graph_line *line, const struct column *c,\n+\t\t\t\t    char col_char)\n {\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n-\tstrbuf_addch(sb, col_char);\n+\t\tgraph_line_addcolor(line, c->color);\n+\tgraph_line_addch(line, col_char);\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n+\t\tgraph_line_addcolor(line, column_colors_max);\n }\n \n struct git_graph {\n@@ -686,8 +714,7 @@ 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-\t\t\t\t   int chars_written)\n+static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -696,12 +723,12 @@ 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-\tif (chars_written < graph->width)\n-\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n+\tif (line->width < graph->width)\n+\t\tgraph_line_addchars(line, ' ', graph->width - line->width);\n }\n \n static void graph_output_padding_line(struct git_graph *graph,\n-\t\t\t\t      struct strbuf *sb)\n+\t\t\t\t      struct graph_line *line)\n {\n \tint i;\n \n@@ -719,11 +746,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_write_column(sb, &graph->new_columns[i], '|');\n-\t\tstrbuf_addch(sb, ' ');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n+\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)\n }\n \n \n-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Output an ellipsis to indicate that a portion\n \t * of the graph is missing.\n \t */\n-\tstrbuf_addstr(sb, \"...\");\n-\tgraph_pad_horizontally(graph, sb, 3);\n+\tgraph_line_addstr(line, \"...\");\n+\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -750,11 +777,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n }\n \n static void graph_output_pre_commit_line(struct git_graph *graph,\n-\t\t\t\t\t struct strbuf *sb)\n+\t\t\t\t\t struct graph_line *line)\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@@ -777,14 +803,12 @@ 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_write_column(sb, col, '|');\n-\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n-\t\t\tchars_written += 1 + graph->expansion_row;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addchars(line, ' ', 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@@ -797,22 +821,18 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -823,7 +843,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n-static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * For boundary commits, print 'o'\n@@ -831,22 +851,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tif (graph->commit->object.flags & BOUNDARY) {\n \t\tassert(graph->revs->boundary);\n-\t\tstrbuf_addch(sb, 'o');\n+\t\tgraph_line_addch(line, 'o');\n \t\treturn;\n \t}\n \n \t/*\n \t * get_revision_mark() handles all other cases without assert()\n \t */\n-\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n+\tgraph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));\n }\n \n /*\n- * Draw the horizontal dashes of an octopus merge and return the number of\n- * characters written.\n+ * Draw the horizontal dashes of an octopus merge.\n  */\n-static int graph_draw_octopus_merge(struct git_graph *graph,\n-\t\t\t\t    struct strbuf *sb)\n+static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Here dashless_parents represents the number of parents which don't\n@@ -886,17 +904,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n \n \tint i;\n \tfor (i = 0; i < dashful_parents; i++) {\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n+\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n \t}\n-\treturn 2 * dashful_parents;\n }\n \n-static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, chars_written;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -906,7 +923,6 @@ 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@@ -920,15 +936,12 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \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+\t\t\tgraph_output_commit_char(graph, line);\n \n \t\t\tif (graph->num_parents > 2)\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\t\tgraph_draw_octopus_merge(graph, line);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, 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@@ -945,19 +958,16 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -981,15 +991,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\n \treturn NULL;\n }\n \n-static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, j, chars_written;\n+\tint i, j;\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@@ -1016,29 +1025,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tgraph_line_write_column(line, 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\tgraph_line_write_column(line, par_column, '\\\\');\n+\t\t\t\tgraph_line_addch(line, ' ');\n \t\t\t}\n-\t\t\tchars_written += j * 2;\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -1049,7 +1054,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n-static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint i;\n \tshort used_horizontal = 0;\n@@ -1159,9 +1164,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \tfor (i = 0; i < graph->mapping_size; i++) {\n \t\tint target = graph->new_mapping[i];\n \t\tif (target < 0)\n-\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '|');\n \t\telse if (target == horizontal_edge_target &&\n \t\t\t i != horizontal_edge - 1) {\n \t\t\t\t/*\n@@ -1172,16 +1177,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\tif (i != (target * 2)+3)\n \t\t\t\t\tgraph->new_mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n \t\t\t\tgraph->new_mapping[i] = -1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -1199,24 +1204,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n-\t\tgraph_output_padding_line(graph, sb);\n+\t\tgraph_output_padding_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_SKIP:\n-\t\tgraph_output_skip_line(graph, sb);\n+\t\tgraph_output_skip_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_PRE_COMMIT:\n-\t\tgraph_output_pre_commit_line(graph, sb);\n+\t\tgraph_output_pre_commit_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COMMIT:\n-\t\tgraph_output_commit_line(graph, sb);\n+\t\tgraph_output_commit_line(graph, &line);\n \t\treturn 1;\n \tcase GRAPH_POST_MERGE:\n-\t\tgraph_output_post_merge_line(graph, sb);\n+\t\tgraph_output_post_merge_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COLLAPSING:\n-\t\tgraph_output_collapsing_line(graph, sb);\n+\t\tgraph_output_collapsing_line(graph, &line);\n \t\treturn 0;\n \t}\n \n@@ -1227,7 +1234,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint i;\n-\tint chars_written = 0;\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n \n \tif (graph->state != GRAPH_COMMIT) {\n \t\tgraph_next_line(graph, sb);\n@@ -1244,20 +1251,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \n-\t\tstrbuf_write_column(sb, col, '|');\n-\t\tchars_written++;\n+\t\tgraph_line_write_column(&line, col, '|');\n \n \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n \t\t\tint len = (graph->num_parents - 2) * 2;\n-\t\t\tstrbuf_addchars(sb, ' ', len);\n-\t\t\tchars_written += len;\n+\t\t\tgraph_line_addchars(&line, ' ', len);\n \t\t} else {\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_addch(&line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, &line);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\n-- \n2.23.0\n\n"},{"id":"384016","messageId":"0dc3a92d-6cc7-fe9e-4d6e-786443621c10@gmail.com","threadId":"52011","inReplyTo":"66b8ed45-4baa-37d9-c97f-72188ece32eb@gmail.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-14T13:01:45Z","receivedAt":"2019-10-14T13:01:53Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 14/10/2019 13:55, James Coglan wrote:\n> Thanks! For completeness, here is the current state of this patch after I've been through the feedback up to this point:\n> \n> From 241180570fbaae04a2263c5ff1ab3b92f54f8004 Mon Sep 17 00:00:00 2001\n> From: James Coglan <jcoglan@gmail.com>\n> Date: Thu, 22 Aug 2019 09:30:08 +0100\n> Subject: [PATCH] graph: automatically track display width of graph lines\n> \n> All the output functions called by `graph_next_line()` currently keep\n> track of how many printable chars they've written to the buffer, before\n> calling `graph_pad_horizontally()` to pad the line with spaces. Some\n> functions do this by incrementing a counter whenever they write to the\n> buffer, and others do it by encoding an assumption about how many chars\n> are written, as in:\n> \n>     graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n> \n> This adds a fair amount of noise to the functions' logic and is easily\n> broken if one forgets to increment the right counter or update the\n> calculations used for padding.\n> \n> To make this easier to use, I'm introducing a new struct called\n> `graph_line` that wraps a `strbuf` and keeps count of its display width\n> implicitly. `graph_next_line()` wraps this around the `struct strbuf *`\n> it's given and passes a `struct graph_line *` to the output functions,\n> which use its interface.\n> \n> The `graph_line` interface wraps the `strbuf_addch()`,\n> `strbuf_addchars()` and `strbuf_addstr()` functions, and adds the\n> `graph_line_write_column()` function for adding a single character with\n> color formatting. The `graph_pad_horizontally()` function can then use\n> the `width` field from the struct rather than taking a character count\n> as a parameter.\n> \n> Signed-off-by: James Coglan <jcoglan@gmail.com>\n> ---\n>  graph.c | 194 +++++++++++++++++++++++++++++---------------------------\n>  1 file changed, 99 insertions(+), 95 deletions(-)\n> \n> diff --git a/graph.c b/graph.c\n> index f53135485f..2f81a5d23d 100644\n> --- a/graph.c\n> +++ b/graph.c\n> @@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color)\n>  \treturn column_colors[color];\n>  }\n>  \n> -static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n> -\t\t\t\tchar col_char)\n> +struct graph_line {\n> +\tstruct strbuf *buf;\n> +\tsize_t width;\n> +};\n> +\n> +static inline void graph_line_addch(struct graph_line *line, int c)\n> +{\n> +\tstrbuf_addch(line->buf, c);\n> +\tline->width++;\n> +}\n> +\n> +static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n> +{\n> +\tstrbuf_addchars(line->buf, c, n);\n> +\tline->width += n;\n> +}\n> +\n> +static inline void graph_line_addstr(struct graph_line *line, const char *s)\n> +{\n> +\tstrbuf_addstr(line->buf, s);\n> +\tline->width += strlen(s);\n> +}\n> +\n> +static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n> +{\n> +\tstrbuf_addstr(line->buf, column_get_color_code(color));\n> +}\n> +\n> +static void graph_line_write_column(struct graph_line *line, const struct column *c,\n> +\t\t\t\t    char col_char)\n>  {\n>  \tif (c->color < column_colors_max)\n> -\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n> -\tstrbuf_addch(sb, col_char);\n> +\t\tgraph_line_addcolor(line, c->color);\n> +\tgraph_line_addch(line, col_char);\n>  \tif (c->color < column_colors_max)\n> -\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n> +\t\tgraph_line_addcolor(line, column_colors_max);\n>  }\n>  \n>  struct git_graph {\n> @@ -686,8 +714,7 @@ 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> -\t\t\t\t   int chars_written)\n> +static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \t/*\n>  \t * Add additional spaces to the end of the strbuf, so that all\n> @@ -696,12 +723,12 @@ 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> -\tif (chars_written < graph->width)\n> -\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n> +\tif (line->width < graph->width)\n> +\t\tgraph_line_addchars(line, ' ', graph->width - line->width);\n>  }\n>  \n>  static void graph_output_padding_line(struct git_graph *graph,\n> -\t\t\t\t      struct strbuf *sb)\n> +\t\t\t\t      struct graph_line *line)\n>  {\n>  \tint i;\n>  \n> @@ -719,11 +746,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_write_column(sb, &graph->new_columns[i], '|');\n> -\t\tstrbuf_addch(sb, ' ');\n> +\t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n> +\t\tgraph_line_addch(line, ' ');\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n> +\tgraph_pad_horizontally(graph, line);\n>  }\n>  \n>  \n> @@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)\n>  }\n>  \n>  \n> -static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n> +static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \t/*\n>  \t * Output an ellipsis to indicate that a portion\n>  \t * of the graph is missing.\n>  \t */\n> -\tstrbuf_addstr(sb, \"...\");\n> -\tgraph_pad_horizontally(graph, sb, 3);\n> +\tgraph_line_addstr(line, \"...\");\n> +\tgraph_pad_horizontally(graph, line);\n>  \n>  \tif (graph->num_parents >= 3 &&\n>  \t    graph->commit_index < (graph->num_columns - 1))\n> @@ -750,11 +777,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n>  }\n>  \n>  static void graph_output_pre_commit_line(struct git_graph *graph,\n> -\t\t\t\t\t struct strbuf *sb)\n> +\t\t\t\t\t struct graph_line *line)\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> @@ -777,14 +803,12 @@ 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_write_column(sb, col, '|');\n> -\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n> -\t\t\tchars_written += 1 + graph->expansion_row;\n> +\t\t\tgraph_line_write_column(line, col, '|');\n> +\t\t\tgraph_line_addchars(line, ' ', 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> @@ -797,22 +821,18 @@ 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_write_column(sb, col, '\\\\');\n> +\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n>  \t\t\telse\n> -\t\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tchars_written++;\n> +\t\t\t\tgraph_line_write_column(line, col, '|');\n>  \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n> -\t\t\tstrbuf_write_column(sb, col, '\\\\');\n> -\t\t\tchars_written++;\n> +\t\t\tgraph_line_write_column(line, col, '\\\\');\n>  \t\t} else {\n> -\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tchars_written++;\n> +\t\t\tgraph_line_write_column(line, col, '|');\n>  \t\t}\n> -\t\tstrbuf_addch(sb, ' ');\n> -\t\tchars_written++;\n> +\t\tgraph_line_addch(line, ' ');\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, chars_written);\n> +\tgraph_pad_horizontally(graph, line);\n>  \n>  \t/*\n>  \t * Increment graph->expansion_row,\n> @@ -823,7 +843,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n>  \t\tgraph_update_state(graph, GRAPH_COMMIT);\n>  }\n>  \n> -static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n> +static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \t/*\n>  \t * For boundary commits, print 'o'\n> @@ -831,22 +851,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n>  \t */\n>  \tif (graph->commit->object.flags & BOUNDARY) {\n>  \t\tassert(graph->revs->boundary);\n> -\t\tstrbuf_addch(sb, 'o');\n> +\t\tgraph_line_addch(line, 'o');\n>  \t\treturn;\n>  \t}\n>  \n>  \t/*\n>  \t * get_revision_mark() handles all other cases without assert()\n>  \t */\n> -\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n> +\tgraph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));\n>  }\n>  \n>  /*\n> - * Draw the horizontal dashes of an octopus merge and return the number of\n> - * characters written.\n> + * Draw the horizontal dashes of an octopus merge.\n>   */\n> -static int graph_draw_octopus_merge(struct git_graph *graph,\n> -\t\t\t\t    struct strbuf *sb)\n> +static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \t/*\n>  \t * Here dashless_parents represents the number of parents which don't\n> @@ -886,17 +904,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n>  \n>  \tint i;\n>  \tfor (i = 0; i < dashful_parents; i++) {\n> -\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n> -\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n> -\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n> +\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n> +\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n> +\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n>  \t}\n> -\treturn 2 * dashful_parents;\n>  }\n>  \n> -static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n> +static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \tint seen_this = 0;\n> -\tint i, chars_written;\n> +\tint i;\n>  \n>  \t/*\n>  \t * Output the row containing this commit\n> @@ -906,7 +923,6 @@ 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> @@ -920,15 +936,12 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n>  \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> +\t\t\tgraph_output_commit_char(graph, line);\n>  \n>  \t\t\tif (graph->num_parents > 2)\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\t\tgraph_draw_octopus_merge(graph, line);\n>  \t\t} else if (seen_this && (graph->num_parents > 2)) {\n> -\t\t\tstrbuf_write_column(sb, col, '\\\\');\n> -\t\t\tchars_written++;\n> +\t\t\tgraph_line_write_column(line, 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> @@ -945,19 +958,16 @@ 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_write_column(sb, col, '\\\\');\n> +\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n>  \t\t\telse\n> -\t\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tchars_written++;\n> +\t\t\t\tgraph_line_write_column(line, col, '|');\n>  \t\t} else {\n> -\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tchars_written++;\n> +\t\t\tgraph_line_write_column(line, col, '|');\n>  \t\t}\n> -\t\tstrbuf_addch(sb, ' ');\n> -\t\tchars_written++;\n> +\t\tgraph_line_addch(line, ' ');\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, chars_written);\n> +\tgraph_pad_horizontally(graph, line);\n>  \n>  \t/*\n>  \t * Update graph->state\n> @@ -981,15 +991,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\n>  \treturn NULL;\n>  }\n>  \n> -static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n> +static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \tint seen_this = 0;\n> -\tint i, j, chars_written;\n> +\tint i, j;\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> @@ -1016,29 +1025,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tgraph_line_write_column(line, 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\tgraph_line_write_column(line, par_column, '\\\\');\n> +\t\t\t\tgraph_line_addch(line, ' ');\n>  \t\t\t}\n> -\t\t\tchars_written += j * 2;\n>  \t\t} else if (seen_this) {\n> -\t\t\tstrbuf_write_column(sb, col, '\\\\');\n> -\t\t\tstrbuf_addch(sb, ' ');\n> -\t\t\tchars_written += 2;\n> +\t\t\tgraph_line_write_column(line, col, '\\\\');\n> +\t\t\tgraph_line_addch(line, ' ');\n>  \t\t} else {\n> -\t\t\tstrbuf_write_column(sb, col, '|');\n> -\t\t\tstrbuf_addch(sb, ' ');\n> -\t\t\tchars_written += 2;\n> +\t\t\tgraph_line_write_column(line, col, '|');\n> +\t\t\tgraph_line_addch(line, ' ');\n>  \t\t}\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, chars_written);\n> +\tgraph_pad_horizontally(graph, line);\n>  \n>  \t/*\n>  \t * Update graph->state\n> @@ -1049,7 +1054,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n>  \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n>  }\n>  \n> -static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n> +static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)\n>  {\n>  \tint i;\n>  \tshort used_horizontal = 0;\n> @@ -1159,9 +1164,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>  \tfor (i = 0; i < graph->mapping_size; i++) {\n>  \t\tint target = graph->new_mapping[i];\n>  \t\tif (target < 0)\n> -\t\t\tstrbuf_addch(sb, ' ');\n> +\t\t\tgraph_line_addch(line, ' ');\n>  \t\telse if (target * 2 == i)\n> -\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n> +\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '|');\n>  \t\telse if (target == horizontal_edge_target &&\n>  \t\t\t i != horizontal_edge - 1) {\n>  \t\t\t\t/*\n> @@ -1172,16 +1177,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>  \t\t\t\tif (i != (target * 2)+3)\n>  \t\t\t\t\tgraph->new_mapping[i] = -1;\n>  \t\t\t\tused_horizontal = 1;\n> -\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n> +\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n>  \t\t} else {\n>  \t\t\tif (used_horizontal && i < horizontal_edge)\n>  \t\t\t\tgraph->new_mapping[i] = -1;\n> -\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n> +\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n>  \n>  \t\t}\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n> +\tgraph_pad_horizontally(graph, line);\n>  \n>  \t/*\n>  \t * Swap mapping and new_mapping\n> @@ -1199,24 +1204,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n>  \n>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>  {\n> +\tstruct graph_line line = { .buf = sb, .width = 0 };\n> +\n>  \tswitch (graph->state) {\n>  \tcase GRAPH_PADDING:\n> -\t\tgraph_output_padding_line(graph, sb);\n> +\t\tgraph_output_padding_line(graph, &line);\n>  \t\treturn 0;\n>  \tcase GRAPH_SKIP:\n> -\t\tgraph_output_skip_line(graph, sb);\n> +\t\tgraph_output_skip_line(graph, &line);\n>  \t\treturn 0;\n>  \tcase GRAPH_PRE_COMMIT:\n> -\t\tgraph_output_pre_commit_line(graph, sb);\n> +\t\tgraph_output_pre_commit_line(graph, &line);\n>  \t\treturn 0;\n>  \tcase GRAPH_COMMIT:\n> -\t\tgraph_output_commit_line(graph, sb);\n> +\t\tgraph_output_commit_line(graph, &line);\n>  \t\treturn 1;\n>  \tcase GRAPH_POST_MERGE:\n> -\t\tgraph_output_post_merge_line(graph, sb);\n> +\t\tgraph_output_post_merge_line(graph, &line);\n>  \t\treturn 0;\n>  \tcase GRAPH_COLLAPSING:\n> -\t\tgraph_output_collapsing_line(graph, sb);\n> +\t\tgraph_output_collapsing_line(graph, &line);\n>  \t\treturn 0;\n>  \t}\n>  \n> @@ -1227,7 +1234,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>  static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>  {\n>  \tint i;\n> -\tint chars_written = 0;\n> +\tstruct graph_line line = { .buf = sb, .width = 0 };\n>  \n>  \tif (graph->state != GRAPH_COMMIT) {\n>  \t\tgraph_next_line(graph, sb);\n> @@ -1244,20 +1251,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n>  \tfor (i = 0; i < graph->num_columns; i++) {\n>  \t\tstruct column *col = &graph->columns[i];\n>  \n> -\t\tstrbuf_write_column(sb, col, '|');\n> -\t\tchars_written++;\n> +\t\tgraph_line_write_column(&line, col, '|');\n>  \n>  \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n>  \t\t\tint len = (graph->num_parents - 2) * 2;\n> -\t\t\tstrbuf_addchars(sb, ' ', len);\n> -\t\t\tchars_written += len;\n> +\t\t\tgraph_line_addchars(&line, ' ', len);\n>  \t\t} else {\n> -\t\t\tstrbuf_addch(sb, ' ');\n> -\t\t\tchars_written++;\n> +\t\t\tgraph_line_addch(&line, ' ');\n>  \t\t}\n>  \t}\n>  \n> -\tgraph_pad_horizontally(graph, sb, chars_written);\n> +\tgraph_pad_horizontally(graph, &line);\n>  \n>  \t/*\n>  \t * Update graph->prev_state since we have output a padding line\n\nIn addition to this, I'd like to include the following patch that moves the `graph_pad_horizontally()` calls. By the way, what's the best way for me to submit the whole updated patch series after responding to all feedback?\n\nFrom 97d230c8a8d9b677b331e37482039384589b096a Mon Sep 17 00:00:00 2001\nFrom: James Coglan <jcoglan@gmail.com>\nDate: Mon, 14 Oct 2019 12:10:06 +0100\nSubject: [PATCH] graph: handle line padding in `graph_next_line()`\n\nNow that the display width of graph lines is implicitly tracked via the\n`graph_line` interface, the calls to `graph_pad_horizontally()` no\nlonger need to be located inside the individual output functions, where\nthe character counting was previously being done.\n\nAll the functions called by `graph_next_line()` generate a line of\noutput, then call `graph_pad_horizontally()`, and finally change the\ngraph state if necessary. As padding is the final change to the output\ndone by all these functions, it can be removed from all of them and done\nin `graph_next_line()` instead.\n\nI've also moved the guard in `graph_output_padding_line()` that checks\nthe graph has a commit; this function is only called by\n`graph_next_line()` and we must not pad the `graph_line` if no commit is\nset.\n---\n graph.c | 49 ++++++++++++++++++++-----------------------------\n 1 file changed, 20 insertions(+), 29 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 2f81a5d23d..4c68557b17 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -732,16 +732,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n {\n \tint i;\n \n-\t/*\n-\t * We could conceivable be called with a NULL commit\n-\t * if our caller has a bug, and invokes graph_next_line()\n-\t * immediately after graph_init(), without first calling\n-\t * graph_update().  Return without outputting anything in this\n-\t * case.\n-\t */\n-\tif (!graph->commit)\n-\t\treturn;\n-\n \t/*\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n@@ -749,8 +739,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n \t\tgraph_line_addch(line, ' ');\n \t}\n-\n-\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -767,7 +755,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n \t * of the graph is missing.\n \t */\n \tgraph_line_addstr(line, \"...\");\n-\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -832,8 +819,6 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Increment graph->expansion_row,\n \t * and move to state GRAPH_COMMIT if necessary\n@@ -967,8 +952,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1043,8 +1026,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1186,8 +1167,6 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Swap mapping and new_mapping\n \t */\n@@ -1204,31 +1183,43 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tint shown_commit_line = 0;\n \tstruct graph_line line = { .buf = sb, .width = 0 };\n \n+\t/*\n+\t * We could conceivable be called with a NULL commit\n+\t * if our caller has a bug, and invokes graph_next_line()\n+\t * immediately after graph_init(), without first calling\n+\t * graph_update().  Return without outputting anything in this\n+\t * case.\n+\t */\n+\tif (!graph->commit)\n+\t\treturn -1;\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n \t\tgraph_output_padding_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_SKIP:\n \t\tgraph_output_skip_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_PRE_COMMIT:\n \t\tgraph_output_pre_commit_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COMMIT:\n \t\tgraph_output_commit_line(graph, &line);\n-\t\treturn 1;\n+\t\tshown_commit_line = 1;\n+\t\tbreak;\n \tcase GRAPH_POST_MERGE:\n \t\tgraph_output_post_merge_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COLLAPSING:\n \t\tgraph_output_collapsing_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \t}\n \n-\tassert(0);\n-\treturn 0;\n+\tgraph_pad_horizontally(graph, &line);\n+\treturn shown_commit_line;\n }\n \n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n-- \n2.23.0\n\n\n"},{"id":"384017","messageId":"6c3cb137-7460-b498-4111-40e578a7bd2f@gmail.com","threadId":"52011","inReplyTo":"ece84770-edcb-e6dd-488e-629cd7d8b698@gmail.com","subject":"Re: [PATCH 06/11] graph: tidy up display of left-skewed merges","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-14T13:11:31Z","receivedAt":"2019-10-14T13:11:36Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 12/10/2019 02:36, Derrick Stolee wrote:\n> On 10/11/2019 12:50 PM, James Coglan wrote:\n>> On 10/10/2019 18:19, Derrick Stolee wrote:\n>>> On 10/10/2019 12:13 PM, James Coglan via GitGitGadget wrote:\n>>>> +++ b/t/t4215-log-skewed-merges.sh\n>>>> @@ -0,0 +1,42 @@\n>>>> +#!/bin/sh\n>>>> +\n>>>> +test_description='git log --graph of skewed merges'\n>>>> +\n>>>> +. ./test-lib.sh\n>>>> +\n>>>> +test_expect_success 'setup left-skewed merge' '\n>>>\n>>>\n>>> Could you skew this example to include a left-skewed octopus merge\n>>> (and use fewer Git processes) with the following:\n>>>\n>>> \tgit checkout --orphan _a && test_commit A &&\n>>> \tgit switch -c _b _a && test_commit B &&\n>>> \tgit switch -c _c _a && test_commit C &&\n>>> \tgit switch -c _d _a && test_commit D &&\tgit switch -c _e _b && git merge --no-ff _c _d E &&\n>>> \tgit switch -c _f _a && git merge --no-ff _d -m F &&\tgit checkout _a && git merge --no-ff _b _c _e _f -m G\n>>> and I think the resulting output will be:\n>>>\n>>> *-----.   G\n>>> |\\ \\ \\ \\\n>>> | | | | * F\n>>> | |_|_|/|\n>>> |/| | | |\n>>> | | | * | E\n>>> | |_|/|\\|\n>>> |/| | | |\n>>> | | |/  * D\n>>> | |_|__/\n>>> |/| |\n>>> | | * C\n>>> | |/\n>>> |/|\n>>> | * B\n>>> |/\n>>> * A\n>>\n>> At this point in the history, commit E won't render like that -- this is before the change that flattens edges that fuse with the merge's last parent. I think the display of this history at this point will be:\n>>\n>> \t*-----.   G\n>> \t|\\ \\ \\ \\\n>> \t| | | | * F\n>> \t| |_|_|/|\n>> \t|/| | | |\n>> \t| | | * |   E\n>> \t| |_|/|\\ \\\n>> \t|/| |/ / /\n>> \t| | | | /\n>> \t| | | |/\n>> \t| | | * D\n>> \t| |_|/\n>> \t|/| |\n>> \t| | * C\n>> \t| |/\n>> \t|/|\n>> \t| * B\n>> \t|/\n>> \t* A\n>>\n>> Is there a particular reason for wanting to include this test case? What particular combination of states is it designed to test? (My guess is that it includes an octopus merge where the original test does not.) I'd be happy to add it at the appropriate point in the history if it's adding coverage not provided by the other tests.\n> \n> Thanks for correcting my test case. It also helps that you would show the change in behavior in your later commits.\n> \n> My reason to include this test is because it includes a regular merge and an octopus merge, both of which have a skewed render. Many times logic that applies to a normal merge breaks with octopus merges, so I try to include them whenever possible.\n\nThanks, I've now incorporated your suggested test into my patch. I had to amend it slightly as it turns out the above history is not valid; G is not a possible merge because one of its parents (C) is an ancestor of another (E). The actual example I've added is this:\n\n\t*-----.   0_H\n\t|\\ \\ \\ \\\n\t| | | | * 0_G\n\t| |_|_|/|\n\t|/| | | |\n\t| | | * \\   0_F\n\t| |_|/|\\ \\\n\t|/| | | |/\n\t| | | | * 0_E\n\t| |_|_|/\n\t|/| | |\n\t| | * | 0_D\n\t| | |/\n\t| | * 0_C\n\t| |/\n\t|/|\n\t| * 0_B\n\t|/\n\t* 0_A\n\nI've also added another commit before beginning this work that adds the example from the cover letter, so you can see it changing with each commit, namely this history:\n\n\t*   H\n\t|\\\n\t| *   G\n\t| |\\\n\t| | * F\n\t| | |\n\t| |  \\\n\t| *-. \\   E\n\t| |\\ \\ \\\n\t|/ / / /\n\t| | | /\n\t| | |/\n\t| | * D\n\t| * | C\n\t| |/\n\t* | B\n\t|/\n\t* A\n"},{"id":"384021","messageId":"5661fb1e-d850-f193-6d00-23de9f48e742@gmail.com","threadId":"52011","inReplyTo":"20191013065609.GA30601@sigill.intra.peff.net","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-14T15:38:32Z","receivedAt":"2019-10-14T15:38:38Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 13/10/2019 07:56, Jeff King wrote:\n> On Fri, Oct 11, 2019 at 06:04:21PM +0100, James Coglan wrote:\n> \n>>> I should have noticed in your earlier commits, but why don't you keep\n>>> the output inside the test suite? You can start with \"cat >expect <<-EOF\"\n>>> to have it ignore leading whitespace. Sorry if there's something else about\n>>> this that is causing issues.\n>>\n>> I was following a pattern used in t/t4202-log.sh. I believe it was\n>> easier to debug these tests with the setup and expectations split into\n>> separate blocks, but I wouldn't be opposed to merging them.\n> \n> Some of the older tests used that style, but we've been slowly\n> modernizing (I know, it's hard to pick up the style by example in such\n> cases!). The usual style these days is making sure everything goes in a\n> test_expect_* block, with \"<<-\" to indent here-documents.\n> \n> Another minor style nit that you picked up from t4202:\n> \n>>>> +cat > expect <<\\EOF\n> \n> We'd omit the space after \">\" here.\n\nThanks, I've now made the style changes you've suggested on my branch. How should I go about sharing the current state of my patch series after I've incorporated all the changes suggested here? Should I post them as replies on this thread, or start a new pull request via GitGitGadget?\n"},{"id":"384022","messageId":"34315593-7284-86a7-fe45-6d830352e69b@gmail.com","threadId":"52011","inReplyTo":"20191013071500.GA30652@sigill.intra.peff.net","subject":"Re: [PATCH 00/11] Improve the readability of log --graph output","fromName":"James Coglan","fromEmail":"jcoglan@gmail.com","sentAt":"2019-10-14T15:49:05Z","receivedAt":"2019-10-14T15:49:10Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"On 13/10/2019 08:15, Jeff King wrote:\n> On Thu, Oct 10, 2019 at 09:13:42AM -0700, James Coglan via GitGitGadget wrote:\n> \n>> A final addition to that set of changes fixes the coloring of dashes that\n>> are drawn next to octopus merges, in a manner compatible with all these\n>> changes. The early commits in this set are refactorings that make the\n>> functional changes easier to introduce.\n> \n> As somebody who has pondered the octopus coloring code (for an\n> embarrassingly long time considering that it still has some bugs!), let\n> me just say thank you for taking this on. :)\n> \n> Moreover, I'll echo Dscho's comments elsewhere on the quality of this\n> series. It's a tricky topic to explain, and the way you've broken it up,\n> along with the commit messages, comments, and diagrams made it much\n> easier to follow.\n> \n> Others have already commented on things I saw while reading it, so I'll\n> just add a few more thoughts.\n> \n>> This series of patches are designed to improve the output of the log --graph\n>> command; their effect can be summed up in the following diagram:\n>>\n>>     Before                    After\n>>     ------                    -----\n>>\n>>     *\n>>     |\\\n>>     | *                       *\n>>     | |\\                      |\\\n>>     | | *                     | *\n>>     | | |                     | |\\\n>>     | |  \\                    | | *\n>>     | *-. \\                   | * |\n>>     | |\\ \\ \\                  |/|\\|\n>>     |/ / / /                  | | *\n>>     | | | /                   | * |\n>>     | | |/                    | |/\n>>     | | *                     * /\n>>     | * |                     |/\n>>     | |/                      *\n>>     * |\n>>     |/\n>>     *\n> \n> I wondered if anybody would prefer the sparseness of the \"before\"\n> diagram, and if that would merit having two modes that could selected at\n> runtime. I'm not sure I'd want to carry the code for both types, though;\n> it seems like a recipe for the non-default output format to accrue a\n> bunch of bugs (since the graph code has proven itself to be a magnet for\n> off-by-ones and other weirdness).\n\nYou're probably right about the non-default mode hiding bugs, but if you did want to do this, I believe the original rendering can be preserved by the following small switches:\n\n- always set `merge_layout = 1`; this will prevent skewing to the left\n- do not modify `edges_added` if the last parent fuses with its neighbor\n\nI believe all the layout changes are driven by these values, so you should be able to set in such a way as to preserve the original rendering by ignoring the special cases that lead to them having different values.\n"},{"id":"384024","messageId":"bea5d779-434a-5995-fff2-d8e6401e59a5@gmail.com","threadId":"52011","inReplyTo":"5661fb1e-d850-f193-6d00-23de9f48e742@gmail.com","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-14T17:41:04Z","receivedAt":"2019-10-14T17:41:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/14/2019 11:38 AM, James Coglan wrote:\n> On 13/10/2019 07:56, Jeff King wrote:\n>> On Fri, Oct 11, 2019 at 06:04:21PM +0100, James Coglan wrote:\n>>\n>>>> I should have noticed in your earlier commits, but why don't you keep\n>>>> the output inside the test suite? You can start with \"cat >expect <<-EOF\"\n>>>> to have it ignore leading whitespace. Sorry if there's something else about\n>>>> this that is causing issues.\n>>>\n>>> I was following a pattern used in t/t4202-log.sh. I believe it was\n>>> easier to debug these tests with the setup and expectations split into\n>>> separate blocks, but I wouldn't be opposed to merging them.\n>>\n>> Some of the older tests used that style, but we've been slowly\n>> modernizing (I know, it's hard to pick up the style by example in such\n>> cases!). The usual style these days is making sure everything goes in a\n>> test_expect_* block, with \"<<-\" to indent here-documents.\n>>\n>> Another minor style nit that you picked up from t4202:\n>>\n>>>>> +cat > expect <<\\EOF\n>>\n>> We'd omit the space after \">\" here.\n> \n> Thanks, I've now made the style changes you've suggested on my branch. How should I go about sharing the current state of my patch series after I've incorporated all the changes suggested here? Should I post them as replies on this thread, or start a new pull request via GitGitGadget?\n\nSince you sent v1 via GitGitGadget, all you need to do is\nadd another \"/submit\" comment on the same PR and it will\nsend a v2 to this thread.\n\nIt will auto-generate the range-diff from v1 and append it\nto your cover letter.\n\n-Stolee\n"},{"id":"384035","messageId":"nycvar.QRO.7.76.6.1910142241130.46@tvgsbejvaqbjf.bet","threadId":"52011","inReplyTo":"5661fb1e-d850-f193-6d00-23de9f48e742@gmail.com","subject":"Re: [PATCH 07/11] graph: commit and post-merge lines for left-skewed merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-10-14T20:42:14Z","receivedAt":"2019-10-14T20:42:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi James,\n\nOn Mon, 14 Oct 2019, James Coglan wrote:\n\n> [...] How should I go about sharing the current state of my patch\n> series after I've incorporated all the changes suggested here? Should\n> I post them as replies on this thread, or start a new pull request via\n> GitGitGadget?\n\nJust force-push the branch, and tell GitGitGadget to `/submit`. It will\ntake care of tagging a new iteration, generating a range-diff, and\nsending it off to the list.\n\nThanks,\nDscho\n"},{"id":"384120","messageId":"ecfcfbfd5cfc54a7c005dadc9eb2b82a036c31b5.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 03/13] graph: reuse `find_new_column_by_commit()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:54Z","receivedAt":"2019-10-15T23:41:12Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to\n`graph_insert_into_new_columns()` and so am trying to simplify it. One\npossible simplification is that we can extract the loop for finding the\nelement in `new_columns` containing the given commit.\n\n`find_new_column_by_commit()` contains a very similar loop but it\nreturns a `struct column *` rather than an `int` offset into the array.\nHere I'm introducing a version that returns `int` and using that in\n`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 48 +++++++++++++++++++++++-------------------------\n 1 file changed, 23 insertions(+), 25 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 4c68557b17..c9646d9e00 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -460,22 +460,31 @@ static unsigned short graph_find_commit_color(const struct git_graph *graph,\n \treturn graph_get_current_column_color(graph);\n }\n \n+static int graph_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 i;\n+\t}\n+\treturn -1;\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 {\n-\tint i;\n+\tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n \t * If the commit is already in the new_columns list, we don't need to\n \t * add it.  Just update the mapping correctly.\n \t */\n-\tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tif (graph->new_columns[i].commit == commit) {\n-\t\t\tgraph->mapping[*mapping_index] = i;\n-\t\t\t*mapping_index += 2;\n-\t\t\treturn;\n-\t\t}\n+\tif (i >= 0) {\n+\t\tgraph->mapping[*mapping_index] = i;\n+\t\t*mapping_index += 2;\n+\t\treturn;\n \t}\n \n \t/*\n@@ -963,17 +972,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\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 NULL;\n-}\n-\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n@@ -1001,20 +999,20 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\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\tint par_column;\n \t\t\tseen_this = 1;\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+\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\tassert(par_column >= 0);\n \n-\t\t\tgraph_line_write_column(line, par_column, '|');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[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\tgraph_line_write_column(line, par_column, '\\\\');\n+\t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\t\tassert(par_column >= 0);\n+\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n \t\t\t\tgraph_line_addch(line, ' ');\n \t\t\t}\n \t\t} else if (seen_this) {\n-- \ngitgitgadget\n\n"},{"id":"384121","messageId":"d962eb2e029fcee66dd25a59746d36cf953d2a2e.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 02/13] graph: handle line padding in `graph_next_line()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:53Z","receivedAt":"2019-10-15T23:41:12Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nNow that the display width of graph lines is implicitly tracked via the\n`graph_line` interface, the calls to `graph_pad_horizontally()` no\nlonger need to be located inside the individual output functions, where\nthe character counting was previously being done.\n\nAll the functions called by `graph_next_line()` generate a line of\noutput, then call `graph_pad_horizontally()`, and finally change the\ngraph state if necessary. As padding is the final change to the output\ndone by all these functions, it can be removed from all of them and done\nin `graph_next_line()` instead.\n\nI've also moved the guard in `graph_output_padding_line()` that checks\nthe graph has a commit; this function is only called by\n`graph_next_line()` and we must not pad the `graph_line` if no commit is\nset.\n---\n graph.c | 49 ++++++++++++++++++++-----------------------------\n 1 file changed, 20 insertions(+), 29 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 2f81a5d23d..4c68557b17 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -732,16 +732,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n {\n \tint i;\n \n-\t/*\n-\t * We could conceivable be called with a NULL commit\n-\t * if our caller has a bug, and invokes graph_next_line()\n-\t * immediately after graph_init(), without first calling\n-\t * graph_update().  Return without outputting anything in this\n-\t * case.\n-\t */\n-\tif (!graph->commit)\n-\t\treturn;\n-\n \t/*\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n@@ -749,8 +739,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n \t\tgraph_line_addch(line, ' ');\n \t}\n-\n-\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -767,7 +755,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n \t * of the graph is missing.\n \t */\n \tgraph_line_addstr(line, \"...\");\n-\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -832,8 +819,6 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Increment graph->expansion_row,\n \t * and move to state GRAPH_COMMIT if necessary\n@@ -967,8 +952,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1043,8 +1026,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1186,8 +1167,6 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Swap mapping and new_mapping\n \t */\n@@ -1204,31 +1183,43 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tint shown_commit_line = 0;\n \tstruct graph_line line = { .buf = sb, .width = 0 };\n \n+\t/*\n+\t * We could conceivable be called with a NULL commit\n+\t * if our caller has a bug, and invokes graph_next_line()\n+\t * immediately after graph_init(), without first calling\n+\t * graph_update().  Return without outputting anything in this\n+\t * case.\n+\t */\n+\tif (!graph->commit)\n+\t\treturn -1;\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n \t\tgraph_output_padding_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_SKIP:\n \t\tgraph_output_skip_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_PRE_COMMIT:\n \t\tgraph_output_pre_commit_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COMMIT:\n \t\tgraph_output_commit_line(graph, &line);\n-\t\treturn 1;\n+\t\tshown_commit_line = 1;\n+\t\tbreak;\n \tcase GRAPH_POST_MERGE:\n \t\tgraph_output_post_merge_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COLLAPSING:\n \t\tgraph_output_collapsing_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \t}\n \n-\tassert(0);\n-\treturn 0;\n+\tgraph_pad_horizontally(graph, &line);\n+\treturn shown_commit_line;\n }\n \n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n-- \ngitgitgadget\n\n"},{"id":"384123","messageId":"d2e8958eed3c51650de4b1dc18d3ec94df2cfe08.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 05/13] graph: remove `mapping_idx` and `graph_update_width()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:56Z","receivedAt":"2019-10-15T23:41:16Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThere's a duplication of logic between `graph_insert_into_new_columns()`\nand `graph_update_width()`. `graph_insert_into_new_columns()` is called\nrepeatedly by `graph_update_columns()` with an `int *` that tracks the\noffset into the `mapping` array where we should write the next value.\nEach call to `graph_insert_into_new_columns()` effectively pushes one\ncolumn index and one \"null\" value (-1) onto the `mapping` array and\ntherefore increments `mapping_idx` by 2.\n\n`graph_update_width()` duplicates this process: the `width` of the graph\nis essentially the initial width of the `mapping` array before edges\nbegin collapsing. The `graph_update_width()` function's logic\neffectively works out how many times `graph_insert_into_new_columns()`\nwas called based on the relationship of the current commit to the rest\nof the graph.\n\nI'm about to make some changes that make the assignment of values into\nthe `mapping` array more complicated. Rather than make\n`graph_update_width()` more complicated at the same time, we can simply\nremove this function and use `graph->width` to track the offset into the\n`mapping` array as we're building it. This removes the duplication and\nmakes sure that `graph->width` is the same as the visual width of the\n`mapping` array once `graph_update_columns()` is complete.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 65 +++++++++------------------------------------------------\n 1 file changed, 10 insertions(+), 55 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 512ae16535..d724ef25c3 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -472,8 +472,7 @@ static int graph_find_new_column_by_commit(struct git_graph *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+\t\t\t\t\t  struct commit *commit)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n@@ -487,50 +486,14 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[*mapping_index] = i;\n-\t*mapping_index += 2;\n-}\n-\n-static void graph_update_width(struct git_graph *graph,\n-\t\t\t       int is_commit_in_existing_columns)\n-{\n-\t/*\n-\t * Compute the width needed to display the graph for this commit.\n-\t * This is the maximum width needed for any row.  All other rows\n-\t * will be padded to this width.\n-\t *\n-\t * Compute the number of columns in the widest row:\n-\t * Count each existing column (graph->num_columns), and each new\n-\t * column added by this commit.\n-\t */\n-\tint max_cols = graph->num_columns + graph->num_parents;\n-\n-\t/*\n-\t * Even if the current commit has no parents to be printed, it\n-\t * still takes up a column for itself.\n-\t */\n-\tif (graph->num_parents < 1)\n-\t\tmax_cols++;\n-\n-\t/*\n-\t * We added a column for the current commit as part of\n-\t * graph->num_parents.  If the current commit was already in\n-\t * graph->columns, then we have double counted it.\n-\t */\n-\tif (is_commit_in_existing_columns)\n-\t\tmax_cols--;\n-\n-\t/*\n-\t * Each column takes up 2 spaces\n-\t */\n-\tgraph->width = max_cols * 2;\n+\tgraph->mapping[graph->width] = i;\n+\tgraph->width += 2;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n {\n \tstruct commit_list *parent;\n \tint max_new_columns;\n-\tint mapping_idx;\n \tint i, seen_this, is_commit_in_columns;\n \n \t/*\n@@ -563,6 +526,8 @@ static void graph_update_columns(struct git_graph *graph)\n \tfor (i = 0; i < graph->mapping_size; i++)\n \t\tgraph->mapping[i] = -1;\n \n+\tgraph->width = 0;\n+\n \t/*\n \t * Populate graph->new_columns and graph->mapping\n \t *\n@@ -573,7 +538,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t * supposed to end up after the collapsing is performed.\n \t */\n \tseen_this = 0;\n-\tmapping_idx = 0;\n \tis_commit_in_columns = 1;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n \t\tstruct commit *col_commit;\n@@ -587,7 +551,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n-\t\t\tint old_mapping_idx = mapping_idx;\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n \t\t\tfor (parent = first_interesting_parent(graph);\n@@ -602,21 +565,18 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\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+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n \t\t\t}\n \t\t\t/*\n-\t\t\t * We always need to increment mapping_idx by at\n+\t\t\t * We always need to increment graph->width by at\n \t\t\t * least 2, even if it has no interesting parents.\n \t\t\t * The current commit always takes up at least 2\n \t\t\t * spaces.\n \t\t\t */\n-\t\t\tif (mapping_idx == old_mapping_idx)\n-\t\t\t\tmapping_idx += 2;\n+\t\t\tif (graph->num_parents == 0)\n+\t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit,\n-\t\t\t\t\t\t      &mapping_idx);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n \t\t}\n \t}\n \n@@ -626,11 +586,6 @@ static void graph_update_columns(struct git_graph *graph)\n \twhile (graph->mapping_size > 1 &&\n \t       graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n-\n-\t/*\n-\t * Compute graph->width for this commit\n-\t */\n-\tgraph_update_width(graph, is_commit_in_columns);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n-- \ngitgitgadget\n\n"},{"id":"384124","messageId":"c30f5bb43b485adf459fa8cc378c1c54ee5592f9.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 01/13] graph: automatically track display width of graph lines","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:52Z","receivedAt":"2019-10-15T23:41:16Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nAll the output functions called by `graph_next_line()` currently keep\ntrack of how many printable chars they've written to the buffer, before\ncalling `graph_pad_horizontally()` to pad the line with spaces. Some\nfunctions do this by incrementing a counter whenever they write to the\nbuffer, and others do it by encoding an assumption about how many chars\nare written, as in:\n\n    graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n\nThis adds a fair amount of noise to the functions' logic and is easily\nbroken if one forgets to increment the right counter or update the\ncalculations used for padding.\n\nTo make this easier to use, I'm introducing a new struct called\n`graph_line` that wraps a `strbuf` and keeps count of its display width\nimplicitly. `graph_next_line()` wraps this around the `struct strbuf *`\nit's given and passes a `struct graph_line *` to the output functions,\nwhich use its interface.\n\nThe `graph_line` interface wraps the `strbuf_addch()`,\n`strbuf_addchars()` and `strbuf_addstr()` functions, and adds the\n`graph_line_write_column()` function for adding a single character with\ncolor formatting. The `graph_pad_horizontally()` function can then use\nthe `width` field from the struct rather than taking a character count\nas a parameter.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 194 +++++++++++++++++++++++++++++---------------------------\n 1 file changed, 99 insertions(+), 95 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex f53135485f..2f81a5d23d 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color)\n \treturn column_colors[color];\n }\n \n-static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n-\t\t\t\tchar col_char)\n+struct graph_line {\n+\tstruct strbuf *buf;\n+\tsize_t width;\n+};\n+\n+static inline void graph_line_addch(struct graph_line *line, int c)\n+{\n+\tstrbuf_addch(line->buf, c);\n+\tline->width++;\n+}\n+\n+static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n+{\n+\tstrbuf_addchars(line->buf, c, n);\n+\tline->width += n;\n+}\n+\n+static inline void graph_line_addstr(struct graph_line *line, const char *s)\n+{\n+\tstrbuf_addstr(line->buf, s);\n+\tline->width += strlen(s);\n+}\n+\n+static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n+{\n+\tstrbuf_addstr(line->buf, column_get_color_code(color));\n+}\n+\n+static void graph_line_write_column(struct graph_line *line, const struct column *c,\n+\t\t\t\t    char col_char)\n {\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n-\tstrbuf_addch(sb, col_char);\n+\t\tgraph_line_addcolor(line, c->color);\n+\tgraph_line_addch(line, col_char);\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n+\t\tgraph_line_addcolor(line, column_colors_max);\n }\n \n struct git_graph {\n@@ -686,8 +714,7 @@ 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-\t\t\t\t   int chars_written)\n+static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -696,12 +723,12 @@ 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-\tif (chars_written < graph->width)\n-\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n+\tif (line->width < graph->width)\n+\t\tgraph_line_addchars(line, ' ', graph->width - line->width);\n }\n \n static void graph_output_padding_line(struct git_graph *graph,\n-\t\t\t\t      struct strbuf *sb)\n+\t\t\t\t      struct graph_line *line)\n {\n \tint i;\n \n@@ -719,11 +746,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_write_column(sb, &graph->new_columns[i], '|');\n-\t\tstrbuf_addch(sb, ' ');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n+\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)\n }\n \n \n-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Output an ellipsis to indicate that a portion\n \t * of the graph is missing.\n \t */\n-\tstrbuf_addstr(sb, \"...\");\n-\tgraph_pad_horizontally(graph, sb, 3);\n+\tgraph_line_addstr(line, \"...\");\n+\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -750,11 +777,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n }\n \n static void graph_output_pre_commit_line(struct git_graph *graph,\n-\t\t\t\t\t struct strbuf *sb)\n+\t\t\t\t\t struct graph_line *line)\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@@ -777,14 +803,12 @@ 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_write_column(sb, col, '|');\n-\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n-\t\t\tchars_written += 1 + graph->expansion_row;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addchars(line, ' ', 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@@ -797,22 +821,18 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -823,7 +843,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n-static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * For boundary commits, print 'o'\n@@ -831,22 +851,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tif (graph->commit->object.flags & BOUNDARY) {\n \t\tassert(graph->revs->boundary);\n-\t\tstrbuf_addch(sb, 'o');\n+\t\tgraph_line_addch(line, 'o');\n \t\treturn;\n \t}\n \n \t/*\n \t * get_revision_mark() handles all other cases without assert()\n \t */\n-\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n+\tgraph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));\n }\n \n /*\n- * Draw the horizontal dashes of an octopus merge and return the number of\n- * characters written.\n+ * Draw the horizontal dashes of an octopus merge.\n  */\n-static int graph_draw_octopus_merge(struct git_graph *graph,\n-\t\t\t\t    struct strbuf *sb)\n+static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Here dashless_parents represents the number of parents which don't\n@@ -886,17 +904,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n \n \tint i;\n \tfor (i = 0; i < dashful_parents; i++) {\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n+\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n \t}\n-\treturn 2 * dashful_parents;\n }\n \n-static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, chars_written;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -906,7 +923,6 @@ 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@@ -920,15 +936,12 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \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+\t\t\tgraph_output_commit_char(graph, line);\n \n \t\t\tif (graph->num_parents > 2)\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\t\tgraph_draw_octopus_merge(graph, line);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, 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@@ -945,19 +958,16 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -981,15 +991,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\n \treturn NULL;\n }\n \n-static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, j, chars_written;\n+\tint i, j;\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@@ -1016,29 +1025,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tgraph_line_write_column(line, 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\tgraph_line_write_column(line, par_column, '\\\\');\n+\t\t\t\tgraph_line_addch(line, ' ');\n \t\t\t}\n-\t\t\tchars_written += j * 2;\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -1049,7 +1054,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n-static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint i;\n \tshort used_horizontal = 0;\n@@ -1159,9 +1164,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \tfor (i = 0; i < graph->mapping_size; i++) {\n \t\tint target = graph->new_mapping[i];\n \t\tif (target < 0)\n-\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '|');\n \t\telse if (target == horizontal_edge_target &&\n \t\t\t i != horizontal_edge - 1) {\n \t\t\t\t/*\n@@ -1172,16 +1177,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\tif (i != (target * 2)+3)\n \t\t\t\t\tgraph->new_mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n \t\t\t\tgraph->new_mapping[i] = -1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -1199,24 +1204,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n-\t\tgraph_output_padding_line(graph, sb);\n+\t\tgraph_output_padding_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_SKIP:\n-\t\tgraph_output_skip_line(graph, sb);\n+\t\tgraph_output_skip_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_PRE_COMMIT:\n-\t\tgraph_output_pre_commit_line(graph, sb);\n+\t\tgraph_output_pre_commit_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COMMIT:\n-\t\tgraph_output_commit_line(graph, sb);\n+\t\tgraph_output_commit_line(graph, &line);\n \t\treturn 1;\n \tcase GRAPH_POST_MERGE:\n-\t\tgraph_output_post_merge_line(graph, sb);\n+\t\tgraph_output_post_merge_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COLLAPSING:\n-\t\tgraph_output_collapsing_line(graph, sb);\n+\t\tgraph_output_collapsing_line(graph, &line);\n \t\treturn 0;\n \t}\n \n@@ -1227,7 +1234,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint i;\n-\tint chars_written = 0;\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n \n \tif (graph->state != GRAPH_COMMIT) {\n \t\tgraph_next_line(graph, sb);\n@@ -1244,20 +1251,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \n-\t\tstrbuf_write_column(sb, col, '|');\n-\t\tchars_written++;\n+\t\tgraph_line_write_column(&line, col, '|');\n \n \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n \t\t\tint len = (graph->num_parents - 2) * 2;\n-\t\t\tstrbuf_addchars(sb, ' ', len);\n-\t\t\tchars_written += len;\n+\t\t\tgraph_line_addchars(&line, ' ', len);\n \t\t} else {\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_addch(&line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, &line);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\n-- \ngitgitgadget\n\n"},{"id":"384126","messageId":"50ce875ed9d162dc62a8a21f08919068728774ff.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 04/13] graph: reduce duplication in `graph_insert_into_new_columns()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:55Z","receivedAt":"2019-10-15T23:41:16Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to this function and so am trying\nto simplify it. It currently contains some duplicated logic; both\nbranches the function can take assign the commit's column index into\nthe `mapping` array and increment `mapping_index`.\n\nHere I change the function so that the only conditional behaviour is\nthat it appends the commit to `new_columns` if it's not present. All\nmanipulation of `mapping` now happens on a single code path.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex c9646d9e00..512ae16535 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -478,23 +478,17 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n-\t * If the commit is already in the new_columns list, we don't need to\n-\t * add it.  Just update the mapping correctly.\n+\t * If the commit is not already in the new_columns array, then add it\n+\t * and record it as being in the final column.\n \t */\n-\tif (i >= 0) {\n-\t\tgraph->mapping[*mapping_index] = i;\n-\t\t*mapping_index += 2;\n-\t\treturn;\n+\tif (i < 0) {\n+\t\ti = graph->num_new_columns++;\n+\t\tgraph->new_columns[i].commit = commit;\n+\t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\t/*\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+\tgraph->mapping[*mapping_index] = i;\n \t*mapping_index += 2;\n-\tgraph->num_new_columns++;\n }\n \n static void graph_update_width(struct git_graph *graph,\n-- \ngitgitgadget\n\n"},{"id":"384122","messageId":"163600585c4425d9173faac3a2807088335701c2.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 06/13] graph: extract logic for moving to GRAPH_PRE_COMMIT state","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:57Z","receivedAt":"2019-10-15T23:41:17Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThis computation is repeated in a couple of places and I need to add\nanother condition to it to implement a further improvement to the graph\nrendering, so I'm extracting this into a function.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex d724ef25c3..bd7403065e 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -588,6 +588,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_needs_pre_commit_line(struct git_graph *graph)\n+{\n+\treturn graph->num_parents >= 3 &&\n+\t       graph->commit_index < (graph->num_columns - 1);\n+}\n+\n void graph_update(struct git_graph *graph, struct commit *commit)\n {\n \tstruct commit_list *parent;\n@@ -643,8 +649,7 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tif (graph->state != GRAPH_PADDING)\n \t\tgraph->state = GRAPH_SKIP;\n-\telse if (graph->num_parents >= 3 &&\n-\t\t graph->commit_index < (graph->num_columns - 1))\n+\telse if (graph_needs_pre_commit_line(graph))\n \t\tgraph->state = GRAPH_PRE_COMMIT;\n \telse\n \t\tgraph->state = GRAPH_COMMIT;\n@@ -714,8 +719,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n \t */\n \tgraph_line_addstr(line, \"...\");\n \n-\tif (graph->num_parents >= 3 &&\n-\t    graph->commit_index < (graph->num_columns - 1))\n+\tif (graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_PRE_COMMIT);\n \telse\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n-- \ngitgitgadget\n\n"},{"id":"384125","messageId":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.git.gitgitgadget@gmail.com","subject":"[PATCH v2 00/13] Improve the readability of log --graph output","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:51Z","receivedAt":"2019-10-15T23:41:17Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"This series of patches are designed to improve the output of the log --graph\ncommand; their effect can be summed up in the following diagram:\n\n    Before                    After\n    ------                    -----\n\n    *\n    |\\\n    | *                       *\n    | |\\                      |\\\n    | | *                     | *\n    | | |                     | |\\\n    | |  \\                    | | *\n    | *-. \\                   | * |\n    | |\\ \\ \\                  |/|\\|\n    |/ / / /                  | | *\n    | | | /                   | * |\n    | | |/                    | |/\n    | | *                     * /\n    | * |                     |/\n    | |/                      *\n    * |\n    |/\n    *\n\nThese changes aim to make the edges in graph diagrams easier to read, by\nstraightening lines and making certain kinds of topologies display more\ncompactly. Three distinct changes are included.\n\nFirst, if the first parent of a merge fuses with an edge to the left of the\ncommit, then we display that by making the edges fuse immediately rather\nthan by drawing a line straight down and then having it track to the left.\nThat is, where we currently display these graphs:\n\n    | *             | | | *\n    | |\\            | | | |\\\n    |/ /            | |_|/ /\n    | |             |/| | |\n\nWe will now display these merges as follows:\n\n    | *             | | | *\n    |/|             | |_|/|\n    | |             |/| | |\n\nThis transformation is applied to merges with any number of parents, for\nexample we currently display 3-parent merges like this:\n\n    | *-.           | | | *-.\n    | |\\ \\          | | | |\\ \\\n    |/ / /          | |_|/ / /\n    | | |           |/| | | |\n\nAnd we will now display them like this:\n\n    | *             | | | *\n    |/|\\            | |_|/|\\\n    | | |           |/| | | |\n\nIf the edge the first parent fuses with is separated from the commit by\nmultiple columns, a horizontal edge is drawn just as we currently do in the\n'collapsing' state. This change also affects the display of commit and\npost-merge lines in subtle ways that are more thoroughly described in the\nrelevant commits.\n\nThe second change is that if the final parent of a merge fuses with the edge\nto the right of the commit, then we can remove the zig-zag effect that\ncurrently results. We currently display these merges like this:\n\n    * |\n    |\\ \\\n    | |/\n    | *\n\nAfter these changes, this merge will now be displayed like so:\n\n    * |\n    |\\|\n    | *\n\nIf the final parent fuses with an edge that's further to the right, its\ndisplay is unchanged and it will still display like this:\n\n    * | | |\n    |\\ \\ \\ \\\n    | | |_|/\n    | |/| |\n    | * | |\n\nThe final structural change smooths out lines that are collapsing through\ncommit lines. For example, consider the following history:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * | |\n    |/ /\n    * |\n    |/\n    *\n\nThis is now rendered so that commit lines display an edge using / instead of\n|, if that edge is tracking to the left both above and below the commit\nline. That results in this improved display:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * / /\n    |/ /\n    * /\n    |/\n    *\n\nTaken together, these changes produce the change shown in the first diagram\nabove, with the current rendering on the left and the new rendering on the\nright.\n\nA final addition to that set of changes fixes the coloring of dashes that\nare drawn next to octopus merges, in a manner compatible with all these\nchanges. The early commits in this set are refactorings that make the\nfunctional changes easier to introduce.\n\nJames Coglan (13):\n  graph: automatically track display width of graph lines\n  graph: handle line padding in `graph_next_line()`\n  graph: reuse `find_new_column_by_commit()`\n  graph: reduce duplication in `graph_insert_into_new_columns()`\n  graph: remove `mapping_idx` and `graph_update_width()`\n  graph: extract logic for moving to GRAPH_PRE_COMMIT state\n  graph: example of graph output that can be simplified\n  graph: tidy up display of left-skewed merges\n  graph: commit and post-merge lines for left-skewed merges\n  graph: rename `new_mapping` to `old_mapping`\n  graph: smooth appearance of collapsing edges on commit lines\n  graph: flatten edges that fuse with their right neighbor\n  graph: fix coloring of octopus dashes\n\n graph.c                                    | 646 ++++++++++++---------\n t/t3430-rebase-merges.sh                   |   2 +-\n t/t4202-log.sh                             |   2 +-\n t/t4214-log-graph-octopus.sh               |  62 +-\n t/t4215-log-skewed-merges.sh               | 257 ++++++++\n t/t6016-rev-list-graph-simplify-history.sh |  30 +-\n 6 files changed, 673 insertions(+), 326 deletions(-)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\n\nbase-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-383/jcoglan/jc/simplify-graph-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/383\n\nRange-diff vs v1:\n\n  1:  4bc0a05961 !  1:  c30f5bb43b graph: automatically track visible width of `strbuf`\n     @@ -1,13 +1,13 @@\n      Author: James Coglan <jcoglan@gmail.com>\n      \n     -    graph: automatically track visible width of `strbuf`\n     +    graph: automatically track display width of graph lines\n      \n     -    All the output functions in `graph.c` currently keep track of how many\n     -    printable chars they've written to the buffer, before calling\n     -    `graph_pad_horizontally()` to pad the line with spaces. Some functions\n     -    do this by incrementing a counter whenever they write to the buffer, and\n     -    others do it by encoding an assumption about how many chars are written,\n     -    as in:\n     +    All the output functions called by `graph_next_line()` currently keep\n     +    track of how many printable chars they've written to the buffer, before\n     +    calling `graph_pad_horizontally()` to pad the line with spaces. Some\n     +    functions do this by incrementing a counter whenever they write to the\n     +    buffer, and others do it by encoding an assumption about how many chars\n     +    are written, as in:\n      \n              graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n      \n     @@ -15,22 +15,18 @@\n          broken if one forgets to increment the right counter or update the\n          calculations used for padding.\n      \n     -    To make this easier to use, I'm adding a `width` field to `strbuf` that\n     -    tracks the number of printing characters added after the line prefix.\n     -    It's set to 0 at the start of `graph_next_line()`, and then various\n     -    `strbuf` functions update it as follows:\n     +    To make this easier to use, I'm introducing a new struct called\n     +    `graph_line` that wraps a `strbuf` and keeps count of its display width\n     +    implicitly. `graph_next_line()` wraps this around the `struct strbuf *`\n     +    it's given and passes a `struct graph_line *` to the output functions,\n     +    which use its interface.\n      \n     -    - `strbuf_write_column()` increments `width` by 1\n     -\n     -    - `strbuf_setlen()` changes `width` by the amount added to `len` if\n     -      `len` is increased, or makes `width` and `len` the same if it's\n     -      decreased\n     -\n     -    - `strbuf_addch()` increments `width` by 1\n     -\n     -    This is enough to ensure that the functions used by `graph.c` update\n     -    `strbuf->width` correctly, and `graph_pad_horizontally()` can then use\n     -    this field instead of taking `chars_written` as a parameter.\n     +    The `graph_line` interface wraps the `strbuf_addch()`,\n     +    `strbuf_addchars()` and `strbuf_addstr()` functions, and adds the\n     +    `graph_line_write_column()` function for adding a single character with\n     +    color formatting. The `graph_pad_horizontally()` function can then use\n     +    the `width` field from the struct rather than taking a character count\n     +    as a parameter.\n      \n          Signed-off-by: James Coglan <jcoglan@gmail.com>\n      \n     @@ -38,23 +34,50 @@\n       --- a/graph.c\n       +++ b/graph.c\n      @@\n     - static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n     - \t\t\t\tchar col_char)\n     - {\n     -+\t/*\n     -+\t * Remember the buffer's width as we're about to add non-printing\n     -+\t * content to it, and we want to avoid counting the byte length\n     -+\t * of this content towards the buffer's visible width\n     -+\t */\n     -+\tsize_t prev_width = sb->width;\n     + \treturn column_colors[color];\n     + }\n     + \n     +-static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n     +-\t\t\t\tchar col_char)\n     ++struct graph_line {\n     ++\tstruct strbuf *buf;\n     ++\tsize_t width;\n     ++};\n     ++\n     ++static inline void graph_line_addch(struct graph_line *line, int c)\n     ++{\n     ++\tstrbuf_addch(line->buf, c);\n     ++\tline->width++;\n     ++}\n      +\n     ++static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n     ++{\n     ++\tstrbuf_addchars(line->buf, c, n);\n     ++\tline->width += n;\n     ++}\n     ++\n     ++static inline void graph_line_addstr(struct graph_line *line, const char *s)\n     ++{\n     ++\tstrbuf_addstr(line->buf, s);\n     ++\tline->width += strlen(s);\n     ++}\n     ++\n     ++static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n     ++{\n     ++\tstrbuf_addstr(line->buf, column_get_color_code(color));\n     ++}\n     ++\n     ++static void graph_line_write_column(struct graph_line *line, const struct column *c,\n     ++\t\t\t\t    char col_char)\n     + {\n       \tif (c->color < column_colors_max)\n     - \t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n     - \tstrbuf_addch(sb, col_char);\n     +-\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n     +-\tstrbuf_addch(sb, col_char);\n     ++\t\tgraph_line_addcolor(line, c->color);\n     ++\tgraph_line_addch(line, col_char);\n       \tif (c->color < column_colors_max)\n     - \t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n     -+\n     -+\tsb->width = prev_width + 1;\n     +-\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n     ++\t\tgraph_line_addcolor(line, column_colors_max);\n       }\n       \n       struct git_graph {\n     @@ -64,7 +87,7 @@\n       \n      -static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,\n      -\t\t\t\t   int chars_written)\n     -+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)\n       {\n       \t/*\n       \t * Add additional spaces to the end of the strbuf, so that all\n     @@ -74,30 +97,55 @@\n       \t */\n      -\tif (chars_written < graph->width)\n      -\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n     -+\tif (sb->width < graph->width)\n     -+\t\tstrbuf_addchars(sb, ' ', graph->width - sb->width);\n     ++\tif (line->width < graph->width)\n     ++\t\tgraph_line_addchars(line, ' ', graph->width - line->width);\n       }\n       \n       static void graph_output_padding_line(struct git_graph *graph,\n     +-\t\t\t\t      struct strbuf *sb)\n     ++\t\t\t\t      struct graph_line *line)\n     + {\n     + \tint i;\n     + \n      @@\n     - \t\tstrbuf_addch(sb, ' ');\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_write_column(sb, &graph->new_columns[i], '|');\n     +-\t\tstrbuf_addch(sb, ' ');\n     ++\t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n     ++\t\tgraph_line_addch(line, ' ');\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, line);\n       }\n       \n       \n      @@\n     + }\n     + \n     + \n     +-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)\n     + {\n     + \t/*\n     + \t * Output an ellipsis to indicate that a portion\n       \t * of the graph is missing.\n       \t */\n     - \tstrbuf_addstr(sb, \"...\");\n     +-\tstrbuf_addstr(sb, \"...\");\n      -\tgraph_pad_horizontally(graph, sb, 3);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_line_addstr(line, \"...\");\n     ++\tgraph_pad_horizontally(graph, line);\n       \n       \tif (graph->num_parents >= 3 &&\n       \t    graph->commit_index < (graph->num_columns - 1))\n      @@\n     + }\n     + \n     + static void graph_output_pre_commit_line(struct git_graph *graph,\n     +-\t\t\t\t\t struct strbuf *sb)\n     ++\t\t\t\t\t struct graph_line *line)\n       {\n       \tint num_expansion_rows;\n       \tint i, seen_this;\n     @@ -114,34 +162,66 @@\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_write_column(sb, col, '|');\n     - \t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n     +-\t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n      -\t\t\tchars_written += 1 + graph->expansion_row;\n     ++\t\t\tgraph_line_write_column(line, col, '|');\n     ++\t\t\tgraph_line_addchars(line, ' ', 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      @@\n     - \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\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_write_column(sb, col, '\\\\');\n     ++\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n       \t\t\telse\n     - \t\t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\t\tstrbuf_write_column(sb, col, '|');\n      -\t\t\tchars_written++;\n     ++\t\t\t\tgraph_line_write_column(line, col, '|');\n       \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n     - \t\t\tstrbuf_write_column(sb, col, '\\\\');\n     +-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_write_column(line, col, '\\\\');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\tstrbuf_write_column(sb, col, '|');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_write_column(line, col, '|');\n       \t\t}\n     - \t\tstrbuf_addch(sb, ' ');\n     +-\t\tstrbuf_addch(sb, ' ');\n      -\t\tchars_written++;\n     ++\t\tgraph_line_addch(line, ' ');\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, chars_written);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, line);\n       \n       \t/*\n       \t * Increment graph->expansion_row,\n      @@\n     + \t\tgraph_update_state(graph, GRAPH_COMMIT);\n     + }\n     + \n     +-static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)\n     + {\n     + \t/*\n     + \t * For boundary commits, print 'o'\n     +@@\n     + \t */\n     + \tif (graph->commit->object.flags & BOUNDARY) {\n     + \t\tassert(graph->revs->boundary);\n     +-\t\tstrbuf_addch(sb, 'o');\n     ++\t\tgraph_line_addch(line, 'o');\n     + \t\treturn;\n     + \t}\n     + \n     + \t/*\n     + \t * get_revision_mark() handles all other cases without assert()\n     + \t */\n     +-\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n     ++\tgraph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));\n       }\n       \n       /*\n     @@ -151,18 +231,26 @@\n        */\n      -static int graph_draw_octopus_merge(struct git_graph *graph,\n      -\t\t\t\t    struct strbuf *sb)\n     -+static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n       {\n       \t/*\n       \t * Here dashless_parents represents the number of parents which don't\n      @@\n     - \t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n     - \t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n     + \n     + \tint i;\n     + \tfor (i = 0; i < dashful_parents; i++) {\n     +-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n     +-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n     +-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n     ++\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n     ++\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n     ++\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n       \t}\n      -\treturn 2 * dashful_parents;\n       }\n       \n     - static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n     +-static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n       {\n       \tint seen_this = 0;\n      -\tint i, chars_written;\n     @@ -179,41 +267,55 @@\n       \t\tstruct column *col = &graph->columns[i];\n       \t\tstruct commit *col_commit;\n      @@\n     + \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\tgraph_output_commit_char(graph, sb);\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_output_commit_char(graph, line);\n       \n       \t\t\tif (graph->num_parents > 2)\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\t\tgraph_draw_octopus_merge(graph, sb);\n     ++\t\t\t\tgraph_draw_octopus_merge(graph, line);\n       \t\t} else if (seen_this && (graph->num_parents > 2)) {\n     - \t\t\tstrbuf_write_column(sb, col, '\\\\');\n     +-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_write_column(line, 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      @@\n     - \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\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_write_column(sb, col, '\\\\');\n     ++\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n       \t\t\telse\n     - \t\t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\t\tstrbuf_write_column(sb, col, '|');\n      -\t\t\tchars_written++;\n     ++\t\t\t\tgraph_line_write_column(line, col, '|');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\tstrbuf_write_column(sb, col, '|');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_write_column(line, col, '|');\n       \t\t}\n     - \t\tstrbuf_addch(sb, ' ');\n     +-\t\tstrbuf_addch(sb, ' ');\n      -\t\tchars_written++;\n     ++\t\tgraph_line_addch(line, ' ');\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, chars_written);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, line);\n       \n       \t/*\n       \t * Update graph->state\n      @@\n     - static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n     + \treturn NULL;\n     + }\n     + \n     +-static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n       {\n       \tint seen_this = 0;\n      -\tint i, j, chars_written;\n     @@ -227,40 +329,81 @@\n       \t\tstruct column *col = &graph->columns[i];\n       \t\tstruct commit *col_commit;\n      @@\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\tstrbuf_write_column(sb, par_column, '|');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_write_column(line, 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     -@@\n     - \t\t\t\tstrbuf_write_column(sb, par_column, '\\\\');\n     - \t\t\t\tstrbuf_addch(sb, ' ');\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\tgraph_line_write_column(line, par_column, '\\\\');\n     ++\t\t\t\tgraph_line_addch(line, ' ');\n       \t\t\t}\n      -\t\t\tchars_written += j * 2;\n       \t\t} else if (seen_this) {\n     - \t\t\tstrbuf_write_column(sb, col, '\\\\');\n     - \t\t\tstrbuf_addch(sb, ' ');\n     +-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n     +-\t\t\tstrbuf_addch(sb, ' ');\n      -\t\t\tchars_written += 2;\n     ++\t\t\tgraph_line_write_column(line, col, '\\\\');\n     ++\t\t\tgraph_line_addch(line, ' ');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     - \t\t\tstrbuf_addch(sb, ' ');\n     +-\t\t\tstrbuf_write_column(sb, col, '|');\n     +-\t\t\tstrbuf_addch(sb, ' ');\n      -\t\t\tchars_written += 2;\n     ++\t\t\tgraph_line_write_column(line, col, '|');\n     ++\t\t\tgraph_line_addch(line, ' ');\n       \t\t}\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, chars_written);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, line);\n       \n       \t/*\n       \t * Update graph->state\n      @@\n     + \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n     + }\n     + \n     +-static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n     ++static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)\n     + {\n     + \tint i;\n     + \tshort used_horizontal = 0;\n     +@@\n     + \tfor (i = 0; i < graph->mapping_size; i++) {\n     + \t\tint target = graph->new_mapping[i];\n     + \t\tif (target < 0)\n     +-\t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\tgraph_line_addch(line, ' ');\n     + \t\telse if (target * 2 == i)\n     +-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n     ++\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '|');\n     + \t\telse if (target == horizontal_edge_target &&\n     + \t\t\t i != horizontal_edge - 1) {\n     + \t\t\t\t/*\n     +@@\n     + \t\t\t\tif (i != (target * 2)+3)\n     + \t\t\t\t\tgraph->new_mapping[i] = -1;\n     + \t\t\t\tused_horizontal = 1;\n     +-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n     ++\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n     + \t\t} else {\n     + \t\t\tif (used_horizontal && i < horizontal_edge)\n     + \t\t\t\tgraph->new_mapping[i] = -1;\n     +-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n     ++\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n     + \n       \t\t}\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, line);\n       \n       \t/*\n       \t * Swap mapping and new_mapping\n     @@ -268,74 +411,66 @@\n       \n       int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n       {\n     -+\tsb->width = 0;\n     ++\tstruct graph_line line = { .buf = sb, .width = 0 };\n      +\n       \tswitch (graph->state) {\n       \tcase GRAPH_PADDING:\n     - \t\tgraph_output_padding_line(graph, sb);\n     +-\t\tgraph_output_padding_line(graph, sb);\n     ++\t\tgraph_output_padding_line(graph, &line);\n     + \t\treturn 0;\n     + \tcase GRAPH_SKIP:\n     +-\t\tgraph_output_skip_line(graph, sb);\n     ++\t\tgraph_output_skip_line(graph, &line);\n     + \t\treturn 0;\n     + \tcase GRAPH_PRE_COMMIT:\n     +-\t\tgraph_output_pre_commit_line(graph, sb);\n     ++\t\tgraph_output_pre_commit_line(graph, &line);\n     + \t\treturn 0;\n     + \tcase GRAPH_COMMIT:\n     +-\t\tgraph_output_commit_line(graph, sb);\n     ++\t\tgraph_output_commit_line(graph, &line);\n     + \t\treturn 1;\n     + \tcase GRAPH_POST_MERGE:\n     +-\t\tgraph_output_post_merge_line(graph, sb);\n     ++\t\tgraph_output_post_merge_line(graph, &line);\n     + \t\treturn 0;\n     + \tcase GRAPH_COLLAPSING:\n     +-\t\tgraph_output_collapsing_line(graph, sb);\n     ++\t\tgraph_output_collapsing_line(graph, &line);\n     + \t\treturn 0;\n     + \t}\n     + \n      @@\n       static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n       {\n       \tint i;\n      -\tint chars_written = 0;\n     ++\tstruct graph_line line = { .buf = sb, .width = 0 };\n       \n       \tif (graph->state != GRAPH_COMMIT) {\n       \t\tgraph_next_line(graph, sb);\n      @@\n     + \tfor (i = 0; i < graph->num_columns; i++) {\n       \t\tstruct column *col = &graph->columns[i];\n       \n     - \t\tstrbuf_write_column(sb, col, '|');\n     +-\t\tstrbuf_write_column(sb, col, '|');\n      -\t\tchars_written++;\n     ++\t\tgraph_line_write_column(&line, col, '|');\n       \n       \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n       \t\t\tint len = (graph->num_parents - 2) * 2;\n     - \t\t\tstrbuf_addchars(sb, ' ', len);\n     +-\t\t\tstrbuf_addchars(sb, ' ', len);\n      -\t\t\tchars_written += len;\n     ++\t\t\tgraph_line_addchars(&line, ' ', len);\n       \t\t} else {\n     - \t\t\tstrbuf_addch(sb, ' ');\n     +-\t\t\tstrbuf_addch(sb, ' ');\n      -\t\t\tchars_written++;\n     ++\t\t\tgraph_line_addch(&line, ' ');\n       \t\t}\n       \t}\n       \n      -\tgraph_pad_horizontally(graph, sb, chars_written);\n     -+\tgraph_pad_horizontally(graph, sb);\n     ++\tgraph_pad_horizontally(graph, &line);\n       \n       \t/*\n       \t * Update graph->prev_state since we have output a padding line\n     -\n     - diff --git a/strbuf.h b/strbuf.h\n     - --- a/strbuf.h\n     - +++ b/strbuf.h\n     -@@\n     - struct strbuf {\n     - \tsize_t alloc;\n     - \tsize_t len;\n     -+\tsize_t width;\n     - \tchar *buf;\n     - };\n     - \n     - extern char strbuf_slopbuf[];\n     --#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }\n     -+#define STRBUF_INIT  { .alloc = 0, .len = 0, .width = 0, .buf = strbuf_slopbuf }\n     - \n     - /*\n     -  * Predeclare this here, since cache.h includes this file before it defines the\n     -@@\n     - {\n     - \tif (len > (sb->alloc ? sb->alloc - 1 : 0))\n     - \t\tdie(\"BUG: strbuf_setlen() beyond buffer\");\n     -+\tif (len > sb->len)\n     -+\t\tsb->width += len - sb->len;\n     -+\telse\n     -+\t\tsb->width = len;\n     - \tsb->len = len;\n     - \tif (sb->buf != strbuf_slopbuf)\n     - \t\tsb->buf[len] = '\\0';\n     -@@\n     - \t\tstrbuf_grow(sb, 1);\n     - \tsb->buf[sb->len++] = c;\n     - \tsb->buf[sb->len] = '\\0';\n     -+\tsb->width++;\n     - }\n     - \n     - /**\n  -:  ---------- >  2:  d962eb2e02 graph: handle line padding in `graph_next_line()`\n  2:  a130574191 !  3:  ecfcfbfd5c graph: reuse `find_new_column_by_commit()`\n     @@ -71,7 +71,7 @@\n      -\treturn NULL;\n      -}\n      -\n     - static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n     + static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n       {\n       \tint seen_this = 0;\n      @@\n     @@ -88,17 +88,17 @@\n      +\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n      +\t\t\tassert(par_column >= 0);\n       \n     --\t\t\tstrbuf_write_column(sb, par_column, '|');\n     -+\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '|');\n     +-\t\t\tgraph_line_write_column(line, par_column, '|');\n     ++\t\t\tgraph_line_write_column(line, &graph->new_columns[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\tgraph_line_write_column(line, par_column, '\\\\');\n      +\t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n      +\t\t\t\tassert(par_column >= 0);\n     -+\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '\\\\');\n     - \t\t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n     + \t\t\t\tgraph_line_addch(line, ' ');\n       \t\t\t}\n       \t\t} else if (seen_this) {\n  3:  21a36efd7b =  4:  50ce875ed9 graph: reduce duplication in `graph_insert_into_new_columns()`\n  4:  674b992371 =  5:  d2e8958eed graph: remove `mapping_idx` and `graph_update_width()`\n  5:  d5d60ca9a2 !  6:  163600585c graph: extract logic for moving to GRAPH_PRE_COMMIT state\n     @@ -35,8 +35,8 @@\n       \telse\n       \t\tgraph->state = GRAPH_COMMIT;\n      @@\n     - \tstrbuf_addstr(sb, \"...\");\n     - \tgraph_pad_horizontally(graph, sb);\n     + \t */\n     + \tgraph_line_addstr(line, \"...\");\n       \n      -\tif (graph->num_parents >= 3 &&\n      -\t    graph->commit_index < (graph->num_columns - 1))\n  -:  ---------- >  7:  51495be940 graph: example of graph output that can be simplified\n  6:  12c0916cb1 !  8:  2ab0f9775b graph: tidy up display of left-skewed merges\n     @@ -4,7 +4,7 @@\n      \n          Currently, when we display a merge whose first parent is already present\n          in a column to the left of the merge commit, we display the first parent\n     -    as a veritcal pipe `|` in the GRAPH_POST_MERGE line and then immediately\n     +    as a vertical pipe `|` in the GRAPH_POST_MERGE line and then immediately\n          enter the GRAPH_COLLAPSING state. The first-parent line tracks to the\n          left and all the other parent lines follow it; this creates a \"kink\" in\n          those lines:\n     @@ -187,7 +187,7 @@\n       void graph_update(struct git_graph *graph, struct commit *commit)\n      @@\n       static void graph_output_pre_commit_line(struct git_graph *graph,\n     - \t\t\t\t\t struct strbuf *sb)\n     + \t\t\t\t\t struct graph_line *line)\n       {\n      -\tint num_expansion_rows;\n       \tint i, seen_this;\n     @@ -246,7 +246,7 @@\n       \n      +const char merge_chars[] = {'/', '|', '\\\\'};\n      +\n     - static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n     + static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n       {\n       \tint seen_this = 0;\n      -\tint i, j;\n     @@ -272,7 +272,7 @@\n      -\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n      -\t\t\tassert(par_column >= 0);\n      -\n     --\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '|');\n     +-\t\t\tgraph_line_write_column(line, &graph->new_columns[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     @@ -280,37 +280,37 @@\n      +\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n       \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n       \t\t\t\tassert(par_column >= 0);\n     --\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], '\\\\');\n     --\t\t\t\tstrbuf_addch(sb, ' ');\n     +-\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n     +-\t\t\t\tgraph_line_addch(line, ' ');\n      +\n      +\t\t\t\tc = merge_chars[idx];\n     -+\t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], c);\n     ++\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n      +\t\t\t\tif (idx == 2)\n     -+\t\t\t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\t\t\tgraph_line_addch(line, ' ');\n      +\t\t\t\telse\n      +\t\t\t\t\tidx++;\n       \t\t\t}\n       \t\t} else if (seen_this) {\n     - \t\t\tstrbuf_write_column(sb, col, '\\\\');\n     - \t\t\tstrbuf_addch(sb, ' ');\n     + \t\t\tgraph_line_write_column(line, col, '\\\\');\n     + \t\t\tgraph_line_addch(line, ' ');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     --\t\t\tstrbuf_addch(sb, ' ');\n     + \t\t\tgraph_line_write_column(line, col, '|');\n     +-\t\t\tgraph_line_addch(line, ' ');\n      +\t\t\tif (graph->merge_layout != 0 || i != graph->commit_index - 1)\n     -+\t\t\t\tstrbuf_addch(sb, seen_parent ? '_' : ' ');\n     ++\t\t\t\tgraph_line_addch(line, seen_parent ? '_' : ' ');\n       \t\t}\n      +\n      +\t\tif (col_commit == first_parent->item)\n      +\t\t\tseen_parent = 1;\n       \t}\n       \n     - \tgraph_pad_horizontally(graph, sb);\n     + \t/*\n      \n       diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\n       --- a/t/t4214-log-graph-octopus.sh\n       +++ b/t/t4214-log-graph-octopus.sh\n      @@\n     - test_expect_success 'set up merge history' '\n     + test_expect_success 'log --graph with tricky octopus merge, no color' '\n       \tcat >expect.uncolored <<-\\EOF &&\n       \t* left\n      -\t| *---.   octopus-merge\n     @@ -322,7 +322,7 @@\n       \t| | * | 3\n       \t| | |/\n      @@\n     - \tEOF\n     + \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n       \tcat >expect.colors <<-\\EOF &&\n       \t* left\n      -\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n     @@ -333,51 +333,88 @@\n       \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n       \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n       \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n     +@@\n     + \tcat >expect.uncolored <<-\\EOF &&\n     + \t* left\n     + \t| * after-merge\n     +-\t| *---.   octopus-merge\n     +-\t| |\\ \\ \\\n     +-\t|/ / / /\n     ++\t| *-.   octopus-merge\n     ++\t|/|\\ \\\n     + \t| | | * 4\n     + \t| | * | 3\n     + \t| | |/\n     +@@\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* left\n     + \t<RED>|<RESET> * after-merge\n     +-\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n     +-\t<RED>|<RESET> <RED>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET> <CYAN>\\<RESET>\n     +-\t<RED>|<RESET><RED>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> <CYAN>/<RESET>\n     ++\t<RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n     ++\t<RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\\<RESET> <CYAN>\\<RESET>\n     + \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n     + \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n     + \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n      \n       diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n     - new file mode 100755\n     - --- /dev/null\n     + --- a/t/t4215-log-skewed-merges.sh\n       +++ b/t/t4215-log-skewed-merges.sh\n      @@\n     -+#!/bin/sh\n     -+\n     -+test_description='git log --graph of skewed merges'\n     -+\n     -+. ./test-lib.sh\n     -+\n     -+test_expect_success 'setup left-skewed merge' '\n     -+\tgit checkout --orphan _a && test_commit A &&\n     -+\tgit branch _b &&\n     -+\tgit branch _c &&\n     -+\tgit branch _d &&\n     -+\tgit branch _e &&\n     -+\tgit checkout _b && test_commit B &&\n     -+\tgit checkout _c && test_commit C &&\n     -+\tgit checkout _d && test_commit D &&\n     -+\tgit checkout _e && git merge --no-ff _d -m E &&\n     -+\tgit checkout _a && git merge --no-ff _b _c _e -m F\n     -+'\n     + \t| *   G\n     + \t| |\\\n     + \t| | * F\n     +-\t| | |\n     +-\t| |  \\\n     +-\t| *-. \\   E\n     +-\t| |\\ \\ \\\n     +-\t|/ / / /\n     +-\t| | | /\n     ++\t| * \\   E\n     ++\t|/|\\ \\\n     + \t| | |/\n     + \t| | * D\n     + \t| * | C\n     +@@\n     + \ttest_cmp expect actual\n     + '\n     + \n     ++test_expect_success 'log --graph with left-skewed merge' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*-----.   0_H\n     ++\t|\\ \\ \\ \\\n     ++\t| | | | * 0_G\n     ++\t| |_|_|/|\n     ++\t|/| | | |\n     ++\t| | | * \\   0_F\n     ++\t| |_|/|\\ \\\n     ++\t|/| | | |/\n     ++\t| | | | * 0_E\n     ++\t| |_|_|/\n     ++\t|/| | |\n     ++\t| | * | 0_D\n     ++\t| | |/\n     ++\t| | * 0_C\n     ++\t| |/\n     ++\t|/|\n     ++\t| * 0_B\n     ++\t|/\n     ++\t* 0_A\n     ++\tEOF\n      +\n     -+cat > expect <<\\EOF\n     -+*---.   F\n     -+|\\ \\ \\\n     -+| | | * E\n     -+| |_|/|\n     -+|/| | |\n     -+| | | * D\n     -+| |_|/\n     -+|/| |\n     -+| | * C\n     -+| |/\n     -+|/|\n     -+| * B\n     -+|/\n     -+* A\n     -+EOF\n     ++\tgit checkout --orphan 0_p && test_commit 0_A &&\n     ++\tgit checkout -b 0_q 0_p && test_commit 0_B &&\n     ++\tgit checkout -b 0_r 0_p &&\n     ++\ttest_commit 0_C &&\n     ++\ttest_commit 0_D &&\n     ++\tgit checkout -b 0_s 0_p && test_commit 0_E &&\n     ++\tgit checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&\n     ++\tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n     ++\tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n      +\n     -+test_expect_success 'log --graph with left-skewed merge' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_done\n     + test_done\n  7:  6c173663aa !  9:  0e37c88c60 graph: commit and post-merge lines for left-skewed merges\n     @@ -183,10 +183,10 @@\n      @@\n       \n       \t\t\tif (graph->num_parents > 2)\n     - \t\t\t\tgraph_draw_octopus_merge(graph, sb);\n     + \t\t\t\tgraph_draw_octopus_merge(graph, line);\n      -\t\t} else if (seen_this && (graph->num_parents > 2)) {\n      +\t\t} else if (seen_this && (graph->edges_added > 1)) {\n     - \t\t\tstrbuf_write_column(sb, col, '\\\\');\n     + \t\t\tgraph_line_write_column(line, col, '\\\\');\n      -\t\t} else if (seen_this && (graph->num_parents == 2)) {\n      +\t\t} else if (seen_this && (graph->edges_added == 1)) {\n       \t\t\t/*\n     @@ -204,33 +204,67 @@\n       \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n      +\t\t\t    graph->prev_edges_added > 0 &&\n       \t\t\t    graph->prev_commit_index < i)\n     - \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n     + \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n       \t\t\telse\n      @@\n       \t\t\t\telse\n       \t\t\t\t\tidx++;\n       \t\t\t}\n      +\t\t\tif (graph->edges_added == 0)\n     -+\t\t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\t\tgraph_line_addch(line, ' ');\n      +\n       \t\t} else if (seen_this) {\n     --\t\t\tstrbuf_write_column(sb, col, '\\\\');\n     +-\t\t\tgraph_line_write_column(line, col, '\\\\');\n      +\t\t\tif (graph->edges_added > 0)\n     -+\t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n     ++\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n      +\t\t\telse\n     -+\t\t\t\tstrbuf_write_column(sb, col, '|');\n     - \t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\t\tgraph_line_write_column(line, col, '|');\n     + \t\t\tgraph_line_addch(line, ' ');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     + \t\t\tgraph_line_write_column(line, col, '|');\n      \n       diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n       --- a/t/t4215-log-skewed-merges.sh\n       +++ b/t/t4215-log-skewed-merges.sh\n     +@@\n     + \t| *   G\n     + \t| |\\\n     + \t| | * F\n     +-\t| * \\   E\n     ++\t| * |   E\n     + \t|/|\\ \\\n     + \t| | |/\n     + \t| | * D\n     +@@\n     + \t| | | | * 0_G\n     + \t| |_|_|/|\n     + \t|/| | | |\n     +-\t| | | * \\   0_F\n     ++\t| | | * |   0_F\n     + \t| |_|/|\\ \\\n     + \t|/| | | |/\n     + \t| | | | * 0_E\n      @@\n       \ttest_cmp expect actual\n       '\n       \n     -+test_expect_success 'setup nested left-skewed merge' '\n     ++test_expect_success 'log --graph with nested left-skewed merge' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*   1_H\n     ++\t|\\\n     ++\t| *   1_G\n     ++\t| |\\\n     ++\t| | * 1_F\n     ++\t| * | 1_E\n     ++\t|/| |\n     ++\t| * | 1_D\n     ++\t* | | 1_C\n     ++\t|/ /\n     ++\t* | 1_B\n     ++\t|/\n     ++\t* 1_A\n     ++\tEOF\n     ++\n      +\tgit checkout --orphan 1_p &&\n      +\ttest_commit 1_A &&\n      +\ttest_commit 1_B &&\n     @@ -239,31 +273,34 @@\n      +\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n      +\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n      +\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n     -+\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H\n     -+'\n     -+\n     -+cat > expect <<\\EOF\n     -+*   1_H\n     -+|\\\n     -+| *   1_G\n     -+| |\\\n     -+| | * 1_F\n     -+| * | 1_E\n     -+|/| |\n     -+| * | 1_D\n     -+* | | 1_C\n     -+|/ /\n     -+* | 1_B\n     -+|/\n     -+* 1_A\n     -+EOF\n     ++\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n      +\n     -+test_expect_success 'log --graph with nested left-skewed merge' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'setup nested left-skewed merge following normal merge' '\n     ++test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*   2_K\n     ++\t|\\\n     ++\t| *   2_J\n     ++\t| |\\\n     ++\t| | *   2_H\n     ++\t| | |\\\n     ++\t| | * | 2_G\n     ++\t| |/| |\n     ++\t| | * | 2_F\n     ++\t| * | | 2_E\n     ++\t| |/ /\n     ++\t| * | 2_D\n     ++\t* | | 2_C\n     ++\t| |/\n     ++\t|/|\n     ++\t* | 2_B\n     ++\t|/\n     ++\t* 2_A\n     ++\tEOF\n     ++\n      +\tgit checkout --orphan 2_p &&\n      +\ttest_commit 2_A &&\n      +\ttest_commit 2_B &&\n     @@ -276,36 +313,32 @@\n      +\tgit merge --no-ff 2_r -m 2_G &&\n      +\tgit merge --no-ff 2_p^ -m 2_H &&\n      +\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n     -+\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K\n     -+'\n     -+\n     -+cat > expect <<\\EOF\n     -+*   2_K\n     -+|\\\n     -+| *   2_J\n     -+| |\\\n     -+| | *   2_H\n     -+| | |\\\n     -+| | * | 2_G\n     -+| |/| |\n     -+| | * | 2_F\n     -+| * | | 2_E\n     -+| |/ /\n     -+| * | 2_D\n     -+* | | 2_C\n     -+| |/\n     -+|/|\n     -+* | 2_B\n     -+|/\n     -+* 2_A\n     -+EOF\n     ++\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n      +\n     -+test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'setup nested right-skewed merge following left-skewed merge' '\n     ++test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*   3_J\n     ++\t|\\\n     ++\t| *   3_H\n     ++\t| |\\\n     ++\t| | * 3_G\n     ++\t| * | 3_F\n     ++\t|/| |\n     ++\t| * |   3_E\n     ++\t| |\\ \\\n     ++\t| | |/\n     ++\t| | * 3_D\n     ++\t| * | 3_C\n     ++\t| |/\n     ++\t| * 3_B\n     ++\t|/\n     ++\t* 3_A\n     ++\tEOF\n     ++\n      +\tgit checkout --orphan 3_p &&\n      +\ttest_commit 3_A &&\n      +\tgit checkout -b 3_q &&\n     @@ -317,34 +350,32 @@\n      +\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n      +\tgit checkout 3_r && test_commit 3_G &&\n      +\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n     -+\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J\n     -+'\n     -+\n     -+cat > expect <<\\EOF\n     -+*   3_J\n     -+|\\\n     -+| *   3_H\n     -+| |\\\n     -+| | * 3_G\n     -+| * | 3_F\n     -+|/| |\n     -+| * |   3_E\n     -+| |\\ \\\n     -+| | |/\n     -+| | * 3_D\n     -+| * | 3_C\n     -+| |/\n     -+| * 3_B\n     -+|/\n     -+* 3_A\n     -+EOF\n     ++\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n      +\n     -+test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'setup right-skewed merge following a left-skewed one' '\n     ++test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*   4_H\n     ++\t|\\\n     ++\t| *   4_G\n     ++\t| |\\\n     ++\t| * | 4_F\n     ++\t|/| |\n     ++\t| * |   4_E\n     ++\t| |\\ \\\n     ++\t| | * | 4_D\n     ++\t| |/ /\n     ++\t|/| |\n     ++\t| | * 4_C\n     ++\t| |/\n     ++\t| * 4_B\n     ++\t|/\n     ++\t* 4_A\n     ++\tEOF\n     ++\n      +\tgit checkout --orphan 4_p &&\n      +\ttest_commit 4_A &&\n      +\ttest_commit 4_B &&\n     @@ -354,30 +385,9 @@\n      +\tgit checkout -b 4_s 4_p^^ &&\n      +\tgit merge --no-ff 4_r -m 4_F &&\n      +\tgit merge --no-ff 4_p -m 4_G &&\n     -+\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H\n     -+'\n     ++\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n      +\n     -+cat > expect <<\\EOF\n     -+*   4_H\n     -+|\\\n     -+| *   4_G\n     -+| |\\\n     -+| * | 4_F\n     -+|/| |\n     -+| * |   4_E\n     -+| |\\ \\\n     -+| | * | 4_D\n     -+| |/ /\n     -+|/| |\n     -+| | * 4_C\n     -+| |/\n     -+| * 4_B\n     -+|/\n     -+* 4_A\n     -+EOF\n     -+\n     -+test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n     -+\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n  8:  23f13bbefa ! 10:  9bbf738e6d graph: rename `new_mapping` to `old_mapping`\n     @@ -146,7 +146,7 @@\n      -\t\tint target = graph->new_mapping[i];\n      +\t\tint target = graph->mapping[i];\n       \t\tif (target < 0)\n     - \t\t\tstrbuf_addch(sb, ' ');\n     + \t\t\tgraph_line_addch(line, ' ');\n       \t\telse if (target * 2 == i)\n      @@\n       \t\t\t\t * won't continue into the next line.\n     @@ -155,17 +155,15 @@\n      -\t\t\t\t\tgraph->new_mapping[i] = -1;\n      +\t\t\t\t\tgraph->mapping[i] = -1;\n       \t\t\t\tused_horizontal = 1;\n     - \t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n     + \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n       \t\t} else {\n       \t\t\tif (used_horizontal && i < horizontal_edge)\n      -\t\t\t\tgraph->new_mapping[i] = -1;\n      +\t\t\t\tgraph->mapping[i] = -1;\n     - \t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n     + \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n       \n       \t\t}\n     -@@\n     - \n     - \tgraph_pad_horizontally(graph, sb);\n     + \t}\n       \n      -\t/*\n      -\t * Swap mapping and new_mapping\n  9:  f9ced9090d ! 11:  67051ec31a graph: smooth appearance of collapsing edges on commit lines\n     @@ -77,15 +77,15 @@\n       \tint *old_mapping;\n       \t/*\n      @@\n     - \t\t\t\tstrbuf_write_column(sb, col, '\\\\');\n     + \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n       \t\t\telse\n     - \t\t\t\tstrbuf_write_column(sb, col, '|');\n     + \t\t\t\tgraph_line_write_column(line, col, '|');\n      +\t\t} else if (graph->prev_state == GRAPH_COLLAPSING &&\n      +\t\t\t   graph->old_mapping[2 * i + 1] == i &&\n      +\t\t\t   graph->mapping[2 * i] < i) {\n     -+\t\t\tstrbuf_write_column(sb, col, '/');\n     ++\t\t\tgraph_line_write_column(line, col, '/');\n       \t\t} else {\n     - \t\t\tstrbuf_write_column(sb, col, '|');\n     + \t\t\tgraph_line_write_column(line, col, '|');\n       \t\t}\n      @@\n       \t\t}\n     @@ -177,19 +177,76 @@\n       \t<BLUE>|<RESET><BLUE>/<RESET>\n       \t* initial\n       \tEOF\n     +@@\n     + \t| | | * 4\n     + \t| | * | 3\n     + \t| | |/\n     +-\t| * | 2\n     ++\t| * / 2\n     + \t| |/\n     +-\t* | 1\n     ++\t* / 1\n     + \t|/\n     + \t* initial\n     + \tEOF\n     +@@\n     + \t<GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n     + \t<GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n     + \t<GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n     +-\t<GREEN>|<RESET> * <MAGENTA>|<RESET> 2\n     ++\t<GREEN>|<RESET> * <MAGENTA>/<RESET> 2\n     + \t<GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n     +-\t* <MAGENTA>|<RESET> 1\n     ++\t* <MAGENTA>/<RESET> 1\n     + \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n     + \t* initial\n     + \tEOF\n     +@@\n     + \t| | | * 4\n     + \t| | * | 3\n     + \t| | |/\n     +-\t| * | 2\n     ++\t| * / 2\n     + \t| |/\n     +-\t* | 1\n     ++\t* / 1\n     + \t|/\n     + \t* initial\n     + \tEOF\n     +@@\n     + \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n     + \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n     + \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n     +-\t<RED>|<RESET> * <CYAN>|<RESET> 2\n     ++\t<RED>|<RESET> * <CYAN>/<RESET> 2\n     + \t<RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n     +-\t* <CYAN>|<RESET> 1\n     ++\t* <CYAN>/<RESET> 1\n     + \t<CYAN>|<RESET><CYAN>/<RESET>\n     + \t* initial\n     + \tEOF\n      \n       diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n       --- a/t/t4215-log-skewed-merges.sh\n       +++ b/t/t4215-log-skewed-merges.sh\n      @@\n     - | * | 1_D\n     - * | | 1_C\n     - |/ /\n     --* | 1_B\n     -+* / 1_B\n     - |/\n     - * 1_A\n     - EOF\n     + \t| | * D\n     + \t| * | C\n     + \t| |/\n     +-\t* | B\n     ++\t* / B\n     + \t|/\n     + \t* A\n     + \tEOF\n     +@@\n     + \t| * | 1_D\n     + \t* | | 1_C\n     + \t|/ /\n     +-\t* | 1_B\n     ++\t* / 1_B\n     + \t|/\n     + \t* 1_A\n     + \tEOF\n      \n       diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\n       --- a/t/t6016-rev-list-graph-simplify-history.sh\n 10:  50756edcf7 ! 12:  503c846d2b graph: flatten edges that join to their right neighbor\n     @@ -1,6 +1,6 @@\n      Author: James Coglan <jcoglan@gmail.com>\n      \n     -    graph: flatten edges that join to their right neighbor\n     +    graph: flatten edges that fuse with their right neighbor\n      \n          When a merge commit is printed and its final parent is the same commit\n          that occupies the column to the right of the merge, this results in a\n     @@ -109,7 +109,7 @@\n       \n       \t/*\n      @@\n     - static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n     + static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n       {\n       \tint seen_this = 0;\n      -\tint i;\n     @@ -127,41 +127,86 @@\n       \t\t\t\tassert(par_column >= 0);\n       \n       \t\t\t\tc = merge_chars[idx];\n     - \t\t\t\tstrbuf_write_column(sb, &graph->new_columns[par_column], c);\n     + \t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n      -\t\t\t\tif (idx == 2)\n     --\t\t\t\t\tstrbuf_addch(sb, ' ');\n     +-\t\t\t\t\tgraph_line_addch(line, ' ');\n      -\t\t\t\telse\n      +\t\t\t\tif (idx == 2) {\n      +\t\t\t\t\tif (graph->edges_added > 0 || j < graph->num_parents - 1)\n     -+\t\t\t\t\t\tstrbuf_addch(sb, ' ');\n     ++\t\t\t\t\t\tgraph_line_addch(line, ' ');\n      +\t\t\t\t} else {\n       \t\t\t\t\tidx++;\n      +\t\t\t\t}\n      +\t\t\t\tparents = next_interesting_parent(graph, parents);\n       \t\t\t}\n       \t\t\tif (graph->edges_added == 0)\n     - \t\t\t\tstrbuf_addch(sb, ' ');\n     + \t\t\t\tgraph_line_addch(line, ' ');\n      \n       diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n       --- a/t/t4215-log-skewed-merges.sh\n       +++ b/t/t4215-log-skewed-merges.sh\n      @@\n     - | | * 3_G\n     - | * | 3_F\n     - |/| |\n     --| * |   3_E\n     --| |\\ \\\n     --| | |/\n     -+| * | 3_E\n     -+| |\\|\n     - | | * 3_D\n     - | * | 3_C\n     - | |/\n     + \t| *   G\n     + \t| |\\\n     + \t| | * F\n     +-\t| * |   E\n     +-\t|/|\\ \\\n     +-\t| | |/\n     ++\t| * | E\n     ++\t|/|\\|\n     + \t| | * D\n     + \t| * | C\n     + \t| |/\n     +@@\n     + \t| | | | * 0_G\n     + \t| |_|_|/|\n     + \t|/| | | |\n     +-\t| | | * |   0_F\n     +-\t| |_|/|\\ \\\n     +-\t|/| | | |/\n     ++\t| | | * | 0_F\n     ++\t| |_|/|\\|\n     ++\t|/| | | |\n     + \t| | | | * 0_E\n     + \t| |_|_|/\n     + \t|/| | |\n     +@@\n     + \t| | * 3_G\n     + \t| * | 3_F\n     + \t|/| |\n     +-\t| * |   3_E\n     +-\t| |\\ \\\n     +-\t| | |/\n     ++\t| * | 3_E\n     ++\t| |\\|\n     + \t| | * 3_D\n     + \t| * | 3_C\n     + \t| |/\n      @@\n       \ttest_cmp expect actual\n       '\n       \n     -+test_expect_success 'setup octopus merge with column joining its penultimate parent' '\n     ++test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\t*   5_H\n     ++\t|\\\n     ++\t| *-.   5_G\n     ++\t| |\\ \\\n     ++\t| | | * 5_F\n     ++\t| | * |   5_E\n     ++\t| |/|\\ \\\n     ++\t| |_|/ /\n     ++\t|/| | /\n     ++\t| | |/\n     ++\t* | | 5_D\n     ++\t| | * 5_C\n     ++\t| |/\n     ++\t|/|\n     ++\t| * 5_B\n     ++\t|/\n     ++\t* 5_A\n     ++\tEOF\n     ++\n      +\tgit checkout --orphan 5_p &&\n      +\ttest_commit 5_A &&\n      +\tgit branch 5_q &&\n     @@ -175,64 +220,9 @@\n      +\tgit checkout -b 5_s 5_p^ &&\n      +\tgit merge --no-ff 5_p 5_q -m 5_G &&\n      +\tgit checkout 5_r &&\n     -+\tgit merge --no-ff 5_s -m 5_H\n     -+'\n     -+\n     -+cat > expect <<\\EOF\n     -+*   5_H\n     -+|\\\n     -+| *-.   5_G\n     -+| |\\ \\\n     -+| | | * 5_F\n     -+| | * |   5_E\n     -+| |/|\\ \\\n     -+| |_|/ /\n     -+|/| | /\n     -+| | |/\n     -+* | | 5_D\n     -+| | * 5_C\n     -+| |/\n     -+|/|\n     -+| * 5_B\n     -+|/\n     -+* 5_A\n     -+EOF\n     -+\n     -+test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     -+\ttest_cmp expect actual\n     -+'\n     -+\n     -+test_expect_success 'setup merge fusing with its left and right neighbors' '\n     -+\tgit checkout --orphan 6_p &&\n     -+\ttest_commit 6_A &&\n     -+\ttest_commit 6_B &&\n     -+\tgit checkout -b 6_q @^ && test_commit 6_C &&\n     -+\tgit checkout -b 6_r @^ && test_commit 6_D &&\n     -+\tgit checkout 6_p && git merge --no-ff 6_q 6_r -m 6_E &&\n     -+\tgit checkout 6_r && test_commit 6_F &&\n     -+\tgit checkout 6_p && git merge --no-ff 6_r -m 6_G &&\n     -+\tgit checkout @^^ && git merge --no-ff 6_p -m 6_H\n     -+'\n     -+\n     -+cat > expect <<\\EOF\n     -+*   6_H\n     -+|\\\n     -+| *   6_G\n     -+| |\\\n     -+| | * 6_F\n     -+| * | 6_E\n     -+|/|\\|\n     -+| | * 6_D\n     -+| * | 6_C\n     -+| |/\n     -+* / 6_B\n     -+|/\n     -+* 6_A\n     -+EOF\n     ++\tgit merge --no-ff 5_s -m 5_H &&\n      +\n     -+test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n     -+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" > actual &&\n     ++\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n 11:  ea0df1d94a ! 13:  07ddd509c5 graph: fix coloring of octopus dashes\n     @@ -81,7 +81,7 @@\n       \n       static int graph_needs_pre_commit_line(struct git_graph *graph)\n      @@\n     - static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf *sb)\n     + static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n       {\n       \t/*\n      -\t * Here dashless_parents represents the number of parents which don't\n     @@ -142,103 +142,69 @@\n       \n      -\tint i;\n      -\tfor (i = 0; i < dashful_parents; i++) {\n     --\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n     --\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n     --\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n     +-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n     +-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n     +-\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n      +\tint dashed_parents = graph_num_dashed_parents(graph);\n      +\n      +\tfor (i = 0; i < dashed_parents; i++) {\n      +\t\tj = graph->mapping[(graph->commit_index + i + 2) * 2];\n      +\t\tcol = &graph->new_columns[j];\n      +\n     -+\t\tstrbuf_write_column(sb, col, '-');\n     -+\t\tstrbuf_write_column(sb, col, (i == dashed_parents - 1) ? '.' : '-');\n     ++\t\tgraph_line_write_column(line, col, '-');\n     ++\t\tgraph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-');\n       \t}\n      +\n      +\treturn;\n       }\n       \n     - static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n     + static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n      \n       diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\n       --- a/t/t4214-log-graph-octopus.sh\n       +++ b/t/t4214-log-graph-octopus.sh\n      @@\n     - \ttest_tick &&\n     - \tgit merge -m octopus-merge 1 2 3 4 &&\n     - \tgit checkout 1 -b L &&\n     --\ttest_commit left\n     -+\ttest_commit left &&\n     -+\tgit checkout 4 -b R &&\n     -+\ttest_commit right\n     + \ttest_cmp expect.uncolored actual\n     + '\n     + \n     +-test_expect_failure 'log --graph with normal octopus and child merge with colors' '\n     ++test_expect_success 'log --graph with normal octopus and child merge with colors' '\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* after-merge\n     + \t*<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n     +@@\n     + \ttest_cmp expect.uncolored actual\n       '\n       \n     - test_expect_success 'log --graph with tricky octopus merge with colors' '\n     +-test_expect_failure 'log --graph with tricky octopus merge and its child with colors' '\n     ++test_expect_success 'log --graph with tricky octopus merge and its child with colors' '\n       \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n     --\tgit log --color=always --graph --date-order --pretty=tformat:%s --all >actual.colors.raw &&\n     -+\tgit log --color=always --graph --date-order --pretty=tformat:%s L merge >actual.colors.raw &&\n     - \ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n     - \ttest_cmp expect.colors actual.colors\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* left\n     +@@\n     + \ttest_cmp expect.uncolored actual\n       '\n       \n     - test_expect_success 'log --graph with tricky octopus merge, no color' '\n     --\tgit log --color=never --graph --date-order --pretty=tformat:%s --all >actual.raw &&\n     -+\tgit log --color=never --graph --date-order --pretty=tformat:%s L merge >actual.raw &&\n     - \tsed \"s/ *\\$//\" actual.raw >actual &&\n     +-test_expect_failure 'log --graph with crossover in octopus merge with colors' '\n     ++test_expect_success 'log --graph with crossover in octopus merge with colors' '\n     + \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* after-4\n     +@@\n       \ttest_cmp expect.uncolored actual\n       '\n       \n     --# Repeat the previous two tests with \"normal\" octopus merge (i.e.,\n     -+# Repeat the previous two tests with an octopus merge whose final parent skews left\n     -+\n     -+test_expect_success 'log --graph with left-skewed final parent, no color' '\n     -+\tcat >expect.uncolored <<-\\EOF &&\n     -+\t* right\n     -+\t| *---.   octopus-merge\n     -+\t| |\\ \\ \\\n     -+\t| |_|_|/\n     -+\t|/| | |\n     -+\t* | | | 4\n     -+\t| | | * 3\n     -+\t| |_|/\n     -+\t|/| |\n     -+\t| | * 2\n     -+\t| |/\n     -+\t|/|\n     -+\t| * 1\n     -+\t|/\n     -+\t* initial\n     -+\tEOF\n     -+\tgit log --color=never --graph --date-order --pretty=tformat:%s R merge >actual.raw &&\n     -+\tsed \"s/ *\\$//\" actual.raw >actual &&\n     -+\ttest_cmp expect.uncolored actual\n     -+'\n     -+\n     -+test_expect_success 'log --graph with left-skewed final parent with colors' '\n     -+\tcat >expect.colors <<-\\EOF &&\n     -+\t* right\n     -+\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><RED>-<RESET><RED>.<RESET>   octopus-merge\n     -+\t<RED>|<RESET> <GREEN>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <RED>\\<RESET>\n     -+\t<RED>|<RESET> <GREEN>|<RESET><RED>_<RESET><YELLOW>|<RESET><RED>_<RESET><BLUE>|<RESET><RED>/<RESET>\n     -+\t<RED>|<RESET><RED>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET>\n     -+\t* <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> 4\n     -+\t<MAGENTA>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 3\n     -+\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>_<RESET><YELLOW>|<RESET><MAGENTA>/<RESET>\n     -+\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET> <YELLOW>|<RESET>\n     -+\t<MAGENTA>|<RESET> <GREEN>|<RESET> * 2\n     -+\t<MAGENTA>|<RESET> <GREEN>|<RESET><MAGENTA>/<RESET>\n     -+\t<MAGENTA>|<RESET><MAGENTA>/<RESET><GREEN>|<RESET>\n     -+\t<MAGENTA>|<RESET> * 1\n     -+\t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n     -+\t* initial\n     -+\tEOF\n     -+\ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n     -+\tgit log --color=always --graph --date-order --pretty=tformat:%s R merge >actual.colors.raw &&\n     -+\ttest_decode_color <actual.colors.raw | sed \"s/ *\\$//\" >actual.colors &&\n     -+\ttest_cmp expect.colors actual.colors\n     -+'\n     -+\n     -+# Repeat the first two tests with \"normal\" octopus merge (i.e.,\n     - # without the first parent skewing to the \"left\" branch column).\n     +-test_expect_failure 'log --graph with crossover in octopus merge and its child with colors' '\n     ++test_expect_success 'log --graph with crossover in octopus merge and its child with colors' '\n     + \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* after-4\n     +@@\n     + \ttest_cmp expect.uncolored actual\n     + '\n       \n     - test_expect_success 'log --graph with normal octopus merge, no color' '\n     +-test_expect_failure 'log --graph with unrelated commit and octopus child with colors' '\n     ++test_expect_success 'log --graph with unrelated commit and octopus child with colors' '\n     + \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n     + \tcat >expect.colors <<-\\EOF &&\n     + \t* after-initial\n\n-- \ngitgitgadget\n"},{"id":"384127","messageId":"0e37c88c6043ad25df1337fc7d69b3ab56294d0b.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 09/13] graph: commit and post-merge lines for left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:41:00Z","receivedAt":"2019-10-15T23:41:17Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nFollowing the introduction of \"left-skewed\" merges, which are merges\nwhose first parent fuses with another edge to its left, we have some\nmore edge cases to deal with in the display of commit and post-merge\nlines.\n\nThe current graph code handles the following cases for edges appearing\nto the right of the commit (*) on commit lines. A 2-way merge is usually\nfollowed by vertical lines:\n\n        | | |\n        | * |\n        | |\\ \\\n\nAn octopus merge (more than two parents) is always followed by edges\nsloping to the right:\n\n        | |  \\          | |    \\\n        | *-. \\         | *---. \\\n        | |\\ \\ \\        | |\\ \\ \\ \\\n\nA 2-way merge is followed by a right-sloping edge if the commit line\nimmediately follows a post-merge line for a commit that appears in the\nsame column as the current commit, or any column to the left of that:\n\n        | *             | * |\n        | |\\            | |\\ \\\n        | * \\           | | * \\\n        | |\\ \\          | | |\\ \\\n\nThis commit introduces the following new cases for commit lines. If a\n2-way merge skews to the left, then the edges to its right are always\nvertical lines, even if the commit follows a post-merge line:\n\n        | | |           | |\\\n        | * |           | * |\n        |/| |           |/| |\n\nA commit with 3 parents that skews left is followed by vertical edges:\n\n        | | |\n        | * |\n        |/|\\ \\\n\nIf a 3-way left-skewed merge commit appears immediately after a\npost-merge line, then it may be followed the right-sloping edges, just\nlike a 2-way merge that is not skewed.\n\n        | |\\\n        | * \\\n        |/|\\ \\\n\nOctopus merges with 4 or more parents that skew to the left will always\nbe followed by right-sloping edges, because the existing columns need to\nexpand around the merge.\n\n        | |  \\\n        | *-. \\\n        |/|\\ \\ \\\n\nOn post-merge lines, usually all edges following the current commit\nslope to the right:\n\n        | * | |\n        | |\\ \\ \\\n\nHowever, if the commit is a left-skewed 2-way merge, the edges to its\nright remain vertical. We also need to display a space after the\nvertical line descending from the commit marker, whereas this line would\nnormally be followed by a backslash.\n\n        | * | |\n        |/| | |\n\nIf a left-skewed merge has more than 2 parents, then the edges to its\nright are still sloped as they bend around the edges introduced by the\nmerge.\n\n        | * | |\n        |/|\\ \\ \\\n\nTo handle these new cases, we need to know not just how many parents\neach commit has, but how many new columns it adds to the display; this\nquantity is recorded in the `edges_added` field for the current commit,\nand `prev_edges_added` field for the previous commit.\n\nHere, \"column\" refers to visual columns, not the logical columns of the\n`columns` array. This is because even if all the commit's parents end up\nfusing with existing edges, they initially introduce distinct edges in\nthe commit and post-merge lines before those edges collapse. For\nexample, a 3-way merge whose 2nd and 3rd parents fuse with existing\nedges still introduces 2 visual columns that affect the display of edges\nto their right.\n\n        | | |  \\\n        | | *-. \\\n        | | |\\ \\ \\\n        | |_|/ / /\n        |/| | / /\n        | | |/ /\n        | |/| |\n        | | | |\n\nThis merge does not introduce any *logical* columns; there are 4 edges\nbefore and after this commit once all edges have collapsed. But it does\ninitially introduce 2 new edges that need to be accommodated by the\nedges to their right.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      |  63 +++++++++++++--\n t/t4215-log-skewed-merges.sh | 147 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 203 insertions(+), 7 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex e37127f5ab..21edad8085 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -216,6 +216,46 @@ struct git_graph {\n \t * \t\t|/| | | | |\t\t| | | | | *\n \t */\n \tint merge_layout;\n+\t/*\n+\t * The number of columns added to the graph by the current commit. For\n+\t * 2-way and octopus merges, this is is usually one less than the\n+\t * number of parents:\n+\t *\n+\t * \t\t| | |\t\t\t| |    \\\n+\t *\t\t| * |\t\t\t| *---. \\\n+\t *\t\t| |\\ \\\t\t\t| |\\ \\ \\ \\\n+\t *\t\t| | | |         \t| | | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 1\t\tedges_added: 3\n+\t *\n+\t * For left-skewed merges, the first parent fuses with its neighbor and\n+\t * so one less column is added:\n+\t *\n+\t *\t\t| | |\t\t\t| |  \\\n+\t *\t\t| * |\t\t\t| *-. \\\n+\t *\t\t|/| |\t\t\t|/|\\ \\ \\\n+\t *\t\t| | |\t\t\t| | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 0\t\tedges_added: 2\n+\t *\n+\t * This number determines how edges to the right of the merge are\n+\t * displayed in commit and post-merge lines; if no columns have been\n+\t * added then a vertical line should be used where a right-tracking\n+\t * line would otherwise be used.\n+\t *\n+\t *\t\t| * \\\t\t\t| * |\n+\t *\t\t| |\\ \\\t\t\t|/| |\n+\t *\t\t| | * \\\t\t\t| * |\n+\t */\n+\tint edges_added;\n+\t/*\n+\t * The number of columns added by the previous commit, which is used to\n+\t * smooth edges appearing to the right of a commit in a commit line\n+\t * following a post-merge line.\n+\t */\n+\tint prev_edges_added;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -328,6 +368,8 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n \tgraph->merge_layout = 0;\n+\tgraph->edges_added = 0;\n+\tgraph->prev_edges_added = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -689,6 +731,9 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n+\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -947,12 +992,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \n \t\t\tif (graph->num_parents > 2)\n \t\t\t\tgraph_draw_octopus_merge(graph, line);\n-\t\t} else if (seen_this && (graph->num_parents > 2)) {\n+\t\t} else if (seen_this && (graph->edges_added > 1)) {\n \t\t\tgraph_line_write_column(line, col, '\\\\');\n-\t\t} else if (seen_this && (graph->num_parents == 2)) {\n+\t\t} else if (seen_this && (graph->edges_added == 1)) {\n \t\t\t/*\n-\t\t\t * This is a 2-way merge commit.\n-\t\t\t * There is no GRAPH_PRE_COMMIT stage for 2-way\n+\t\t\t * This is either a right-skewed 2-way merge\n+\t\t\t * commit, or a left-skewed 3-way merge.\n+\t\t\t * There is no GRAPH_PRE_COMMIT stage for such\n \t\t\t * merges, so this is the first line of output\n \t\t\t * for this commit.  Check to see what the previous\n \t\t\t * line of output was.\n@@ -964,6 +1010,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\t\t * makes the output look nicer.\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n+\t\t\t    graph->prev_edges_added > 0 &&\n \t\t\t    graph->prev_commit_index < i)\n \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n@@ -1033,8 +1080,14 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t\t\telse\n \t\t\t\t\tidx++;\n \t\t\t}\n+\t\t\tif (graph->edges_added == 0)\n+\t\t\t\tgraph_line_addch(line, ' ');\n+\n \t\t} else if (seen_this) {\n-\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\tif (graph->edges_added > 0)\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\telse\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex dc187b5caf..e673cdb6f7 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,7 +11,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| * \\   E\n+\t| * |   E\n \t|/|\\ \\\n \t| | |/\n \t| | * D\n@@ -43,7 +43,7 @@ test_expect_success 'log --graph with left-skewed merge' '\n \t| | | | * 0_G\n \t| |_|_|/|\n \t|/| | | |\n-\t| | | * \\   0_F\n+\t| | | * |   0_F\n \t| |_|/|\\ \\\n \t|/| | | |/\n \t| | | | * 0_E\n@@ -73,4 +73,147 @@ test_expect_success 'log --graph with left-skewed merge' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with nested left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   1_H\n+\t|\\\n+\t| *   1_G\n+\t| |\\\n+\t| | * 1_F\n+\t| * | 1_E\n+\t|/| |\n+\t| * | 1_D\n+\t* | | 1_C\n+\t|/ /\n+\t* | 1_B\n+\t|/\n+\t* 1_A\n+\tEOF\n+\n+\tgit checkout --orphan 1_p &&\n+\ttest_commit 1_A &&\n+\ttest_commit 1_B &&\n+\ttest_commit 1_C &&\n+\tgit checkout -b 1_q @^ && test_commit 1_D &&\n+\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n+\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n+\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n+\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   2_K\n+\t|\\\n+\t| *   2_J\n+\t| |\\\n+\t| | *   2_H\n+\t| | |\\\n+\t| | * | 2_G\n+\t| |/| |\n+\t| | * | 2_F\n+\t| * | | 2_E\n+\t| |/ /\n+\t| * | 2_D\n+\t* | | 2_C\n+\t| |/\n+\t|/|\n+\t* | 2_B\n+\t|/\n+\t* 2_A\n+\tEOF\n+\n+\tgit checkout --orphan 2_p &&\n+\ttest_commit 2_A &&\n+\ttest_commit 2_B &&\n+\ttest_commit 2_C &&\n+\tgit checkout -b 2_q @^^ &&\n+\ttest_commit 2_D &&\n+\ttest_commit 2_E &&\n+\tgit checkout -b 2_r @^ && test_commit 2_F &&\n+\tgit checkout 2_q &&\n+\tgit merge --no-ff 2_r -m 2_G &&\n+\tgit merge --no-ff 2_p^ -m 2_H &&\n+\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n+\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   3_J\n+\t|\\\n+\t| *   3_H\n+\t| |\\\n+\t| | * 3_G\n+\t| * | 3_F\n+\t|/| |\n+\t| * |   3_E\n+\t| |\\ \\\n+\t| | |/\n+\t| | * 3_D\n+\t| * | 3_C\n+\t| |/\n+\t| * 3_B\n+\t|/\n+\t* 3_A\n+\tEOF\n+\n+\tgit checkout --orphan 3_p &&\n+\ttest_commit 3_A &&\n+\tgit checkout -b 3_q &&\n+\ttest_commit 3_B &&\n+\ttest_commit 3_C &&\n+\tgit checkout -b 3_r @^ &&\n+\ttest_commit 3_D &&\n+\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n+\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n+\tgit checkout 3_r && test_commit 3_G &&\n+\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n+\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n+\tcat >expect <<-\\EOF &&\n+\t*   4_H\n+\t|\\\n+\t| *   4_G\n+\t| |\\\n+\t| * | 4_F\n+\t|/| |\n+\t| * |   4_E\n+\t| |\\ \\\n+\t| | * | 4_D\n+\t| |/ /\n+\t|/| |\n+\t| | * 4_C\n+\t| |/\n+\t| * 4_B\n+\t|/\n+\t* 4_A\n+\tEOF\n+\n+\tgit checkout --orphan 4_p &&\n+\ttest_commit 4_A &&\n+\ttest_commit 4_B &&\n+\ttest_commit 4_C &&\n+\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n+\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n+\tgit checkout -b 4_s 4_p^^ &&\n+\tgit merge --no-ff 4_r -m 4_F &&\n+\tgit merge --no-ff 4_p -m 4_G &&\n+\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n+\n+\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"384128","messageId":"51495be940a571c9f0ca501f668dde641e023ab3.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 07/13] graph: example of graph output that can be simplified","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:58Z","receivedAt":"2019-10-15T23:41:20Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThe commits following this one introduce a series of improvements to the\nlayout of graphs, tidying up a few edge cases, namely:\n\n- merge whose first parent fuses with an existing column to the left\n- merge whose last parent fuses with its immediate neighbor on the right\n- edges that collapse to the left above and below a commit line\n\nThis test case exemplifies these cases and provides a motivating example\nof the kind of history I'm aiming to clear up.\n\nThe first parent of merge E is the same as the parent of H, so those\nedges fuse together.\n\n        * H\n        |\n        | *-.   E\n        | |\\ \\\n        |/ / /\n        |\n        * B\n\nWe can \"skew\" the display of this merge so that it doesn't introduce\nadditional columns that immediately collapse:\n\n        * H\n        |\n        | *   E\n        |/|\\\n        |\n        * B\n\nThe last parent of E is D, the same as the parent of F which is the edge\nto the right of the merge.\n\n            * F\n            |\n             \\\n          *-. \\   E\n          |\\ \\ \\\n         / / / /\n            | /\n            |/\n            * D\n\nThe two edges leading to D could be fused sooner: rather than expanding\nthe F edge around the merge and then letting the edges collapse, the F\nedge could fuse with the E edge in the post-merge line:\n\n            * F\n            |\n             \\\n          *-. | E\n          |\\ \\|\n         / / /\n            |\n            * D\n\nIf this is combined with the \"skew\" effect above, we get a much cleaner\ngraph display for these edges:\n\n            * F\n            |\n          * | E\n         /|\\|\n            |\n            * D\n\nFinally, the edge leading from C to A appears jagged as it passes\nthrough the commit line for B:\n\n        | * | C\n        | |/\n        * | B\n        |/\n        * A\n\nThis can be smoothed out so that such edges are easier to read:\n\n        | * | C\n        | |/\n        * / B\n        |/\n        * A\n---\n t/t4215-log-skewed-merges.sh | 43 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 43 insertions(+)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nnew file mode 100755\nindex 0000000000..4582ba066a\n--- /dev/null\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -0,0 +1,43 @@\n+#!/bin/sh\n+\n+test_description='git log --graph of skewed merges'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n+\tcat >expect <<-\\EOF &&\n+\t*   H\n+\t|\\\n+\t| *   G\n+\t| |\\\n+\t| | * F\n+\t| | |\n+\t| |  \\\n+\t| *-. \\   E\n+\t| |\\ \\ \\\n+\t|/ / / /\n+\t| | | /\n+\t| | |/\n+\t| | * D\n+\t| * | C\n+\t| |/\n+\t* | B\n+\t|/\n+\t* A\n+\tEOF\n+\n+\tgit checkout --orphan _p &&\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tgit checkout -b _q @^ && test_commit C &&\n+\tgit checkout -b _r @^ && test_commit D &&\n+\tgit checkout _p && git merge --no-ff _q _r -m E &&\n+\tgit checkout _r && test_commit F &&\n+\tgit checkout _p && git merge --no-ff _r -m G &&\n+\tgit checkout @^^ && git merge --no-ff _p -m H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"384129","messageId":"67051ec31a6262a263a4ab4760f53dff8ea7bce2.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 11/13] graph: smooth appearance of collapsing edges on commit lines","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:41:02Z","receivedAt":"2019-10-15T23:41:20Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a graph contains edges that are in the process of collapsing to the\nleft, but those edges cross a commit line, the effect is that the edges\nhave a jagged appearance:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * | |\n        |/ /\n        * |\n        |/\n        *\n\nWe already takes steps to smooth edges like this when they're expanding;\nwhen an edge appears to the right of a merge commit marker on a\nGRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we\nrender it as a `\\`:\n\n        * \\\n        |\\ \\\n        | * \\\n        | |\\ \\\n\nWe can make a similar improvement to collapsing edges, making them\neasier to follow and giving the overall graph a feeling of increased\nsymmetry:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * / /\n        |/ /\n        * /\n        |/\n        *\n\nTo do this, we introduce a new special case for edges on GRAPH_COMMIT\nlines that immediately follow a GRAPH_COLLAPSING line. By retaining a\ncopy of the `mapping` array used to render the GRAPH_COLLAPSING line in\nthe `old_mapping` array, we can determine that an edge is collapsing\nthrough the GRAPH_COMMIT line and should be smoothed.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 17 +++++++++---\n t/t3430-rebase-merges.sh                   |  2 +-\n t/t4202-log.sh                             |  2 +-\n t/t4214-log-graph-octopus.sh               | 32 +++++++++++-----------\n t/t4215-log-skewed-merges.sh               |  4 +--\n t/t6016-rev-list-graph-simplify-history.sh |  4 +--\n 6 files changed, 35 insertions(+), 26 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 2315f3604d..63f8d18baa 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -297,10 +297,10 @@ struct git_graph {\n \t */\n \tint *mapping;\n \t/*\n-\t * A temporary array for computing the next mapping state\n-\t * while we are outputting a mapping line.  This is stored as part\n-\t * of the git_graph simply so we don't have to allocate a new\n-\t * temporary array each time we have to output a collapsing line.\n+\t * A copy of the contents of the mapping array from the last commit,\n+\t * which we use to improve the display of columns that are tracking\n+\t * from right to left through a commit line.  We also use this to\n+\t * avoid allocating a fresh array when we compute the next mapping.\n \t */\n \tint *old_mapping;\n \t/*\n@@ -1015,6 +1015,10 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n \t\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t} else if (graph->prev_state == GRAPH_COLLAPSING &&\n+\t\t\t   graph->old_mapping[2 * i + 1] == i &&\n+\t\t\t   graph->mapping[2 * i] < i) {\n+\t\t\tgraph_line_write_column(line, col, '/');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n+\t/*\n+\t * Copy the current mapping array into old_mapping\n+\t */\n+\tCOPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);\n+\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 9efcf4808a..a30d27e9f3 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -408,7 +408,7 @@ test_expect_success 'octopus merges' '\n \t| | * three\n \t| * | two\n \t| |/\n-\t* | one\n+\t* / one\n \t|/\n \to before-octopus\n \tEOF\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex e803ba402e..ab0d021365 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -667,7 +667,7 @@ cat > expect <<\\EOF\n * | | fifth\n * | | fourth\n |/ /\n-* | third\n+* / third\n |/\n * second\n * initial\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 1b96276894..21bc600a82 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -31,9 +31,9 @@ test_expect_success 'log --graph with tricky octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -51,9 +51,9 @@ test_expect_success 'log --graph with tricky octopus merge with colors' '\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<RED>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<RED>|<RESET> * <MAGENTA>/<RESET> 2\n \t<RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -72,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -90,9 +90,9 @@ test_expect_success 'log --graph with normal octopus merge with colors' '\n \t<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4\n \t<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3\n \t<RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t<RED>|<RESET> * <BLUE>|<RESET> 2\n+\t<RED>|<RESET> * <BLUE>/<RESET> 2\n \t<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t* <BLUE>|<RESET> 1\n+\t* <BLUE>/<RESET> 1\n \t<BLUE>|<RESET><BLUE>/<RESET>\n \t* initial\n \tEOF\n@@ -110,9 +110,9 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -129,9 +129,9 @@ test_expect_failure 'log --graph with normal octopus and child merge with colors\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<GREEN>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<GREEN>|<RESET> * <MAGENTA>/<RESET> 2\n \t<GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -150,9 +150,9 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -171,9 +171,9 @@ test_expect_failure 'log --graph with tricky octopus merge and its child with co\n \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t<RED>|<RESET> * <CYAN>|<RESET> 2\n+\t<RED>|<RESET> * <CYAN>/<RESET> 2\n \t<RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t* <CYAN>|<RESET> 1\n+\t* <CYAN>/<RESET> 1\n \t<CYAN>|<RESET><CYAN>/<RESET>\n \t* initial\n \tEOF\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex e673cdb6f7..1745b3b64c 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -17,7 +17,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| | * D\n \t| * | C\n \t| |/\n-\t* | B\n+\t* / B\n \t|/\n \t* A\n \tEOF\n@@ -85,7 +85,7 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n \t| * | 1_D\n \t* | | 1_C\n \t|/ /\n-\t* | 1_B\n+\t* / 1_B\n \t|/\n \t* 1_A\n \tEOF\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex f7181d1d6a..ca1682f29b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -154,7 +154,7 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"* |   $A4\" >> expected &&\n \techo \"|\\\\ \\\\  \" >> expected &&\n \techo \"| |/  \" >> expected &&\n-\techo \"* | $A3\" >> expected &&\n+\techo \"* / $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n@@ -255,7 +255,7 @@ test_expect_success '--graph --boundary ^C3' '\n \techo \"* | | | $A3\" >> expected &&\n \techo \"o | | | $A2\" >> expected &&\n \techo \"|/ / /  \" >> expected &&\n-\techo \"o | | $A1\" >> expected &&\n+\techo \"o / / $A1\" >> expected &&\n \techo \" / /  \" >> expected &&\n \techo \"| o $C3\" >> expected &&\n \techo \"|/  \" >> expected &&\n-- \ngitgitgadget\n\n"},{"id":"384133","messageId":"2ab0f9775ba57b544a767bd8eebb41ff64d3d6d5.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 08/13] graph: tidy up display of left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:40:59Z","receivedAt":"2019-10-15T23:41:20Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nCurrently, when we display a merge whose first parent is already present\nin a column to the left of the merge commit, we display the first parent\nas a vertical pipe `|` in the GRAPH_POST_MERGE line and then immediately\nenter the GRAPH_COLLAPSING state. The first-parent line tracks to the\nleft and all the other parent lines follow it; this creates a \"kink\" in\nthose lines:\n\n        | *---.\n        | |\\ \\ \\\n        |/ / / /\n        | | | *\n\nThis change tidies the display of such commits such that if the first\nparent appears to the left of the merge, we render it as a `/` and the\nsecond parent as a `|`. This reduces the horizontal and vertical space\nneeded to render the merge, and makes the resulting lines easier to\nread.\n\n        | *-.\n        |/|\\ \\\n        | | | *\n\nIf the first parent is separated from the merge by several columns, a\nhorizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING\nstate displays the line.\n\n        | | | *-.\n        | |_|/|\\ \\\n        |/| | | | *\n\nThis effect is applied to both \"normal\" two-parent merges, and to\noctopus merges. It also reduces the vertical space needed for pre-commit\nlines, as the merge occupies one less column than usual.\n\n        Before:         After:\n\n        | *             | *\n        | |\\            | |\\\n        | | \\           | * \\\n        | |  \\          |/|\\ \\\n        | *-. \\\n        | |\\ \\ \\\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 125 +++++++++++++++++++++++++++--------\n t/t4214-log-graph-octopus.sh |  20 +++---\n t/t4215-log-skewed-merges.sh |  45 +++++++++++--\n 3 files changed, 144 insertions(+), 46 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex bd7403065e..e37127f5ab 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -202,6 +202,20 @@ struct git_graph {\n \t * previous commit.\n \t */\n \tint prev_commit_index;\n+\t/*\n+\t * Which layout variant to use to display merge commits. If the\n+\t * commit's first parent is known to be in a column to the left of the\n+\t * merge, then this value is 0 and we use the layout on the left.\n+\t * Otherwise, the value is 1 and the layout on the right is used. This\n+\t * field tells us how many columns the first parent occupies.\n+\t *\n+\t * \t\t0)\t\t\t1)\n+\t *\n+\t * \t\t| | | *-.\t\t| | *---.\n+\t * \t\t| |_|/|\\ \\\t\t| | |\\ \\ \\\n+\t * \t\t|/| | | | |\t\t| | | | | *\n+\t */\n+\tint merge_layout;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -313,6 +327,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->prev_state = GRAPH_PADDING;\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n+\tgraph->merge_layout = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -472,9 +487,11 @@ static int graph_find_new_column_by_commit(struct git_graph *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  struct commit *commit,\n+\t\t\t\t\t  int idx)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n+\tint mapping_idx;\n \n \t/*\n \t * If the commit is not already in the new_columns array, then add it\n@@ -486,8 +503,26 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[graph->width] = i;\n-\tgraph->width += 2;\n+\tif (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {\n+\t\t/*\n+\t\t * If this is the first parent of a merge, choose a layout for\n+\t\t * the merge line based on whether the parent appears in a\n+\t\t * column to the left of the merge\n+\t\t */\n+\t\tint dist, shift;\n+\n+\t\tdist = idx - i;\n+\t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n+\n+\t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n+\t\tgraph->width += 2 * graph->merge_layout;\n+\t} else {\n+\t\tmapping_idx = graph->width;\n+\t\tgraph->width += 2;\n+\t}\n+\n+\tgraph->mapping[mapping_idx] = i;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n@@ -553,6 +588,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tif (col_commit == graph->commit) {\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n+\t\t\tgraph->merge_layout = -1;\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@@ -565,7 +601,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\n-\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item, i);\n \t\t\t}\n \t\t\t/*\n \t\t\t * We always need to increment graph->width by at\n@@ -576,7 +612,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tif (graph->num_parents == 0)\n \t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit, -1);\n \t\t}\n \t}\n \n@@ -588,10 +624,36 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_expansion_rows(struct git_graph *graph)\n+{\n+\t/*\n+\t * Normally, we need two expansion rows for each dashed parent line from\n+\t * an octopus merge:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| | \\\n+\t * \t\t| |  \\\n+\t * \t\t| *-. \\\n+\t * \t\t| |\\ \\ \\\n+\t *\n+\t * If the merge is skewed to the left, then its parents occupy one less\n+\t * column, and we don't need as many expansion rows to route around it;\n+\t * in some cases that means we don't need any expansion rows at all:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| * \\\n+\t * \t\t|/|\\ \\\n+\t */\n+\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+}\n+\n static int graph_needs_pre_commit_line(struct git_graph *graph)\n {\n \treturn graph->num_parents >= 3 &&\n-\t       graph->commit_index < (graph->num_columns - 1);\n+\t       graph->commit_index < (graph->num_columns - 1) &&\n+\t       graph->expansion_row < graph_num_expansion_rows(graph);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n@@ -728,7 +790,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t\t\t struct graph_line *line)\n {\n-\tint num_expansion_rows;\n \tint i, seen_this;\n \n \t/*\n@@ -739,14 +800,13 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * We need 2 extra rows for every parent over 2.\n \t */\n \tassert(graph->num_parents >= 3);\n-\tnum_expansion_rows = (graph->num_parents - 2) * 2;\n \n \t/*\n \t * graph->expansion_row tracks the current expansion row we are on.\n \t * It should be in the range [0, num_expansion_rows - 1]\n \t */\n \tassert(0 <= graph->expansion_row &&\n-\t       graph->expansion_row < num_expansion_rows);\n+\t       graph->expansion_row < graph_num_expansion_rows(graph));\n \n \t/*\n \t * Output the row\n@@ -786,7 +846,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * and move to state GRAPH_COMMIT if necessary\n \t */\n \tgraph->expansion_row++;\n-\tif (graph->expansion_row >= num_expansion_rows)\n+\tif (!graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n@@ -824,7 +884,7 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line\n \t * x 0 1 2 3\n \t *\n \t */\n-\tconst int dashless_parents = 2;\n+\tconst int dashless_parents = 3 - graph->merge_layout;\n \tint dashful_parents = graph->num_parents - dashless_parents;\n \n \t/*\n@@ -832,9 +892,9 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line\n \t * above) but sometimes the first parent goes into an existing column,\n \t * like this:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * |/ / / /\n+\t * | *-.\n+\t * |/|\\ \\\n+\t * | | | |\n \t * x 0 1 2\n \t *\n \t * In which case the number of parents will be one greater than the\n@@ -925,10 +985,15 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+const char merge_chars[] = {'/', '|', '\\\\'};\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i;\n+\n+\tstruct commit_list *first_parent = first_interesting_parent(graph);\n+\tint seen_parent = 0;\n \n \t/*\n \t * Output the post-merge row\n@@ -951,30 +1016,34 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\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 commit_list *parents = first_parent;\n \t\t\tint par_column;\n+\t\t\tint idx = graph->merge_layout;\n+\t\t\tchar c;\n \t\t\tseen_this = 1;\n-\t\t\tparents = first_interesting_parent(graph);\n-\t\t\tassert(parents);\n-\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n-\t\t\tassert(par_column >= 0);\n-\n-\t\t\tgraph_line_write_column(line, &graph->new_columns[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+\n+\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n-\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n-\t\t\t\tgraph_line_addch(line, ' ');\n+\n+\t\t\t\tc = merge_chars[idx];\n+\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n+\t\t\t\tif (idx == 2)\n+\t\t\t\t\tgraph_line_addch(line, ' ');\n+\t\t\t\telse\n+\t\t\t\t\tidx++;\n \t\t\t}\n \t\t} else if (seen_this) {\n \t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\n-\t\t\tgraph_line_addch(line, ' ');\n+\t\t\tif (graph->merge_layout != 0 || i != graph->commit_index - 1)\n+\t\t\t\tgraph_line_addch(line, seen_parent ? '_' : ' ');\n \t\t}\n+\n+\t\tif (col_commit == first_parent->item)\n+\t\t\tseen_parent = 1;\n \t}\n \n \t/*\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 3ae8e51e50..1b96276894 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -26,9 +26,8 @@ test_expect_success 'set up merge history' '\n test_expect_success 'log --graph with tricky octopus merge, no color' '\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n@@ -47,9 +46,8 @@ test_expect_success 'log --graph with tricky octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n-\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <MAGENTA>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>\n+\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET>\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n@@ -147,9 +145,8 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n \t| * after-merge\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n@@ -169,9 +166,8 @@ test_expect_failure 'log --graph with tricky octopus merge and its child with co\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n \t<RED>|<RESET> * after-merge\n-\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET> <CYAN>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> <CYAN>/<RESET>\n+\t<RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\\<RESET> <CYAN>\\<RESET>\n \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex 4582ba066a..dc187b5caf 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,12 +11,8 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| | |\n-\t| |  \\\n-\t| *-. \\   E\n-\t| |\\ \\ \\\n-\t|/ / / /\n-\t| | | /\n+\t| * \\   E\n+\t|/|\\ \\\n \t| | |/\n \t| | * D\n \t| * | C\n@@ -40,4 +36,41 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*-----.   0_H\n+\t|\\ \\ \\ \\\n+\t| | | | * 0_G\n+\t| |_|_|/|\n+\t|/| | | |\n+\t| | | * \\   0_F\n+\t| |_|/|\\ \\\n+\t|/| | | |/\n+\t| | | | * 0_E\n+\t| |_|_|/\n+\t|/| | |\n+\t| | * | 0_D\n+\t| | |/\n+\t| | * 0_C\n+\t| |/\n+\t|/|\n+\t| * 0_B\n+\t|/\n+\t* 0_A\n+\tEOF\n+\n+\tgit checkout --orphan 0_p && test_commit 0_A &&\n+\tgit checkout -b 0_q 0_p && test_commit 0_B &&\n+\tgit checkout -b 0_r 0_p &&\n+\ttest_commit 0_C &&\n+\ttest_commit 0_D &&\n+\tgit checkout -b 0_s 0_p && test_commit 0_E &&\n+\tgit checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&\n+\tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n+\tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"384130","messageId":"503c846d2bda0d72c4fe5a3d4cb21a79e81423c1.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 12/13] graph: flatten edges that fuse with their right neighbor","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:41:03Z","receivedAt":"2019-10-15T23:41:21Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a merge commit is printed and its final parent is the same commit\nthat occupies the column to the right of the merge, this results in a\nkink in the displayed edges:\n\n        * |\n        |\\ \\\n        | |/\n        | *\n\nGraphs containing these shapes can be hard to read, as the expansion to\nthe right followed immediately by collapsing back to the left creates a\nlot of zig-zagging edges, especially when many columns are present.\n\nWe can improve this by eliminating the zig-zag and having the merge's\nfinal parent edge fuse immediately with its neighbor:\n\n        * |\n        |\\|\n        | *\n\nThis reduces the horizontal width for the current commit by 2, and\nrequires one less row, making the graph display more compact. Taken in\ncombination with other graph-smoothing enhancements, it greatly\ncompresses the space needed to display certain histories:\n\n        *\n        |\\\n        | *                       *\n        | |\\                      |\\\n        | | *                     | *\n        | | |                     | |\\\n        | |  \\                    | | *\n        | *-. \\                   | * |\n        | |\\ \\ \\        =>        |/|\\|\n        |/ / / /                  | | *\n        | | | /                   | * |\n        | | |/                    | |/\n        | | *                     * /\n        | * |                     |/\n        | |/                      *\n        * |\n        |/\n        *\n\nOne of the test cases here cannot be correctly rendered in Git v2.23.0;\nit produces this output following commit E:\n\n        | | *-. \\   5_E\n        | | |\\ \\ \\\n        | |/ / / /\n        | | | / _\n        | |_|/\n        |/| |\n\nThe new implementation makes sure that the rightmost edge in this\nhistory is not left dangling as above.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 34 +++++++++----\n t/t4215-log-skewed-merges.sh               | 56 ++++++++++++++++++----\n t/t6016-rev-list-graph-simplify-history.sh | 30 +++++-------\n 3 files changed, 86 insertions(+), 34 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 63f8d18baa..80db74aee6 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -557,8 +557,24 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n \n \t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tgraph->edges_added = graph->num_parents + graph->merge_layout  - 2;\n+\n \t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n \t\tgraph->width += 2 * graph->merge_layout;\n+\n+\t} else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 2]) {\n+\t\t/*\n+\t\t * If some columns have been added by a merge, but this commit\n+\t\t * was found in the last existing column, then adjust the\n+\t\t * numbers so that the two edges immediately join, i.e.:\n+\t\t *\n+\t\t *\t\t* |\t\t* |\n+\t\t *\t\t|\\ \\\t=>\t|\\|\n+\t\t *\t\t| |/\t\t| *\n+\t\t *\t\t| *\n+\t\t */\n+\t\tmapping_idx = graph->width - 2;\n+\t\tgraph->edges_added = -1;\n \t} else {\n \t\tmapping_idx = graph->width;\n \t\tgraph->width += 2;\n@@ -604,6 +620,8 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping[i] = -1;\n \n \tgraph->width = 0;\n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = 0;\n \n \t/*\n \t * Populate graph->new_columns and graph->mapping\n@@ -731,9 +749,6 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n-\tgraph->prev_edges_added = graph->edges_added;\n-\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n-\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -1041,7 +1056,7 @@ const char merge_chars[] = {'/', '|', '\\\\'};\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i;\n+\tint i, j;\n \n \tstruct commit_list *first_parent = first_interesting_parent(graph);\n \tint seen_parent = 0;\n@@ -1073,16 +1088,19 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t\tchar c;\n \t\t\tseen_this = 1;\n \n-\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n+\t\t\tfor (j = 0; j < graph->num_parents; j++) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n \n \t\t\t\tc = merge_chars[idx];\n \t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n-\t\t\t\tif (idx == 2)\n-\t\t\t\t\tgraph_line_addch(line, ' ');\n-\t\t\t\telse\n+\t\t\t\tif (idx == 2) {\n+\t\t\t\t\tif (graph->edges_added > 0 || j < graph->num_parents - 1)\n+\t\t\t\t\t\tgraph_line_addch(line, ' ');\n+\t\t\t\t} else {\n \t\t\t\t\tidx++;\n+\t\t\t\t}\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n \t\t\t}\n \t\t\tif (graph->edges_added == 0)\n \t\t\t\tgraph_line_addch(line, ' ');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex 1745b3b64c..d33c6438d8 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,9 +11,8 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| * |   E\n-\t|/|\\ \\\n-\t| | |/\n+\t| * | E\n+\t|/|\\|\n \t| | * D\n \t| * | C\n \t| |/\n@@ -43,9 +42,9 @@ test_expect_success 'log --graph with left-skewed merge' '\n \t| | | | * 0_G\n \t| |_|_|/|\n \t|/| | | |\n-\t| | | * |   0_F\n-\t| |_|/|\\ \\\n-\t|/| | | |/\n+\t| | | * | 0_F\n+\t| |_|/|\\|\n+\t|/| | | |\n \t| | | | * 0_E\n \t| |_|_|/\n \t|/| | |\n@@ -153,9 +152,8 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n \t| | * 3_G\n \t| * | 3_F\n \t|/| |\n-\t| * |   3_E\n-\t| |\\ \\\n-\t| | |/\n+\t| * | 3_E\n+\t| |\\|\n \t| | * 3_D\n \t| * | 3_C\n \t| |/\n@@ -216,4 +214,44 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n+\tcat >expect <<-\\EOF &&\n+\t*   5_H\n+\t|\\\n+\t| *-.   5_G\n+\t| |\\ \\\n+\t| | | * 5_F\n+\t| | * |   5_E\n+\t| |/|\\ \\\n+\t| |_|/ /\n+\t|/| | /\n+\t| | |/\n+\t* | | 5_D\n+\t| | * 5_C\n+\t| |/\n+\t|/|\n+\t| * 5_B\n+\t|/\n+\t* 5_A\n+\tEOF\n+\n+\tgit checkout --orphan 5_p &&\n+\ttest_commit 5_A &&\n+\tgit branch 5_q &&\n+\tgit branch 5_r &&\n+\ttest_commit 5_B &&\n+\tgit checkout 5_q && test_commit 5_C &&\n+\tgit checkout 5_r && test_commit 5_D &&\n+\tgit checkout 5_p &&\n+\tgit merge --no-ff 5_q 5_r -m 5_E &&\n+\tgit checkout 5_q && test_commit 5_F &&\n+\tgit checkout -b 5_s 5_p^ &&\n+\tgit merge --no-ff 5_p 5_q -m 5_G &&\n+\tgit checkout 5_r &&\n+\tgit merge --no-ff 5_s -m 5_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex ca1682f29b..f5e6e92f5b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -67,11 +67,10 @@ test_expect_success '--graph --all' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -97,11 +96,10 @@ test_expect_success '--graph --simplify-by-decoration' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -131,9 +129,8 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n \techo \"| * $C2\" >> expected &&\n \techo \"| * $C1\" >> expected &&\n \techo \"* | $A3\" >> expected &&\n@@ -151,10 +148,9 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"|\\\\  \" >> expected &&\n \techo \"| * $C4\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n-\techo \"* / $A3\" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n+\techo \"* | $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n-- \ngitgitgadget\n\n"},{"id":"384131","messageId":"9bbf738e6d6ebf36f9fc62f8ab6479f994e3adbe.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 10/13] graph: rename `new_mapping` to `old_mapping`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:41:01Z","receivedAt":"2019-10-15T23:41:22Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThe change I'm about to make requires being able to inspect the mapping\narray that was used to render the last GRAPH_COLLAPSING line while\nrendering a GRAPH_COMMIT line. The `new_mapping` array currently exists\nas a pre-allocated space for computing the next `mapping` array during\n`graph_output_collapsing_line()`, but we can repurpose it to let us see\nthe previous `mapping` state.\n\nTo support this use it will make more sense if this array is named\n`old_mapping`, as it will contain the mapping data for the previous line\nwe rendered, at the point we're rendering a commit line.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 54 +++++++++++++++++++++++++++---------------------------\n 1 file changed, 27 insertions(+), 27 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 21edad8085..2315f3604d 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -259,7 +259,7 @@ struct git_graph {\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n-\t * that can be stored in the mapping and new_mapping arrays.\n+\t * that can be stored in the mapping and old_mapping arrays.\n \t */\n \tint column_capacity;\n \t/*\n@@ -302,7 +302,7 @@ struct git_graph {\n \t * of the git_graph simply so we don't have to allocate a new\n \t * temporary array each time we have to output a collapsing line.\n \t */\n-\tint *new_mapping;\n+\tint *old_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@@ -388,7 +388,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);\n-\tALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);\n+\tALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);\n \n \t/*\n \t * The diff output prefix callback, with this we can make\n@@ -418,7 +418,7 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)\n \tREALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);\n-\tREALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);\n+\tREALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);\n }\n \n /*\n@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \tint horizontal_edge_target = -1;\n \n \t/*\n-\t * Clear out the new_mapping array\n+\t * Swap the mapping and old_mapping arrays\n+\t */\n+\tSWAP(graph->mapping, graph->old_mapping);\n+\n+\t/*\n+\t * Clear out the mapping array\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++)\n-\t\tgraph->new_mapping[i] = -1;\n+\t\tgraph->mapping[i] = -1;\n \n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->mapping[i];\n+\t\tint target = graph->old_mapping[i];\n \t\tif (target < 0)\n \t\t\tcontinue;\n \n@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * This column is already in the\n \t\t\t * correct place\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i] == -1);\n-\t\t\tgraph->new_mapping[i] = target;\n-\t\t} else if (graph->new_mapping[i - 1] < 0) {\n+\t\t\tassert(graph->mapping[i] == -1);\n+\t\t\tgraph->mapping[i] = target;\n+\t\t} else if (graph->mapping[i - 1] < 0) {\n \t\t\t/*\n \t\t\t * Nothing is to the left.\n \t\t\t * Move to the left by one\n \t\t\t */\n-\t\t\tgraph->new_mapping[i - 1] = target;\n+\t\t\tgraph->mapping[i - 1] = target;\n \t\t\t/*\n \t\t\t * If there isn't already an edge moving horizontally\n \t\t\t * select this one.\n@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t\t * line.\n \t\t\t\t */\n \t\t\t\tfor (j = (target * 2)+3; j < (i - 2); j += 2)\n-\t\t\t\t\tgraph->new_mapping[j] = target;\n+\t\t\t\t\tgraph->mapping[j] = target;\n \t\t\t}\n-\t\t} else if (graph->new_mapping[i - 1] == target) {\n+\t\t} else if (graph->mapping[i - 1] == target) {\n \t\t\t/*\n \t\t\t * There is a branch line to our left\n \t\t\t * already, and it is our target.  We\n@@ -1176,7 +1181,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * the same parent commit.\n \t\t\t *\n \t\t\t * We don't have to add anything to the\n-\t\t\t * output or new_mapping, since the\n+\t\t\t * output or mapping, since the\n \t\t\t * existing branch line has already taken\n \t\t\t * care of it.\n \t\t\t */\n@@ -1192,10 +1197,10 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * The branch to the left of that space\n \t\t\t * should be our eventual target.\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i - 1] > target);\n-\t\t\tassert(graph->new_mapping[i - 2] < 0);\n-\t\t\tassert(graph->new_mapping[i - 3] == target);\n-\t\t\tgraph->new_mapping[i - 2] = target;\n+\t\t\tassert(graph->mapping[i - 1] > target);\n+\t\t\tassert(graph->mapping[i - 2] < 0);\n+\t\t\tassert(graph->mapping[i - 3] == target);\n+\t\t\tgraph->mapping[i - 2] = target;\n \t\t\t/*\n \t\t\t * Mark this branch as the horizontal edge to\n \t\t\t * prevent any other edges from moving\n@@ -1209,14 +1214,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\n-\tif (graph->new_mapping[graph->mapping_size - 1] < 0)\n+\tif (graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n \n \t/*\n \t * Output out a line based on the new mapping info\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->new_mapping[i];\n+\t\tint target = graph->mapping[i];\n \t\tif (target < 0)\n \t\t\tgraph_line_addch(line, ' ');\n \t\telse if (target * 2 == i)\n@@ -1229,22 +1234,17 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t\t * won't continue into the next line.\n \t\t\t\t */\n \t\t\t\tif (i != (target * 2)+3)\n-\t\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\t\tgraph->mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n-\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\tgraph->mapping[i] = -1;\n \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\t/*\n-\t * Swap mapping and new_mapping\n-\t */\n-\tSWAP(graph->mapping, graph->new_mapping);\n-\n \t/*\n \t * If graph->mapping indicates that all of the branch lines\n \t * are already in the correct positions, we are done.\n-- \ngitgitgadget\n\n"},{"id":"384132","messageId":"07ddd509c5c9fb9cb4c5c5e1dc00594ffd109f73.1571182864.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v2 13/13] graph: fix coloring of octopus dashes","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:41:04Z","receivedAt":"2019-10-15T23:41:25Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nIn 04005834ed (\"log: fix coloring of certain octopus merge shapes\",\n2018-09-01) there is a fix for the coloring of dashes following an\noctopus merge. It makes a distinction between the case where all parents\nintroduce a new column, versus the case where the first parent collapses\ninto an existing column:\n\n        | *-.           | *-.\n        | |\\ \\          | |\\ \\\n        | | | |         |/ / /\n\nThe latter case means that the columns for the merge parents begin one\nplace to the left in the `new_columns` array compared to the former\ncase.\n\nHowever, the implementation only works if the commit's parents are kept\nin order as they map onto the visual columns, as we get the colors by\niterating over `new_columns` as we print the dashes. In general, the\ncommit's parents can arbitrarily merge with existing columns, and change\ntheir ordering in the process.\n\nFor example, in the following diagram, the number of each column\nindicates which commit parent appears in each column.\n\n        | | *---.\n        | | |\\ \\ \\\n        | | |/ / /\n        | |/| | /\n        | |_|_|/\n        |/| | |\n        3 1 0 2\n\nIf the columns are colored (red, green, yellow, blue), then the dashes\nwill currently be colored yellow and blue, whereas they should be blue\nand red.\n\nTo fix this, we need to look up each column in the `mapping` array,\nwhich before the `GRAPH_COLLAPSING` state indicates which logical column\nis displayed in each visual column. This implementation is simpler as it\ndoesn't have any edge cases, and it also handles how left-skewed first\nparents are now displayed:\n\n        | *-.\n        |/|\\ \\\n        | | | |\n        0 1 2 3\n\nThe color of the first dashes is always the color found in `mapping` two\ncolumns to the right of the commit symbol. Because commits are displayed\nafter all edges have been collapsed together and the visual columns\nmatch the logical ones, we can find the visual offset of the commit\nsymbol using `commit_index`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 71 +++++++++++++++++++-----------------\n t/t4214-log-graph-octopus.sh | 10 ++---\n 2 files changed, 42 insertions(+), 39 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 80db74aee6..e3fd0ea5f8 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -684,6 +684,11 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_dashed_parents(struct git_graph *graph)\n+{\n+\treturn graph->num_parents + graph->merge_layout - 3;\n+}\n+\n static int graph_num_expansion_rows(struct git_graph *graph)\n {\n \t/*\n@@ -706,7 +711,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)\n \t * \t\t| * \\\n \t * \t\t|/|\\ \\\n \t */\n-\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+\treturn graph_num_dashed_parents(graph) * 2;\n }\n \n static int graph_needs_pre_commit_line(struct git_graph *graph)\n@@ -934,47 +939,45 @@ static void graph_output_commit_char(struct git_graph *graph, struct graph_line\n static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n-\t * Here dashless_parents represents the number of parents which don't\n-\t * need to have dashes (the edges labeled \"0\" and \"1\").  And\n-\t * dashful_parents are the remaining ones.\n+\t * The parents of a merge commit can be arbitrarily reordered as they\n+\t * are mapped onto display columns, for example this is a valid merge:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * | | | | |\n-\t * x 0 1 2 3\n+\t *\t| | *---.\n+\t *\t| | |\\ \\ \\\n+\t *\t| | |/ / /\n+\t *\t| |/| | /\n+\t *\t| |_|_|/\n+\t *\t|/| | |\n+\t *\t3 1 0 2\n \t *\n-\t */\n-\tconst int dashless_parents = 3 - graph->merge_layout;\n-\tint dashful_parents = graph->num_parents - dashless_parents;\n-\n-\t/*\n-\t * Usually, we add one new column for each parent (like the diagram\n-\t * above) but sometimes the first parent goes into an existing column,\n-\t * like this:\n+\t * The numbers denote which parent of the merge each visual column\n+\t * corresponds to; we can't assume that the parents will initially\n+\t * display in the order given by new_columns.\n \t *\n-\t * | *-.\n-\t * |/|\\ \\\n-\t * | | | |\n-\t * x 0 1 2\n+\t * To find the right color for each dash, we need to consult the\n+\t * mapping array, starting from the column 2 places to the right of the\n+\t * merge commit, and use that to find out which logical column each\n+\t * edge will collapse to.\n \t *\n-\t * In which case the number of parents will be one greater than the\n-\t * number of added columns.\n+\t * Commits are rendered once all edges have collapsed to their correct\n+\t * logcial column, so commit_index gives us the right visual offset for\n+\t * the merge commit.\n \t */\n-\tint added_cols = (graph->num_new_columns - graph->num_columns);\n-\tint parent_in_old_cols = graph->num_parents - added_cols;\n \n-\t/*\n-\t * In both cases, commit_index corresponds to the edge labeled \"0\".\n-\t */\n-\tint first_col = graph->commit_index + dashless_parents\n-\t    - parent_in_old_cols;\n+\tint i, j;\n+\tstruct column *col;\n \n-\tint i;\n-\tfor (i = 0; i < dashful_parents; i++) {\n-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n-\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n+\tint dashed_parents = graph_num_dashed_parents(graph);\n+\n+\tfor (i = 0; i < dashed_parents; i++) {\n+\t\tj = graph->mapping[(graph->commit_index + i + 2) * 2];\n+\t\tcol = &graph->new_columns[j];\n+\n+\t\tgraph_line_write_column(line, col, '-');\n+\t\tgraph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-');\n \t}\n+\n+\treturn;\n }\n \n static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 21bc600a82..40d27db674 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -121,7 +121,7 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with normal octopus and child merge with colors' '\n+test_expect_success 'log --graph with normal octopus and child merge with colors' '\n \tcat >expect.colors <<-\\EOF &&\n \t* after-merge\n \t*<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n@@ -161,7 +161,7 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with tricky octopus merge and its child with colors' '\n+test_expect_success 'log --graph with tricky octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n@@ -205,7 +205,7 @@ test_expect_success 'log --graph with crossover in octopus merge, no color' '\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -253,7 +253,7 @@ test_expect_success 'log --graph with crossover in octopus merge and its child,\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge and its child with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -349,7 +349,7 @@ test_expect_success 'log --graph with unrelated commit and octopus child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with unrelated commit and octopus child with colors' '\n+test_expect_success 'log --graph with unrelated commit and octopus child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-initial\n-- \ngitgitgadget\n"},{"id":"384134","messageId":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v2.git.1571182864.gitgitgadget@gmail.com","subject":"[PATCH v3 00/13] Improve the readability of log --graph output","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:46Z","receivedAt":"2019-10-15T23:48:05Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"This series of patches are designed to improve the output of the log --graph\ncommand; their effect can be summed up in the following diagram:\n\n    Before                    After\n    ------                    -----\n\n    *\n    |\\\n    | *                       *\n    | |\\                      |\\\n    | | *                     | *\n    | | |                     | |\\\n    | |  \\                    | | *\n    | *-. \\                   | * |\n    | |\\ \\ \\                  |/|\\|\n    |/ / / /                  | | *\n    | | | /                   | * |\n    | | |/                    | |/\n    | | *                     * /\n    | * |                     |/\n    | |/                      *\n    * |\n    |/\n    *\n\nThese changes aim to make the edges in graph diagrams easier to read, by\nstraightening lines and making certain kinds of topologies display more\ncompactly. Three distinct changes are included.\n\nFirst, if the first parent of a merge fuses with an edge to the left of the\ncommit, then we display that by making the edges fuse immediately rather\nthan by drawing a line straight down and then having it track to the left.\nThat is, where we currently display these graphs:\n\n    | *             | | | *\n    | |\\            | | | |\\\n    |/ /            | |_|/ /\n    | |             |/| | |\n\nWe will now display these merges as follows:\n\n    | *             | | | *\n    |/|             | |_|/|\n    | |             |/| | |\n\nThis transformation is applied to merges with any number of parents, for\nexample we currently display 3-parent merges like this:\n\n    | *-.           | | | *-.\n    | |\\ \\          | | | |\\ \\\n    |/ / /          | |_|/ / /\n    | | |           |/| | | |\n\nAnd we will now display them like this:\n\n    | *             | | | *\n    |/|\\            | |_|/|\\\n    | | |           |/| | | |\n\nIf the edge the first parent fuses with is separated from the commit by\nmultiple columns, a horizontal edge is drawn just as we currently do in the\n'collapsing' state. This change also affects the display of commit and\npost-merge lines in subtle ways that are more thoroughly described in the\nrelevant commits.\n\nThe second change is that if the final parent of a merge fuses with the edge\nto the right of the commit, then we can remove the zig-zag effect that\ncurrently results. We currently display these merges like this:\n\n    * |\n    |\\ \\\n    | |/\n    | *\n\nAfter these changes, this merge will now be displayed like so:\n\n    * |\n    |\\|\n    | *\n\nIf the final parent fuses with an edge that's further to the right, its\ndisplay is unchanged and it will still display like this:\n\n    * | | |\n    |\\ \\ \\ \\\n    | | |_|/\n    | |/| |\n    | * | |\n\nThe final structural change smooths out lines that are collapsing through\ncommit lines. For example, consider the following history:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * | |\n    |/ /\n    * |\n    |/\n    *\n\nThis is now rendered so that commit lines display an edge using / instead of\n|, if that edge is tracking to the left both above and below the commit\nline. That results in this improved display:\n\n    *-. \\\n    |\\ \\ \\\n    | | * |\n    | * | |\n    | |/ /\n    * / /\n    |/ /\n    * /\n    |/\n    *\n\nTaken together, these changes produce the change shown in the first diagram\nabove, with the current rendering on the left and the new rendering on the\nright.\n\nA final addition to that set of changes fixes the coloring of dashes that\nare drawn next to octopus merges, in a manner compatible with all these\nchanges. The early commits in this set are refactorings that make the\nfunctional changes easier to introduce.\n\nJames Coglan (13):\n  graph: automatically track display width of graph lines\n  graph: handle line padding in `graph_next_line()`\n  graph: reuse `find_new_column_by_commit()`\n  graph: reduce duplication in `graph_insert_into_new_columns()`\n  graph: remove `mapping_idx` and `graph_update_width()`\n  graph: extract logic for moving to GRAPH_PRE_COMMIT state\n  graph: example of graph output that can be simplified\n  graph: tidy up display of left-skewed merges\n  graph: commit and post-merge lines for left-skewed merges\n  graph: rename `new_mapping` to `old_mapping`\n  graph: smooth appearance of collapsing edges on commit lines\n  graph: flatten edges that fuse with their right neighbor\n  graph: fix coloring of octopus dashes\n\n graph.c                                    | 646 ++++++++++++---------\n t/t3430-rebase-merges.sh                   |   2 +-\n t/t4202-log.sh                             |   2 +-\n t/t4214-log-graph-octopus.sh               |  62 +-\n t/t4215-log-skewed-merges.sh               | 257 ++++++++\n t/t6016-rev-list-graph-simplify-history.sh |  30 +-\n 6 files changed, 673 insertions(+), 326 deletions(-)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\n\nbase-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-383/jcoglan/jc/simplify-graph-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/383\n\nRange-diff vs v2:\n\n  1:  c30f5bb43b =  1:  722ab8973a graph: automatically track display width of graph lines\n  2:  d962eb2e02 !  2:  056c95d4ed graph: handle line padding in `graph_next_line()`\n     @@ -18,6 +18,8 @@\n          `graph_next_line()` and we must not pad the `graph_line` if no commit is\n          set.\n      \n     +    Signed-off-by: James Coglan <jcoglan@gmail.com>\n     +\n       diff --git a/graph.c b/graph.c\n       --- a/graph.c\n       +++ b/graph.c\n  3:  ecfcfbfd5c =  3:  376236ab0b graph: reuse `find_new_column_by_commit()`\n  4:  50ce875ed9 =  4:  6a4717bf05 graph: reduce duplication in `graph_insert_into_new_columns()`\n  5:  d2e8958eed =  5:  678b53b671 graph: remove `mapping_idx` and `graph_update_width()`\n  6:  163600585c =  6:  8ed32b1c74 graph: extract logic for moving to GRAPH_PRE_COMMIT state\n  7:  51495be940 !  7:  631ee3cecb graph: example of graph output that can be simplified\n     @@ -86,6 +86,8 @@\n                  |/\n                  * A\n      \n     +    Signed-off-by: James Coglan <jcoglan@gmail.com>\n     +\n       diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n       new file mode 100755\n       --- /dev/null\n  8:  2ab0f9775b =  8:  c34a5eb160 graph: tidy up display of left-skewed merges\n  9:  0e37c88c60 =  9:  2f75c697be graph: commit and post-merge lines for left-skewed merges\n 10:  9bbf738e6d = 10:  f5c2791436 graph: rename `new_mapping` to `old_mapping`\n 11:  67051ec31a = 11:  9b24893de6 graph: smooth appearance of collapsing edges on commit lines\n 12:  503c846d2b = 12:  04ba5169bc graph: flatten edges that fuse with their right neighbor\n 13:  07ddd509c5 = 13:  cd761b3a32 graph: fix coloring of octopus dashes\n\n-- \ngitgitgadget\n"},{"id":"384135","messageId":"376236ab0bc0f4b3711c994f2e0029f50899edf1.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 03/13] graph: reuse `find_new_column_by_commit()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:49Z","receivedAt":"2019-10-15T23:48:07Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to\n`graph_insert_into_new_columns()` and so am trying to simplify it. One\npossible simplification is that we can extract the loop for finding the\nelement in `new_columns` containing the given commit.\n\n`find_new_column_by_commit()` contains a very similar loop but it\nreturns a `struct column *` rather than an `int` offset into the array.\nHere I'm introducing a version that returns `int` and using that in\n`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 48 +++++++++++++++++++++++-------------------------\n 1 file changed, 23 insertions(+), 25 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 4c68557b17..c9646d9e00 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -460,22 +460,31 @@ static unsigned short graph_find_commit_color(const struct git_graph *graph,\n \treturn graph_get_current_column_color(graph);\n }\n \n+static int graph_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 i;\n+\t}\n+\treturn -1;\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 {\n-\tint i;\n+\tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n \t * If the commit is already in the new_columns list, we don't need to\n \t * add it.  Just update the mapping correctly.\n \t */\n-\tfor (i = 0; i < graph->num_new_columns; i++) {\n-\t\tif (graph->new_columns[i].commit == commit) {\n-\t\t\tgraph->mapping[*mapping_index] = i;\n-\t\t\t*mapping_index += 2;\n-\t\t\treturn;\n-\t\t}\n+\tif (i >= 0) {\n+\t\tgraph->mapping[*mapping_index] = i;\n+\t\t*mapping_index += 2;\n+\t\treturn;\n \t}\n \n \t/*\n@@ -963,17 +972,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\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 NULL;\n-}\n-\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n@@ -1001,20 +999,20 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\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\tint par_column;\n \t\t\tseen_this = 1;\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+\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\tassert(par_column >= 0);\n \n-\t\t\tgraph_line_write_column(line, par_column, '|');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[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\tgraph_line_write_column(line, par_column, '\\\\');\n+\t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n+\t\t\t\tassert(par_column >= 0);\n+\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n \t\t\t\tgraph_line_addch(line, ' ');\n \t\t\t}\n \t\t} else if (seen_this) {\n-- \ngitgitgadget\n\n"},{"id":"384136","messageId":"056c95d4edc8748c513db8c159c5e3d347ffc894.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 02/13] graph: handle line padding in `graph_next_line()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:48Z","receivedAt":"2019-10-15T23:48:07Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nNow that the display width of graph lines is implicitly tracked via the\n`graph_line` interface, the calls to `graph_pad_horizontally()` no\nlonger need to be located inside the individual output functions, where\nthe character counting was previously being done.\n\nAll the functions called by `graph_next_line()` generate a line of\noutput, then call `graph_pad_horizontally()`, and finally change the\ngraph state if necessary. As padding is the final change to the output\ndone by all these functions, it can be removed from all of them and done\nin `graph_next_line()` instead.\n\nI've also moved the guard in `graph_output_padding_line()` that checks\nthe graph has a commit; this function is only called by\n`graph_next_line()` and we must not pad the `graph_line` if no commit is\nset.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 49 ++++++++++++++++++++-----------------------------\n 1 file changed, 20 insertions(+), 29 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 2f81a5d23d..4c68557b17 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -732,16 +732,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n {\n \tint i;\n \n-\t/*\n-\t * We could conceivable be called with a NULL commit\n-\t * if our caller has a bug, and invokes graph_next_line()\n-\t * immediately after graph_init(), without first calling\n-\t * graph_update().  Return without outputting anything in this\n-\t * case.\n-\t */\n-\tif (!graph->commit)\n-\t\treturn;\n-\n \t/*\n \t * Output a padding row, that leaves all branch lines unchanged\n \t */\n@@ -749,8 +739,6 @@ static void graph_output_padding_line(struct git_graph *graph,\n \t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n \t\tgraph_line_addch(line, ' ');\n \t}\n-\n-\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -767,7 +755,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n \t * of the graph is missing.\n \t */\n \tgraph_line_addstr(line, \"...\");\n-\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -832,8 +819,6 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Increment graph->expansion_row,\n \t * and move to state GRAPH_COMMIT if necessary\n@@ -967,8 +952,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1043,8 +1026,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Update graph->state\n \t */\n@@ -1186,8 +1167,6 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, line);\n-\n \t/*\n \t * Swap mapping and new_mapping\n \t */\n@@ -1204,31 +1183,43 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tint shown_commit_line = 0;\n \tstruct graph_line line = { .buf = sb, .width = 0 };\n \n+\t/*\n+\t * We could conceivable be called with a NULL commit\n+\t * if our caller has a bug, and invokes graph_next_line()\n+\t * immediately after graph_init(), without first calling\n+\t * graph_update().  Return without outputting anything in this\n+\t * case.\n+\t */\n+\tif (!graph->commit)\n+\t\treturn -1;\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n \t\tgraph_output_padding_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_SKIP:\n \t\tgraph_output_skip_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_PRE_COMMIT:\n \t\tgraph_output_pre_commit_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COMMIT:\n \t\tgraph_output_commit_line(graph, &line);\n-\t\treturn 1;\n+\t\tshown_commit_line = 1;\n+\t\tbreak;\n \tcase GRAPH_POST_MERGE:\n \t\tgraph_output_post_merge_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \tcase GRAPH_COLLAPSING:\n \t\tgraph_output_collapsing_line(graph, &line);\n-\t\treturn 0;\n+\t\tbreak;\n \t}\n \n-\tassert(0);\n-\treturn 0;\n+\tgraph_pad_horizontally(graph, &line);\n+\treturn shown_commit_line;\n }\n \n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n-- \ngitgitgadget\n\n"},{"id":"384137","messageId":"722ab8973ace92aeef8669eced2919a144240cc0.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 01/13] graph: automatically track display width of graph lines","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:47Z","receivedAt":"2019-10-15T23:48:08Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nAll the output functions called by `graph_next_line()` currently keep\ntrack of how many printable chars they've written to the buffer, before\ncalling `graph_pad_horizontally()` to pad the line with spaces. Some\nfunctions do this by incrementing a counter whenever they write to the\nbuffer, and others do it by encoding an assumption about how many chars\nare written, as in:\n\n    graph_pad_horizontally(graph, sb, graph->num_columns * 2);\n\nThis adds a fair amount of noise to the functions' logic and is easily\nbroken if one forgets to increment the right counter or update the\ncalculations used for padding.\n\nTo make this easier to use, I'm introducing a new struct called\n`graph_line` that wraps a `strbuf` and keeps count of its display width\nimplicitly. `graph_next_line()` wraps this around the `struct strbuf *`\nit's given and passes a `struct graph_line *` to the output functions,\nwhich use its interface.\n\nThe `graph_line` interface wraps the `strbuf_addch()`,\n`strbuf_addchars()` and `strbuf_addstr()` functions, and adds the\n`graph_line_write_column()` function for adding a single character with\ncolor formatting. The `graph_pad_horizontally()` function can then use\nthe `width` field from the struct rather than taking a character count\nas a parameter.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 194 +++++++++++++++++++++++++++++---------------------------\n 1 file changed, 99 insertions(+), 95 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex f53135485f..2f81a5d23d 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color)\n \treturn column_colors[color];\n }\n \n-static void strbuf_write_column(struct strbuf *sb, const struct column *c,\n-\t\t\t\tchar col_char)\n+struct graph_line {\n+\tstruct strbuf *buf;\n+\tsize_t width;\n+};\n+\n+static inline void graph_line_addch(struct graph_line *line, int c)\n+{\n+\tstrbuf_addch(line->buf, c);\n+\tline->width++;\n+}\n+\n+static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n+{\n+\tstrbuf_addchars(line->buf, c, n);\n+\tline->width += n;\n+}\n+\n+static inline void graph_line_addstr(struct graph_line *line, const char *s)\n+{\n+\tstrbuf_addstr(line->buf, s);\n+\tline->width += strlen(s);\n+}\n+\n+static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n+{\n+\tstrbuf_addstr(line->buf, column_get_color_code(color));\n+}\n+\n+static void graph_line_write_column(struct graph_line *line, const struct column *c,\n+\t\t\t\t    char col_char)\n {\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(c->color));\n-\tstrbuf_addch(sb, col_char);\n+\t\tgraph_line_addcolor(line, c->color);\n+\tgraph_line_addch(line, col_char);\n \tif (c->color < column_colors_max)\n-\t\tstrbuf_addstr(sb, column_get_color_code(column_colors_max));\n+\t\tgraph_line_addcolor(line, column_colors_max);\n }\n \n struct git_graph {\n@@ -686,8 +714,7 @@ 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-\t\t\t\t   int chars_written)\n+static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Add additional spaces to the end of the strbuf, so that all\n@@ -696,12 +723,12 @@ 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-\tif (chars_written < graph->width)\n-\t\tstrbuf_addchars(sb, ' ', graph->width - chars_written);\n+\tif (line->width < graph->width)\n+\t\tgraph_line_addchars(line, ' ', graph->width - line->width);\n }\n \n static void graph_output_padding_line(struct git_graph *graph,\n-\t\t\t\t      struct strbuf *sb)\n+\t\t\t\t      struct graph_line *line)\n {\n \tint i;\n \n@@ -719,11 +746,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_write_column(sb, &graph->new_columns[i], '|');\n-\t\tstrbuf_addch(sb, ' ');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i], '|');\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->num_new_columns * 2);\n+\tgraph_pad_horizontally(graph, line);\n }\n \n \n@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)\n }\n \n \n-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Output an ellipsis to indicate that a portion\n \t * of the graph is missing.\n \t */\n-\tstrbuf_addstr(sb, \"...\");\n-\tgraph_pad_horizontally(graph, sb, 3);\n+\tgraph_line_addstr(line, \"...\");\n+\tgraph_pad_horizontally(graph, line);\n \n \tif (graph->num_parents >= 3 &&\n \t    graph->commit_index < (graph->num_columns - 1))\n@@ -750,11 +777,10 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)\n }\n \n static void graph_output_pre_commit_line(struct git_graph *graph,\n-\t\t\t\t\t struct strbuf *sb)\n+\t\t\t\t\t struct graph_line *line)\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@@ -777,14 +803,12 @@ 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_write_column(sb, col, '|');\n-\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n-\t\t\tchars_written += 1 + graph->expansion_row;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addchars(line, ' ', 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@@ -797,22 +821,18 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else if (seen_this && (graph->expansion_row > 0)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Increment graph->expansion_row,\n@@ -823,7 +843,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n-static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * For boundary commits, print 'o'\n@@ -831,22 +851,20 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)\n \t */\n \tif (graph->commit->object.flags & BOUNDARY) {\n \t\tassert(graph->revs->boundary);\n-\t\tstrbuf_addch(sb, 'o');\n+\t\tgraph_line_addch(line, 'o');\n \t\treturn;\n \t}\n \n \t/*\n \t * get_revision_mark() handles all other cases without assert()\n \t */\n-\tstrbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit));\n+\tgraph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));\n }\n \n /*\n- * Draw the horizontal dashes of an octopus merge and return the number of\n- * characters written.\n+ * Draw the horizontal dashes of an octopus merge.\n  */\n-static int graph_draw_octopus_merge(struct git_graph *graph,\n-\t\t\t\t    struct strbuf *sb)\n+static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n \t * Here dashless_parents represents the number of parents which don't\n@@ -886,17 +904,16 @@ static int graph_draw_octopus_merge(struct git_graph *graph,\n \n \tint i;\n \tfor (i = 0; i < dashful_parents; i++) {\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col], '-');\n-\t\tstrbuf_write_column(sb, &graph->new_columns[i+first_col],\n-\t\t\t\t    i == dashful_parents-1 ? '.' : '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n+\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n+\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n \t}\n-\treturn 2 * dashful_parents;\n }\n \n-static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, chars_written;\n+\tint i;\n \n \t/*\n \t * Output the row containing this commit\n@@ -906,7 +923,6 @@ 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@@ -920,15 +936,12 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)\n \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+\t\t\tgraph_output_commit_char(graph, line);\n \n \t\t\tif (graph->num_parents > 2)\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\t\tgraph_draw_octopus_merge(graph, line);\n \t\t} else if (seen_this && (graph->num_parents > 2)) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, 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@@ -945,19 +958,16 @@ 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_write_column(sb, col, '\\\\');\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n-\t\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n-\t\tstrbuf_addch(sb, ' ');\n-\t\tchars_written++;\n+\t\tgraph_line_addch(line, ' ');\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -981,15 +991,14 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,\n \treturn NULL;\n }\n \n-static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, j, chars_written;\n+\tint i, j;\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@@ -1016,29 +1025,25 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\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\tgraph_line_write_column(line, 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\tgraph_line_write_column(line, par_column, '\\\\');\n+\t\t\t\tgraph_line_addch(line, ' ');\n \t\t\t}\n-\t\t\tchars_written += j * 2;\n \t\t} else if (seen_this) {\n-\t\t\tstrbuf_write_column(sb, col, '\\\\');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n-\t\t\tstrbuf_write_column(sb, col, '|');\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written += 2;\n+\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Update graph->state\n@@ -1049,7 +1054,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n-static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)\n+static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint i;\n \tshort used_horizontal = 0;\n@@ -1159,9 +1164,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \tfor (i = 0; i < graph->mapping_size; i++) {\n \t\tint target = graph->new_mapping[i];\n \t\tif (target < 0)\n-\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tgraph_line_addch(line, ' ');\n \t\telse if (target * 2 == i)\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '|');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '|');\n \t\telse if (target == horizontal_edge_target &&\n \t\t\t i != horizontal_edge - 1) {\n \t\t\t\t/*\n@@ -1172,16 +1177,16 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\t\t\tif (i != (target * 2)+3)\n \t\t\t\t\tgraph->new_mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '_');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n \t\t\t\tgraph->new_mapping[i] = -1;\n-\t\t\tstrbuf_write_column(sb, &graph->new_columns[target], '/');\n+\t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, graph->mapping_size);\n+\tgraph_pad_horizontally(graph, line);\n \n \t/*\n \t * Swap mapping and new_mapping\n@@ -1199,24 +1204,26 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \n int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n+\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\n-\t\tgraph_output_padding_line(graph, sb);\n+\t\tgraph_output_padding_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_SKIP:\n-\t\tgraph_output_skip_line(graph, sb);\n+\t\tgraph_output_skip_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_PRE_COMMIT:\n-\t\tgraph_output_pre_commit_line(graph, sb);\n+\t\tgraph_output_pre_commit_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COMMIT:\n-\t\tgraph_output_commit_line(graph, sb);\n+\t\tgraph_output_commit_line(graph, &line);\n \t\treturn 1;\n \tcase GRAPH_POST_MERGE:\n-\t\tgraph_output_post_merge_line(graph, sb);\n+\t\tgraph_output_post_merge_line(graph, &line);\n \t\treturn 0;\n \tcase GRAPH_COLLAPSING:\n-\t\tgraph_output_collapsing_line(graph, sb);\n+\t\tgraph_output_collapsing_line(graph, &line);\n \t\treturn 0;\n \t}\n \n@@ -1227,7 +1234,7 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tint i;\n-\tint chars_written = 0;\n+\tstruct graph_line line = { .buf = sb, .width = 0 };\n \n \tif (graph->state != GRAPH_COMMIT) {\n \t\tgraph_next_line(graph, sb);\n@@ -1244,20 +1251,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)\n \tfor (i = 0; i < graph->num_columns; i++) {\n \t\tstruct column *col = &graph->columns[i];\n \n-\t\tstrbuf_write_column(sb, col, '|');\n-\t\tchars_written++;\n+\t\tgraph_line_write_column(&line, col, '|');\n \n \t\tif (col->commit == graph->commit && graph->num_parents > 2) {\n \t\t\tint len = (graph->num_parents - 2) * 2;\n-\t\t\tstrbuf_addchars(sb, ' ', len);\n-\t\t\tchars_written += len;\n+\t\t\tgraph_line_addchars(&line, ' ', len);\n \t\t} else {\n-\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tchars_written++;\n+\t\t\tgraph_line_addch(&line, ' ');\n \t\t}\n \t}\n \n-\tgraph_pad_horizontally(graph, sb, chars_written);\n+\tgraph_pad_horizontally(graph, &line);\n \n \t/*\n \t * Update graph->prev_state since we have output a padding line\n-- \ngitgitgadget\n\n"},{"id":"384138","messageId":"6a4717bf05d7cf9fa37fab824634c79394e76476.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 04/13] graph: reduce duplication in `graph_insert_into_new_columns()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:50Z","receivedAt":"2019-10-15T23:48:11Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nI will shortly be making some changes to this function and so am trying\nto simplify it. It currently contains some duplicated logic; both\nbranches the function can take assign the commit's column index into\nthe `mapping` array and increment `mapping_index`.\n\nHere I change the function so that the only conditional behaviour is\nthat it appends the commit to `new_columns` if it's not present. All\nmanipulation of `mapping` now happens on a single code path.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 20 +++++++-------------\n 1 file changed, 7 insertions(+), 13 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex c9646d9e00..512ae16535 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -478,23 +478,17 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n \t/*\n-\t * If the commit is already in the new_columns list, we don't need to\n-\t * add it.  Just update the mapping correctly.\n+\t * If the commit is not already in the new_columns array, then add it\n+\t * and record it as being in the final column.\n \t */\n-\tif (i >= 0) {\n-\t\tgraph->mapping[*mapping_index] = i;\n-\t\t*mapping_index += 2;\n-\t\treturn;\n+\tif (i < 0) {\n+\t\ti = graph->num_new_columns++;\n+\t\tgraph->new_columns[i].commit = commit;\n+\t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\t/*\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+\tgraph->mapping[*mapping_index] = i;\n \t*mapping_index += 2;\n-\tgraph->num_new_columns++;\n }\n \n static void graph_update_width(struct git_graph *graph,\n-- \ngitgitgadget\n\n"},{"id":"384139","messageId":"8ed32b1c743282716bd2bc1f2ce19218119629d9.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 06/13] graph: extract logic for moving to GRAPH_PRE_COMMIT state","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:52Z","receivedAt":"2019-10-15T23:48:12Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThis computation is repeated in a couple of places and I need to add\nanother condition to it to implement a further improvement to the graph\nrendering, so I'm extracting this into a function.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex d724ef25c3..bd7403065e 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -588,6 +588,12 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_needs_pre_commit_line(struct git_graph *graph)\n+{\n+\treturn graph->num_parents >= 3 &&\n+\t       graph->commit_index < (graph->num_columns - 1);\n+}\n+\n void graph_update(struct git_graph *graph, struct commit *commit)\n {\n \tstruct commit_list *parent;\n@@ -643,8 +649,7 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tif (graph->state != GRAPH_PADDING)\n \t\tgraph->state = GRAPH_SKIP;\n-\telse if (graph->num_parents >= 3 &&\n-\t\t graph->commit_index < (graph->num_columns - 1))\n+\telse if (graph_needs_pre_commit_line(graph))\n \t\tgraph->state = GRAPH_PRE_COMMIT;\n \telse\n \t\tgraph->state = GRAPH_COMMIT;\n@@ -714,8 +719,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n \t */\n \tgraph_line_addstr(line, \"...\");\n \n-\tif (graph->num_parents >= 3 &&\n-\t    graph->commit_index < (graph->num_columns - 1))\n+\tif (graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_PRE_COMMIT);\n \telse\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n-- \ngitgitgadget\n\n"},{"id":"384140","messageId":"678b53b6713a85454db21c2dbcf1afde9b765dfb.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 05/13] graph: remove `mapping_idx` and `graph_update_width()`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:51Z","receivedAt":"2019-10-15T23:48:12Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThere's a duplication of logic between `graph_insert_into_new_columns()`\nand `graph_update_width()`. `graph_insert_into_new_columns()` is called\nrepeatedly by `graph_update_columns()` with an `int *` that tracks the\noffset into the `mapping` array where we should write the next value.\nEach call to `graph_insert_into_new_columns()` effectively pushes one\ncolumn index and one \"null\" value (-1) onto the `mapping` array and\ntherefore increments `mapping_idx` by 2.\n\n`graph_update_width()` duplicates this process: the `width` of the graph\nis essentially the initial width of the `mapping` array before edges\nbegin collapsing. The `graph_update_width()` function's logic\neffectively works out how many times `graph_insert_into_new_columns()`\nwas called based on the relationship of the current commit to the rest\nof the graph.\n\nI'm about to make some changes that make the assignment of values into\nthe `mapping` array more complicated. Rather than make\n`graph_update_width()` more complicated at the same time, we can simply\nremove this function and use `graph->width` to track the offset into the\n`mapping` array as we're building it. This removes the duplication and\nmakes sure that `graph->width` is the same as the visual width of the\n`mapping` array once `graph_update_columns()` is complete.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 65 +++++++++------------------------------------------------\n 1 file changed, 10 insertions(+), 55 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 512ae16535..d724ef25c3 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -472,8 +472,7 @@ static int graph_find_new_column_by_commit(struct git_graph *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+\t\t\t\t\t  struct commit *commit)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n \n@@ -487,50 +486,14 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[*mapping_index] = i;\n-\t*mapping_index += 2;\n-}\n-\n-static void graph_update_width(struct git_graph *graph,\n-\t\t\t       int is_commit_in_existing_columns)\n-{\n-\t/*\n-\t * Compute the width needed to display the graph for this commit.\n-\t * This is the maximum width needed for any row.  All other rows\n-\t * will be padded to this width.\n-\t *\n-\t * Compute the number of columns in the widest row:\n-\t * Count each existing column (graph->num_columns), and each new\n-\t * column added by this commit.\n-\t */\n-\tint max_cols = graph->num_columns + graph->num_parents;\n-\n-\t/*\n-\t * Even if the current commit has no parents to be printed, it\n-\t * still takes up a column for itself.\n-\t */\n-\tif (graph->num_parents < 1)\n-\t\tmax_cols++;\n-\n-\t/*\n-\t * We added a column for the current commit as part of\n-\t * graph->num_parents.  If the current commit was already in\n-\t * graph->columns, then we have double counted it.\n-\t */\n-\tif (is_commit_in_existing_columns)\n-\t\tmax_cols--;\n-\n-\t/*\n-\t * Each column takes up 2 spaces\n-\t */\n-\tgraph->width = max_cols * 2;\n+\tgraph->mapping[graph->width] = i;\n+\tgraph->width += 2;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n {\n \tstruct commit_list *parent;\n \tint max_new_columns;\n-\tint mapping_idx;\n \tint i, seen_this, is_commit_in_columns;\n \n \t/*\n@@ -563,6 +526,8 @@ static void graph_update_columns(struct git_graph *graph)\n \tfor (i = 0; i < graph->mapping_size; i++)\n \t\tgraph->mapping[i] = -1;\n \n+\tgraph->width = 0;\n+\n \t/*\n \t * Populate graph->new_columns and graph->mapping\n \t *\n@@ -573,7 +538,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t * supposed to end up after the collapsing is performed.\n \t */\n \tseen_this = 0;\n-\tmapping_idx = 0;\n \tis_commit_in_columns = 1;\n \tfor (i = 0; i <= graph->num_columns; i++) {\n \t\tstruct commit *col_commit;\n@@ -587,7 +551,6 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t}\n \n \t\tif (col_commit == graph->commit) {\n-\t\t\tint old_mapping_idx = mapping_idx;\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n \t\t\tfor (parent = first_interesting_parent(graph);\n@@ -602,21 +565,18 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\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+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n \t\t\t}\n \t\t\t/*\n-\t\t\t * We always need to increment mapping_idx by at\n+\t\t\t * We always need to increment graph->width by at\n \t\t\t * least 2, even if it has no interesting parents.\n \t\t\t * The current commit always takes up at least 2\n \t\t\t * spaces.\n \t\t\t */\n-\t\t\tif (mapping_idx == old_mapping_idx)\n-\t\t\t\tmapping_idx += 2;\n+\t\t\tif (graph->num_parents == 0)\n+\t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit,\n-\t\t\t\t\t\t      &mapping_idx);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n \t\t}\n \t}\n \n@@ -626,11 +586,6 @@ static void graph_update_columns(struct git_graph *graph)\n \twhile (graph->mapping_size > 1 &&\n \t       graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n-\n-\t/*\n-\t * Compute graph->width for this commit\n-\t */\n-\tgraph_update_width(graph, is_commit_in_columns);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n-- \ngitgitgadget\n\n"},{"id":"384141","messageId":"631ee3cecb68d9f776d4a8fb30c1bca70797ba14.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 07/13] graph: example of graph output that can be simplified","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:53Z","receivedAt":"2019-10-15T23:48:13Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThe commits following this one introduce a series of improvements to the\nlayout of graphs, tidying up a few edge cases, namely:\n\n- merge whose first parent fuses with an existing column to the left\n- merge whose last parent fuses with its immediate neighbor on the right\n- edges that collapse to the left above and below a commit line\n\nThis test case exemplifies these cases and provides a motivating example\nof the kind of history I'm aiming to clear up.\n\nThe first parent of merge E is the same as the parent of H, so those\nedges fuse together.\n\n        * H\n        |\n        | *-.   E\n        | |\\ \\\n        |/ / /\n        |\n        * B\n\nWe can \"skew\" the display of this merge so that it doesn't introduce\nadditional columns that immediately collapse:\n\n        * H\n        |\n        | *   E\n        |/|\\\n        |\n        * B\n\nThe last parent of E is D, the same as the parent of F which is the edge\nto the right of the merge.\n\n            * F\n            |\n             \\\n          *-. \\   E\n          |\\ \\ \\\n         / / / /\n            | /\n            |/\n            * D\n\nThe two edges leading to D could be fused sooner: rather than expanding\nthe F edge around the merge and then letting the edges collapse, the F\nedge could fuse with the E edge in the post-merge line:\n\n            * F\n            |\n             \\\n          *-. | E\n          |\\ \\|\n         / / /\n            |\n            * D\n\nIf this is combined with the \"skew\" effect above, we get a much cleaner\ngraph display for these edges:\n\n            * F\n            |\n          * | E\n         /|\\|\n            |\n            * D\n\nFinally, the edge leading from C to A appears jagged as it passes\nthrough the commit line for B:\n\n        | * | C\n        | |/\n        * | B\n        |/\n        * A\n\nThis can be smoothed out so that such edges are easier to read:\n\n        | * | C\n        | |/\n        * / B\n        |/\n        * A\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n t/t4215-log-skewed-merges.sh | 43 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 43 insertions(+)\n create mode 100755 t/t4215-log-skewed-merges.sh\n\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nnew file mode 100755\nindex 0000000000..4582ba066a\n--- /dev/null\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -0,0 +1,43 @@\n+#!/bin/sh\n+\n+test_description='git log --graph of skewed merges'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n+\tcat >expect <<-\\EOF &&\n+\t*   H\n+\t|\\\n+\t| *   G\n+\t| |\\\n+\t| | * F\n+\t| | |\n+\t| |  \\\n+\t| *-. \\   E\n+\t| |\\ \\ \\\n+\t|/ / / /\n+\t| | | /\n+\t| | |/\n+\t| | * D\n+\t| * | C\n+\t| |/\n+\t* | B\n+\t|/\n+\t* A\n+\tEOF\n+\n+\tgit checkout --orphan _p &&\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tgit checkout -b _q @^ && test_commit C &&\n+\tgit checkout -b _r @^ && test_commit D &&\n+\tgit checkout _p && git merge --no-ff _q _r -m E &&\n+\tgit checkout _r && test_commit F &&\n+\tgit checkout _p && git merge --no-ff _r -m G &&\n+\tgit checkout @^^ && git merge --no-ff _p -m H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"384142","messageId":"2f75c697beb4cab52815ef57716a9e1810bedfe3.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 09/13] graph: commit and post-merge lines for left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:55Z","receivedAt":"2019-10-15T23:48:15Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nFollowing the introduction of \"left-skewed\" merges, which are merges\nwhose first parent fuses with another edge to its left, we have some\nmore edge cases to deal with in the display of commit and post-merge\nlines.\n\nThe current graph code handles the following cases for edges appearing\nto the right of the commit (*) on commit lines. A 2-way merge is usually\nfollowed by vertical lines:\n\n        | | |\n        | * |\n        | |\\ \\\n\nAn octopus merge (more than two parents) is always followed by edges\nsloping to the right:\n\n        | |  \\          | |    \\\n        | *-. \\         | *---. \\\n        | |\\ \\ \\        | |\\ \\ \\ \\\n\nA 2-way merge is followed by a right-sloping edge if the commit line\nimmediately follows a post-merge line for a commit that appears in the\nsame column as the current commit, or any column to the left of that:\n\n        | *             | * |\n        | |\\            | |\\ \\\n        | * \\           | | * \\\n        | |\\ \\          | | |\\ \\\n\nThis commit introduces the following new cases for commit lines. If a\n2-way merge skews to the left, then the edges to its right are always\nvertical lines, even if the commit follows a post-merge line:\n\n        | | |           | |\\\n        | * |           | * |\n        |/| |           |/| |\n\nA commit with 3 parents that skews left is followed by vertical edges:\n\n        | | |\n        | * |\n        |/|\\ \\\n\nIf a 3-way left-skewed merge commit appears immediately after a\npost-merge line, then it may be followed the right-sloping edges, just\nlike a 2-way merge that is not skewed.\n\n        | |\\\n        | * \\\n        |/|\\ \\\n\nOctopus merges with 4 or more parents that skew to the left will always\nbe followed by right-sloping edges, because the existing columns need to\nexpand around the merge.\n\n        | |  \\\n        | *-. \\\n        |/|\\ \\ \\\n\nOn post-merge lines, usually all edges following the current commit\nslope to the right:\n\n        | * | |\n        | |\\ \\ \\\n\nHowever, if the commit is a left-skewed 2-way merge, the edges to its\nright remain vertical. We also need to display a space after the\nvertical line descending from the commit marker, whereas this line would\nnormally be followed by a backslash.\n\n        | * | |\n        |/| | |\n\nIf a left-skewed merge has more than 2 parents, then the edges to its\nright are still sloped as they bend around the edges introduced by the\nmerge.\n\n        | * | |\n        |/|\\ \\ \\\n\nTo handle these new cases, we need to know not just how many parents\neach commit has, but how many new columns it adds to the display; this\nquantity is recorded in the `edges_added` field for the current commit,\nand `prev_edges_added` field for the previous commit.\n\nHere, \"column\" refers to visual columns, not the logical columns of the\n`columns` array. This is because even if all the commit's parents end up\nfusing with existing edges, they initially introduce distinct edges in\nthe commit and post-merge lines before those edges collapse. For\nexample, a 3-way merge whose 2nd and 3rd parents fuse with existing\nedges still introduces 2 visual columns that affect the display of edges\nto their right.\n\n        | | |  \\\n        | | *-. \\\n        | | |\\ \\ \\\n        | |_|/ / /\n        |/| | / /\n        | | |/ /\n        | |/| |\n        | | | |\n\nThis merge does not introduce any *logical* columns; there are 4 edges\nbefore and after this commit once all edges have collapsed. But it does\ninitially introduce 2 new edges that need to be accommodated by the\nedges to their right.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      |  63 +++++++++++++--\n t/t4215-log-skewed-merges.sh | 147 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 203 insertions(+), 7 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex e37127f5ab..21edad8085 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -216,6 +216,46 @@ struct git_graph {\n \t * \t\t|/| | | | |\t\t| | | | | *\n \t */\n \tint merge_layout;\n+\t/*\n+\t * The number of columns added to the graph by the current commit. For\n+\t * 2-way and octopus merges, this is is usually one less than the\n+\t * number of parents:\n+\t *\n+\t * \t\t| | |\t\t\t| |    \\\n+\t *\t\t| * |\t\t\t| *---. \\\n+\t *\t\t| |\\ \\\t\t\t| |\\ \\ \\ \\\n+\t *\t\t| | | |         \t| | | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 1\t\tedges_added: 3\n+\t *\n+\t * For left-skewed merges, the first parent fuses with its neighbor and\n+\t * so one less column is added:\n+\t *\n+\t *\t\t| | |\t\t\t| |  \\\n+\t *\t\t| * |\t\t\t| *-. \\\n+\t *\t\t|/| |\t\t\t|/|\\ \\ \\\n+\t *\t\t| | |\t\t\t| | | | |\n+\t *\n+\t *\t\tnum_parents: 2\t\tnum_parents: 4\n+\t *\t\tedges_added: 0\t\tedges_added: 2\n+\t *\n+\t * This number determines how edges to the right of the merge are\n+\t * displayed in commit and post-merge lines; if no columns have been\n+\t * added then a vertical line should be used where a right-tracking\n+\t * line would otherwise be used.\n+\t *\n+\t *\t\t| * \\\t\t\t| * |\n+\t *\t\t| |\\ \\\t\t\t|/| |\n+\t *\t\t| | * \\\t\t\t| * |\n+\t */\n+\tint edges_added;\n+\t/*\n+\t * The number of columns added by the previous commit, which is used to\n+\t * smooth edges appearing to the right of a commit in a commit line\n+\t * following a post-merge line.\n+\t */\n+\tint prev_edges_added;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -328,6 +368,8 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n \tgraph->merge_layout = 0;\n+\tgraph->edges_added = 0;\n+\tgraph->prev_edges_added = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -689,6 +731,9 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n+\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -947,12 +992,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \n \t\t\tif (graph->num_parents > 2)\n \t\t\t\tgraph_draw_octopus_merge(graph, line);\n-\t\t} else if (seen_this && (graph->num_parents > 2)) {\n+\t\t} else if (seen_this && (graph->edges_added > 1)) {\n \t\t\tgraph_line_write_column(line, col, '\\\\');\n-\t\t} else if (seen_this && (graph->num_parents == 2)) {\n+\t\t} else if (seen_this && (graph->edges_added == 1)) {\n \t\t\t/*\n-\t\t\t * This is a 2-way merge commit.\n-\t\t\t * There is no GRAPH_PRE_COMMIT stage for 2-way\n+\t\t\t * This is either a right-skewed 2-way merge\n+\t\t\t * commit, or a left-skewed 3-way merge.\n+\t\t\t * There is no GRAPH_PRE_COMMIT stage for such\n \t\t\t * merges, so this is the first line of output\n \t\t\t * for this commit.  Check to see what the previous\n \t\t\t * line of output was.\n@@ -964,6 +1010,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\t\t * makes the output look nicer.\n \t\t\t */\n \t\t\tif (graph->prev_state == GRAPH_POST_MERGE &&\n+\t\t\t    graph->prev_edges_added > 0 &&\n \t\t\t    graph->prev_commit_index < i)\n \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n@@ -1033,8 +1080,14 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t\t\telse\n \t\t\t\t\tidx++;\n \t\t\t}\n+\t\t\tif (graph->edges_added == 0)\n+\t\t\t\tgraph_line_addch(line, ' ');\n+\n \t\t} else if (seen_this) {\n-\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\tif (graph->edges_added > 0)\n+\t\t\t\tgraph_line_write_column(line, col, '\\\\');\n+\t\t\telse\n+\t\t\t\tgraph_line_write_column(line, col, '|');\n \t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex dc187b5caf..e673cdb6f7 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,7 +11,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| * \\   E\n+\t| * |   E\n \t|/|\\ \\\n \t| | |/\n \t| | * D\n@@ -43,7 +43,7 @@ test_expect_success 'log --graph with left-skewed merge' '\n \t| | | | * 0_G\n \t| |_|_|/|\n \t|/| | | |\n-\t| | | * \\   0_F\n+\t| | | * |   0_F\n \t| |_|/|\\ \\\n \t|/| | | |/\n \t| | | | * 0_E\n@@ -73,4 +73,147 @@ test_expect_success 'log --graph with left-skewed merge' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with nested left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   1_H\n+\t|\\\n+\t| *   1_G\n+\t| |\\\n+\t| | * 1_F\n+\t| * | 1_E\n+\t|/| |\n+\t| * | 1_D\n+\t* | | 1_C\n+\t|/ /\n+\t* | 1_B\n+\t|/\n+\t* 1_A\n+\tEOF\n+\n+\tgit checkout --orphan 1_p &&\n+\ttest_commit 1_A &&\n+\ttest_commit 1_B &&\n+\ttest_commit 1_C &&\n+\tgit checkout -b 1_q @^ && test_commit 1_D &&\n+\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n+\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n+\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n+\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   2_K\n+\t|\\\n+\t| *   2_J\n+\t| |\\\n+\t| | *   2_H\n+\t| | |\\\n+\t| | * | 2_G\n+\t| |/| |\n+\t| | * | 2_F\n+\t| * | | 2_E\n+\t| |/ /\n+\t| * | 2_D\n+\t* | | 2_C\n+\t| |/\n+\t|/|\n+\t* | 2_B\n+\t|/\n+\t* 2_A\n+\tEOF\n+\n+\tgit checkout --orphan 2_p &&\n+\ttest_commit 2_A &&\n+\ttest_commit 2_B &&\n+\ttest_commit 2_C &&\n+\tgit checkout -b 2_q @^^ &&\n+\ttest_commit 2_D &&\n+\ttest_commit 2_E &&\n+\tgit checkout -b 2_r @^ && test_commit 2_F &&\n+\tgit checkout 2_q &&\n+\tgit merge --no-ff 2_r -m 2_G &&\n+\tgit merge --no-ff 2_p^ -m 2_H &&\n+\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n+\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*   3_J\n+\t|\\\n+\t| *   3_H\n+\t| |\\\n+\t| | * 3_G\n+\t| * | 3_F\n+\t|/| |\n+\t| * |   3_E\n+\t| |\\ \\\n+\t| | |/\n+\t| | * 3_D\n+\t| * | 3_C\n+\t| |/\n+\t| * 3_B\n+\t|/\n+\t* 3_A\n+\tEOF\n+\n+\tgit checkout --orphan 3_p &&\n+\ttest_commit 3_A &&\n+\tgit checkout -b 3_q &&\n+\ttest_commit 3_B &&\n+\ttest_commit 3_C &&\n+\tgit checkout -b 3_r @^ &&\n+\ttest_commit 3_D &&\n+\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n+\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n+\tgit checkout 3_r && test_commit 3_G &&\n+\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n+\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n+\tcat >expect <<-\\EOF &&\n+\t*   4_H\n+\t|\\\n+\t| *   4_G\n+\t| |\\\n+\t| * | 4_F\n+\t|/| |\n+\t| * |   4_E\n+\t| |\\ \\\n+\t| | * | 4_D\n+\t| |/ /\n+\t|/| |\n+\t| | * 4_C\n+\t| |/\n+\t| * 4_B\n+\t|/\n+\t* 4_A\n+\tEOF\n+\n+\tgit checkout --orphan 4_p &&\n+\ttest_commit 4_A &&\n+\ttest_commit 4_B &&\n+\ttest_commit 4_C &&\n+\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n+\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n+\tgit checkout -b 4_s 4_p^^ &&\n+\tgit merge --no-ff 4_r -m 4_F &&\n+\tgit merge --no-ff 4_p -m 4_G &&\n+\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n+\n+\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"384143","messageId":"cd761b3a320c4f4285021bfa3ff04bb0a67ee688.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 13/13] graph: fix coloring of octopus dashes","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:59Z","receivedAt":"2019-10-15T23:48:17Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nIn 04005834ed (\"log: fix coloring of certain octopus merge shapes\",\n2018-09-01) there is a fix for the coloring of dashes following an\noctopus merge. It makes a distinction between the case where all parents\nintroduce a new column, versus the case where the first parent collapses\ninto an existing column:\n\n        | *-.           | *-.\n        | |\\ \\          | |\\ \\\n        | | | |         |/ / /\n\nThe latter case means that the columns for the merge parents begin one\nplace to the left in the `new_columns` array compared to the former\ncase.\n\nHowever, the implementation only works if the commit's parents are kept\nin order as they map onto the visual columns, as we get the colors by\niterating over `new_columns` as we print the dashes. In general, the\ncommit's parents can arbitrarily merge with existing columns, and change\ntheir ordering in the process.\n\nFor example, in the following diagram, the number of each column\nindicates which commit parent appears in each column.\n\n        | | *---.\n        | | |\\ \\ \\\n        | | |/ / /\n        | |/| | /\n        | |_|_|/\n        |/| | |\n        3 1 0 2\n\nIf the columns are colored (red, green, yellow, blue), then the dashes\nwill currently be colored yellow and blue, whereas they should be blue\nand red.\n\nTo fix this, we need to look up each column in the `mapping` array,\nwhich before the `GRAPH_COLLAPSING` state indicates which logical column\nis displayed in each visual column. This implementation is simpler as it\ndoesn't have any edge cases, and it also handles how left-skewed first\nparents are now displayed:\n\n        | *-.\n        |/|\\ \\\n        | | | |\n        0 1 2 3\n\nThe color of the first dashes is always the color found in `mapping` two\ncolumns to the right of the commit symbol. Because commits are displayed\nafter all edges have been collapsed together and the visual columns\nmatch the logical ones, we can find the visual offset of the commit\nsymbol using `commit_index`.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 71 +++++++++++++++++++-----------------\n t/t4214-log-graph-octopus.sh | 10 ++---\n 2 files changed, 42 insertions(+), 39 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 80db74aee6..e3fd0ea5f8 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -684,6 +684,11 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_dashed_parents(struct git_graph *graph)\n+{\n+\treturn graph->num_parents + graph->merge_layout - 3;\n+}\n+\n static int graph_num_expansion_rows(struct git_graph *graph)\n {\n \t/*\n@@ -706,7 +711,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)\n \t * \t\t| * \\\n \t * \t\t|/|\\ \\\n \t */\n-\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+\treturn graph_num_dashed_parents(graph) * 2;\n }\n \n static int graph_needs_pre_commit_line(struct git_graph *graph)\n@@ -934,47 +939,45 @@ static void graph_output_commit_char(struct git_graph *graph, struct graph_line\n static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)\n {\n \t/*\n-\t * Here dashless_parents represents the number of parents which don't\n-\t * need to have dashes (the edges labeled \"0\" and \"1\").  And\n-\t * dashful_parents are the remaining ones.\n+\t * The parents of a merge commit can be arbitrarily reordered as they\n+\t * are mapped onto display columns, for example this is a valid merge:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * | | | | |\n-\t * x 0 1 2 3\n+\t *\t| | *---.\n+\t *\t| | |\\ \\ \\\n+\t *\t| | |/ / /\n+\t *\t| |/| | /\n+\t *\t| |_|_|/\n+\t *\t|/| | |\n+\t *\t3 1 0 2\n \t *\n-\t */\n-\tconst int dashless_parents = 3 - graph->merge_layout;\n-\tint dashful_parents = graph->num_parents - dashless_parents;\n-\n-\t/*\n-\t * Usually, we add one new column for each parent (like the diagram\n-\t * above) but sometimes the first parent goes into an existing column,\n-\t * like this:\n+\t * The numbers denote which parent of the merge each visual column\n+\t * corresponds to; we can't assume that the parents will initially\n+\t * display in the order given by new_columns.\n \t *\n-\t * | *-.\n-\t * |/|\\ \\\n-\t * | | | |\n-\t * x 0 1 2\n+\t * To find the right color for each dash, we need to consult the\n+\t * mapping array, starting from the column 2 places to the right of the\n+\t * merge commit, and use that to find out which logical column each\n+\t * edge will collapse to.\n \t *\n-\t * In which case the number of parents will be one greater than the\n-\t * number of added columns.\n+\t * Commits are rendered once all edges have collapsed to their correct\n+\t * logcial column, so commit_index gives us the right visual offset for\n+\t * the merge commit.\n \t */\n-\tint added_cols = (graph->num_new_columns - graph->num_columns);\n-\tint parent_in_old_cols = graph->num_parents - added_cols;\n \n-\t/*\n-\t * In both cases, commit_index corresponds to the edge labeled \"0\".\n-\t */\n-\tint first_col = graph->commit_index + dashless_parents\n-\t    - parent_in_old_cols;\n+\tint i, j;\n+\tstruct column *col;\n \n-\tint i;\n-\tfor (i = 0; i < dashful_parents; i++) {\n-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col], '-');\n-\t\tgraph_line_write_column(line, &graph->new_columns[i+first_col],\n-\t\t\t\t\t  i == dashful_parents-1 ? '.' : '-');\n+\tint dashed_parents = graph_num_dashed_parents(graph);\n+\n+\tfor (i = 0; i < dashed_parents; i++) {\n+\t\tj = graph->mapping[(graph->commit_index + i + 2) * 2];\n+\t\tcol = &graph->new_columns[j];\n+\n+\t\tgraph_line_write_column(line, col, '-');\n+\t\tgraph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-');\n \t}\n+\n+\treturn;\n }\n \n static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 21bc600a82..40d27db674 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -121,7 +121,7 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with normal octopus and child merge with colors' '\n+test_expect_success 'log --graph with normal octopus and child merge with colors' '\n \tcat >expect.colors <<-\\EOF &&\n \t* after-merge\n \t*<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n@@ -161,7 +161,7 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with tricky octopus merge and its child with colors' '\n+test_expect_success 'log --graph with tricky octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n@@ -205,7 +205,7 @@ test_expect_success 'log --graph with crossover in octopus merge, no color' '\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -253,7 +253,7 @@ test_expect_success 'log --graph with crossover in octopus merge and its child,\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with crossover in octopus merge and its child with colors' '\n+test_expect_success 'log --graph with crossover in octopus merge and its child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-4\n@@ -349,7 +349,7 @@ test_expect_success 'log --graph with unrelated commit and octopus child, no col\n \ttest_cmp expect.uncolored actual\n '\n \n-test_expect_failure 'log --graph with unrelated commit and octopus child with colors' '\n+test_expect_success 'log --graph with unrelated commit and octopus child with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* after-initial\n-- \ngitgitgadget\n"},{"id":"384145","messageId":"c34a5eb160310613cbde6313cda6cff753d6d7fd.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 08/13] graph: tidy up display of left-skewed merges","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:54Z","receivedAt":"2019-10-15T23:48:18Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nCurrently, when we display a merge whose first parent is already present\nin a column to the left of the merge commit, we display the first parent\nas a vertical pipe `|` in the GRAPH_POST_MERGE line and then immediately\nenter the GRAPH_COLLAPSING state. The first-parent line tracks to the\nleft and all the other parent lines follow it; this creates a \"kink\" in\nthose lines:\n\n        | *---.\n        | |\\ \\ \\\n        |/ / / /\n        | | | *\n\nThis change tidies the display of such commits such that if the first\nparent appears to the left of the merge, we render it as a `/` and the\nsecond parent as a `|`. This reduces the horizontal and vertical space\nneeded to render the merge, and makes the resulting lines easier to\nread.\n\n        | *-.\n        |/|\\ \\\n        | | | *\n\nIf the first parent is separated from the merge by several columns, a\nhorizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING\nstate displays the line.\n\n        | | | *-.\n        | |_|/|\\ \\\n        |/| | | | *\n\nThis effect is applied to both \"normal\" two-parent merges, and to\noctopus merges. It also reduces the vertical space needed for pre-commit\nlines, as the merge occupies one less column than usual.\n\n        Before:         After:\n\n        | *             | *\n        | |\\            | |\\\n        | | \\           | * \\\n        | |  \\          |/|\\ \\\n        | *-. \\\n        | |\\ \\ \\\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                      | 125 +++++++++++++++++++++++++++--------\n t/t4214-log-graph-octopus.sh |  20 +++---\n t/t4215-log-skewed-merges.sh |  45 +++++++++++--\n 3 files changed, 144 insertions(+), 46 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex bd7403065e..e37127f5ab 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -202,6 +202,20 @@ struct git_graph {\n \t * previous commit.\n \t */\n \tint prev_commit_index;\n+\t/*\n+\t * Which layout variant to use to display merge commits. If the\n+\t * commit's first parent is known to be in a column to the left of the\n+\t * merge, then this value is 0 and we use the layout on the left.\n+\t * Otherwise, the value is 1 and the layout on the right is used. This\n+\t * field tells us how many columns the first parent occupies.\n+\t *\n+\t * \t\t0)\t\t\t1)\n+\t *\n+\t * \t\t| | | *-.\t\t| | *---.\n+\t * \t\t| |_|/|\\ \\\t\t| | |\\ \\ \\\n+\t * \t\t|/| | | | |\t\t| | | | | *\n+\t */\n+\tint merge_layout;\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n@@ -313,6 +327,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tgraph->prev_state = GRAPH_PADDING;\n \tgraph->commit_index = 0;\n \tgraph->prev_commit_index = 0;\n+\tgraph->merge_layout = 0;\n \tgraph->num_columns = 0;\n \tgraph->num_new_columns = 0;\n \tgraph->mapping_size = 0;\n@@ -472,9 +487,11 @@ static int graph_find_new_column_by_commit(struct git_graph *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  struct commit *commit,\n+\t\t\t\t\t  int idx)\n {\n \tint i = graph_find_new_column_by_commit(graph, commit);\n+\tint mapping_idx;\n \n \t/*\n \t * If the commit is not already in the new_columns array, then add it\n@@ -486,8 +503,26 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tgraph->new_columns[i].color = graph_find_commit_color(graph, commit);\n \t}\n \n-\tgraph->mapping[graph->width] = i;\n-\tgraph->width += 2;\n+\tif (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {\n+\t\t/*\n+\t\t * If this is the first parent of a merge, choose a layout for\n+\t\t * the merge line based on whether the parent appears in a\n+\t\t * column to the left of the merge\n+\t\t */\n+\t\tint dist, shift;\n+\n+\t\tdist = idx - i;\n+\t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n+\n+\t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n+\t\tgraph->width += 2 * graph->merge_layout;\n+\t} else {\n+\t\tmapping_idx = graph->width;\n+\t\tgraph->width += 2;\n+\t}\n+\n+\tgraph->mapping[mapping_idx] = i;\n }\n \n static void graph_update_columns(struct git_graph *graph)\n@@ -553,6 +588,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tif (col_commit == graph->commit) {\n \t\t\tseen_this = 1;\n \t\t\tgraph->commit_index = i;\n+\t\t\tgraph->merge_layout = -1;\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@@ -565,7 +601,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\t\t    !is_commit_in_columns) {\n \t\t\t\t\tgraph_increment_column_color(graph);\n \t\t\t\t}\n-\t\t\t\tgraph_insert_into_new_columns(graph, parent->item);\n+\t\t\t\tgraph_insert_into_new_columns(graph, parent->item, i);\n \t\t\t}\n \t\t\t/*\n \t\t\t * We always need to increment graph->width by at\n@@ -576,7 +612,7 @@ static void graph_update_columns(struct git_graph *graph)\n \t\t\tif (graph->num_parents == 0)\n \t\t\t\tgraph->width += 2;\n \t\t} else {\n-\t\t\tgraph_insert_into_new_columns(graph, col_commit);\n+\t\t\tgraph_insert_into_new_columns(graph, col_commit, -1);\n \t\t}\n \t}\n \n@@ -588,10 +624,36 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping_size--;\n }\n \n+static int graph_num_expansion_rows(struct git_graph *graph)\n+{\n+\t/*\n+\t * Normally, we need two expansion rows for each dashed parent line from\n+\t * an octopus merge:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| | \\\n+\t * \t\t| |  \\\n+\t * \t\t| *-. \\\n+\t * \t\t| |\\ \\ \\\n+\t *\n+\t * If the merge is skewed to the left, then its parents occupy one less\n+\t * column, and we don't need as many expansion rows to route around it;\n+\t * in some cases that means we don't need any expansion rows at all:\n+\t *\n+\t * \t\t| *\n+\t * \t\t| |\\\n+\t * \t\t| * \\\n+\t * \t\t|/|\\ \\\n+\t */\n+\treturn (graph->num_parents + graph->merge_layout - 3) * 2;\n+}\n+\n static int graph_needs_pre_commit_line(struct git_graph *graph)\n {\n \treturn graph->num_parents >= 3 &&\n-\t       graph->commit_index < (graph->num_columns - 1);\n+\t       graph->commit_index < (graph->num_columns - 1) &&\n+\t       graph->expansion_row < graph_num_expansion_rows(graph);\n }\n \n void graph_update(struct git_graph *graph, struct commit *commit)\n@@ -728,7 +790,6 @@ static void graph_output_skip_line(struct git_graph *graph, struct graph_line *l\n static void graph_output_pre_commit_line(struct git_graph *graph,\n \t\t\t\t\t struct graph_line *line)\n {\n-\tint num_expansion_rows;\n \tint i, seen_this;\n \n \t/*\n@@ -739,14 +800,13 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * We need 2 extra rows for every parent over 2.\n \t */\n \tassert(graph->num_parents >= 3);\n-\tnum_expansion_rows = (graph->num_parents - 2) * 2;\n \n \t/*\n \t * graph->expansion_row tracks the current expansion row we are on.\n \t * It should be in the range [0, num_expansion_rows - 1]\n \t */\n \tassert(0 <= graph->expansion_row &&\n-\t       graph->expansion_row < num_expansion_rows);\n+\t       graph->expansion_row < graph_num_expansion_rows(graph));\n \n \t/*\n \t * Output the row\n@@ -786,7 +846,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,\n \t * and move to state GRAPH_COMMIT if necessary\n \t */\n \tgraph->expansion_row++;\n-\tif (graph->expansion_row >= num_expansion_rows)\n+\tif (!graph_needs_pre_commit_line(graph))\n \t\tgraph_update_state(graph, GRAPH_COMMIT);\n }\n \n@@ -824,7 +884,7 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line\n \t * x 0 1 2 3\n \t *\n \t */\n-\tconst int dashless_parents = 2;\n+\tconst int dashless_parents = 3 - graph->merge_layout;\n \tint dashful_parents = graph->num_parents - dashless_parents;\n \n \t/*\n@@ -832,9 +892,9 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line\n \t * above) but sometimes the first parent goes into an existing column,\n \t * like this:\n \t *\n-\t * | *---.\n-\t * | |\\ \\ \\\n-\t * |/ / / /\n+\t * | *-.\n+\t * |/|\\ \\\n+\t * | | | |\n \t * x 0 1 2\n \t *\n \t * In which case the number of parents will be one greater than the\n@@ -925,10 +985,15 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\tgraph_update_state(graph, GRAPH_COLLAPSING);\n }\n \n+const char merge_chars[] = {'/', '|', '\\\\'};\n+\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i, j;\n+\tint i;\n+\n+\tstruct commit_list *first_parent = first_interesting_parent(graph);\n+\tint seen_parent = 0;\n \n \t/*\n \t * Output the post-merge row\n@@ -951,30 +1016,34 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\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 commit_list *parents = first_parent;\n \t\t\tint par_column;\n+\t\t\tint idx = graph->merge_layout;\n+\t\t\tchar c;\n \t\t\tseen_this = 1;\n-\t\t\tparents = first_interesting_parent(graph);\n-\t\t\tassert(parents);\n-\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n-\t\t\tassert(par_column >= 0);\n-\n-\t\t\tgraph_line_write_column(line, &graph->new_columns[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+\n+\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n-\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], '\\\\');\n-\t\t\t\tgraph_line_addch(line, ' ');\n+\n+\t\t\t\tc = merge_chars[idx];\n+\t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n+\t\t\t\tif (idx == 2)\n+\t\t\t\t\tgraph_line_addch(line, ' ');\n+\t\t\t\telse\n+\t\t\t\t\tidx++;\n \t\t\t}\n \t\t} else if (seen_this) {\n \t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\tgraph_line_addch(line, ' ');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\n-\t\t\tgraph_line_addch(line, ' ');\n+\t\t\tif (graph->merge_layout != 0 || i != graph->commit_index - 1)\n+\t\t\t\tgraph_line_addch(line, seen_parent ? '_' : ' ');\n \t\t}\n+\n+\t\tif (col_commit == first_parent->item)\n+\t\t\tseen_parent = 1;\n \t}\n \n \t/*\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 3ae8e51e50..1b96276894 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -26,9 +26,8 @@ test_expect_success 'set up merge history' '\n test_expect_success 'log --graph with tricky octopus merge, no color' '\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n@@ -47,9 +46,8 @@ test_expect_success 'log --graph with tricky octopus merge with colors' '\n \ttest_config log.graphColors red,green,yellow,blue,magenta,cyan &&\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n-\t<RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><YELLOW>\\<RESET> <BLUE>\\<RESET> <MAGENTA>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET>\n+\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET>\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n@@ -147,9 +145,8 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \tcat >expect.uncolored <<-\\EOF &&\n \t* left\n \t| * after-merge\n-\t| *---.   octopus-merge\n-\t| |\\ \\ \\\n-\t|/ / / /\n+\t| *-.   octopus-merge\n+\t|/|\\ \\\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n@@ -169,9 +166,8 @@ test_expect_failure 'log --graph with tricky octopus merge and its child with co\n \tcat >expect.colors <<-\\EOF &&\n \t* left\n \t<RED>|<RESET> * after-merge\n-\t<RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n-\t<RED>|<RESET> <RED>|<RESET><BLUE>\\<RESET> <MAGENTA>\\<RESET> <CYAN>\\<RESET>\n-\t<RED>|<RESET><RED>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> <CYAN>/<RESET>\n+\t<RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET>   octopus-merge\n+\t<RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\\<RESET> <CYAN>\\<RESET>\n \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex 4582ba066a..dc187b5caf 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,12 +11,8 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| | |\n-\t| |  \\\n-\t| *-. \\   E\n-\t| |\\ \\ \\\n-\t|/ / / /\n-\t| | | /\n+\t| * \\   E\n+\t|/|\\ \\\n \t| | |/\n \t| | * D\n \t| * | C\n@@ -40,4 +36,41 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with left-skewed merge' '\n+\tcat >expect <<-\\EOF &&\n+\t*-----.   0_H\n+\t|\\ \\ \\ \\\n+\t| | | | * 0_G\n+\t| |_|_|/|\n+\t|/| | | |\n+\t| | | * \\   0_F\n+\t| |_|/|\\ \\\n+\t|/| | | |/\n+\t| | | | * 0_E\n+\t| |_|_|/\n+\t|/| | |\n+\t| | * | 0_D\n+\t| | |/\n+\t| | * 0_C\n+\t| |/\n+\t|/|\n+\t| * 0_B\n+\t|/\n+\t* 0_A\n+\tEOF\n+\n+\tgit checkout --orphan 0_p && test_commit 0_A &&\n+\tgit checkout -b 0_q 0_p && test_commit 0_B &&\n+\tgit checkout -b 0_r 0_p &&\n+\ttest_commit 0_C &&\n+\ttest_commit 0_D &&\n+\tgit checkout -b 0_s 0_p && test_commit 0_E &&\n+\tgit checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&\n+\tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n+\tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"384144","messageId":"f5c27914360e4f9139d676b777484fa55e87d78d.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 10/13] graph: rename `new_mapping` to `old_mapping`","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:56Z","receivedAt":"2019-10-15T23:48:19Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nThe change I'm about to make requires being able to inspect the mapping\narray that was used to render the last GRAPH_COLLAPSING line while\nrendering a GRAPH_COMMIT line. The `new_mapping` array currently exists\nas a pre-allocated space for computing the next `mapping` array during\n`graph_output_collapsing_line()`, but we can repurpose it to let us see\nthe previous `mapping` state.\n\nTo support this use it will make more sense if this array is named\n`old_mapping`, as it will contain the mapping data for the previous line\nwe rendered, at the point we're rendering a commit line.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c | 54 +++++++++++++++++++++++++++---------------------------\n 1 file changed, 27 insertions(+), 27 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 21edad8085..2315f3604d 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -259,7 +259,7 @@ struct git_graph {\n \t/*\n \t * The maximum number of columns that can be stored in the columns\n \t * and new_columns arrays.  This is also half the number of entries\n-\t * that can be stored in the mapping and new_mapping arrays.\n+\t * that can be stored in the mapping and old_mapping arrays.\n \t */\n \tint column_capacity;\n \t/*\n@@ -302,7 +302,7 @@ struct git_graph {\n \t * of the git_graph simply so we don't have to allocate a new\n \t * temporary array each time we have to output a collapsing line.\n \t */\n-\tint *new_mapping;\n+\tint *old_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@@ -388,7 +388,7 @@ struct git_graph *graph_init(struct rev_info *opt)\n \tALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);\n-\tALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);\n+\tALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);\n \n \t/*\n \t * The diff output prefix callback, with this we can make\n@@ -418,7 +418,7 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)\n \tREALLOC_ARRAY(graph->columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->new_columns, graph->column_capacity);\n \tREALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);\n-\tREALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);\n+\tREALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);\n }\n \n /*\n@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \tint horizontal_edge_target = -1;\n \n \t/*\n-\t * Clear out the new_mapping array\n+\t * Swap the mapping and old_mapping arrays\n+\t */\n+\tSWAP(graph->mapping, graph->old_mapping);\n+\n+\t/*\n+\t * Clear out the mapping array\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++)\n-\t\tgraph->new_mapping[i] = -1;\n+\t\tgraph->mapping[i] = -1;\n \n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->mapping[i];\n+\t\tint target = graph->old_mapping[i];\n \t\tif (target < 0)\n \t\t\tcontinue;\n \n@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * This column is already in the\n \t\t\t * correct place\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i] == -1);\n-\t\t\tgraph->new_mapping[i] = target;\n-\t\t} else if (graph->new_mapping[i - 1] < 0) {\n+\t\t\tassert(graph->mapping[i] == -1);\n+\t\t\tgraph->mapping[i] = target;\n+\t\t} else if (graph->mapping[i - 1] < 0) {\n \t\t\t/*\n \t\t\t * Nothing is to the left.\n \t\t\t * Move to the left by one\n \t\t\t */\n-\t\t\tgraph->new_mapping[i - 1] = target;\n+\t\t\tgraph->mapping[i - 1] = target;\n \t\t\t/*\n \t\t\t * If there isn't already an edge moving horizontally\n \t\t\t * select this one.\n@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t\t * line.\n \t\t\t\t */\n \t\t\t\tfor (j = (target * 2)+3; j < (i - 2); j += 2)\n-\t\t\t\t\tgraph->new_mapping[j] = target;\n+\t\t\t\t\tgraph->mapping[j] = target;\n \t\t\t}\n-\t\t} else if (graph->new_mapping[i - 1] == target) {\n+\t\t} else if (graph->mapping[i - 1] == target) {\n \t\t\t/*\n \t\t\t * There is a branch line to our left\n \t\t\t * already, and it is our target.  We\n@@ -1176,7 +1181,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * the same parent commit.\n \t\t\t *\n \t\t\t * We don't have to add anything to the\n-\t\t\t * output or new_mapping, since the\n+\t\t\t * output or mapping, since the\n \t\t\t * existing branch line has already taken\n \t\t\t * care of it.\n \t\t\t */\n@@ -1192,10 +1197,10 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t * The branch to the left of that space\n \t\t\t * should be our eventual target.\n \t\t\t */\n-\t\t\tassert(graph->new_mapping[i - 1] > target);\n-\t\t\tassert(graph->new_mapping[i - 2] < 0);\n-\t\t\tassert(graph->new_mapping[i - 3] == target);\n-\t\t\tgraph->new_mapping[i - 2] = target;\n+\t\t\tassert(graph->mapping[i - 1] > target);\n+\t\t\tassert(graph->mapping[i - 2] < 0);\n+\t\t\tassert(graph->mapping[i - 3] == target);\n+\t\t\tgraph->mapping[i - 2] = target;\n \t\t\t/*\n \t\t\t * Mark this branch as the horizontal edge to\n \t\t\t * prevent any other edges from moving\n@@ -1209,14 +1214,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\n-\tif (graph->new_mapping[graph->mapping_size - 1] < 0)\n+\tif (graph->mapping[graph->mapping_size - 1] < 0)\n \t\tgraph->mapping_size--;\n \n \t/*\n \t * Output out a line based on the new mapping info\n \t */\n \tfor (i = 0; i < graph->mapping_size; i++) {\n-\t\tint target = graph->new_mapping[i];\n+\t\tint target = graph->mapping[i];\n \t\tif (target < 0)\n \t\t\tgraph_line_addch(line, ' ');\n \t\telse if (target * 2 == i)\n@@ -1229,22 +1234,17 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t\t\t * won't continue into the next line.\n \t\t\t\t */\n \t\t\t\tif (i != (target * 2)+3)\n-\t\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\t\tgraph->mapping[i] = -1;\n \t\t\t\tused_horizontal = 1;\n \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '_');\n \t\t} else {\n \t\t\tif (used_horizontal && i < horizontal_edge)\n-\t\t\t\tgraph->new_mapping[i] = -1;\n+\t\t\t\tgraph->mapping[i] = -1;\n \t\t\tgraph_line_write_column(line, &graph->new_columns[target], '/');\n \n \t\t}\n \t}\n \n-\t/*\n-\t * Swap mapping and new_mapping\n-\t */\n-\tSWAP(graph->mapping, graph->new_mapping);\n-\n \t/*\n \t * If graph->mapping indicates that all of the branch lines\n \t * are already in the correct positions, we are done.\n-- \ngitgitgadget\n\n"},{"id":"384146","messageId":"04ba5169bc855ae4a65c33a0484926c22e68714a.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 12/13] graph: flatten edges that fuse with their right neighbor","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:58Z","receivedAt":"2019-10-15T23:48:20Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a merge commit is printed and its final parent is the same commit\nthat occupies the column to the right of the merge, this results in a\nkink in the displayed edges:\n\n        * |\n        |\\ \\\n        | |/\n        | *\n\nGraphs containing these shapes can be hard to read, as the expansion to\nthe right followed immediately by collapsing back to the left creates a\nlot of zig-zagging edges, especially when many columns are present.\n\nWe can improve this by eliminating the zig-zag and having the merge's\nfinal parent edge fuse immediately with its neighbor:\n\n        * |\n        |\\|\n        | *\n\nThis reduces the horizontal width for the current commit by 2, and\nrequires one less row, making the graph display more compact. Taken in\ncombination with other graph-smoothing enhancements, it greatly\ncompresses the space needed to display certain histories:\n\n        *\n        |\\\n        | *                       *\n        | |\\                      |\\\n        | | *                     | *\n        | | |                     | |\\\n        | |  \\                    | | *\n        | *-. \\                   | * |\n        | |\\ \\ \\        =>        |/|\\|\n        |/ / / /                  | | *\n        | | | /                   | * |\n        | | |/                    | |/\n        | | *                     * /\n        | * |                     |/\n        | |/                      *\n        * |\n        |/\n        *\n\nOne of the test cases here cannot be correctly rendered in Git v2.23.0;\nit produces this output following commit E:\n\n        | | *-. \\   5_E\n        | | |\\ \\ \\\n        | |/ / / /\n        | | | / _\n        | |_|/\n        |/| |\n\nThe new implementation makes sure that the rightmost edge in this\nhistory is not left dangling as above.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 34 +++++++++----\n t/t4215-log-skewed-merges.sh               | 56 ++++++++++++++++++----\n t/t6016-rev-list-graph-simplify-history.sh | 30 +++++-------\n 3 files changed, 86 insertions(+), 34 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 63f8d18baa..80db74aee6 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -557,8 +557,24 @@ static void graph_insert_into_new_columns(struct git_graph *graph,\n \t\tshift = (dist > 1) ? 2 * dist - 3 : 1;\n \n \t\tgraph->merge_layout = (dist > 0) ? 0 : 1;\n+\t\tgraph->edges_added = graph->num_parents + graph->merge_layout  - 2;\n+\n \t\tmapping_idx = graph->width + (graph->merge_layout - 1) * shift;\n \t\tgraph->width += 2 * graph->merge_layout;\n+\n+\t} else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 2]) {\n+\t\t/*\n+\t\t * If some columns have been added by a merge, but this commit\n+\t\t * was found in the last existing column, then adjust the\n+\t\t * numbers so that the two edges immediately join, i.e.:\n+\t\t *\n+\t\t *\t\t* |\t\t* |\n+\t\t *\t\t|\\ \\\t=>\t|\\|\n+\t\t *\t\t| |/\t\t| *\n+\t\t *\t\t| *\n+\t\t */\n+\t\tmapping_idx = graph->width - 2;\n+\t\tgraph->edges_added = -1;\n \t} else {\n \t\tmapping_idx = graph->width;\n \t\tgraph->width += 2;\n@@ -604,6 +620,8 @@ static void graph_update_columns(struct git_graph *graph)\n \t\tgraph->mapping[i] = -1;\n \n \tgraph->width = 0;\n+\tgraph->prev_edges_added = graph->edges_added;\n+\tgraph->edges_added = 0;\n \n \t/*\n \t * Populate graph->new_columns and graph->mapping\n@@ -731,9 +749,6 @@ void graph_update(struct git_graph *graph, struct commit *commit)\n \t */\n \tgraph_update_columns(graph);\n \n-\tgraph->prev_edges_added = graph->edges_added;\n-\tgraph->edges_added = graph->num_parents + graph->merge_layout - 2;\n-\n \tgraph->expansion_row = 0;\n \n \t/*\n@@ -1041,7 +1056,7 @@ const char merge_chars[] = {'/', '|', '\\\\'};\n static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)\n {\n \tint seen_this = 0;\n-\tint i;\n+\tint i, j;\n \n \tstruct commit_list *first_parent = first_interesting_parent(graph);\n \tint seen_parent = 0;\n@@ -1073,16 +1088,19 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l\n \t\t\tchar c;\n \t\t\tseen_this = 1;\n \n-\t\t\tfor (; parents; parents = next_interesting_parent(graph, parents)) {\n+\t\t\tfor (j = 0; j < graph->num_parents; j++) {\n \t\t\t\tpar_column = graph_find_new_column_by_commit(graph, parents->item);\n \t\t\t\tassert(par_column >= 0);\n \n \t\t\t\tc = merge_chars[idx];\n \t\t\t\tgraph_line_write_column(line, &graph->new_columns[par_column], c);\n-\t\t\t\tif (idx == 2)\n-\t\t\t\t\tgraph_line_addch(line, ' ');\n-\t\t\t\telse\n+\t\t\t\tif (idx == 2) {\n+\t\t\t\t\tif (graph->edges_added > 0 || j < graph->num_parents - 1)\n+\t\t\t\t\t\tgraph_line_addch(line, ' ');\n+\t\t\t\t} else {\n \t\t\t\t\tidx++;\n+\t\t\t\t}\n+\t\t\t\tparents = next_interesting_parent(graph, parents);\n \t\t\t}\n \t\t\tif (graph->edges_added == 0)\n \t\t\t\tgraph_line_addch(line, ' ');\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex 1745b3b64c..d33c6438d8 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -11,9 +11,8 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| *   G\n \t| |\\\n \t| | * F\n-\t| * |   E\n-\t|/|\\ \\\n-\t| | |/\n+\t| * | E\n+\t|/|\\|\n \t| | * D\n \t| * | C\n \t| |/\n@@ -43,9 +42,9 @@ test_expect_success 'log --graph with left-skewed merge' '\n \t| | | | * 0_G\n \t| |_|_|/|\n \t|/| | | |\n-\t| | | * |   0_F\n-\t| |_|/|\\ \\\n-\t|/| | | |/\n+\t| | | * | 0_F\n+\t| |_|/|\\|\n+\t|/| | | |\n \t| | | | * 0_E\n \t| |_|_|/\n \t|/| | |\n@@ -153,9 +152,8 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n \t| | * 3_G\n \t| * | 3_F\n \t|/| |\n-\t| * |   3_E\n-\t| |\\ \\\n-\t| | |/\n+\t| * | 3_E\n+\t| |\\|\n \t| | * 3_D\n \t| * | 3_C\n \t| |/\n@@ -216,4 +214,44 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n+\tcat >expect <<-\\EOF &&\n+\t*   5_H\n+\t|\\\n+\t| *-.   5_G\n+\t| |\\ \\\n+\t| | | * 5_F\n+\t| | * |   5_E\n+\t| |/|\\ \\\n+\t| |_|/ /\n+\t|/| | /\n+\t| | |/\n+\t* | | 5_D\n+\t| | * 5_C\n+\t| |/\n+\t|/|\n+\t| * 5_B\n+\t|/\n+\t* 5_A\n+\tEOF\n+\n+\tgit checkout --orphan 5_p &&\n+\ttest_commit 5_A &&\n+\tgit branch 5_q &&\n+\tgit branch 5_r &&\n+\ttest_commit 5_B &&\n+\tgit checkout 5_q && test_commit 5_C &&\n+\tgit checkout 5_r && test_commit 5_D &&\n+\tgit checkout 5_p &&\n+\tgit merge --no-ff 5_q 5_r -m 5_E &&\n+\tgit checkout 5_q && test_commit 5_F &&\n+\tgit checkout -b 5_s 5_p^ &&\n+\tgit merge --no-ff 5_p 5_q -m 5_G &&\n+\tgit checkout 5_r &&\n+\tgit merge --no-ff 5_s -m 5_H &&\n+\n+\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex ca1682f29b..f5e6e92f5b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -67,11 +67,10 @@ test_expect_success '--graph --all' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -97,11 +96,10 @@ test_expect_success '--graph --simplify-by-decoration' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"| |     \" >> expected &&\n-\techo \"|  \\\\    \" >> expected &&\n-\techo \"*-. \\\\   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\ \\\\  \" >> expected &&\n-\techo \"| | |/  \" >> expected &&\n+\techo \"| |   \" >> expected &&\n+\techo \"|  \\\\  \" >> expected &&\n+\techo \"*-. | $A4\" >> expected &&\n+\techo \"|\\\\ \\\\| \" >> expected &&\n \techo \"| | * $C2\" >> expected &&\n \techo \"| | * $C1\" >> expected &&\n \techo \"| * | $B2\" >> expected &&\n@@ -131,9 +129,8 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '\n \techo \"| * $C4\" >> expected &&\n \techo \"| * $C3\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n \techo \"| * $C2\" >> expected &&\n \techo \"| * $C1\" >> expected &&\n \techo \"* | $A3\" >> expected &&\n@@ -151,10 +148,9 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"|\\\\  \" >> expected &&\n \techo \"| * $C4\" >> expected &&\n \techo \"* | $A5\" >> expected &&\n-\techo \"* |   $A4\" >> expected &&\n-\techo \"|\\\\ \\\\  \" >> expected &&\n-\techo \"| |/  \" >> expected &&\n-\techo \"* / $A3\" >> expected &&\n+\techo \"* | $A4\" >> expected &&\n+\techo \"|\\\\| \" >> expected &&\n+\techo \"* | $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n-- \ngitgitgadget\n\n"},{"id":"384147","messageId":"9b24893de6b7ddb95dc8813d5bbe6c68b0983dfd.1571183279.git.gitgitgadget@gmail.com","threadId":"52011","inReplyTo":"pull.383.v3.git.1571183279.gitgitgadget@gmail.com","subject":"[PATCH v3 11/13] graph: smooth appearance of collapsing edges on commit lines","fromName":"James Coglan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-10-15T23:47:57Z","receivedAt":"2019-10-15T23:48:21Z","isPatch":true,"sender":{"key":"jcoglan@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9265?v=4"},"body":"From: James Coglan <jcoglan@gmail.com>\n\nWhen a graph contains edges that are in the process of collapsing to the\nleft, but those edges cross a commit line, the effect is that the edges\nhave a jagged appearance:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * | |\n        |/ /\n        * |\n        |/\n        *\n\nWe already takes steps to smooth edges like this when they're expanding;\nwhen an edge appears to the right of a merge commit marker on a\nGRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we\nrender it as a `\\`:\n\n        * \\\n        |\\ \\\n        | * \\\n        | |\\ \\\n\nWe can make a similar improvement to collapsing edges, making them\neasier to follow and giving the overall graph a feeling of increased\nsymmetry:\n\n        *\n        |\\\n        | *\n        |  \\\n        *-. \\\n        |\\ \\ \\\n        | | * |\n        | * | |\n        | |/ /\n        * / /\n        |/ /\n        * /\n        |/\n        *\n\nTo do this, we introduce a new special case for edges on GRAPH_COMMIT\nlines that immediately follow a GRAPH_COLLAPSING line. By retaining a\ncopy of the `mapping` array used to render the GRAPH_COLLAPSING line in\nthe `old_mapping` array, we can determine that an edge is collapsing\nthrough the GRAPH_COMMIT line and should be smoothed.\n\nSigned-off-by: James Coglan <jcoglan@gmail.com>\n---\n graph.c                                    | 17 +++++++++---\n t/t3430-rebase-merges.sh                   |  2 +-\n t/t4202-log.sh                             |  2 +-\n t/t4214-log-graph-octopus.sh               | 32 +++++++++++-----------\n t/t4215-log-skewed-merges.sh               |  4 +--\n t/t6016-rev-list-graph-simplify-history.sh |  4 +--\n 6 files changed, 35 insertions(+), 26 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex 2315f3604d..63f8d18baa 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -297,10 +297,10 @@ struct git_graph {\n \t */\n \tint *mapping;\n \t/*\n-\t * A temporary array for computing the next mapping state\n-\t * while we are outputting a mapping line.  This is stored as part\n-\t * of the git_graph simply so we don't have to allocate a new\n-\t * temporary array each time we have to output a collapsing line.\n+\t * A copy of the contents of the mapping array from the last commit,\n+\t * which we use to improve the display of columns that are tracking\n+\t * from right to left through a commit line.  We also use this to\n+\t * avoid allocating a fresh array when we compute the next mapping.\n \t */\n \tint *old_mapping;\n \t/*\n@@ -1015,6 +1015,10 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line\n \t\t\t\tgraph_line_write_column(line, col, '\\\\');\n \t\t\telse\n \t\t\t\tgraph_line_write_column(line, col, '|');\n+\t\t} else if (graph->prev_state == GRAPH_COLLAPSING &&\n+\t\t\t   graph->old_mapping[2 * i + 1] == i &&\n+\t\t\t   graph->mapping[2 * i] < i) {\n+\t\t\tgraph_line_write_column(line, col, '/');\n \t\t} else {\n \t\t\tgraph_line_write_column(line, col, '|');\n \t\t}\n@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l\n \t\t}\n \t}\n \n+\t/*\n+\t * Copy the current mapping array into old_mapping\n+\t */\n+\tCOPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);\n+\n \t/*\n \t * The new mapping may be 1 smaller than the old mapping\n \t */\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 9efcf4808a..a30d27e9f3 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -408,7 +408,7 @@ test_expect_success 'octopus merges' '\n \t| | * three\n \t| * | two\n \t| |/\n-\t* | one\n+\t* / one\n \t|/\n \to before-octopus\n \tEOF\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex e803ba402e..ab0d021365 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -667,7 +667,7 @@ cat > expect <<\\EOF\n * | | fifth\n * | | fourth\n |/ /\n-* | third\n+* / third\n |/\n * second\n * initial\ndiff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh\nindex 1b96276894..21bc600a82 100755\n--- a/t/t4214-log-graph-octopus.sh\n+++ b/t/t4214-log-graph-octopus.sh\n@@ -31,9 +31,9 @@ test_expect_success 'log --graph with tricky octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -51,9 +51,9 @@ test_expect_success 'log --graph with tricky octopus merge with colors' '\n \t<RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<RED>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<RED>|<RESET> * <MAGENTA>/<RESET> 2\n \t<RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -72,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, no color' '\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -90,9 +90,9 @@ test_expect_success 'log --graph with normal octopus merge with colors' '\n \t<RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4\n \t<RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3\n \t<RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t<RED>|<RESET> * <BLUE>|<RESET> 2\n+\t<RED>|<RESET> * <BLUE>/<RESET> 2\n \t<RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET>\n-\t* <BLUE>|<RESET> 1\n+\t* <BLUE>/<RESET> 1\n \t<BLUE>|<RESET><BLUE>/<RESET>\n \t* initial\n \tEOF\n@@ -110,9 +110,9 @@ test_expect_success 'log --graph with normal octopus merge and child, no color'\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -129,9 +129,9 @@ test_expect_failure 'log --graph with normal octopus and child merge with colors\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4\n \t<GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3\n \t<GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t<GREEN>|<RESET> * <MAGENTA>|<RESET> 2\n+\t<GREEN>|<RESET> * <MAGENTA>/<RESET> 2\n \t<GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET>\n-\t* <MAGENTA>|<RESET> 1\n+\t* <MAGENTA>/<RESET> 1\n \t<MAGENTA>|<RESET><MAGENTA>/<RESET>\n \t* initial\n \tEOF\n@@ -150,9 +150,9 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col\n \t| | | * 4\n \t| | * | 3\n \t| | |/\n-\t| * | 2\n+\t| * / 2\n \t| |/\n-\t* | 1\n+\t* / 1\n \t|/\n \t* initial\n \tEOF\n@@ -171,9 +171,9 @@ test_expect_failure 'log --graph with tricky octopus merge and its child with co\n \t<RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4\n \t<RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3\n \t<RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t<RED>|<RESET> * <CYAN>|<RESET> 2\n+\t<RED>|<RESET> * <CYAN>/<RESET> 2\n \t<RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET>\n-\t* <CYAN>|<RESET> 1\n+\t* <CYAN>/<RESET> 1\n \t<CYAN>|<RESET><CYAN>/<RESET>\n \t* initial\n \tEOF\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex e673cdb6f7..1745b3b64c 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -17,7 +17,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t| | * D\n \t| * | C\n \t| |/\n-\t* | B\n+\t* / B\n \t|/\n \t* A\n \tEOF\n@@ -85,7 +85,7 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n \t| * | 1_D\n \t* | | 1_C\n \t|/ /\n-\t* | 1_B\n+\t* / 1_B\n \t|/\n \t* 1_A\n \tEOF\ndiff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh\nindex f7181d1d6a..ca1682f29b 100755\n--- a/t/t6016-rev-list-graph-simplify-history.sh\n+++ b/t/t6016-rev-list-graph-simplify-history.sh\n@@ -154,7 +154,7 @@ test_expect_success '--graph --full-history -- bar.txt' '\n \techo \"* |   $A4\" >> expected &&\n \techo \"|\\\\ \\\\  \" >> expected &&\n \techo \"| |/  \" >> expected &&\n-\techo \"* | $A3\" >> expected &&\n+\techo \"* / $A3\" >> expected &&\n \techo \"|/  \" >> expected &&\n \techo \"* $A2\" >> expected &&\n \tgit rev-list --graph --full-history --all -- bar.txt > actual &&\n@@ -255,7 +255,7 @@ test_expect_success '--graph --boundary ^C3' '\n \techo \"* | | | $A3\" >> expected &&\n \techo \"o | | | $A2\" >> expected &&\n \techo \"|/ / /  \" >> expected &&\n-\techo \"o | | $A1\" >> expected &&\n+\techo \"o / / $A1\" >> expected &&\n \techo \" / /  \" >> expected &&\n \techo \"| o $C3\" >> expected &&\n \techo \"|/  \" >> expected &&\n-- \ngitgitgadget\n\n"},{"id":"384155","messageId":"xmqqd0exa11q.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"66b8ed45-4baa-37d9-c97f-72188ece32eb@gmail.com","subject":"Re: [PATCH 01/11] graph: automatically track visible width of `strbuf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-16T02:15:13Z","receivedAt":"2019-10-16T02:15:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"James Coglan <jcoglan@gmail.com> writes:\n\n>> Is there a reason why you need a pointer to a strbuf that is\n>> allocated separately?  E.g. would it make it harder to manage\n>> if the above were\n>> \n>> \tstruct graphbuf {\n>> \t\tstruct strbuf buf;\n>> \t\tint width; /* display width in columns */\n>> \t};\n>> \n>> which is essentially what Dscho suggested?\n>\n> I used a pointer here because I create the wrapper struct in\n> `graph_next_line()`, which is an external interface that takes a\n> `struct strbuf *`:\n>\n> int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n> {\n> \tstruct graph_line line = { .buf = sb, .width = 0 };\n> \t// ...\n> }\n>\n> So I'm not allocating the strbuf here, just wrapping a pointer to\n> it.\n\nOK, so existing callers allocate strbuf, and you are merely adding a\nwrapper structure to keep track of the width.  The management of the\nlifetime of the strbuf is not your business so there is no reason to\ninline the structure in graph_line.  Makes sense.  Thanks.\n"},{"id":"384156","messageId":"xmqq8spl9xcd.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"722ab8973ace92aeef8669eced2919a144240cc0.1571183279.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/13] graph: automatically track display width of graph lines","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-16T03:35:14Z","receivedAt":"2019-10-16T03:35:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"James Coglan via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +struct graph_line {\n> +\tstruct strbuf *buf;\n> +\tsize_t width;\n> +};\n> +\n> +static inline void graph_line_addch(struct graph_line *line, int c)\n> +{\n> +\tstrbuf_addch(line->buf, c);\n> +\tline->width++;\n> +}\n> +\n> +static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)\n> +{\n> +\tstrbuf_addchars(line->buf, c, n);\n> +\tline->width += n;\n> +}\n> +\n> +static inline void graph_line_addstr(struct graph_line *line, const char *s)\n> +{\n> +\tstrbuf_addstr(line->buf, s);\n> +\tline->width += strlen(s);\n> +}\n> +\n> +static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)\n> +{\n> +\tstrbuf_addstr(line->buf, column_get_color_code(color));\n> +}\n\nAll makes sense, and as long as nobody uses strbuf_add*() on\nline->buf directly, it shouldn't be too hard to extend these\nto support graph drawn characters outside ASCII in the future\nafter this series settles.\n\n>  static void graph_output_pre_commit_line(struct git_graph *graph,\n> ...\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_write_column(sb, col, '|');\n> -\t\t\tstrbuf_addchars(sb, ' ', graph->expansion_row);\n> -\t\t\tchars_written += 1 + graph->expansion_row;\n> +\t\t\tgraph_line_write_column(line, col, '|');\n> +\t\t\tgraph_line_addchars(line, ' ', graph->expansion_row);\n\nNice reduction of noise, as the proposed log message promises.\n\n>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n\nI just noticed it but does this have to be extern?  Nobody outside\ngraph.[ch] seems to have any reference to it.  That might mean that\nit may be easier than we thought earlier to change the second\nparameter of this to \"struct graph_line *\", instead of us wrapping\nan incoming strbuf (which external callers are aware of, as opposed\nto graph_line which we prefer not to expose to outsiders).  Whether\nwe change the second parameter or not, the first clean-up may be to\nturn this function into a file-scope static, perhaps?\n\nAll the changes are very pleasing to see.  Thanks.\n"},{"id":"384157","messageId":"xmqq4l099x8z.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"056c95d4edc8748c513db8c159c5e3d347ffc894.1571183279.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/13] graph: handle line padding in `graph_next_line()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-16T03:37:16Z","receivedAt":"2019-10-16T03:37:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"James Coglan via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: James Coglan <jcoglan@gmail.com>\n>\n> Now that the display width of graph lines is implicitly tracked via the\n> `graph_line` interface, the calls to `graph_pad_horizontally()` no\n> longer need to be located inside the individual output functions, where\n> the character counting was previously being done.\n>\n> All the functions called by `graph_next_line()` generate a line of\n> output, then call `graph_pad_horizontally()`, and finally change the\n> graph state if necessary. As padding is the final change to the output\n> done by all these functions, it can be removed from all of them and done\n> in `graph_next_line()` instead.\n\nVery well explained.\n"},{"id":"384158","messageId":"xmqqsgnt8hlh.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"c34a5eb160310613cbde6313cda6cff753d6d7fd.1571183279.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 08/13] graph: tidy up display of left-skewed merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-16T04:00:42Z","receivedAt":"2019-10-16T04:00:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"James Coglan via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This effect is applied to both \"normal\" two-parent merges, and to\n> octopus merges. It also reduces the vertical space needed for pre-commit\n> lines, as the merge occupies one less column than usual.\n>\n>         Before:         After:\n>\n>         | *             | *\n>         | |\\            | |\\\n>         | | \\           | * \\\n>         | |  \\          |/|\\ \\\n>         | *-. \\\n>         | |\\ \\ \\\n\nLooking at these drawings reminded me of a tangent that is brought\nup from time to time.  We do not do great job when showing multiple\nroots.\n\nIf you have a history like this:\n\n      A---D---E\n         /\n    B---C\n\ndrawing the graph _with_ the merge gives a reasonable representation\nof the entire topology.\n\n    * 46f67dd E\n    *   6f89516 D\n    |\\  \n    | * e6277a9 C\n    | * 13ae9b2 B\n    * afee005 A\n\nBut if you start drawing from parents of D (excluding D), you'd get\nthis:\n\n    * e6277a9 C\n    * 13ae9b2 B\n    * afee005 A\n\nand the fact that B and A do not share parent-child relationships is\nlost.  An easy way to show that would be to draw the bottom three\nlines of the full history output we saw earlier:\n\n    | * e6277a9 C\n    | * 13ae9b2 B\n    * afee005 A\n\neither with or without the vertical bar to imply that A may have a\nchild.\n\nThis is not something that has to be done as part of this series,\nbut I am hoping that the internal simplification and code\nrestructuring that is done by this series would make it easier to\nenhance the system to allow such an output.\n\nThanks.\n"},{"id":"384160","messageId":"xmqqo8yh8ecf.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"xmqq8spl9xcd.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 01/13] graph: automatically track display width of graph lines","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-16T05:10:56Z","receivedAt":"2019-10-16T05:11:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n>\n> I just noticed it but does this have to be extern?  Nobody outside\n> graph.[ch] seems to have any reference to it.\n\nI was stupid; strike this part out.\n\nThanks.\n\n"},{"id":"384254","messageId":"3dfddde2-05e9-01ca-2ce8-d504f49b72bc@gmail.com","threadId":"52011","inReplyTo":"631ee3cecb68d9f776d4a8fb30c1bca70797ba14.1571183279.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/13] graph: example of graph output that can be simplified","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-17T12:30:40Z","receivedAt":"2019-10-17T12:30:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/15/2019 7:47 PM, James Coglan via GitGitGadget wrote:\n> diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n> new file mode 100755\n> index 0000000000..4582ba066a\n> --- /dev/null\n> +++ b/t/t4215-log-skewed-merges.sh\n> @@ -0,0 +1,43 @@\n> +#!/bin/sh\n> +\n> +test_description='git log --graph of skewed merges'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n> +\tcat >expect <<-\\EOF &&\n> +\t*   H\n> +\t|\\\n> +\t| *   G\n> +\t| |\\\n> +\t| | * F\n> +\t| | |\n> +\t| |  \\\n> +\t| *-. \\   E\n> +\t| |\\ \\ \\\n> +\t|/ / / /\n> +\t| | | /\n> +\t| | |/\n> +\t| | * D\n> +\t| * | C\n> +\t| |/\n> +\t* | B\n> +\t|/\n> +\t* A\n> +\tEOF\n\nThanks for adding this test! I really think it helps show some\nof your improvements later as this test is mutated.\n\n-Stolee\n\n> +\n> +\tgit checkout --orphan _p &&\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\tgit checkout -b _q @^ && test_commit C &&\n> +\tgit checkout -b _r @^ && test_commit D &&\n> +\tgit checkout _p && git merge --no-ff _q _r -m E &&\n> +\tgit checkout _r && test_commit F &&\n> +\tgit checkout _p && git merge --no-ff _r -m G &&\n> +\tgit checkout @^^ && git merge --no-ff _p -m H &&\n> +\n> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_done\n> \n\n"},{"id":"384255","messageId":"863fb1b9-8829-64db-d355-56a97e979427@gmail.com","threadId":"52011","inReplyTo":"xmqqsgnt8hlh.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 08/13] graph: tidy up display of left-skewed merges","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-10-17T12:34:56Z","receivedAt":"2019-10-17T12:35:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/16/2019 12:00 AM, Junio C Hamano wrote:\n> \"James Coglan via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> This effect is applied to both \"normal\" two-parent merges, and to\n>> octopus merges. It also reduces the vertical space needed for pre-commit\n>> lines, as the merge occupies one less column than usual.\n>>\n>>         Before:         After:\n>>\n>>         | *             | *\n>>         | |\\            | |\\\n>>         | | \\           | * \\\n>>         | |  \\          |/|\\ \\\n>>         | *-. \\\n>>         | |\\ \\ \\\n> \n> Looking at these drawings reminded me of a tangent that is brought\n> up from time to time.  We do not do great job when showing multiple\n> roots.\n> \n> If you have a history like this:\n> \n>       A---D---E\n>          /\n>     B---C\n> \n> drawing the graph _with_ the merge gives a reasonable representation\n> of the entire topology.\n> \n>     * 46f67dd E\n>     *   6f89516 D\n>     |\\  \n>     | * e6277a9 C\n>     | * 13ae9b2 B\n>     * afee005 A\n> \n> But if you start drawing from parents of D (excluding D), you'd get\n> this:\n> \n>     * e6277a9 C\n>     * 13ae9b2 B\n>     * afee005 A\n\nI hit this very situation recently when I was experimenting with\n'git fast-import' and accidentally created many parallel, independent\nhistories. Running \"git log --graph --all --simplify-by-decoration\"\nmade it look like all the refs were in a line, but they were not.\n(The one way I knew something was up: the base commits also appeared\nwithout a decoration. That was the only clue that the histories did\nnot continue in a line.)\n\n> \n> and the fact that B and A do not share parent-child relationships is\n> lost.  An easy way to show that would be to draw the bottom three\n> lines of the full history output we saw earlier:\n> \n>     | * e6277a9 C\n>     | * 13ae9b2 B\n>     * afee005 A\n> \n> either with or without the vertical bar to imply that A may have a\n> child.\nThe natural extension of this would be multiple columns:\n\n | | | | | *\n | | | | *\n | | | *\n | | *\n | *\n *\n\nThis does not appear too cumbersome, and such a situation should\nbe rare.\n\n> This is not something that has to be done as part of this series,\n> but I am hoping that the internal simplification and code\n> restructuring that is done by this series would make it easier to\n> enhance the system to allow such an output.\n\nI agree. An excellent idea for a follow-up.\n\nThanks,\n-Stolee\n\n"},{"id":"384329","messageId":"xmqq5zkm7u88.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"863fb1b9-8829-64db-d355-56a97e979427@gmail.com","subject":"Re: [PATCH v3 08/13] graph: tidy up display of left-skewed merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-18T00:49:59Z","receivedAt":"2019-10-18T00:50:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n> I hit this very situation recently when I was experimenting with\n> 'git fast-import' and accidentally created many parallel, independent\n> histories. Running \"git log --graph --all --simplify-by-decoration\"\n> made it look like all the refs were in a line, but they were not.\n> (The one way I knew something was up: the base commits also appeared\n> without a decoration. That was the only clue that the histories did\n> not continue in a line.)\n>\n>> \n>> and the fact that B and A do not share parent-child relationships is\n>> lost.  An easy way to show that would be to draw the bottom three\n>> lines of the full history output we saw earlier:\n>> \n>>     | * e6277a9 C\n>>     | * 13ae9b2 B\n>>     * afee005 A\n>> \n>> either with or without the vertical bar to imply that A may have a\n>> child.\n> The natural extension of this would be multiple columns:\n>\n>  | | | | | *\n>  | | | | *\n>  | | | *\n>  | | *\n>  | *\n>  *\n\nAfter sleeping over it, I now think we shouldn't draw lines that\nimply a child for each of these commits, as we haven't seen, but\nI agree that this can be extended to 3 or more roots.\n\n"},{"id":"384386","messageId":"20191018152121.GE29845@szeder.dev","threadId":"52011","inReplyTo":"631ee3cecb68d9f776d4a8fb30c1bca70797ba14.1571183279.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/13] graph: example of graph output that can be simplified","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-10-18T15:21:21Z","receivedAt":"2019-10-18T15:21:27Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Oct 15, 2019 at 11:47:53PM +0000, James Coglan via GitGitGadget wrote:\n> diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\n> new file mode 100755\n> index 0000000000..4582ba066a\n> --- /dev/null\n> +++ b/t/t4215-log-skewed-merges.sh\n> @@ -0,0 +1,43 @@\n> +#!/bin/sh\n> +\n> +test_description='git log --graph of skewed merges'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n> +\tcat >expect <<-\\EOF &&\n> +\t*   H\n> +\t|\\\n> +\t| *   G\n> +\t| |\\\n> +\t| | * F\n> +\t| | |\n> +\t| |  \\\n> +\t| *-. \\   E\n> +\t| |\\ \\ \\\n> +\t|/ / / /\n> +\t| | | /\n> +\t| | |/\n> +\t| | * D\n> +\t| * | C\n> +\t| |/\n> +\t* | B\n> +\t|/\n> +\t* A\n> +\tEOF\n> +\n> +\tgit checkout --orphan _p &&\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\tgit checkout -b _q @^ && test_commit C &&\n> +\tgit checkout -b _r @^ && test_commit D &&\n> +\tgit checkout _p && git merge --no-ff _q _r -m E &&\n> +\tgit checkout _r && test_commit F &&\n> +\tgit checkout _p && git merge --no-ff _r -m G &&\n> +\tgit checkout @^^ && git merge --no-ff _p -m H &&\n> +\n> +\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n\nPlease don't pipe 'git log --graph's output, but use an intermediate\nfile instead:\n\n  git log --graph ... >out &&\n  sed s/// out >actual &&\n  test_cmp expect actual\n\nThe exit code of a pipeline is the exit code of the last process in\nthe pipeline, and the exit codes of processes upstream of a pipe are\nignored.  Consequently, if 'git log --graph' produced the expected\noutput but were to fail during housekeeping before exiting (segfault,\ndouble free(), whatever), then that failure would go unnoticed.\n\nThis applies to several (all?) new tests added in this patch series as\nwell.\n\n\nI'd like to join the praises from others: this is one excellent\nfirst-time submission, thanks.\n\n\n"},{"id":"385972","messageId":"8e950ddfba3fa0f6d0551a153228548da6af6117.1573520653.git.liu.denton@gmail.com","threadId":"52011","inReplyTo":"20191018152121.GE29845@szeder.dev","subject":"[PATCH] t4215: don't put git commands upstream of pipe","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-11-12T01:08:29Z","receivedAt":"2019-11-12T01:08:37Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When git commands are placed in the upstream of a pipe, their return\ncodes are lost. In this particular case, it is especially bad since we\nare testing the intricacies of `git log --graph` behavior and if we hit\nan unexpected failure or segfault, we want to know this.\n\nRedirect the output of git commands upstream of pipe into a file and\nhave sed read from that file so that git failures are detected.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\nThanks for the suggestion, Gábor. (Is that how I should refer to you? I\nrecently learned that some poeple write their names in ALL CAPS as a\nconvention.)\n\nA little late to the party but since this cleanup hasn't been done yet,\nlet's do it now. We can apply this patch to the tip of\n'jc/log-graph-simplify'.\n\n t/t4215-log-skewed-merges.sh | 21 ++++++++++++++-------\n 1 file changed, 14 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex d33c6438d8..0a2555fb28 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -31,7 +31,8 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \tgit checkout _p && git merge --no-ff _r -m G &&\n \tgit checkout @^^ && git merge --no-ff _p -m H &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -68,7 +69,8 @@ test_expect_success 'log --graph with left-skewed merge' '\n \tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n \tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -99,7 +101,8 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n \tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n \tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -139,7 +142,8 @@ test_expect_success 'log --graph with nested left-skewed merge following normal\n \tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n \tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -175,7 +179,8 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n \tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n \tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -210,7 +215,8 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n \tgit merge --no-ff 4_p -m 4_G &&\n \tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n \n-\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --date-order --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -250,7 +256,8 @@ test_expect_success 'log --graph with octopus merge with column joining its penu\n \tgit checkout 5_r &&\n \tgit merge --no-ff 5_s -m 5_H &&\n \n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n+\tgit log --graph --pretty=tformat:%s >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n \ttest_cmp expect actual\n '\n \n-- \n2.24.0.300.g722ba42680\n\n"},{"id":"385984","messageId":"xmqqzhh1eg57.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"8e950ddfba3fa0f6d0551a153228548da6af6117.1573520653.git.liu.denton@gmail.com","subject":"Re: [PATCH] t4215: don't put git commands upstream of pipe","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-12T06:57:08Z","receivedAt":"2019-11-12T06:57:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> A little late to the party but since this cleanup hasn't been done yet,\n> let's do it now. We can apply this patch to the tip of\n> 'jc/log-graph-simplify'.\n> ...\n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n\nObviously good but I wonder if\n\n\tshow_graph () {\n\t\tgit log --graph --pretty=tformat:%s >actual.raw &&\n\t\tsed \"s/ *$//\" actual.raw &&\n\t\trm actual.raw\n\t}\n\nwould help to make it even more readable without too much repetition.\n\nWill queue; thanks.\n\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -68,7 +69,8 @@ test_expect_success 'log --graph with left-skewed merge' '\n>  \tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n>  \tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n>  \n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -99,7 +101,8 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n>  \tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n>  \tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n>  \n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -139,7 +142,8 @@ test_expect_success 'log --graph with nested left-skewed merge following normal\n>  \tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n>  \tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n>  \n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -175,7 +179,8 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n>  \tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n>  \tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n>  \n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -210,7 +215,8 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n>  \tgit merge --no-ff 4_p -m 4_G &&\n>  \tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n>  \n> -\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --date-order --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n>  \n> @@ -250,7 +256,8 @@ test_expect_success 'log --graph with octopus merge with column joining its penu\n>  \tgit checkout 5_r &&\n>  \tgit merge --no-ff 5_s -m 5_H &&\n>  \n> -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> +\tsed \"s/ *$//\" actual.raw >actual &&\n>  \ttest_cmp expect actual\n>  '\n"},{"id":"386003","messageId":"20191112105400.GM4348@szeder.dev","threadId":"52011","inReplyTo":"xmqqzhh1eg57.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] t4215: don't put git commands upstream of pipe","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-11-12T10:54:00Z","receivedAt":"2019-11-12T10:54:06Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Nov 12, 2019 at 03:57:08PM +0900, Junio C Hamano wrote:\n> Denton Liu <liu.denton@gmail.com> writes:\n> \n> > A little late to the party but since this cleanup hasn't been done yet,\n> > let's do it now. We can apply this patch to the tip of\n> > 'jc/log-graph-simplify'.\n> > ...\n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> \n> Obviously good but I wonder if\n> \n> \tshow_graph () {\n> \t\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> \t\tsed \"s/ *$//\" actual.raw &&\n> \t\trm actual.raw\n> \t}\n> \n> would help to make it even more readable without too much repetition.\n\nI think it would indeed, but then let's go one step further, and add\nthat 'test_cmp expect actual' to the function, too, and call it\n'check_graph'.\n\n> Will queue; thanks.\n> \n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -68,7 +69,8 @@ test_expect_success 'log --graph with left-skewed merge' '\n> >  \tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n> >  \tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n> >  \n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -99,7 +101,8 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n> >  \tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n> >  \tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n> >  \n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -139,7 +142,8 @@ test_expect_success 'log --graph with nested left-skewed merge following normal\n> >  \tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n> >  \tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n> >  \n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -175,7 +179,8 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n> >  \tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n> >  \tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n> >  \n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -210,7 +215,8 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n> >  \tgit merge --no-ff 4_p -m 4_G &&\n> >  \tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n> >  \n> > -\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --date-order --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > @@ -250,7 +256,8 @@ test_expect_success 'log --graph with octopus merge with column joining its penu\n> >  \tgit checkout 5_r &&\n> >  \tgit merge --no-ff 5_s -m 5_H &&\n> >  \n> > -\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n> > +\tgit log --graph --pretty=tformat:%s >actual.raw &&\n> > +\tsed \"s/ *$//\" actual.raw >actual &&\n> >  \ttest_cmp expect actual\n> >  '\n"},{"id":"386034","messageId":"86af5739a4e5f5f38b2597a86d03d5501ad5468b.1573584933.git.liu.denton@gmail.com","threadId":"52011","inReplyTo":"8e950ddfba3fa0f6d0551a153228548da6af6117.1573520653.git.liu.denton@gmail.com","subject":"[PATCH v3] t4215: use helper function to check output","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-11-12T18:56:22Z","receivedAt":"2019-11-12T18:56:27Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When git commands are placed in the upstream of a pipe, their return\ncodes are lost. In this particular case, it is especially bad since we\nare testing the intricacies of `git log --graph` behavior and if we hit\nan unexpected failure or segfault, we want to know this.\n\nExtract the common output checking logic into check_graph() where we\nredirect the output of git commands upstream of pipe into a file and\nhave sed read from that file so that git failures are detected.\n\nThis patch is best viewed with `--color-moved`.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\nThanks for the feedback, everyone. I decided to take Gábor's suggestion\nand write the check_graph() helper function.\n\nUnfortunately, even though in practice I was moving the here-doc lines\ndown, the diff shows up as me moving the commit and checkout lines up\nand this might affect how the blame ends up looking. Is there any way to\nforce git to make the diff show up the other way in both this diff and\nin the blame or is this the best that we can do?\n\nThanks!\n\n t/t4215-log-skewed-merges.sh | 208 ++++++++++++++++-------------------\n 1 file changed, 97 insertions(+), 111 deletions(-)\n\ndiff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh\nindex d33c6438d8..18709a723e 100755\n--- a/t/t4215-log-skewed-merges.sh\n+++ b/t/t4215-log-skewed-merges.sh\n@@ -4,8 +4,25 @@ test_description='git log --graph of skewed merges'\n \n . ./test-lib.sh\n \n+check_graph () {\n+\tcat >expect &&\n+\tgit log --graph --pretty=tformat:%s \"$@\" >actual.raw &&\n+\tsed \"s/ *$//\" actual.raw >actual &&\n+\ttest_cmp expect actual\n+}\n+\n test_expect_success 'log --graph with merge fusing with its left and right neighbors' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan _p &&\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tgit checkout -b _q @^ && test_commit C &&\n+\tgit checkout -b _r @^ && test_commit D &&\n+\tgit checkout _p && git merge --no-ff _q _r -m E &&\n+\tgit checkout _r && test_commit F &&\n+\tgit checkout _p && git merge --no-ff _r -m G &&\n+\tgit checkout @^^ && git merge --no-ff _p -m H &&\n+\n+\tcheck_graph <<-\\EOF\n \t*   H\n \t|\\\n \t| *   G\n@@ -20,23 +37,20 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh\n \t|/\n \t* A\n \tEOF\n-\n-\tgit checkout --orphan _p &&\n-\ttest_commit A &&\n-\ttest_commit B &&\n-\tgit checkout -b _q @^ && test_commit C &&\n-\tgit checkout -b _r @^ && test_commit D &&\n-\tgit checkout _p && git merge --no-ff _q _r -m E &&\n-\tgit checkout _r && test_commit F &&\n-\tgit checkout _p && git merge --no-ff _r -m G &&\n-\tgit checkout @^^ && git merge --no-ff _p -m H &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with left-skewed merge' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 0_p && test_commit 0_A &&\n+\tgit checkout -b 0_q 0_p && test_commit 0_B &&\n+\tgit checkout -b 0_r 0_p &&\n+\ttest_commit 0_C &&\n+\ttest_commit 0_D &&\n+\tgit checkout -b 0_s 0_p && test_commit 0_E &&\n+\tgit checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&\n+\tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n+\tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n+\n+\tcheck_graph <<-\\EOF\n \t*-----.   0_H\n \t|\\ \\ \\ \\\n \t| | | | * 0_G\n@@ -57,23 +71,20 @@ test_expect_success 'log --graph with left-skewed merge' '\n \t|/\n \t* 0_A\n \tEOF\n-\n-\tgit checkout --orphan 0_p && test_commit 0_A &&\n-\tgit checkout -b 0_q 0_p && test_commit 0_B &&\n-\tgit checkout -b 0_r 0_p &&\n-\ttest_commit 0_C &&\n-\ttest_commit 0_D &&\n-\tgit checkout -b 0_s 0_p && test_commit 0_E &&\n-\tgit checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F &&\n-\tgit checkout 0_p && git merge --no-ff 0_s -m 0_G &&\n-\tgit checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with nested left-skewed merge' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 1_p &&\n+\ttest_commit 1_A &&\n+\ttest_commit 1_B &&\n+\ttest_commit 1_C &&\n+\tgit checkout -b 1_q @^ && test_commit 1_D &&\n+\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n+\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n+\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n+\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n+\n+\tcheck_graph <<-\\EOF\n \t*   1_H\n \t|\\\n \t| *   1_G\n@@ -88,23 +99,24 @@ test_expect_success 'log --graph with nested left-skewed merge' '\n \t|/\n \t* 1_A\n \tEOF\n-\n-\tgit checkout --orphan 1_p &&\n-\ttest_commit 1_A &&\n-\ttest_commit 1_B &&\n-\ttest_commit 1_C &&\n-\tgit checkout -b 1_q @^ && test_commit 1_D &&\n-\tgit checkout 1_p && git merge --no-ff 1_q -m 1_E &&\n-\tgit checkout -b 1_r @~3 && test_commit 1_F &&\n-\tgit checkout 1_p && git merge --no-ff 1_r -m 1_G &&\n-\tgit checkout @^^ && git merge --no-ff 1_p -m 1_H &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with nested left-skewed merge following normal merge' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 2_p &&\n+\ttest_commit 2_A &&\n+\ttest_commit 2_B &&\n+\ttest_commit 2_C &&\n+\tgit checkout -b 2_q @^^ &&\n+\ttest_commit 2_D &&\n+\ttest_commit 2_E &&\n+\tgit checkout -b 2_r @^ && test_commit 2_F &&\n+\tgit checkout 2_q &&\n+\tgit merge --no-ff 2_r -m 2_G &&\n+\tgit merge --no-ff 2_p^ -m 2_H &&\n+\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n+\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n+\n+\tcheck_graph <<-\\EOF\n \t*   2_K\n \t|\\\n \t| *   2_J\n@@ -124,27 +136,23 @@ test_expect_success 'log --graph with nested left-skewed merge following normal\n \t|/\n \t* 2_A\n \tEOF\n-\n-\tgit checkout --orphan 2_p &&\n-\ttest_commit 2_A &&\n-\ttest_commit 2_B &&\n-\ttest_commit 2_C &&\n-\tgit checkout -b 2_q @^^ &&\n-\ttest_commit 2_D &&\n-\ttest_commit 2_E &&\n-\tgit checkout -b 2_r @^ && test_commit 2_F &&\n-\tgit checkout 2_q &&\n-\tgit merge --no-ff 2_r -m 2_G &&\n-\tgit merge --no-ff 2_p^ -m 2_H &&\n-\tgit checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&\n-\tgit checkout 2_p && git merge --no-ff 2_s -m 2_K &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 3_p &&\n+\ttest_commit 3_A &&\n+\tgit checkout -b 3_q &&\n+\ttest_commit 3_B &&\n+\ttest_commit 3_C &&\n+\tgit checkout -b 3_r @^ &&\n+\ttest_commit 3_D &&\n+\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n+\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n+\tgit checkout 3_r && test_commit 3_G &&\n+\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n+\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n+\n+\tcheck_graph <<-\\EOF\n \t*   3_J\n \t|\\\n \t| *   3_H\n@@ -161,26 +169,21 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s\n \t|/\n \t* 3_A\n \tEOF\n-\n-\tgit checkout --orphan 3_p &&\n-\ttest_commit 3_A &&\n-\tgit checkout -b 3_q &&\n-\ttest_commit 3_B &&\n-\ttest_commit 3_C &&\n-\tgit checkout -b 3_r @^ &&\n-\ttest_commit 3_D &&\n-\tgit checkout 3_q && git merge --no-ff 3_r -m 3_E &&\n-\tgit checkout 3_p && git merge --no-ff 3_q -m 3_F &&\n-\tgit checkout 3_r && test_commit 3_G &&\n-\tgit checkout 3_p && git merge --no-ff 3_r -m 3_H &&\n-\tgit checkout @^^ && git merge --no-ff 3_p -m 3_J &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with right-skewed merge following a left-skewed one' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 4_p &&\n+\ttest_commit 4_A &&\n+\ttest_commit 4_B &&\n+\ttest_commit 4_C &&\n+\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n+\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n+\tgit checkout -b 4_s 4_p^^ &&\n+\tgit merge --no-ff 4_r -m 4_F &&\n+\tgit merge --no-ff 4_p -m 4_G &&\n+\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n+\n+\tcheck_graph --date-order <<-\\EOF\n \t*   4_H\n \t|\\\n \t| *   4_G\n@@ -198,24 +201,25 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed\n \t|/\n \t* 4_A\n \tEOF\n-\n-\tgit checkout --orphan 4_p &&\n-\ttest_commit 4_A &&\n-\ttest_commit 4_B &&\n-\ttest_commit 4_C &&\n-\tgit checkout -b 4_q @^^ && test_commit 4_D &&\n-\tgit checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E &&\n-\tgit checkout -b 4_s 4_p^^ &&\n-\tgit merge --no-ff 4_r -m 4_F &&\n-\tgit merge --no-ff 4_p -m 4_G &&\n-\tgit checkout @^^ && git merge --no-ff 4_s -m 4_H &&\n-\n-\tgit log --graph --date-order --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' '\n-\tcat >expect <<-\\EOF &&\n+\tgit checkout --orphan 5_p &&\n+\ttest_commit 5_A &&\n+\tgit branch 5_q &&\n+\tgit branch 5_r &&\n+\ttest_commit 5_B &&\n+\tgit checkout 5_q && test_commit 5_C &&\n+\tgit checkout 5_r && test_commit 5_D &&\n+\tgit checkout 5_p &&\n+\tgit merge --no-ff 5_q 5_r -m 5_E &&\n+\tgit checkout 5_q && test_commit 5_F &&\n+\tgit checkout -b 5_s 5_p^ &&\n+\tgit merge --no-ff 5_p 5_q -m 5_G &&\n+\tgit checkout 5_r &&\n+\tgit merge --no-ff 5_s -m 5_H &&\n+\n+\tcheck_graph <<-\\EOF\n \t*   5_H\n \t|\\\n \t| *-.   5_G\n@@ -234,24 +238,6 @@ test_expect_success 'log --graph with octopus merge with column joining its penu\n \t|/\n \t* 5_A\n \tEOF\n-\n-\tgit checkout --orphan 5_p &&\n-\ttest_commit 5_A &&\n-\tgit branch 5_q &&\n-\tgit branch 5_r &&\n-\ttest_commit 5_B &&\n-\tgit checkout 5_q && test_commit 5_C &&\n-\tgit checkout 5_r && test_commit 5_D &&\n-\tgit checkout 5_p &&\n-\tgit merge --no-ff 5_q 5_r -m 5_E &&\n-\tgit checkout 5_q && test_commit 5_F &&\n-\tgit checkout -b 5_s 5_p^ &&\n-\tgit merge --no-ff 5_p 5_q -m 5_G &&\n-\tgit checkout 5_r &&\n-\tgit merge --no-ff 5_s -m 5_H &&\n-\n-\tgit log --graph --pretty=tformat:%s | sed \"s/ *$//\" >actual &&\n-\ttest_cmp expect actual\n '\n \n test_done\n-- \n2.24.0.300.g722ba42680\n\n"},{"id":"386082","messageId":"xmqqeeyccyyy.fsf@gitster-ct.c.googlers.com","threadId":"52011","inReplyTo":"86af5739a4e5f5f38b2597a86d03d5501ad5468b.1573584933.git.liu.denton@gmail.com","subject":"Re: [PATCH v3] t4215: use helper function to check output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-11-13T02:05:41Z","receivedAt":"2019-11-13T02:05:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> .... Is there any way to\n> force git to make the diff show up the other way in both this diff and\n> in the blame or is this the best that we can do?\n\n\"git blame -M\" is your friend, I think.\n\n\t$ git blame -b -M HEAD^ t/t4215-log-skewed-merges.sh\n\nwould show what this patch did rather nicely.\n"}]}