[PATCH 03/11] graph: reduce duplication in `graph_insert_into_new_columns()`

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to this function and so am trying
to simplify it. It currently contains some duplicated logic; both
branches the function can take assign the commit's column index into
the `mapping` array and increment `mapping_index`.

Here I change the function so that the only conditional behaviour is
that it appends the commit to `new_columns` if it's not present. All
manipulation of `mapping` now happens on a single code path.

Signed-off-by: James Coglan 
---
 graph.c | 20 +++-
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/graph.c b/graph.c
index a890c9d8bb..1cd30f80e6 100644
--- a/graph.c
+++ b/graph.c
@@ -459,23 +459,17 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
int i = graph_find_new_column_by_commit(graph, commit);
 
/*
-* If the commit is already in the new_columns list, we don't need to
-* add it.  Just update the mapping correctly.
+* If the commit is not already in the new_columns array, then add it
+* and record it as being in the final column.
 */
-   if (i >= 0) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
+   if (i < 0) {
+   i = graph->num_new_columns++;
+   graph->new_columns[i].commit = commit;
+   graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   /*
-* This commit isn't already in new_columns.  Add it.
-*/
-   graph->new_columns[graph->num_new_columns].commit = commit;
-   graph->new_columns[graph->num_new_columns].color = 
graph_find_commit_color(graph, commit);
-   graph->mapping[*mapping_index] = graph->num_new_columns;
+   graph->mapping[*mapping_index] = i;
*mapping_index += 2;
-   graph->num_new_columns++;
 }
 
 static void graph_update_width(struct git_graph *graph,
-- 
gitgitgadget



[PATCH 02/11] graph: reuse `find_new_column_by_commit()`

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to
`graph_insert_into_new_columns()` and so am trying to simplify it. One
possible simplification is that we can extract the loop for finding the
element in `new_columns` containing the given commit.

`find_new_column_by_commit()` contains a very similar loop but it
returns a `struct column *` rather than an `int` offset into the array.
Here I'm introducing a version that returns `int` and using that in
`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.

Signed-off-by: James Coglan 
---
 graph.c | 48 +++-
 1 file changed, 23 insertions(+), 25 deletions(-)

diff --git a/graph.c b/graph.c
index c56fdec1fc..a890c9d8bb 100644
--- a/graph.c
+++ b/graph.c
@@ -441,22 +441,31 @@ static unsigned short graph_find_commit_color(const 
struct git_graph *graph,
return graph_get_current_column_color(graph);
 }
 
+static int graph_find_new_column_by_commit(struct git_graph *graph,
+  struct commit *commit)
+{
+   int i;
+   for (i = 0; i < graph->num_new_columns; i++) {
+   if (graph->new_columns[i].commit == commit)
+   return i;
+   }
+   return -1;
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
  struct commit *commit,
  int *mapping_index)
 {
-   int i;
+   int i = graph_find_new_column_by_commit(graph, commit);
 
/*
 * If the commit is already in the new_columns list, we don't need to
 * add it.  Just update the mapping correctly.
 */
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
-   }
+   if (i >= 0) {
+   graph->mapping[*mapping_index] = i;
+   *mapping_index += 2;
+   return;
}
 
/*
@@ -961,17 +970,6 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct strbuf *sb)
graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
-static struct column *find_new_column_by_commit(struct git_graph *graph,
-   struct commit *commit)
-{
-   int i;
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit)
-   return &graph->new_columns[i];
-   }
-   return NULL;
-}
-
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
strbuf *sb)
 {
int seen_this = 0;
@@ -999,20 +997,20 @@ static void graph_output_post_merge_line(struct git_graph 
*graph, struct strbuf
 * edges.
 */
struct commit_list *parents = NULL;
-   struct column *par_column;
+   int par_column;
seen_this = 1;
parents = first_interesting_parent(graph);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
+   par_column = graph_find_new_column_by_commit(graph, 
parents->item);
+   assert(par_column >= 0);
 
-   strbuf_write_column(sb, par_column, '|');
+   strbuf_write_column(sb, 
&graph->new_columns[par_column], '|');
for (j = 0; j < graph->num_parents - 1; j++) {
parents = next_interesting_parent(graph, 
parents);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
-   strbuf_write_column(sb, par_column, '\\');
+   par_column = 
graph_find_new_column_by_commit(graph, parents->item);
+   assert(par_column >= 0);
+   strbuf_write_column(sb, 
&graph->new_columns[par_column], '\\');
strbuf_addch(sb, ' ');
}
} else if (seen_this) {
-- 
gitgitgadget



[PATCH 01/11] graph: automatically track visible width of `strbuf`

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

All the output functions in `graph.c` currently keep track of how many
printable chars they've written to the buffer, before calling
`graph_pad_horizontally()` to pad the line with spaces. Some functions
do this by incrementing a counter whenever they write to the buffer, and
others do it by encoding an assumption about how many chars are written,
as in:

graph_pad_horizontally(graph, sb, graph->num_columns * 2);

This adds a fair amount of noise to the functions' logic and is easily
broken if one forgets to increment the right counter or update the
calculations used for padding.

To make this easier to use, I'm adding a `width` field to `strbuf` that
tracks the number of printing characters added after the line prefix.
It's set to 0 at the start of `graph_next_line()`, and then various
`strbuf` functions update it as follows:

- `strbuf_write_column()` increments `width` by 1

- `strbuf_setlen()` changes `width` by the amount added to `len` if
  `len` is increased, or makes `width` and `len` the same if it's
  decreased

- `strbuf_addch()` increments `width` by 1

This is enough to ensure that the functions used by `graph.c` update
`strbuf->width` correctly, and `graph_pad_horizontally()` can then use
this field instead of taking `chars_written` as a parameter.

Signed-off-by: James Coglan 
---
 graph.c  | 68 ++--
 strbuf.h |  8 ++-
 2 files changed, 33 insertions(+), 43 deletions(-)

diff --git a/graph.c b/graph.c
index f53135485f..c56fdec1fc 100644
--- a/graph.c
+++ b/graph.c
@@ -115,11 +115,20 @@ static const char *column_get_color_code(unsigned short 
color)
 static void strbuf_write_column(struct strbuf *sb, const struct column *c,
char col_char)
 {
+   /*
+* Remember the buffer's width as we're about to add non-printing
+* content to it, and we want to avoid counting the byte length
+* of this content towards the buffer's visible width
+*/
+   size_t prev_width = sb->width;
+
if (c->color < column_colors_max)
strbuf_addstr(sb, column_get_color_code(c->color));
strbuf_addch(sb, col_char);
if (c->color < column_colors_max)
strbuf_addstr(sb, column_get_color_code(column_colors_max));
+
+   sb->width = prev_width + 1;
 }
 
 struct git_graph {
@@ -686,8 +695,7 @@ static int graph_is_mapping_correct(struct git_graph *graph)
return 1;
 }
 
-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
-  int chars_written)
+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
 {
/*
 * Add additional spaces to the end of the strbuf, so that all
@@ -696,8 +704,8 @@ static void graph_pad_horizontally(struct git_graph *graph, 
struct strbuf *sb,
 * This way, fields printed to the right of the graph will remain
 * aligned for the entire commit.
 */
-   if (chars_written < graph->width)
-   strbuf_addchars(sb, ' ', graph->width - chars_written);
+   if (sb->width < graph->width)
+   strbuf_addchars(sb, ' ', graph->width - sb->width);
 }
 
 static void graph_output_padding_line(struct git_graph *graph,
@@ -723,7 +731,7 @@ static void graph_output_padding_line(struct git_graph 
*graph,
strbuf_addch(sb, ' ');
}
 
-   graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
+   graph_pad_horizontally(graph, sb);
 }
 
 
@@ -740,7 +748,7 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct strbuf *sb)
 * of the graph is missing.
 */
strbuf_addstr(sb, "...");
-   graph_pad_horizontally(graph, sb, 3);
+   graph_pad_horizontally(graph, sb);
 
if (graph->num_parents >= 3 &&
graph->commit_index < (graph->num_columns - 1))
@@ -754,7 +762,6 @@ static void graph_output_pre_commit_line(struct git_graph 
*graph,
 {
int num_expansion_rows;
int i, seen_this;
-   int chars_written;
 
/*
 * This function formats a row that increases the space around a commit
@@ -777,14 +784,12 @@ static void graph_output_pre_commit_line(struct git_graph 
*graph,
 * Output the row
 */
seen_this = 0;
-   chars_written = 0;
for (i = 0; i < graph->num_columns; i++) {
struct column *col = &graph->columns[i];
if (col->commit == graph->commit) {
seen_this = 1;
strbuf_write_column(sb, col, '|');
strbuf_addchars(sb, ' ', graph->expansion_row);
-   chars_written += 1 + graph->expansion_row;
} else if (seen_this && (graph->expansion_row == 0)) {
/*
 * This is the first line of the pre-commit output.
@@ -800,19 +805,15 @@ static void grap

[PATCH 00/11] Improve the readability of log --graph output

2019-10-10 Thread James Coglan via GitGitGadget
This series of patches are designed to improve the output of the log --graph
command; their effect can be summed up in the following diagram:

BeforeAfter
---

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \  |/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

These changes aim to make the edges in graph diagrams easier to read, by
straightening lines and making certain kinds of topologies display more
compactly. Three distinct changes are included.

First, if the first parent of a merge fuses with an edge to the left of the
commit, then we display that by making the edges fuse immediately rather
than by drawing a line straight down and then having it track to the left.
That is, where we currently display these graphs:

| * | | | *
| |\| | | |\
|/ /| |_|/ /
| | |/| | |

We will now display these merges as follows:

| * | | | *
|/| | |_|/|
| | |/| | |

This transformation is applied to merges with any number of parents, for
example we currently display 3-parent merges like this:

| *-.   | | | *-.
| |\ \  | | | |\ \
|/ / /  | |_|/ / /
| | |   |/| | | |

And we will now display them like this:

| * | | | *
|/|\| |_|/|\
| | |   |/| | | |

If the edge the first parent fuses with is separated from the commit by
multiple columns, a horizontal edge is drawn just as we currently do in the
'collapsing' state. This change also affects the display of commit and
post-merge lines in subtle ways that are more thoroughly described in the
relevant commits.

The second change is that if the final parent of a merge fuses with the edge
to the right of the commit, then we can remove the zig-zag effect that
currently results. We currently display these merges like this:

* |
|\ \
| |/
| *

After these changes, this merge will now be displayed like so:

* |
|\|
| *

If the final parent fuses with an edge that's further to the right, its
display is unchanged and it will still display like this:

* | | |
|\ \ \ \
| | |_|/
| |/| |
| * | |

The final structural change smooths out lines that are collapsing through
commit lines. For example, consider the following history:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

This is now rendered so that commit lines display an edge using / instead of
|, if that edge is tracking to the left both above and below the commit
line. That results in this improved display:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

Taken together, these changes produce the change shown in the first diagram
above, with the current rendering on the left and the new rendering on the
right.

A final addition to that set of changes fixes the coloring of dashes that
are drawn next to octopus merges, in a manner compatible with all these
changes. The early commits in this set are refactorings that make the
functional changes easier to introduce.

James Coglan (11):
  graph: automatically track visible width of `strbuf`
  graph: reuse `find_new_column_by_commit()`
  graph: reduce duplication in `graph_insert_into_new_columns()`
  graph: remove `mapping_idx` and `graph_update_width()`
  graph: extract logic for moving to GRAPH_PRE_COMMIT state
  graph: tidy up display of left-skewed merges
  graph: commit and post-merge lines for left-skewed merges
  graph: rename `new_mapping` to `old_mapping`
  graph: smooth appearance of collapsing edges on commit lines
  graph: flatten edges that join to their right neighbor
  graph: fix coloring of octopus dashes

 graph.c| 499 -
 strbuf.h   |   8 +-
 t/t3430-rebase-merges.sh   |   2 +-
 t/t4202-log.sh |   2 +-
 t/t4214-log-graph-octopus.sh   |  85 +++-
 t/t4215-log-skewed-merges.sh   | 267 +++
 t/t6016-rev-list-graph-simplify-history.sh |  30 +-
 7 files changed, 649 insertions(+), 244 deletions(-)
 create mode 100755 t/t4215-log-skewed-merges.sh


base-commit: 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9
Published-As: 
https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git 
pr-383/jcoglan/jc/simplify-graph-v1
Pull-Request: https://github.com/gitgitgad

[PATCH 04/11] graph: remove `mapping_idx` and `graph_update_width()`

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

There's a duplication of logic between `graph_insert_into_new_columns()`
and `graph_update_width()`. `graph_insert_into_new_columns()` is called
repeatedly by `graph_update_columns()` with an `int *` that tracks the
offset into the `mapping` array where we should write the next value.
Each call to `graph_insert_into_new_columns()` effectively pushes one
column index and one "null" value (-1) onto the `mapping` array and
therefore increments `mapping_idx` by 2.

`graph_update_width()` duplicates this process: the `width` of the graph
is essentially the initial width of the `mapping` array before edges
begin collapsing. The `graph_update_width()` function's logic
effectively works out how many times `graph_insert_into_new_columns()`
was called based on the relationship of the current commit to the rest
of the graph.

I'm about to make some changes that make the assignment of values into
the `mapping` array more complicated. Rather than make
`graph_update_width()` more complicated at the same time, we can simply
remove this function and use `graph->width` to track the offset into the
`mapping` array as we're building it. This removes the duplication and
makes sure that `graph->width` is the same as the visual width of the
`mapping` array once `graph_update_columns()` is complete.

Signed-off-by: James Coglan 
---
 graph.c | 65 +
 1 file changed, 10 insertions(+), 55 deletions(-)

diff --git a/graph.c b/graph.c
index 1cd30f80e6..a71ae5cd61 100644
--- a/graph.c
+++ b/graph.c
@@ -453,8 +453,7 @@ static int graph_find_new_column_by_commit(struct git_graph 
*graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit,
- int *mapping_index)
+ struct commit *commit)
 {
int i = graph_find_new_column_by_commit(graph, commit);
 
@@ -468,50 +467,14 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-}
-
-static void graph_update_width(struct git_graph *graph,
-  int is_commit_in_existing_columns)
-{
-   /*
-* Compute the width needed to display the graph for this commit.
-* This is the maximum width needed for any row.  All other rows
-* will be padded to this width.
-*
-* Compute the number of columns in the widest row:
-* Count each existing column (graph->num_columns), and each new
-* column added by this commit.
-*/
-   int max_cols = graph->num_columns + graph->num_parents;
-
-   /*
-* Even if the current commit has no parents to be printed, it
-* still takes up a column for itself.
-*/
-   if (graph->num_parents < 1)
-   max_cols++;
-
-   /*
-* We added a column for the current commit as part of
-* graph->num_parents.  If the current commit was already in
-* graph->columns, then we have double counted it.
-*/
-   if (is_commit_in_existing_columns)
-   max_cols--;
-
-   /*
-* Each column takes up 2 spaces
-*/
-   graph->width = max_cols * 2;
+   graph->mapping[graph->width] = i;
+   graph->width += 2;
 }
 
 static void graph_update_columns(struct git_graph *graph)
 {
struct commit_list *parent;
int max_new_columns;
-   int mapping_idx;
int i, seen_this, is_commit_in_columns;
 
/*
@@ -544,6 +507,8 @@ static void graph_update_columns(struct git_graph *graph)
for (i = 0; i < graph->mapping_size; i++)
graph->mapping[i] = -1;
 
+   graph->width = 0;
+
/*
 * Populate graph->new_columns and graph->mapping
 *
@@ -554,7 +519,6 @@ static void graph_update_columns(struct git_graph *graph)
 * supposed to end up after the collapsing is performed.
 */
seen_this = 0;
-   mapping_idx = 0;
is_commit_in_columns = 1;
for (i = 0; i <= graph->num_columns; i++) {
struct commit *col_commit;
@@ -568,7 +532,6 @@ static void graph_update_columns(struct git_graph *graph)
}
 
if (col_commit == graph->commit) {
-   int old_mapping_idx = mapping_idx;
seen_this = 1;
graph->commit_index = i;
for (parent = first_interesting_parent(graph);
@@ -583,21 +546,18 @@ static void graph_update_columns(struct git_graph *graph)
!is_commit_in_columns) {
graph_increment_column_color(graph);
}
-   graph_i

[PATCH 05/11] graph: extract logic for moving to GRAPH_PRE_COMMIT state

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

This computation is repeated in a couple of places and I need to add
another condition to it to implement a further improvement to the graph
rendering, so I'm extracting this into a function.

Signed-off-by: James Coglan 
---
 graph.c | 12 
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/graph.c b/graph.c
index a71ae5cd61..98e8777db4 100644
--- a/graph.c
+++ b/graph.c
@@ -569,6 +569,12 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_needs_pre_commit_line(struct git_graph *graph)
+{
+   return graph->num_parents >= 3 &&
+  graph->commit_index < (graph->num_columns - 1);
+}
+
 void graph_update(struct git_graph *graph, struct commit *commit)
 {
struct commit_list *parent;
@@ -624,8 +630,7 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
if (graph->state != GRAPH_PADDING)
graph->state = GRAPH_SKIP;
-   else if (graph->num_parents >= 3 &&
-graph->commit_index < (graph->num_columns - 1))
+   else if (graph_needs_pre_commit_line(graph))
graph->state = GRAPH_PRE_COMMIT;
else
graph->state = GRAPH_COMMIT;
@@ -708,8 +713,7 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct strbuf *sb)
strbuf_addstr(sb, "...");
graph_pad_horizontally(graph, sb);
 
-   if (graph->num_parents >= 3 &&
-   graph->commit_index < (graph->num_columns - 1))
+   if (graph_needs_pre_commit_line(graph))
graph_update_state(graph, GRAPH_PRE_COMMIT);
else
graph_update_state(graph, GRAPH_COMMIT);
-- 
gitgitgadget



[PATCH 09/11] graph: smooth appearance of collapsing edges on commit lines

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

When a graph contains edges that are in the process of collapsing to the
left, but those edges cross a commit line, the effect is that the edges
have a jagged appearance:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

We already takes steps to smooth edges like this when they're expanding;
when an edge appears to the right of a merge commit marker on a
GRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we
render it as a `\`:

* \
|\ \
| * \
| |\ \

We can make a similar improvement to collapsing edges, making them
easier to follow and giving the overall graph a feeling of increased
symmetry:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

To do this, we introduce a new special case for edges on GRAPH_COMMIT
lines that immediately follow a GRAPH_COLLAPSING line. By retaining a
copy of the `mapping` array used to render the GRAPH_COLLAPSING line in
the `old_mapping` array, we can determine that an edge is collapsing
through the GRAPH_COMMIT line and should be smoothed.

Signed-off-by: James Coglan 
---
 graph.c| 17 +
 t/t3430-rebase-merges.sh   |  2 +-
 t/t4202-log.sh |  2 +-
 t/t4214-log-graph-octopus.sh   | 16 
 t/t4215-log-skewed-merges.sh   |  2 +-
 t/t6016-rev-list-graph-simplify-history.sh |  4 ++--
 6 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/graph.c b/graph.c
index b0e31e23ca..6391e393ec 100644
--- a/graph.c
+++ b/graph.c
@@ -278,10 +278,10 @@ struct git_graph {
 */
int *mapping;
/*
-* A temporary array for computing the next mapping state
-* while we are outputting a mapping line.  This is stored as part
-* of the git_graph simply so we don't have to allocate a new
-* temporary array each time we have to output a collapsing line.
+* A copy of the contents of the mapping array from the last commit,
+* which we use to improve the display of columns that are tracking
+* from right to left through a commit line.  We also use this to
+* avoid allocating a fresh array when we compute the next mapping.
 */
int *old_mapping;
/*
@@ -1011,6 +1011,10 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct strbuf *sb)
strbuf_write_column(sb, col, '\\');
else
strbuf_write_column(sb, col, '|');
+   } else if (graph->prev_state == GRAPH_COLLAPSING &&
+  graph->old_mapping[2 * i + 1] == i &&
+  graph->mapping[2 * i] < i) {
+   strbuf_write_column(sb, col, '/');
} else {
strbuf_write_column(sb, col, '|');
}
@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct strbuf
}
}
 
+   /*
+* Copy the current mapping array into old_mapping
+*/
+   COPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);
+
/*
 * The new mapping may be 1 smaller than the old mapping
 */
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 7b6c4847ad..051351ecdf 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -402,7 +402,7 @@ test_expect_success 'octopus merges' '
| | * three
| * | two
| |/
-   * | one
+   * / one
|/
o before-octopus
EOF
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index c20209324c..c7aa0532c1 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -667,7 +667,7 @@ cat > expect <<\EOF
 * | | fifth
 * | | fourth
 |/ /
-* | third
+* / third
 |/
 * second
 * initial
diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh
index 40f420877b..9ada687628 100755
--- a/t/t4214-log-graph-octopus.sh
+++ b/t/t4214-log-graph-octopus.sh
@@ -12,9 +12,9 @@ test_expect_success 'set up merge history' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -25,9 +25,9 @@ test_expect_success 'set up merge history' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -68,9 +68,9 @@ test_expect_success 'log --graph with normal octopus merge, 
no color' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   *

[PATCH 08/11] graph: rename `new_mapping` to `old_mapping`

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

The change I'm about to make requires being able to inspect the mapping
array that was used to render the last GRAPH_COLLAPSING line while
rendering a GRAPH_COMMIT line. The `new_mapping` array currently exists
as a pre-allocated space for computing the next `mapping` array during
`graph_output_collapsing_line()`, but we can repurpose it to let us see
the previous `mapping` state.

To support this use it will make more sense if this array is named
`old_mapping`, as it will contain the mapping data for the previous line
we rendered, at the point we're rendering a commit line.

Signed-off-by: James Coglan 
---
 graph.c | 54 +++---
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/graph.c b/graph.c
index fb2e42850f..b0e31e23ca 100644
--- a/graph.c
+++ b/graph.c
@@ -240,7 +240,7 @@ struct git_graph {
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
-* that can be stored in the mapping and new_mapping arrays.
+* that can be stored in the mapping and old_mapping arrays.
 */
int column_capacity;
/*
@@ -283,7 +283,7 @@ struct git_graph {
 * of the git_graph simply so we don't have to allocate a new
 * temporary array each time we have to output a collapsing line.
 */
-   int *new_mapping;
+   int *old_mapping;
/*
 * The current default column color being used.  This is
 * stored as an index into the array column_colors.
@@ -369,7 +369,7 @@ struct git_graph *graph_init(struct rev_info *opt)
ALLOC_ARRAY(graph->columns, graph->column_capacity);
ALLOC_ARRAY(graph->new_columns, graph->column_capacity);
ALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);
-   ALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);
+   ALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);
 
/*
 * The diff output prefix callback, with this we can make
@@ -399,7 +399,7 @@ static void graph_ensure_capacity(struct git_graph *graph, 
int num_columns)
REALLOC_ARRAY(graph->columns, graph->column_capacity);
REALLOC_ARRAY(graph->new_columns, graph->column_capacity);
REALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);
-   REALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);
+   REALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);
 }
 
 /*
@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct strbuf
int horizontal_edge_target = -1;
 
/*
-* Clear out the new_mapping array
+* Swap the mapping and old_mapping arrays
+*/
+   SWAP(graph->mapping, graph->old_mapping);
+
+   /*
+* Clear out the mapping array
 */
for (i = 0; i < graph->mapping_size; i++)
-   graph->new_mapping[i] = -1;
+   graph->mapping[i] = -1;
 
for (i = 0; i < graph->mapping_size; i++) {
-   int target = graph->mapping[i];
+   int target = graph->old_mapping[i];
if (target < 0)
continue;
 
@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct strbuf
 * This column is already in the
 * correct place
 */
-   assert(graph->new_mapping[i] == -1);
-   graph->new_mapping[i] = target;
-   } else if (graph->new_mapping[i - 1] < 0) {
+   assert(graph->mapping[i] == -1);
+   graph->mapping[i] = target;
+   } else if (graph->mapping[i - 1] < 0) {
/*
 * Nothing is to the left.
 * Move to the left by one
 */
-   graph->new_mapping[i - 1] = target;
+   graph->mapping[i - 1] = target;
/*
 * If there isn't already an edge moving horizontally
 * select this one.
@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph 
*graph, struct strbuf
 * line.
 */
for (j = (target * 2)+3; j < (i - 2); j += 2)
-   graph->new_mapping[j] = target;
+   graph->mapping[j] = target;
}
-   } else if (graph->new_mapping[i - 1] == target) {
+   } else if (graph->mapping[i - 1] == target) {
/*
 * There is a branch line to our left
 * already, and it is our target.  We
@@ -1176,7 +118

[PATCH 11/11] graph: fix coloring of octopus dashes

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

In 04005834ed ("log: fix coloring of certain octopus merge shapes",
2018-09-01) there is a fix for the coloring of dashes following an
octopus merge. It makes a distinction between the case where all parents
introduce a new column, versus the case where the first parent collapses
into an existing column:

| *-.   | *-.
| |\ \  | |\ \
| | | | |/ / /

The latter case means that the columns for the merge parents begin one
place to the left in the `new_columns` array compared to the former
case.

However, the implementation only works if the commit's parents are kept
in order as they map onto the visual columns, as we get the colors by
iterating over `new_columns` as we print the dashes. In general, the
commit's parents can arbitrarily merge with existing columns, and change
their ordering in the process.

For example, in the following diagram, the number of each column
indicates which commit parent appears in each column.

| | *---.
| | |\ \ \
| | |/ / /
| |/| | /
| |_|_|/
|/| | |
3 1 0 2

If the columns are colored (red, green, yellow, blue), then the dashes
will currently be colored yellow and blue, whereas they should be blue
and red.

To fix this, we need to look up each column in the `mapping` array,
which before the `GRAPH_COLLAPSING` state indicates which logical column
is displayed in each visual column. This implementation is simpler as it
doesn't have any edge cases, and it also handles how left-skewed first
parents are now displayed:

| *-.
|/|\ \
| | | |
0 1 2 3

The color of the first dashes is always the color found in `mapping` two
columns to the right of the commit symbol. Because commits are displayed
after all edges have been collapsed together and the visual columns
match the logical ones, we can find the visual offset of the commit
symbol using `commit_index`.

Signed-off-by: James Coglan 
---
 graph.c  | 71 +++-
 t/t4214-log-graph-octopus.sh | 59 --
 2 files changed, 92 insertions(+), 38 deletions(-)

diff --git a/graph.c b/graph.c
index 7dd2fab625..908f257018 100644
--- a/graph.c
+++ b/graph.c
@@ -665,6 +665,11 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_num_dashed_parents(struct git_graph *graph)
+{
+   return graph->num_parents + graph->merge_layout - 3;
+}
+
 static int graph_num_expansion_rows(struct git_graph *graph)
 {
/*
@@ -687,7 +692,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)
 *  | * \
 *  |/|\ \
 */
-   return (graph->num_parents + graph->merge_layout - 3) * 2;
+   return graph_num_dashed_parents(graph) * 2;
 }
 
 static int graph_needs_pre_commit_line(struct git_graph *graph)
@@ -930,47 +935,45 @@ static void graph_output_commit_char(struct git_graph 
*graph, struct strbuf *sb)
 static void graph_draw_octopus_merge(struct git_graph *graph, struct strbuf 
*sb)
 {
/*
-* Here dashless_parents represents the number of parents which don't
-* need to have dashes (the edges labeled "0" and "1").  And
-* dashful_parents are the remaining ones.
+* The parents of a merge commit can be arbitrarily reordered as they
+* are mapped onto display columns, for example this is a valid merge:
 *
-* | *---.
-* | |\ \ \
-* | | | | |
-* x 0 1 2 3
+*  | | *---.
+*  | | |\ \ \
+*  | | |/ / /
+*  | |/| | /
+*  | |_|_|/
+*  |/| | |
+*  3 1 0 2
 *
-*/
-   const int dashless_parents = 3 - graph->merge_layout;
-   int dashful_parents = graph->num_parents - dashless_parents;
-
-   /*
-* Usually, we add one new column for each parent (like the diagram
-* above) but sometimes the first parent goes into an existing column,
-* like this:
+* The numbers denote which parent of the merge each visual column
+* corresponds to; we can't assume that the parents will initially
+* display in the order given by new_columns.
 *
-* | *-.
-* |/|\ \
-* | | | |
-* x 0 1 2
+* To find the right color for each dash, we need to consult the
+* mapping array, starting from the column 2 places to the right of the
+* merge commit, and use that to find out which logical column each
+* edge will collapse to.
 *
-* In which case the number of parents will be one greater than the
-* number of added columns.
+* Commits are rendered once all edges have collapsed to their correct
+* logcial column, so commit_index gives us the right visual offset for
+* the merge commit.

[PATCH 07/11] graph: commit and post-merge lines for left-skewed merges

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

Following the introduction of "left-skewed" merges, which are merges
whose first parent fuses with another edge to its left, we have some
more edge cases to deal with in the display of commit and post-merge
lines.

The current graph code handles the following cases for edges appearing
to the right of the commit (*) on commit lines. A 2-way merge is usually
followed by vertical lines:

| | |
| * |
| |\ \

An octopus merge (more than two parents) is always followed by edges
sloping to the right:

| |  \  | |\
| *-. \ | *---. \
| |\ \ \| |\ \ \ \

A 2-way merge is followed by a right-sloping edge if the commit line
immediately follows a post-merge line for a commit that appears in the
same column as the current commit, or any column to the left of that:

| * | * |
| |\| |\ \
| * \   | | * \
| |\ \  | | |\ \

This commit introduces the following new cases for commit lines. If a
2-way merge skews to the left, then the edges to its right are always
vertical lines, even if the commit follows a post-merge line:

| | |   | |\
| * |   | * |
|/| |   |/| |

A commit with 3 parents that skews left is followed by vertical edges:

| | |
| * |
|/|\ \

If a 3-way left-skewed merge commit appears immediately after a
post-merge line, then it may be followed the right-sloping edges, just
like a 2-way merge that is not skewed.

| |\
| * \
|/|\ \

Octopus merges with 4 or more parents that skew to the left will always
be followed by right-sloping edges, because the existing columns need to
expand around the merge.

| |  \
| *-. \
|/|\ \ \

On post-merge lines, usually all edges following the current commit
slope to the right:

| * | |
| |\ \ \

However, if the commit is a left-skewed 2-way merge, the edges to its
right remain vertical. We also need to display a space after the
vertical line descending from the commit marker, whereas this line would
normally be followed by a backslash.

| * | |
|/| | |

If a left-skewed merge has more than 2 parents, then the edges to its
right are still sloped as they bend around the edges introduced by the
merge.

| * | |
|/|\ \ \

To handle these new cases, we need to know not just how many parents
each commit has, but how many new columns it adds to the display; this
quantity is recorded in the `edges_added` field for the current commit,
and `prev_edges_added` field for the previous commit.

Here, "column" refers to visual columns, not the logical columns of the
`columns` array. This is because even if all the commit's parents end up
fusing with existing edges, they initially introduce distinct edges in
the commit and post-merge lines before those edges collapse. For
example, a 3-way merge whose 2nd and 3rd parents fuse with existing
edges still introduces 2 visual columns that affect the display of edges
to their right.

| | |  \
| | *-. \
| | |\ \ \
| |_|/ / /
|/| | / /
| | |/ /
| |/| |
| | | |

This merge does not introduce any *logical* columns; there are 4 edges
before and after this commit once all edges have collapsed. But it does
initially introduce 2 new edges that need to be accommodated by the
edges to their right.

Signed-off-by: James Coglan 
---
 graph.c  |  63 +--
 t/t4215-log-skewed-merges.sh | 151 +++
 2 files changed, 209 insertions(+), 5 deletions(-)

diff --git a/graph.c b/graph.c
index 9136173e03..fb2e42850f 100644
--- a/graph.c
+++ b/graph.c
@@ -197,6 +197,46 @@ struct git_graph {
 *  |/| | | | | | | | | | *
 */
int merge_layout;
+   /*
+* The number of columns added to the graph by the current commit. For
+* 2-way and octopus merges, this is is usually one less than the
+* number of parents:
+*
+*  | | |   | |\
+*  | * |   | *---. \
+*  | |\ \  | |\ \ \ \
+*  | | | | | | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 1  edges_added: 3
+*
+* For left-skewed merges, the first parent fuses with its neighbor and
+* so one less column is added:
+*
+*  | | |   | |  \
+*  | * |   | *-. \
+*  |/| |   |/|\ \ \
+*  | | |   | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 0 

[PATCH 06/11] graph: tidy up display of left-skewed merges

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

Currently, when we display a merge whose first parent is already present
in a column to the left of the merge commit, we display the first parent
as a veritcal pipe `|` in the GRAPH_POST_MERGE line and then immediately
enter the GRAPH_COLLAPSING state. The first-parent line tracks to the
left and all the other parent lines follow it; this creates a "kink" in
those lines:

| *---.
| |\ \ \
|/ / / /
| | | *

This change tidies the display of such commits such that if the first
parent appears to the left of the merge, we render it as a `/` and the
second parent as a `|`. This reduces the horizontal and vertical space
needed to render the merge, and makes the resulting lines easier to
read.

| *-.
|/|\ \
| | | *

If the first parent is separated from the merge by several columns, a
horizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING
state displays the line.

| | | *-.
| |_|/|\ \
|/| | | | *

This effect is applied to both "normal" two-parent merges, and to
octopus merges. It also reduces the vertical space needed for pre-commit
lines, as the merge occupies one less column than usual.

Before: After:

| * | *
| |\| |\
| | \   | * \
| |  \  |/|\ \
| *-. \
| |\ \ \

Signed-off-by: James Coglan 
---
 graph.c  | 125 +++
 t/t4214-log-graph-octopus.sh |  10 ++-
 t/t4215-log-skewed-merges.sh |  42 
 3 files changed, 143 insertions(+), 34 deletions(-)
 create mode 100755 t/t4215-log-skewed-merges.sh

diff --git a/graph.c b/graph.c
index 98e8777db4..9136173e03 100644
--- a/graph.c
+++ b/graph.c
@@ -183,6 +183,20 @@ struct git_graph {
 * previous commit.
 */
int prev_commit_index;
+   /*
+* Which layout variant to use to display merge commits. If the
+* commit's first parent is known to be in a column to the left of the
+* merge, then this value is 0 and we use the layout on the left.
+* Otherwise, the value is 1 and the layout on the right is used. This
+* field tells us how many columns the first parent occupies.
+*
+*  0)  1)
+*
+*  | | | *-.   | | *---.
+*  | |_|/|\ \  | | |\ \ \
+*  |/| | | | | | | | | | *
+*/
+   int merge_layout;
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
@@ -294,6 +308,7 @@ struct git_graph *graph_init(struct rev_info *opt)
graph->prev_state = GRAPH_PADDING;
graph->commit_index = 0;
graph->prev_commit_index = 0;
+   graph->merge_layout = 0;
graph->num_columns = 0;
graph->num_new_columns = 0;
graph->mapping_size = 0;
@@ -453,9 +468,11 @@ static int graph_find_new_column_by_commit(struct 
git_graph *graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit)
+ struct commit *commit,
+ int idx)
 {
int i = graph_find_new_column_by_commit(graph, commit);
+   int mapping_idx;
 
/*
 * If the commit is not already in the new_columns array, then add it
@@ -467,8 +484,26 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[graph->width] = i;
-   graph->width += 2;
+   if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {
+   /*
+* If this is the first parent of a merge, choose a layout for
+* the merge line based on whether the parent appears in a
+* column to the left of the merge
+*/
+   int dist, shift;
+
+   dist = idx - i;
+   shift = (dist > 1) ? 2 * dist - 3 : 1;
+
+   graph->merge_layout = (dist > 0) ? 0 : 1;
+   mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
+   graph->width += 2 * graph->merge_layout;
+   } else {
+   mapping_idx = graph->width;
+   graph->width += 2;
+   }
+
+   graph->mapping[mapping_idx] = i;
 }
 
 static void graph_update_columns(struct git_graph *graph)
@@ -534,6 +569,7 @@ static void graph_update_columns(struct git_graph *graph)
if (col_commit == graph->commit) {
seen_this = 1;
graph->commit_index = i;
+   graph->merge_layout = -1;
for (par

[PATCH 10/11] graph: flatten edges that join to their right neighbor

2019-10-10 Thread James Coglan via GitGitGadget
From: James Coglan 

When a merge commit is printed and its final parent is the same commit
that occupies the column to the right of the merge, this results in a
kink in the displayed edges:

* |
|\ \
| |/
| *

Graphs containing these shapes can be hard to read, as the expansion to
the right followed immediately by collapsing back to the left creates a
lot of zig-zagging edges, especially when many columns are present.

We can improve this by eliminating the zig-zag and having the merge's
final parent edge fuse immediately with its neighbor:

* |
|\|
| *

This reduces the horizontal width for the current commit by 2, and
requires one less row, making the graph display more compact. Taken in
combination with other graph-smoothing enhancements, it greatly
compresses the space needed to display certain histories:

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \=>|/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

One of the test cases here cannot be correctly rendered in Git v2.23.0;
it produces this output following commit E:

| | *-. \   5_E
| | |\ \ \
| |/ / / /
| | | / _
| |_|/
|/| |

The new implementation makes sure that the rightmost edge in this
history is not left dangling as above.

Signed-off-by: James Coglan 
---
 graph.c| 34 ++---
 t/t4215-log-skewed-merges.sh   | 80 +-
 t/t6016-rev-list-graph-simplify-history.sh | 30 
 3 files changed, 116 insertions(+), 28 deletions(-)

diff --git a/graph.c b/graph.c
index 6391e393ec..7dd2fab625 100644
--- a/graph.c
+++ b/graph.c
@@ -538,8 +538,24 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
shift = (dist > 1) ? 2 * dist - 3 : 1;
 
graph->merge_layout = (dist > 0) ? 0 : 1;
+   graph->edges_added = graph->num_parents + graph->merge_layout  
- 2;
+
mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
graph->width += 2 * graph->merge_layout;
+
+   } else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 
2]) {
+   /*
+* If some columns have been added by a merge, but this commit
+* was found in the last existing column, then adjust the
+* numbers so that the two edges immediately join, i.e.:
+*
+*  * | * |
+*  |\ \=>  |\|
+*  | |/| *
+*  | *
+*/
+   mapping_idx = graph->width - 2;
+   graph->edges_added = -1;
} else {
mapping_idx = graph->width;
graph->width += 2;
@@ -585,6 +601,8 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping[i] = -1;
 
graph->width = 0;
+   graph->prev_edges_added = graph->edges_added;
+   graph->edges_added = 0;
 
/*
 * Populate graph->new_columns and graph->mapping
@@ -712,9 +730,6 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
graph_update_columns(graph);
 
-   graph->prev_edges_added = graph->edges_added;
-   graph->edges_added = graph->num_parents + graph->merge_layout - 2;
-
graph->expansion_row = 0;
 
/*
@@ -1039,7 +1054,7 @@ const char merge_chars[] = {'/', '|', '\\'};
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
strbuf *sb)
 {
int seen_this = 0;
-   int i;
+   int i, j;
 
struct commit_list *first_parent = first_interesting_parent(graph);
int seen_parent = 0;
@@ -1071,16 +1086,19 @@ static void graph_output_post_merge_line(struct 
git_graph *graph, struct strbuf
char c;
seen_this = 1;
 
-   for (; parents; parents = 
next_interesting_parent(graph, parents)) {
+   for (j = 0; j < graph->num_parents; j++) {
par_column = 
graph_find_new_column_by_commit(graph, parents->item);
assert(par_column >= 0);
 
c = merge_chars[idx];
strbuf_write_column(sb, 
&graph->new_columns[par_column], c);
-   if (idx == 2)
-   strbuf_

[PATCH v2 05/13] graph: remove `mapping_idx` and `graph_update_width()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

There's a duplication of logic between `graph_insert_into_new_columns()`
and `graph_update_width()`. `graph_insert_into_new_columns()` is called
repeatedly by `graph_update_columns()` with an `int *` that tracks the
offset into the `mapping` array where we should write the next value.
Each call to `graph_insert_into_new_columns()` effectively pushes one
column index and one "null" value (-1) onto the `mapping` array and
therefore increments `mapping_idx` by 2.

`graph_update_width()` duplicates this process: the `width` of the graph
is essentially the initial width of the `mapping` array before edges
begin collapsing. The `graph_update_width()` function's logic
effectively works out how many times `graph_insert_into_new_columns()`
was called based on the relationship of the current commit to the rest
of the graph.

I'm about to make some changes that make the assignment of values into
the `mapping` array more complicated. Rather than make
`graph_update_width()` more complicated at the same time, we can simply
remove this function and use `graph->width` to track the offset into the
`mapping` array as we're building it. This removes the duplication and
makes sure that `graph->width` is the same as the visual width of the
`mapping` array once `graph_update_columns()` is complete.

Signed-off-by: James Coglan 
---
 graph.c | 65 +
 1 file changed, 10 insertions(+), 55 deletions(-)

diff --git a/graph.c b/graph.c
index 512ae16535..d724ef25c3 100644
--- a/graph.c
+++ b/graph.c
@@ -472,8 +472,7 @@ static int graph_find_new_column_by_commit(struct git_graph 
*graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit,
- int *mapping_index)
+ struct commit *commit)
 {
int i = graph_find_new_column_by_commit(graph, commit);
 
@@ -487,50 +486,14 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-}
-
-static void graph_update_width(struct git_graph *graph,
-  int is_commit_in_existing_columns)
-{
-   /*
-* Compute the width needed to display the graph for this commit.
-* This is the maximum width needed for any row.  All other rows
-* will be padded to this width.
-*
-* Compute the number of columns in the widest row:
-* Count each existing column (graph->num_columns), and each new
-* column added by this commit.
-*/
-   int max_cols = graph->num_columns + graph->num_parents;
-
-   /*
-* Even if the current commit has no parents to be printed, it
-* still takes up a column for itself.
-*/
-   if (graph->num_parents < 1)
-   max_cols++;
-
-   /*
-* We added a column for the current commit as part of
-* graph->num_parents.  If the current commit was already in
-* graph->columns, then we have double counted it.
-*/
-   if (is_commit_in_existing_columns)
-   max_cols--;
-
-   /*
-* Each column takes up 2 spaces
-*/
-   graph->width = max_cols * 2;
+   graph->mapping[graph->width] = i;
+   graph->width += 2;
 }
 
 static void graph_update_columns(struct git_graph *graph)
 {
struct commit_list *parent;
int max_new_columns;
-   int mapping_idx;
int i, seen_this, is_commit_in_columns;
 
/*
@@ -563,6 +526,8 @@ static void graph_update_columns(struct git_graph *graph)
for (i = 0; i < graph->mapping_size; i++)
graph->mapping[i] = -1;
 
+   graph->width = 0;
+
/*
 * Populate graph->new_columns and graph->mapping
 *
@@ -573,7 +538,6 @@ static void graph_update_columns(struct git_graph *graph)
 * supposed to end up after the collapsing is performed.
 */
seen_this = 0;
-   mapping_idx = 0;
is_commit_in_columns = 1;
for (i = 0; i <= graph->num_columns; i++) {
struct commit *col_commit;
@@ -587,7 +551,6 @@ static void graph_update_columns(struct git_graph *graph)
}
 
if (col_commit == graph->commit) {
-   int old_mapping_idx = mapping_idx;
seen_this = 1;
graph->commit_index = i;
for (parent = first_interesting_parent(graph);
@@ -602,21 +565,18 @@ static void graph_update_columns(struct git_graph *graph)
!is_commit_in_columns) {
graph_increment_column_color(graph);
}
-   graph_i

[PATCH v2 06/13] graph: extract logic for moving to GRAPH_PRE_COMMIT state

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

This computation is repeated in a couple of places and I need to add
another condition to it to implement a further improvement to the graph
rendering, so I'm extracting this into a function.

Signed-off-by: James Coglan 
---
 graph.c | 12 
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/graph.c b/graph.c
index d724ef25c3..bd7403065e 100644
--- a/graph.c
+++ b/graph.c
@@ -588,6 +588,12 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_needs_pre_commit_line(struct git_graph *graph)
+{
+   return graph->num_parents >= 3 &&
+  graph->commit_index < (graph->num_columns - 1);
+}
+
 void graph_update(struct git_graph *graph, struct commit *commit)
 {
struct commit_list *parent;
@@ -643,8 +649,7 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
if (graph->state != GRAPH_PADDING)
graph->state = GRAPH_SKIP;
-   else if (graph->num_parents >= 3 &&
-graph->commit_index < (graph->num_columns - 1))
+   else if (graph_needs_pre_commit_line(graph))
graph->state = GRAPH_PRE_COMMIT;
else
graph->state = GRAPH_COMMIT;
@@ -714,8 +719,7 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct graph_line *l
 */
graph_line_addstr(line, "...");
 
-   if (graph->num_parents >= 3 &&
-   graph->commit_index < (graph->num_columns - 1))
+   if (graph_needs_pre_commit_line(graph))
graph_update_state(graph, GRAPH_PRE_COMMIT);
else
graph_update_state(graph, GRAPH_COMMIT);
-- 
gitgitgadget



[PATCH v2 02/13] graph: handle line padding in `graph_next_line()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Now that the display width of graph lines is implicitly tracked via the
`graph_line` interface, the calls to `graph_pad_horizontally()` no
longer need to be located inside the individual output functions, where
the character counting was previously being done.

All the functions called by `graph_next_line()` generate a line of
output, then call `graph_pad_horizontally()`, and finally change the
graph state if necessary. As padding is the final change to the output
done by all these functions, it can be removed from all of them and done
in `graph_next_line()` instead.

I've also moved the guard in `graph_output_padding_line()` that checks
the graph has a commit; this function is only called by
`graph_next_line()` and we must not pad the `graph_line` if no commit is
set.
---
 graph.c | 49 -
 1 file changed, 20 insertions(+), 29 deletions(-)

diff --git a/graph.c b/graph.c
index 2f81a5d23d..4c68557b17 100644
--- a/graph.c
+++ b/graph.c
@@ -732,16 +732,6 @@ static void graph_output_padding_line(struct git_graph 
*graph,
 {
int i;
 
-   /*
-* We could conceivable be called with a NULL commit
-* if our caller has a bug, and invokes graph_next_line()
-* immediately after graph_init(), without first calling
-* graph_update().  Return without outputting anything in this
-* case.
-*/
-   if (!graph->commit)
-   return;
-
/*
 * Output a padding row, that leaves all branch lines unchanged
 */
@@ -749,8 +739,6 @@ static void graph_output_padding_line(struct git_graph 
*graph,
graph_line_write_column(line, &graph->new_columns[i], '|');
graph_line_addch(line, ' ');
}
-
-   graph_pad_horizontally(graph, line);
 }
 
 
@@ -767,7 +755,6 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct graph_line *l
 * of the graph is missing.
 */
graph_line_addstr(line, "...");
-   graph_pad_horizontally(graph, line);
 
if (graph->num_parents >= 3 &&
graph->commit_index < (graph->num_columns - 1))
@@ -832,8 +819,6 @@ static void graph_output_pre_commit_line(struct git_graph 
*graph,
graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Increment graph->expansion_row,
 * and move to state GRAPH_COMMIT if necessary
@@ -967,8 +952,6 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Update graph->state
 */
@@ -1043,8 +1026,6 @@ static void graph_output_post_merge_line(struct git_graph 
*graph, struct graph_l
}
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Update graph->state
 */
@@ -1186,8 +1167,6 @@ static void graph_output_collapsing_line(struct git_graph 
*graph, struct graph_l
}
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Swap mapping and new_mapping
 */
@@ -1204,31 +1183,43 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
 
 int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 {
+   int shown_commit_line = 0;
struct graph_line line = { .buf = sb, .width = 0 };
 
+   /*
+* We could conceivable be called with a NULL commit
+* if our caller has a bug, and invokes graph_next_line()
+* immediately after graph_init(), without first calling
+* graph_update().  Return without outputting anything in this
+* case.
+*/
+   if (!graph->commit)
+   return -1;
+
switch (graph->state) {
case GRAPH_PADDING:
graph_output_padding_line(graph, &line);
-   return 0;
+   break;
case GRAPH_SKIP:
graph_output_skip_line(graph, &line);
-   return 0;
+   break;
case GRAPH_PRE_COMMIT:
graph_output_pre_commit_line(graph, &line);
-   return 0;
+   break;
case GRAPH_COMMIT:
graph_output_commit_line(graph, &line);
-   return 1;
+   shown_commit_line = 1;
+   break;
case GRAPH_POST_MERGE:
graph_output_post_merge_line(graph, &line);
-   return 0;
+   break;
case GRAPH_COLLAPSING:
graph_output_collapsing_line(graph, &line);
-   return 0;
+   break;
}
 
-   assert(0);
-   return 0;
+   graph_pad_horizontally(graph, &line);
+   return shown_commit_line;
 }
 
 static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
-- 
gitgitgadget



[PATCH v2 01/13] graph: automatically track display width of graph lines

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

All the output functions called by `graph_next_line()` currently keep
track of how many printable chars they've written to the buffer, before
calling `graph_pad_horizontally()` to pad the line with spaces. Some
functions do this by incrementing a counter whenever they write to the
buffer, and others do it by encoding an assumption about how many chars
are written, as in:

graph_pad_horizontally(graph, sb, graph->num_columns * 2);

This adds a fair amount of noise to the functions' logic and is easily
broken if one forgets to increment the right counter or update the
calculations used for padding.

To make this easier to use, I'm introducing a new struct called
`graph_line` that wraps a `strbuf` and keeps count of its display width
implicitly. `graph_next_line()` wraps this around the `struct strbuf *`
it's given and passes a `struct graph_line *` to the output functions,
which use its interface.

The `graph_line` interface wraps the `strbuf_addch()`,
`strbuf_addchars()` and `strbuf_addstr()` functions, and adds the
`graph_line_write_column()` function for adding a single character with
color formatting. The `graph_pad_horizontally()` function can then use
the `width` field from the struct rather than taking a character count
as a parameter.

Signed-off-by: James Coglan 
---
 graph.c | 194 +---
 1 file changed, 99 insertions(+), 95 deletions(-)

diff --git a/graph.c b/graph.c
index f53135485f..2f81a5d23d 100644
--- a/graph.c
+++ b/graph.c
@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short 
color)
return column_colors[color];
 }
 
-static void strbuf_write_column(struct strbuf *sb, const struct column *c,
-   char col_char)
+struct graph_line {
+   struct strbuf *buf;
+   size_t width;
+};
+
+static inline void graph_line_addch(struct graph_line *line, int c)
+{
+   strbuf_addch(line->buf, c);
+   line->width++;
+}
+
+static inline void graph_line_addchars(struct graph_line *line, int c, size_t 
n)
+{
+   strbuf_addchars(line->buf, c, n);
+   line->width += n;
+}
+
+static inline void graph_line_addstr(struct graph_line *line, const char *s)
+{
+   strbuf_addstr(line->buf, s);
+   line->width += strlen(s);
+}
+
+static inline void graph_line_addcolor(struct graph_line *line, unsigned short 
color)
+{
+   strbuf_addstr(line->buf, column_get_color_code(color));
+}
+
+static void graph_line_write_column(struct graph_line *line, const struct 
column *c,
+   char col_char)
 {
if (c->color < column_colors_max)
-   strbuf_addstr(sb, column_get_color_code(c->color));
-   strbuf_addch(sb, col_char);
+   graph_line_addcolor(line, c->color);
+   graph_line_addch(line, col_char);
if (c->color < column_colors_max)
-   strbuf_addstr(sb, column_get_color_code(column_colors_max));
+   graph_line_addcolor(line, column_colors_max);
 }
 
 struct git_graph {
@@ -686,8 +714,7 @@ static int graph_is_mapping_correct(struct git_graph *graph)
return 1;
 }
 
-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
-  int chars_written)
+static void graph_pad_horizontally(struct git_graph *graph, struct graph_line 
*line)
 {
/*
 * Add additional spaces to the end of the strbuf, so that all
@@ -696,12 +723,12 @@ static void graph_pad_horizontally(struct git_graph 
*graph, struct strbuf *sb,
 * This way, fields printed to the right of the graph will remain
 * aligned for the entire commit.
 */
-   if (chars_written < graph->width)
-   strbuf_addchars(sb, ' ', graph->width - chars_written);
+   if (line->width < graph->width)
+   graph_line_addchars(line, ' ', graph->width - line->width);
 }
 
 static void graph_output_padding_line(struct git_graph *graph,
- struct strbuf *sb)
+ struct graph_line *line)
 {
int i;
 
@@ -719,11 +746,11 @@ static void graph_output_padding_line(struct git_graph 
*graph,
 * Output a padding row, that leaves all branch lines unchanged
 */
for (i = 0; i < graph->num_new_columns; i++) {
-   strbuf_write_column(sb, &graph->new_columns[i], '|');
-   strbuf_addch(sb, ' ');
+   graph_line_write_column(line, &graph->new_columns[i], '|');
+   graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
+   graph_pad_horizontally(graph, line);
 }
 
 
@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)
 }
 
 
-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_output_skip_line(struct git_graph *graph, struct graph_line 
*line)
 {
/*
 * O

[PATCH v2 04/13] graph: reduce duplication in `graph_insert_into_new_columns()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to this function and so am trying
to simplify it. It currently contains some duplicated logic; both
branches the function can take assign the commit's column index into
the `mapping` array and increment `mapping_index`.

Here I change the function so that the only conditional behaviour is
that it appends the commit to `new_columns` if it's not present. All
manipulation of `mapping` now happens on a single code path.

Signed-off-by: James Coglan 
---
 graph.c | 20 +++-
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/graph.c b/graph.c
index c9646d9e00..512ae16535 100644
--- a/graph.c
+++ b/graph.c
@@ -478,23 +478,17 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
int i = graph_find_new_column_by_commit(graph, commit);
 
/*
-* If the commit is already in the new_columns list, we don't need to
-* add it.  Just update the mapping correctly.
+* If the commit is not already in the new_columns array, then add it
+* and record it as being in the final column.
 */
-   if (i >= 0) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
+   if (i < 0) {
+   i = graph->num_new_columns++;
+   graph->new_columns[i].commit = commit;
+   graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   /*
-* This commit isn't already in new_columns.  Add it.
-*/
-   graph->new_columns[graph->num_new_columns].commit = commit;
-   graph->new_columns[graph->num_new_columns].color = 
graph_find_commit_color(graph, commit);
-   graph->mapping[*mapping_index] = graph->num_new_columns;
+   graph->mapping[*mapping_index] = i;
*mapping_index += 2;
-   graph->num_new_columns++;
 }
 
 static void graph_update_width(struct git_graph *graph,
-- 
gitgitgadget



[PATCH v2 03/13] graph: reuse `find_new_column_by_commit()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to
`graph_insert_into_new_columns()` and so am trying to simplify it. One
possible simplification is that we can extract the loop for finding the
element in `new_columns` containing the given commit.

`find_new_column_by_commit()` contains a very similar loop but it
returns a `struct column *` rather than an `int` offset into the array.
Here I'm introducing a version that returns `int` and using that in
`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.

Signed-off-by: James Coglan 
---
 graph.c | 48 +++-
 1 file changed, 23 insertions(+), 25 deletions(-)

diff --git a/graph.c b/graph.c
index 4c68557b17..c9646d9e00 100644
--- a/graph.c
+++ b/graph.c
@@ -460,22 +460,31 @@ static unsigned short graph_find_commit_color(const 
struct git_graph *graph,
return graph_get_current_column_color(graph);
 }
 
+static int graph_find_new_column_by_commit(struct git_graph *graph,
+  struct commit *commit)
+{
+   int i;
+   for (i = 0; i < graph->num_new_columns; i++) {
+   if (graph->new_columns[i].commit == commit)
+   return i;
+   }
+   return -1;
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
  struct commit *commit,
  int *mapping_index)
 {
-   int i;
+   int i = graph_find_new_column_by_commit(graph, commit);
 
/*
 * If the commit is already in the new_columns list, we don't need to
 * add it.  Just update the mapping correctly.
 */
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
-   }
+   if (i >= 0) {
+   graph->mapping[*mapping_index] = i;
+   *mapping_index += 2;
+   return;
}
 
/*
@@ -963,17 +972,6 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
-static struct column *find_new_column_by_commit(struct git_graph *graph,
-   struct commit *commit)
-{
-   int i;
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit)
-   return &graph->new_columns[i];
-   }
-   return NULL;
-}
-
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
graph_line *line)
 {
int seen_this = 0;
@@ -1001,20 +999,20 @@ static void graph_output_post_merge_line(struct 
git_graph *graph, struct graph_l
 * edges.
 */
struct commit_list *parents = NULL;
-   struct column *par_column;
+   int par_column;
seen_this = 1;
parents = first_interesting_parent(graph);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
+   par_column = graph_find_new_column_by_commit(graph, 
parents->item);
+   assert(par_column >= 0);
 
-   graph_line_write_column(line, par_column, '|');
+   graph_line_write_column(line, 
&graph->new_columns[par_column], '|');
for (j = 0; j < graph->num_parents - 1; j++) {
parents = next_interesting_parent(graph, 
parents);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
-   graph_line_write_column(line, par_column, '\\');
+   par_column = 
graph_find_new_column_by_commit(graph, parents->item);
+   assert(par_column >= 0);
+   graph_line_write_column(line, 
&graph->new_columns[par_column], '\\');
graph_line_addch(line, ' ');
}
} else if (seen_this) {
-- 
gitgitgadget



[PATCH v2 12/13] graph: flatten edges that fuse with their right neighbor

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

When a merge commit is printed and its final parent is the same commit
that occupies the column to the right of the merge, this results in a
kink in the displayed edges:

* |
|\ \
| |/
| *

Graphs containing these shapes can be hard to read, as the expansion to
the right followed immediately by collapsing back to the left creates a
lot of zig-zagging edges, especially when many columns are present.

We can improve this by eliminating the zig-zag and having the merge's
final parent edge fuse immediately with its neighbor:

* |
|\|
| *

This reduces the horizontal width for the current commit by 2, and
requires one less row, making the graph display more compact. Taken in
combination with other graph-smoothing enhancements, it greatly
compresses the space needed to display certain histories:

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \=>|/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

One of the test cases here cannot be correctly rendered in Git v2.23.0;
it produces this output following commit E:

| | *-. \   5_E
| | |\ \ \
| |/ / / /
| | | / _
| |_|/
|/| |

The new implementation makes sure that the rightmost edge in this
history is not left dangling as above.

Signed-off-by: James Coglan 
---
 graph.c| 34 +
 t/t4215-log-skewed-merges.sh   | 56 ++
 t/t6016-rev-list-graph-simplify-history.sh | 30 +---
 3 files changed, 86 insertions(+), 34 deletions(-)

diff --git a/graph.c b/graph.c
index 63f8d18baa..80db74aee6 100644
--- a/graph.c
+++ b/graph.c
@@ -557,8 +557,24 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
shift = (dist > 1) ? 2 * dist - 3 : 1;
 
graph->merge_layout = (dist > 0) ? 0 : 1;
+   graph->edges_added = graph->num_parents + graph->merge_layout  
- 2;
+
mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
graph->width += 2 * graph->merge_layout;
+
+   } else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 
2]) {
+   /*
+* If some columns have been added by a merge, but this commit
+* was found in the last existing column, then adjust the
+* numbers so that the two edges immediately join, i.e.:
+*
+*  * | * |
+*  |\ \=>  |\|
+*  | |/| *
+*  | *
+*/
+   mapping_idx = graph->width - 2;
+   graph->edges_added = -1;
} else {
mapping_idx = graph->width;
graph->width += 2;
@@ -604,6 +620,8 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping[i] = -1;
 
graph->width = 0;
+   graph->prev_edges_added = graph->edges_added;
+   graph->edges_added = 0;
 
/*
 * Populate graph->new_columns and graph->mapping
@@ -731,9 +749,6 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
graph_update_columns(graph);
 
-   graph->prev_edges_added = graph->edges_added;
-   graph->edges_added = graph->num_parents + graph->merge_layout - 2;
-
graph->expansion_row = 0;
 
/*
@@ -1041,7 +1056,7 @@ const char merge_chars[] = {'/', '|', '\\'};
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
graph_line *line)
 {
int seen_this = 0;
-   int i;
+   int i, j;
 
struct commit_list *first_parent = first_interesting_parent(graph);
int seen_parent = 0;
@@ -1073,16 +1088,19 @@ static void graph_output_post_merge_line(struct 
git_graph *graph, struct graph_l
char c;
seen_this = 1;
 
-   for (; parents; parents = 
next_interesting_parent(graph, parents)) {
+   for (j = 0; j < graph->num_parents; j++) {
par_column = 
graph_find_new_column_by_commit(graph, parents->item);
assert(par_column >= 0);
 
c = merge_chars[idx];
graph_line_write_column(line, 
&graph->new_columns[par_column], c);
-   if (idx == 2)
-  

[PATCH v2 09/13] graph: commit and post-merge lines for left-skewed merges

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Following the introduction of "left-skewed" merges, which are merges
whose first parent fuses with another edge to its left, we have some
more edge cases to deal with in the display of commit and post-merge
lines.

The current graph code handles the following cases for edges appearing
to the right of the commit (*) on commit lines. A 2-way merge is usually
followed by vertical lines:

| | |
| * |
| |\ \

An octopus merge (more than two parents) is always followed by edges
sloping to the right:

| |  \  | |\
| *-. \ | *---. \
| |\ \ \| |\ \ \ \

A 2-way merge is followed by a right-sloping edge if the commit line
immediately follows a post-merge line for a commit that appears in the
same column as the current commit, or any column to the left of that:

| * | * |
| |\| |\ \
| * \   | | * \
| |\ \  | | |\ \

This commit introduces the following new cases for commit lines. If a
2-way merge skews to the left, then the edges to its right are always
vertical lines, even if the commit follows a post-merge line:

| | |   | |\
| * |   | * |
|/| |   |/| |

A commit with 3 parents that skews left is followed by vertical edges:

| | |
| * |
|/|\ \

If a 3-way left-skewed merge commit appears immediately after a
post-merge line, then it may be followed the right-sloping edges, just
like a 2-way merge that is not skewed.

| |\
| * \
|/|\ \

Octopus merges with 4 or more parents that skew to the left will always
be followed by right-sloping edges, because the existing columns need to
expand around the merge.

| |  \
| *-. \
|/|\ \ \

On post-merge lines, usually all edges following the current commit
slope to the right:

| * | |
| |\ \ \

However, if the commit is a left-skewed 2-way merge, the edges to its
right remain vertical. We also need to display a space after the
vertical line descending from the commit marker, whereas this line would
normally be followed by a backslash.

| * | |
|/| | |

If a left-skewed merge has more than 2 parents, then the edges to its
right are still sloped as they bend around the edges introduced by the
merge.

| * | |
|/|\ \ \

To handle these new cases, we need to know not just how many parents
each commit has, but how many new columns it adds to the display; this
quantity is recorded in the `edges_added` field for the current commit,
and `prev_edges_added` field for the previous commit.

Here, "column" refers to visual columns, not the logical columns of the
`columns` array. This is because even if all the commit's parents end up
fusing with existing edges, they initially introduce distinct edges in
the commit and post-merge lines before those edges collapse. For
example, a 3-way merge whose 2nd and 3rd parents fuse with existing
edges still introduces 2 visual columns that affect the display of edges
to their right.

| | |  \
| | *-. \
| | |\ \ \
| |_|/ / /
|/| | / /
| | |/ /
| |/| |
| | | |

This merge does not introduce any *logical* columns; there are 4 edges
before and after this commit once all edges have collapsed. But it does
initially introduce 2 new edges that need to be accommodated by the
edges to their right.

Signed-off-by: James Coglan 
---
 graph.c  |  63 +--
 t/t4215-log-skewed-merges.sh | 147 ++-
 2 files changed, 203 insertions(+), 7 deletions(-)

diff --git a/graph.c b/graph.c
index e37127f5ab..21edad8085 100644
--- a/graph.c
+++ b/graph.c
@@ -216,6 +216,46 @@ struct git_graph {
 *  |/| | | | | | | | | | *
 */
int merge_layout;
+   /*
+* The number of columns added to the graph by the current commit. For
+* 2-way and octopus merges, this is is usually one less than the
+* number of parents:
+*
+*  | | |   | |\
+*  | * |   | *---. \
+*  | |\ \  | |\ \ \ \
+*  | | | | | | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 1  edges_added: 3
+*
+* For left-skewed merges, the first parent fuses with its neighbor and
+* so one less column is added:
+*
+*  | | |   | |  \
+*  | * |   | *-. \
+*  |/| |   |/|\ \ \
+*  | | |   | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 0 

[PATCH v2 00/13] Improve the readability of log --graph output

2019-10-15 Thread James Coglan via GitGitGadget
This series of patches are designed to improve the output of the log --graph
command; their effect can be summed up in the following diagram:

BeforeAfter
---

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \  |/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

These changes aim to make the edges in graph diagrams easier to read, by
straightening lines and making certain kinds of topologies display more
compactly. Three distinct changes are included.

First, if the first parent of a merge fuses with an edge to the left of the
commit, then we display that by making the edges fuse immediately rather
than by drawing a line straight down and then having it track to the left.
That is, where we currently display these graphs:

| * | | | *
| |\| | | |\
|/ /| |_|/ /
| | |/| | |

We will now display these merges as follows:

| * | | | *
|/| | |_|/|
| | |/| | |

This transformation is applied to merges with any number of parents, for
example we currently display 3-parent merges like this:

| *-.   | | | *-.
| |\ \  | | | |\ \
|/ / /  | |_|/ / /
| | |   |/| | | |

And we will now display them like this:

| * | | | *
|/|\| |_|/|\
| | |   |/| | | |

If the edge the first parent fuses with is separated from the commit by
multiple columns, a horizontal edge is drawn just as we currently do in the
'collapsing' state. This change also affects the display of commit and
post-merge lines in subtle ways that are more thoroughly described in the
relevant commits.

The second change is that if the final parent of a merge fuses with the edge
to the right of the commit, then we can remove the zig-zag effect that
currently results. We currently display these merges like this:

* |
|\ \
| |/
| *

After these changes, this merge will now be displayed like so:

* |
|\|
| *

If the final parent fuses with an edge that's further to the right, its
display is unchanged and it will still display like this:

* | | |
|\ \ \ \
| | |_|/
| |/| |
| * | |

The final structural change smooths out lines that are collapsing through
commit lines. For example, consider the following history:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

This is now rendered so that commit lines display an edge using / instead of
|, if that edge is tracking to the left both above and below the commit
line. That results in this improved display:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

Taken together, these changes produce the change shown in the first diagram
above, with the current rendering on the left and the new rendering on the
right.

A final addition to that set of changes fixes the coloring of dashes that
are drawn next to octopus merges, in a manner compatible with all these
changes. The early commits in this set are refactorings that make the
functional changes easier to introduce.

James Coglan (13):
  graph: automatically track display width of graph lines
  graph: handle line padding in `graph_next_line()`
  graph: reuse `find_new_column_by_commit()`
  graph: reduce duplication in `graph_insert_into_new_columns()`
  graph: remove `mapping_idx` and `graph_update_width()`
  graph: extract logic for moving to GRAPH_PRE_COMMIT state
  graph: example of graph output that can be simplified
  graph: tidy up display of left-skewed merges
  graph: commit and post-merge lines for left-skewed merges
  graph: rename `new_mapping` to `old_mapping`
  graph: smooth appearance of collapsing edges on commit lines
  graph: flatten edges that fuse with their right neighbor
  graph: fix coloring of octopus dashes

 graph.c| 646 -
 t/t3430-rebase-merges.sh   |   2 +-
 t/t4202-log.sh |   2 +-
 t/t4214-log-graph-octopus.sh   |  62 +-
 t/t4215-log-skewed-merges.sh   | 257 
 t/t6016-rev-list-graph-simplify-history.sh |  30 +-
 6 files changed, 673 insertions(+), 326 deletions(-)
 create mode 100755 t/t4215-log-skewed-merges.sh


base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: 
https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git 
pr-383/jcoglan/jc/simpl

[PATCH v2 07/13] graph: example of graph output that can be simplified

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

The commits following this one introduce a series of improvements to the
layout of graphs, tidying up a few edge cases, namely:

- merge whose first parent fuses with an existing column to the left
- merge whose last parent fuses with its immediate neighbor on the right
- edges that collapse to the left above and below a commit line

This test case exemplifies these cases and provides a motivating example
of the kind of history I'm aiming to clear up.

The first parent of merge E is the same as the parent of H, so those
edges fuse together.

* H
|
| *-.   E
| |\ \
|/ / /
|
* B

We can "skew" the display of this merge so that it doesn't introduce
additional columns that immediately collapse:

* H
|
| *   E
|/|\
|
* B

The last parent of E is D, the same as the parent of F which is the edge
to the right of the merge.

* F
|
 \
  *-. \   E
  |\ \ \
 / / / /
| /
|/
* D

The two edges leading to D could be fused sooner: rather than expanding
the F edge around the merge and then letting the edges collapse, the F
edge could fuse with the E edge in the post-merge line:

* F
|
 \
  *-. | E
  |\ \|
 / / /
|
* D

If this is combined with the "skew" effect above, we get a much cleaner
graph display for these edges:

* F
|
  * | E
 /|\|
|
* D

Finally, the edge leading from C to A appears jagged as it passes
through the commit line for B:

| * | C
| |/
* | B
|/
* A

This can be smoothed out so that such edges are easier to read:

| * | C
| |/
* / B
|/
* A
---
 t/t4215-log-skewed-merges.sh | 43 
 1 file changed, 43 insertions(+)
 create mode 100755 t/t4215-log-skewed-merges.sh

diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh
new file mode 100755
index 00..4582ba066a
--- /dev/null
+++ b/t/t4215-log-skewed-merges.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git log --graph of skewed merges'
+
+. ./test-lib.sh
+
+test_expect_success 'log --graph with merge fusing with its left and right 
neighbors' '
+   cat >expect <<-\EOF &&
+   *   H
+   |\
+   | *   G
+   | |\
+   | | * F
+   | | |
+   | |  \
+   | *-. \   E
+   | |\ \ \
+   |/ / / /
+   | | | /
+   | | |/
+   | | * D
+   | * | C
+   | |/
+   * | B
+   |/
+   * A
+   EOF
+
+   git checkout --orphan _p &&
+   test_commit A &&
+   test_commit B &&
+   git checkout -b _q @^ && test_commit C &&
+   git checkout -b _r @^ && test_commit D &&
+   git checkout _p && git merge --no-ff _q _r -m E &&
+   git checkout _r && test_commit F &&
+   git checkout _p && git merge --no-ff _r -m G &&
+   git checkout @^^ && git merge --no-ff _p -m H &&
+
+   git log --graph --pretty=tformat:%s | sed "s/ *$//" >actual &&
+   test_cmp expect actual
+'
+
+test_done
-- 
gitgitgadget



[PATCH v2 11/13] graph: smooth appearance of collapsing edges on commit lines

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

When a graph contains edges that are in the process of collapsing to the
left, but those edges cross a commit line, the effect is that the edges
have a jagged appearance:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

We already takes steps to smooth edges like this when they're expanding;
when an edge appears to the right of a merge commit marker on a
GRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we
render it as a `\`:

* \
|\ \
| * \
| |\ \

We can make a similar improvement to collapsing edges, making them
easier to follow and giving the overall graph a feeling of increased
symmetry:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

To do this, we introduce a new special case for edges on GRAPH_COMMIT
lines that immediately follow a GRAPH_COLLAPSING line. By retaining a
copy of the `mapping` array used to render the GRAPH_COLLAPSING line in
the `old_mapping` array, we can determine that an edge is collapsing
through the GRAPH_COMMIT line and should be smoothed.

Signed-off-by: James Coglan 
---
 graph.c| 17 +---
 t/t3430-rebase-merges.sh   |  2 +-
 t/t4202-log.sh |  2 +-
 t/t4214-log-graph-octopus.sh   | 32 +++---
 t/t4215-log-skewed-merges.sh   |  4 +--
 t/t6016-rev-list-graph-simplify-history.sh |  4 +--
 6 files changed, 35 insertions(+), 26 deletions(-)

diff --git a/graph.c b/graph.c
index 2315f3604d..63f8d18baa 100644
--- a/graph.c
+++ b/graph.c
@@ -297,10 +297,10 @@ struct git_graph {
 */
int *mapping;
/*
-* A temporary array for computing the next mapping state
-* while we are outputting a mapping line.  This is stored as part
-* of the git_graph simply so we don't have to allocate a new
-* temporary array each time we have to output a collapsing line.
+* A copy of the contents of the mapping array from the last commit,
+* which we use to improve the display of columns that are tracking
+* from right to left through a commit line.  We also use this to
+* avoid allocating a fresh array when we compute the next mapping.
 */
int *old_mapping;
/*
@@ -1015,6 +1015,10 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_line_write_column(line, col, '\\');
else
graph_line_write_column(line, col, '|');
+   } else if (graph->prev_state == GRAPH_COLLAPSING &&
+  graph->old_mapping[2 * i + 1] == i &&
+  graph->mapping[2 * i] < i) {
+   graph_line_write_column(line, col, '/');
} else {
graph_line_write_column(line, col, '|');
}
@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
}
}
 
+   /*
+* Copy the current mapping array into old_mapping
+*/
+   COPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);
+
/*
 * The new mapping may be 1 smaller than the old mapping
 */
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 9efcf4808a..a30d27e9f3 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -408,7 +408,7 @@ test_expect_success 'octopus merges' '
| | * three
| * | two
| |/
-   * | one
+   * / one
|/
o before-octopus
EOF
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..ab0d021365 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -667,7 +667,7 @@ cat > expect <<\EOF
 * | | fifth
 * | | fourth
 |/ /
-* | third
+* / third
 |/
 * second
 * initial
diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh
index 1b96276894..21bc600a82 100755
--- a/t/t4214-log-graph-octopus.sh
+++ b/t/t4214-log-graph-octopus.sh
@@ -31,9 +31,9 @@ test_expect_success 'log --graph with tricky octopus merge, 
no color' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -51,9 +51,9 @@ test_expect_success 'log --graph with tricky octopus merge 
with colors' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -72,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, 
no color' '
| | | * 4

[PATCH v2 08/13] graph: tidy up display of left-skewed merges

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Currently, when we display a merge whose first parent is already present
in a column to the left of the merge commit, we display the first parent
as a vertical pipe `|` in the GRAPH_POST_MERGE line and then immediately
enter the GRAPH_COLLAPSING state. The first-parent line tracks to the
left and all the other parent lines follow it; this creates a "kink" in
those lines:

| *---.
| |\ \ \
|/ / / /
| | | *

This change tidies the display of such commits such that if the first
parent appears to the left of the merge, we render it as a `/` and the
second parent as a `|`. This reduces the horizontal and vertical space
needed to render the merge, and makes the resulting lines easier to
read.

| *-.
|/|\ \
| | | *

If the first parent is separated from the merge by several columns, a
horizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING
state displays the line.

| | | *-.
| |_|/|\ \
|/| | | | *

This effect is applied to both "normal" two-parent merges, and to
octopus merges. It also reduces the vertical space needed for pre-commit
lines, as the merge occupies one less column than usual.

Before: After:

| * | *
| |\| |\
| | \   | * \
| |  \  |/|\ \
| *-. \
| |\ \ \

Signed-off-by: James Coglan 
---
 graph.c  | 125 +++
 t/t4214-log-graph-octopus.sh |  20 +++---
 t/t4215-log-skewed-merges.sh |  45 +++--
 3 files changed, 144 insertions(+), 46 deletions(-)

diff --git a/graph.c b/graph.c
index bd7403065e..e37127f5ab 100644
--- a/graph.c
+++ b/graph.c
@@ -202,6 +202,20 @@ struct git_graph {
 * previous commit.
 */
int prev_commit_index;
+   /*
+* Which layout variant to use to display merge commits. If the
+* commit's first parent is known to be in a column to the left of the
+* merge, then this value is 0 and we use the layout on the left.
+* Otherwise, the value is 1 and the layout on the right is used. This
+* field tells us how many columns the first parent occupies.
+*
+*  0)  1)
+*
+*  | | | *-.   | | *---.
+*  | |_|/|\ \  | | |\ \ \
+*  |/| | | | | | | | | | *
+*/
+   int merge_layout;
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
@@ -313,6 +327,7 @@ struct git_graph *graph_init(struct rev_info *opt)
graph->prev_state = GRAPH_PADDING;
graph->commit_index = 0;
graph->prev_commit_index = 0;
+   graph->merge_layout = 0;
graph->num_columns = 0;
graph->num_new_columns = 0;
graph->mapping_size = 0;
@@ -472,9 +487,11 @@ static int graph_find_new_column_by_commit(struct 
git_graph *graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit)
+ struct commit *commit,
+ int idx)
 {
int i = graph_find_new_column_by_commit(graph, commit);
+   int mapping_idx;
 
/*
 * If the commit is not already in the new_columns array, then add it
@@ -486,8 +503,26 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[graph->width] = i;
-   graph->width += 2;
+   if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {
+   /*
+* If this is the first parent of a merge, choose a layout for
+* the merge line based on whether the parent appears in a
+* column to the left of the merge
+*/
+   int dist, shift;
+
+   dist = idx - i;
+   shift = (dist > 1) ? 2 * dist - 3 : 1;
+
+   graph->merge_layout = (dist > 0) ? 0 : 1;
+   mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
+   graph->width += 2 * graph->merge_layout;
+   } else {
+   mapping_idx = graph->width;
+   graph->width += 2;
+   }
+
+   graph->mapping[mapping_idx] = i;
 }
 
 static void graph_update_columns(struct git_graph *graph)
@@ -553,6 +588,7 @@ static void graph_update_columns(struct git_graph *graph)
if (col_commit == graph->commit) {
seen_this = 1;
graph->commit_index = i;
+   graph->merge_layout = -1;
for (parent = first_interesting_parent(graph);
  

[PATCH v2 13/13] graph: fix coloring of octopus dashes

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

In 04005834ed ("log: fix coloring of certain octopus merge shapes",
2018-09-01) there is a fix for the coloring of dashes following an
octopus merge. It makes a distinction between the case where all parents
introduce a new column, versus the case where the first parent collapses
into an existing column:

| *-.   | *-.
| |\ \  | |\ \
| | | | |/ / /

The latter case means that the columns for the merge parents begin one
place to the left in the `new_columns` array compared to the former
case.

However, the implementation only works if the commit's parents are kept
in order as they map onto the visual columns, as we get the colors by
iterating over `new_columns` as we print the dashes. In general, the
commit's parents can arbitrarily merge with existing columns, and change
their ordering in the process.

For example, in the following diagram, the number of each column
indicates which commit parent appears in each column.

| | *---.
| | |\ \ \
| | |/ / /
| |/| | /
| |_|_|/
|/| | |
3 1 0 2

If the columns are colored (red, green, yellow, blue), then the dashes
will currently be colored yellow and blue, whereas they should be blue
and red.

To fix this, we need to look up each column in the `mapping` array,
which before the `GRAPH_COLLAPSING` state indicates which logical column
is displayed in each visual column. This implementation is simpler as it
doesn't have any edge cases, and it also handles how left-skewed first
parents are now displayed:

| *-.
|/|\ \
| | | |
0 1 2 3

The color of the first dashes is always the color found in `mapping` two
columns to the right of the commit symbol. Because commits are displayed
after all edges have been collapsed together and the visual columns
match the logical ones, we can find the visual offset of the commit
symbol using `commit_index`.

Signed-off-by: James Coglan 
---
 graph.c  | 71 +++-
 t/t4214-log-graph-octopus.sh | 10 ++---
 2 files changed, 42 insertions(+), 39 deletions(-)

diff --git a/graph.c b/graph.c
index 80db74aee6..e3fd0ea5f8 100644
--- a/graph.c
+++ b/graph.c
@@ -684,6 +684,11 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_num_dashed_parents(struct git_graph *graph)
+{
+   return graph->num_parents + graph->merge_layout - 3;
+}
+
 static int graph_num_expansion_rows(struct git_graph *graph)
 {
/*
@@ -706,7 +711,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)
 *  | * \
 *  |/|\ \
 */
-   return (graph->num_parents + graph->merge_layout - 3) * 2;
+   return graph_num_dashed_parents(graph) * 2;
 }
 
 static int graph_needs_pre_commit_line(struct git_graph *graph)
@@ -934,47 +939,45 @@ static void graph_output_commit_char(struct git_graph 
*graph, struct graph_line
 static void graph_draw_octopus_merge(struct git_graph *graph, struct 
graph_line *line)
 {
/*
-* Here dashless_parents represents the number of parents which don't
-* need to have dashes (the edges labeled "0" and "1").  And
-* dashful_parents are the remaining ones.
+* The parents of a merge commit can be arbitrarily reordered as they
+* are mapped onto display columns, for example this is a valid merge:
 *
-* | *---.
-* | |\ \ \
-* | | | | |
-* x 0 1 2 3
+*  | | *---.
+*  | | |\ \ \
+*  | | |/ / /
+*  | |/| | /
+*  | |_|_|/
+*  |/| | |
+*  3 1 0 2
 *
-*/
-   const int dashless_parents = 3 - graph->merge_layout;
-   int dashful_parents = graph->num_parents - dashless_parents;
-
-   /*
-* Usually, we add one new column for each parent (like the diagram
-* above) but sometimes the first parent goes into an existing column,
-* like this:
+* The numbers denote which parent of the merge each visual column
+* corresponds to; we can't assume that the parents will initially
+* display in the order given by new_columns.
 *
-* | *-.
-* |/|\ \
-* | | | |
-* x 0 1 2
+* To find the right color for each dash, we need to consult the
+* mapping array, starting from the column 2 places to the right of the
+* merge commit, and use that to find out which logical column each
+* edge will collapse to.
 *
-* In which case the number of parents will be one greater than the
-* number of added columns.
+* Commits are rendered once all edges have collapsed to their correct
+* logcial column, so commit_index gives us the right visual offset for
+* the merge commit.
 */
-   int adde

[PATCH v2 10/13] graph: rename `new_mapping` to `old_mapping`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

The change I'm about to make requires being able to inspect the mapping
array that was used to render the last GRAPH_COLLAPSING line while
rendering a GRAPH_COMMIT line. The `new_mapping` array currently exists
as a pre-allocated space for computing the next `mapping` array during
`graph_output_collapsing_line()`, but we can repurpose it to let us see
the previous `mapping` state.

To support this use it will make more sense if this array is named
`old_mapping`, as it will contain the mapping data for the previous line
we rendered, at the point we're rendering a commit line.

Signed-off-by: James Coglan 
---
 graph.c | 54 +++---
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/graph.c b/graph.c
index 21edad8085..2315f3604d 100644
--- a/graph.c
+++ b/graph.c
@@ -259,7 +259,7 @@ struct git_graph {
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
-* that can be stored in the mapping and new_mapping arrays.
+* that can be stored in the mapping and old_mapping arrays.
 */
int column_capacity;
/*
@@ -302,7 +302,7 @@ struct git_graph {
 * of the git_graph simply so we don't have to allocate a new
 * temporary array each time we have to output a collapsing line.
 */
-   int *new_mapping;
+   int *old_mapping;
/*
 * The current default column color being used.  This is
 * stored as an index into the array column_colors.
@@ -388,7 +388,7 @@ struct git_graph *graph_init(struct rev_info *opt)
ALLOC_ARRAY(graph->columns, graph->column_capacity);
ALLOC_ARRAY(graph->new_columns, graph->column_capacity);
ALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);
-   ALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);
+   ALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);
 
/*
 * The diff output prefix callback, with this we can make
@@ -418,7 +418,7 @@ static void graph_ensure_capacity(struct git_graph *graph, 
int num_columns)
REALLOC_ARRAY(graph->columns, graph->column_capacity);
REALLOC_ARRAY(graph->new_columns, graph->column_capacity);
REALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);
-   REALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);
+   REALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);
 }
 
 /*
@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
int horizontal_edge_target = -1;
 
/*
-* Clear out the new_mapping array
+* Swap the mapping and old_mapping arrays
+*/
+   SWAP(graph->mapping, graph->old_mapping);
+
+   /*
+* Clear out the mapping array
 */
for (i = 0; i < graph->mapping_size; i++)
-   graph->new_mapping[i] = -1;
+   graph->mapping[i] = -1;
 
for (i = 0; i < graph->mapping_size; i++) {
-   int target = graph->mapping[i];
+   int target = graph->old_mapping[i];
if (target < 0)
continue;
 
@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
 * This column is already in the
 * correct place
 */
-   assert(graph->new_mapping[i] == -1);
-   graph->new_mapping[i] = target;
-   } else if (graph->new_mapping[i - 1] < 0) {
+   assert(graph->mapping[i] == -1);
+   graph->mapping[i] = target;
+   } else if (graph->mapping[i - 1] < 0) {
/*
 * Nothing is to the left.
 * Move to the left by one
 */
-   graph->new_mapping[i - 1] = target;
+   graph->mapping[i - 1] = target;
/*
 * If there isn't already an edge moving horizontally
 * select this one.
@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph 
*graph, struct graph_l
 * line.
 */
for (j = (target * 2)+3; j < (i - 2); j += 2)
-   graph->new_mapping[j] = target;
+   graph->mapping[j] = target;
}
-   } else if (graph->new_mapping[i - 1] == target) {
+   } else if (graph->mapping[i - 1] == target) {
/*
 * There is a branch line to our left
 * already, and it is our target.  We
@@ -1176,7 +

[PATCH v3 06/13] graph: extract logic for moving to GRAPH_PRE_COMMIT state

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

This computation is repeated in a couple of places and I need to add
another condition to it to implement a further improvement to the graph
rendering, so I'm extracting this into a function.

Signed-off-by: James Coglan 
---
 graph.c | 12 
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/graph.c b/graph.c
index d724ef25c3..bd7403065e 100644
--- a/graph.c
+++ b/graph.c
@@ -588,6 +588,12 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_needs_pre_commit_line(struct git_graph *graph)
+{
+   return graph->num_parents >= 3 &&
+  graph->commit_index < (graph->num_columns - 1);
+}
+
 void graph_update(struct git_graph *graph, struct commit *commit)
 {
struct commit_list *parent;
@@ -643,8 +649,7 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
if (graph->state != GRAPH_PADDING)
graph->state = GRAPH_SKIP;
-   else if (graph->num_parents >= 3 &&
-graph->commit_index < (graph->num_columns - 1))
+   else if (graph_needs_pre_commit_line(graph))
graph->state = GRAPH_PRE_COMMIT;
else
graph->state = GRAPH_COMMIT;
@@ -714,8 +719,7 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct graph_line *l
 */
graph_line_addstr(line, "...");
 
-   if (graph->num_parents >= 3 &&
-   graph->commit_index < (graph->num_columns - 1))
+   if (graph_needs_pre_commit_line(graph))
graph_update_state(graph, GRAPH_PRE_COMMIT);
else
graph_update_state(graph, GRAPH_COMMIT);
-- 
gitgitgadget



[PATCH v3 00/13] Improve the readability of log --graph output

2019-10-15 Thread James Coglan via GitGitGadget
This series of patches are designed to improve the output of the log --graph
command; their effect can be summed up in the following diagram:

BeforeAfter
---

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \  |/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

These changes aim to make the edges in graph diagrams easier to read, by
straightening lines and making certain kinds of topologies display more
compactly. Three distinct changes are included.

First, if the first parent of a merge fuses with an edge to the left of the
commit, then we display that by making the edges fuse immediately rather
than by drawing a line straight down and then having it track to the left.
That is, where we currently display these graphs:

| * | | | *
| |\| | | |\
|/ /| |_|/ /
| | |/| | |

We will now display these merges as follows:

| * | | | *
|/| | |_|/|
| | |/| | |

This transformation is applied to merges with any number of parents, for
example we currently display 3-parent merges like this:

| *-.   | | | *-.
| |\ \  | | | |\ \
|/ / /  | |_|/ / /
| | |   |/| | | |

And we will now display them like this:

| * | | | *
|/|\| |_|/|\
| | |   |/| | | |

If the edge the first parent fuses with is separated from the commit by
multiple columns, a horizontal edge is drawn just as we currently do in the
'collapsing' state. This change also affects the display of commit and
post-merge lines in subtle ways that are more thoroughly described in the
relevant commits.

The second change is that if the final parent of a merge fuses with the edge
to the right of the commit, then we can remove the zig-zag effect that
currently results. We currently display these merges like this:

* |
|\ \
| |/
| *

After these changes, this merge will now be displayed like so:

* |
|\|
| *

If the final parent fuses with an edge that's further to the right, its
display is unchanged and it will still display like this:

* | | |
|\ \ \ \
| | |_|/
| |/| |
| * | |

The final structural change smooths out lines that are collapsing through
commit lines. For example, consider the following history:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

This is now rendered so that commit lines display an edge using / instead of
|, if that edge is tracking to the left both above and below the commit
line. That results in this improved display:

*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

Taken together, these changes produce the change shown in the first diagram
above, with the current rendering on the left and the new rendering on the
right.

A final addition to that set of changes fixes the coloring of dashes that
are drawn next to octopus merges, in a manner compatible with all these
changes. The early commits in this set are refactorings that make the
functional changes easier to introduce.

James Coglan (13):
  graph: automatically track display width of graph lines
  graph: handle line padding in `graph_next_line()`
  graph: reuse `find_new_column_by_commit()`
  graph: reduce duplication in `graph_insert_into_new_columns()`
  graph: remove `mapping_idx` and `graph_update_width()`
  graph: extract logic for moving to GRAPH_PRE_COMMIT state
  graph: example of graph output that can be simplified
  graph: tidy up display of left-skewed merges
  graph: commit and post-merge lines for left-skewed merges
  graph: rename `new_mapping` to `old_mapping`
  graph: smooth appearance of collapsing edges on commit lines
  graph: flatten edges that fuse with their right neighbor
  graph: fix coloring of octopus dashes

 graph.c| 646 -
 t/t3430-rebase-merges.sh   |   2 +-
 t/t4202-log.sh |   2 +-
 t/t4214-log-graph-octopus.sh   |  62 +-
 t/t4215-log-skewed-merges.sh   | 257 
 t/t6016-rev-list-graph-simplify-history.sh |  30 +-
 6 files changed, 673 insertions(+), 326 deletions(-)
 create mode 100755 t/t4215-log-skewed-merges.sh


base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: 
https://github.com/gitgitgadget/git/releases/tag/pr-383%2Fjcoglan%2Fjc%2Fsimplify-graph-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git 
pr-383/jcoglan/jc/simpl

[PATCH v3 02/13] graph: handle line padding in `graph_next_line()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Now that the display width of graph lines is implicitly tracked via the
`graph_line` interface, the calls to `graph_pad_horizontally()` no
longer need to be located inside the individual output functions, where
the character counting was previously being done.

All the functions called by `graph_next_line()` generate a line of
output, then call `graph_pad_horizontally()`, and finally change the
graph state if necessary. As padding is the final change to the output
done by all these functions, it can be removed from all of them and done
in `graph_next_line()` instead.

I've also moved the guard in `graph_output_padding_line()` that checks
the graph has a commit; this function is only called by
`graph_next_line()` and we must not pad the `graph_line` if no commit is
set.

Signed-off-by: James Coglan 
---
 graph.c | 49 -
 1 file changed, 20 insertions(+), 29 deletions(-)

diff --git a/graph.c b/graph.c
index 2f81a5d23d..4c68557b17 100644
--- a/graph.c
+++ b/graph.c
@@ -732,16 +732,6 @@ static void graph_output_padding_line(struct git_graph 
*graph,
 {
int i;
 
-   /*
-* We could conceivable be called with a NULL commit
-* if our caller has a bug, and invokes graph_next_line()
-* immediately after graph_init(), without first calling
-* graph_update().  Return without outputting anything in this
-* case.
-*/
-   if (!graph->commit)
-   return;
-
/*
 * Output a padding row, that leaves all branch lines unchanged
 */
@@ -749,8 +739,6 @@ static void graph_output_padding_line(struct git_graph 
*graph,
graph_line_write_column(line, &graph->new_columns[i], '|');
graph_line_addch(line, ' ');
}
-
-   graph_pad_horizontally(graph, line);
 }
 
 
@@ -767,7 +755,6 @@ static void graph_output_skip_line(struct git_graph *graph, 
struct graph_line *l
 * of the graph is missing.
 */
graph_line_addstr(line, "...");
-   graph_pad_horizontally(graph, line);
 
if (graph->num_parents >= 3 &&
graph->commit_index < (graph->num_columns - 1))
@@ -832,8 +819,6 @@ static void graph_output_pre_commit_line(struct git_graph 
*graph,
graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Increment graph->expansion_row,
 * and move to state GRAPH_COMMIT if necessary
@@ -967,8 +952,6 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Update graph->state
 */
@@ -1043,8 +1026,6 @@ static void graph_output_post_merge_line(struct git_graph 
*graph, struct graph_l
}
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Update graph->state
 */
@@ -1186,8 +1167,6 @@ static void graph_output_collapsing_line(struct git_graph 
*graph, struct graph_l
}
}
 
-   graph_pad_horizontally(graph, line);
-
/*
 * Swap mapping and new_mapping
 */
@@ -1204,31 +1183,43 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
 
 int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 {
+   int shown_commit_line = 0;
struct graph_line line = { .buf = sb, .width = 0 };
 
+   /*
+* We could conceivable be called with a NULL commit
+* if our caller has a bug, and invokes graph_next_line()
+* immediately after graph_init(), without first calling
+* graph_update().  Return without outputting anything in this
+* case.
+*/
+   if (!graph->commit)
+   return -1;
+
switch (graph->state) {
case GRAPH_PADDING:
graph_output_padding_line(graph, &line);
-   return 0;
+   break;
case GRAPH_SKIP:
graph_output_skip_line(graph, &line);
-   return 0;
+   break;
case GRAPH_PRE_COMMIT:
graph_output_pre_commit_line(graph, &line);
-   return 0;
+   break;
case GRAPH_COMMIT:
graph_output_commit_line(graph, &line);
-   return 1;
+   shown_commit_line = 1;
+   break;
case GRAPH_POST_MERGE:
graph_output_post_merge_line(graph, &line);
-   return 0;
+   break;
case GRAPH_COLLAPSING:
graph_output_collapsing_line(graph, &line);
-   return 0;
+   break;
}
 
-   assert(0);
-   return 0;
+   graph_pad_horizontally(graph, &line);
+   return shown_commit_line;
 }
 
 static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
-- 
gitgitgadget



[PATCH v3 11/13] graph: smooth appearance of collapsing edges on commit lines

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

When a graph contains edges that are in the process of collapsing to the
left, but those edges cross a commit line, the effect is that the edges
have a jagged appearance:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* | |
|/ /
* |
|/
*

We already takes steps to smooth edges like this when they're expanding;
when an edge appears to the right of a merge commit marker on a
GRAPH_COMMIT line immediately following a GRAPH_POST_MERGE line, we
render it as a `\`:

* \
|\ \
| * \
| |\ \

We can make a similar improvement to collapsing edges, making them
easier to follow and giving the overall graph a feeling of increased
symmetry:

*
|\
| *
|  \
*-. \
|\ \ \
| | * |
| * | |
| |/ /
* / /
|/ /
* /
|/
*

To do this, we introduce a new special case for edges on GRAPH_COMMIT
lines that immediately follow a GRAPH_COLLAPSING line. By retaining a
copy of the `mapping` array used to render the GRAPH_COLLAPSING line in
the `old_mapping` array, we can determine that an edge is collapsing
through the GRAPH_COMMIT line and should be smoothed.

Signed-off-by: James Coglan 
---
 graph.c| 17 +---
 t/t3430-rebase-merges.sh   |  2 +-
 t/t4202-log.sh |  2 +-
 t/t4214-log-graph-octopus.sh   | 32 +++---
 t/t4215-log-skewed-merges.sh   |  4 +--
 t/t6016-rev-list-graph-simplify-history.sh |  4 +--
 6 files changed, 35 insertions(+), 26 deletions(-)

diff --git a/graph.c b/graph.c
index 2315f3604d..63f8d18baa 100644
--- a/graph.c
+++ b/graph.c
@@ -297,10 +297,10 @@ struct git_graph {
 */
int *mapping;
/*
-* A temporary array for computing the next mapping state
-* while we are outputting a mapping line.  This is stored as part
-* of the git_graph simply so we don't have to allocate a new
-* temporary array each time we have to output a collapsing line.
+* A copy of the contents of the mapping array from the last commit,
+* which we use to improve the display of columns that are tracking
+* from right to left through a commit line.  We also use this to
+* avoid allocating a fresh array when we compute the next mapping.
 */
int *old_mapping;
/*
@@ -1015,6 +1015,10 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_line_write_column(line, col, '\\');
else
graph_line_write_column(line, col, '|');
+   } else if (graph->prev_state == GRAPH_COLLAPSING &&
+  graph->old_mapping[2 * i + 1] == i &&
+  graph->mapping[2 * i] < i) {
+   graph_line_write_column(line, col, '/');
} else {
graph_line_write_column(line, col, '|');
}
@@ -1211,6 +1215,11 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
}
}
 
+   /*
+* Copy the current mapping array into old_mapping
+*/
+   COPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);
+
/*
 * The new mapping may be 1 smaller than the old mapping
 */
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 9efcf4808a..a30d27e9f3 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -408,7 +408,7 @@ test_expect_success 'octopus merges' '
| | * three
| * | two
| |/
-   * | one
+   * / one
|/
o before-octopus
EOF
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..ab0d021365 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -667,7 +667,7 @@ cat > expect <<\EOF
 * | | fifth
 * | | fourth
 |/ /
-* | third
+* / third
 |/
 * second
 * initial
diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh
index 1b96276894..21bc600a82 100755
--- a/t/t4214-log-graph-octopus.sh
+++ b/t/t4214-log-graph-octopus.sh
@@ -31,9 +31,9 @@ test_expect_success 'log --graph with tricky octopus merge, 
no color' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -51,9 +51,9 @@ test_expect_success 'log --graph with tricky octopus merge 
with colors' '
| | | * 4
| | * | 3
| | |/
-   | * | 2
+   | * / 2
| |/
-   * | 1
+   * / 1
|/
* initial
EOF
@@ -72,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, 
no color' '
| | | * 4

[PATCH v3 05/13] graph: remove `mapping_idx` and `graph_update_width()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

There's a duplication of logic between `graph_insert_into_new_columns()`
and `graph_update_width()`. `graph_insert_into_new_columns()` is called
repeatedly by `graph_update_columns()` with an `int *` that tracks the
offset into the `mapping` array where we should write the next value.
Each call to `graph_insert_into_new_columns()` effectively pushes one
column index and one "null" value (-1) onto the `mapping` array and
therefore increments `mapping_idx` by 2.

`graph_update_width()` duplicates this process: the `width` of the graph
is essentially the initial width of the `mapping` array before edges
begin collapsing. The `graph_update_width()` function's logic
effectively works out how many times `graph_insert_into_new_columns()`
was called based on the relationship of the current commit to the rest
of the graph.

I'm about to make some changes that make the assignment of values into
the `mapping` array more complicated. Rather than make
`graph_update_width()` more complicated at the same time, we can simply
remove this function and use `graph->width` to track the offset into the
`mapping` array as we're building it. This removes the duplication and
makes sure that `graph->width` is the same as the visual width of the
`mapping` array once `graph_update_columns()` is complete.

Signed-off-by: James Coglan 
---
 graph.c | 65 +
 1 file changed, 10 insertions(+), 55 deletions(-)

diff --git a/graph.c b/graph.c
index 512ae16535..d724ef25c3 100644
--- a/graph.c
+++ b/graph.c
@@ -472,8 +472,7 @@ static int graph_find_new_column_by_commit(struct git_graph 
*graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit,
- int *mapping_index)
+ struct commit *commit)
 {
int i = graph_find_new_column_by_commit(graph, commit);
 
@@ -487,50 +486,14 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-}
-
-static void graph_update_width(struct git_graph *graph,
-  int is_commit_in_existing_columns)
-{
-   /*
-* Compute the width needed to display the graph for this commit.
-* This is the maximum width needed for any row.  All other rows
-* will be padded to this width.
-*
-* Compute the number of columns in the widest row:
-* Count each existing column (graph->num_columns), and each new
-* column added by this commit.
-*/
-   int max_cols = graph->num_columns + graph->num_parents;
-
-   /*
-* Even if the current commit has no parents to be printed, it
-* still takes up a column for itself.
-*/
-   if (graph->num_parents < 1)
-   max_cols++;
-
-   /*
-* We added a column for the current commit as part of
-* graph->num_parents.  If the current commit was already in
-* graph->columns, then we have double counted it.
-*/
-   if (is_commit_in_existing_columns)
-   max_cols--;
-
-   /*
-* Each column takes up 2 spaces
-*/
-   graph->width = max_cols * 2;
+   graph->mapping[graph->width] = i;
+   graph->width += 2;
 }
 
 static void graph_update_columns(struct git_graph *graph)
 {
struct commit_list *parent;
int max_new_columns;
-   int mapping_idx;
int i, seen_this, is_commit_in_columns;
 
/*
@@ -563,6 +526,8 @@ static void graph_update_columns(struct git_graph *graph)
for (i = 0; i < graph->mapping_size; i++)
graph->mapping[i] = -1;
 
+   graph->width = 0;
+
/*
 * Populate graph->new_columns and graph->mapping
 *
@@ -573,7 +538,6 @@ static void graph_update_columns(struct git_graph *graph)
 * supposed to end up after the collapsing is performed.
 */
seen_this = 0;
-   mapping_idx = 0;
is_commit_in_columns = 1;
for (i = 0; i <= graph->num_columns; i++) {
struct commit *col_commit;
@@ -587,7 +551,6 @@ static void graph_update_columns(struct git_graph *graph)
}
 
if (col_commit == graph->commit) {
-   int old_mapping_idx = mapping_idx;
seen_this = 1;
graph->commit_index = i;
for (parent = first_interesting_parent(graph);
@@ -602,21 +565,18 @@ static void graph_update_columns(struct git_graph *graph)
!is_commit_in_columns) {
graph_increment_column_color(graph);
}
-   graph_i

[PATCH v3 07/13] graph: example of graph output that can be simplified

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

The commits following this one introduce a series of improvements to the
layout of graphs, tidying up a few edge cases, namely:

- merge whose first parent fuses with an existing column to the left
- merge whose last parent fuses with its immediate neighbor on the right
- edges that collapse to the left above and below a commit line

This test case exemplifies these cases and provides a motivating example
of the kind of history I'm aiming to clear up.

The first parent of merge E is the same as the parent of H, so those
edges fuse together.

* H
|
| *-.   E
| |\ \
|/ / /
|
* B

We can "skew" the display of this merge so that it doesn't introduce
additional columns that immediately collapse:

* H
|
| *   E
|/|\
|
* B

The last parent of E is D, the same as the parent of F which is the edge
to the right of the merge.

* F
|
 \
  *-. \   E
  |\ \ \
 / / / /
| /
|/
* D

The two edges leading to D could be fused sooner: rather than expanding
the F edge around the merge and then letting the edges collapse, the F
edge could fuse with the E edge in the post-merge line:

* F
|
 \
  *-. | E
  |\ \|
 / / /
|
* D

If this is combined with the "skew" effect above, we get a much cleaner
graph display for these edges:

* F
|
  * | E
 /|\|
|
* D

Finally, the edge leading from C to A appears jagged as it passes
through the commit line for B:

| * | C
| |/
* | B
|/
* A

This can be smoothed out so that such edges are easier to read:

| * | C
| |/
* / B
|/
* A

Signed-off-by: James Coglan 
---
 t/t4215-log-skewed-merges.sh | 43 
 1 file changed, 43 insertions(+)
 create mode 100755 t/t4215-log-skewed-merges.sh

diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh
new file mode 100755
index 00..4582ba066a
--- /dev/null
+++ b/t/t4215-log-skewed-merges.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git log --graph of skewed merges'
+
+. ./test-lib.sh
+
+test_expect_success 'log --graph with merge fusing with its left and right 
neighbors' '
+   cat >expect <<-\EOF &&
+   *   H
+   |\
+   | *   G
+   | |\
+   | | * F
+   | | |
+   | |  \
+   | *-. \   E
+   | |\ \ \
+   |/ / / /
+   | | | /
+   | | |/
+   | | * D
+   | * | C
+   | |/
+   * | B
+   |/
+   * A
+   EOF
+
+   git checkout --orphan _p &&
+   test_commit A &&
+   test_commit B &&
+   git checkout -b _q @^ && test_commit C &&
+   git checkout -b _r @^ && test_commit D &&
+   git checkout _p && git merge --no-ff _q _r -m E &&
+   git checkout _r && test_commit F &&
+   git checkout _p && git merge --no-ff _r -m G &&
+   git checkout @^^ && git merge --no-ff _p -m H &&
+
+   git log --graph --pretty=tformat:%s | sed "s/ *$//" >actual &&
+   test_cmp expect actual
+'
+
+test_done
-- 
gitgitgadget



[PATCH v3 03/13] graph: reuse `find_new_column_by_commit()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to
`graph_insert_into_new_columns()` and so am trying to simplify it. One
possible simplification is that we can extract the loop for finding the
element in `new_columns` containing the given commit.

`find_new_column_by_commit()` contains a very similar loop but it
returns a `struct column *` rather than an `int` offset into the array.
Here I'm introducing a version that returns `int` and using that in
`graph_insert_into_new_columns()` and `graph_output_post_merge_line()`.

Signed-off-by: James Coglan 
---
 graph.c | 48 +++-
 1 file changed, 23 insertions(+), 25 deletions(-)

diff --git a/graph.c b/graph.c
index 4c68557b17..c9646d9e00 100644
--- a/graph.c
+++ b/graph.c
@@ -460,22 +460,31 @@ static unsigned short graph_find_commit_color(const 
struct git_graph *graph,
return graph_get_current_column_color(graph);
 }
 
+static int graph_find_new_column_by_commit(struct git_graph *graph,
+  struct commit *commit)
+{
+   int i;
+   for (i = 0; i < graph->num_new_columns; i++) {
+   if (graph->new_columns[i].commit == commit)
+   return i;
+   }
+   return -1;
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
  struct commit *commit,
  int *mapping_index)
 {
-   int i;
+   int i = graph_find_new_column_by_commit(graph, commit);
 
/*
 * If the commit is already in the new_columns list, we don't need to
 * add it.  Just update the mapping correctly.
 */
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
-   }
+   if (i >= 0) {
+   graph->mapping[*mapping_index] = i;
+   *mapping_index += 2;
+   return;
}
 
/*
@@ -963,17 +972,6 @@ static void graph_output_commit_line(struct git_graph 
*graph, struct graph_line
graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
-static struct column *find_new_column_by_commit(struct git_graph *graph,
-   struct commit *commit)
-{
-   int i;
-   for (i = 0; i < graph->num_new_columns; i++) {
-   if (graph->new_columns[i].commit == commit)
-   return &graph->new_columns[i];
-   }
-   return NULL;
-}
-
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
graph_line *line)
 {
int seen_this = 0;
@@ -1001,20 +999,20 @@ static void graph_output_post_merge_line(struct 
git_graph *graph, struct graph_l
 * edges.
 */
struct commit_list *parents = NULL;
-   struct column *par_column;
+   int par_column;
seen_this = 1;
parents = first_interesting_parent(graph);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
+   par_column = graph_find_new_column_by_commit(graph, 
parents->item);
+   assert(par_column >= 0);
 
-   graph_line_write_column(line, par_column, '|');
+   graph_line_write_column(line, 
&graph->new_columns[par_column], '|');
for (j = 0; j < graph->num_parents - 1; j++) {
parents = next_interesting_parent(graph, 
parents);
assert(parents);
-   par_column = find_new_column_by_commit(graph, 
parents->item);
-   assert(par_column);
-   graph_line_write_column(line, par_column, '\\');
+   par_column = 
graph_find_new_column_by_commit(graph, parents->item);
+   assert(par_column >= 0);
+   graph_line_write_column(line, 
&graph->new_columns[par_column], '\\');
graph_line_addch(line, ' ');
}
} else if (seen_this) {
-- 
gitgitgadget



[PATCH v3 09/13] graph: commit and post-merge lines for left-skewed merges

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Following the introduction of "left-skewed" merges, which are merges
whose first parent fuses with another edge to its left, we have some
more edge cases to deal with in the display of commit and post-merge
lines.

The current graph code handles the following cases for edges appearing
to the right of the commit (*) on commit lines. A 2-way merge is usually
followed by vertical lines:

| | |
| * |
| |\ \

An octopus merge (more than two parents) is always followed by edges
sloping to the right:

| |  \  | |\
| *-. \ | *---. \
| |\ \ \| |\ \ \ \

A 2-way merge is followed by a right-sloping edge if the commit line
immediately follows a post-merge line for a commit that appears in the
same column as the current commit, or any column to the left of that:

| * | * |
| |\| |\ \
| * \   | | * \
| |\ \  | | |\ \

This commit introduces the following new cases for commit lines. If a
2-way merge skews to the left, then the edges to its right are always
vertical lines, even if the commit follows a post-merge line:

| | |   | |\
| * |   | * |
|/| |   |/| |

A commit with 3 parents that skews left is followed by vertical edges:

| | |
| * |
|/|\ \

If a 3-way left-skewed merge commit appears immediately after a
post-merge line, then it may be followed the right-sloping edges, just
like a 2-way merge that is not skewed.

| |\
| * \
|/|\ \

Octopus merges with 4 or more parents that skew to the left will always
be followed by right-sloping edges, because the existing columns need to
expand around the merge.

| |  \
| *-. \
|/|\ \ \

On post-merge lines, usually all edges following the current commit
slope to the right:

| * | |
| |\ \ \

However, if the commit is a left-skewed 2-way merge, the edges to its
right remain vertical. We also need to display a space after the
vertical line descending from the commit marker, whereas this line would
normally be followed by a backslash.

| * | |
|/| | |

If a left-skewed merge has more than 2 parents, then the edges to its
right are still sloped as they bend around the edges introduced by the
merge.

| * | |
|/|\ \ \

To handle these new cases, we need to know not just how many parents
each commit has, but how many new columns it adds to the display; this
quantity is recorded in the `edges_added` field for the current commit,
and `prev_edges_added` field for the previous commit.

Here, "column" refers to visual columns, not the logical columns of the
`columns` array. This is because even if all the commit's parents end up
fusing with existing edges, they initially introduce distinct edges in
the commit and post-merge lines before those edges collapse. For
example, a 3-way merge whose 2nd and 3rd parents fuse with existing
edges still introduces 2 visual columns that affect the display of edges
to their right.

| | |  \
| | *-. \
| | |\ \ \
| |_|/ / /
|/| | / /
| | |/ /
| |/| |
| | | |

This merge does not introduce any *logical* columns; there are 4 edges
before and after this commit once all edges have collapsed. But it does
initially introduce 2 new edges that need to be accommodated by the
edges to their right.

Signed-off-by: James Coglan 
---
 graph.c  |  63 +--
 t/t4215-log-skewed-merges.sh | 147 ++-
 2 files changed, 203 insertions(+), 7 deletions(-)

diff --git a/graph.c b/graph.c
index e37127f5ab..21edad8085 100644
--- a/graph.c
+++ b/graph.c
@@ -216,6 +216,46 @@ struct git_graph {
 *  |/| | | | | | | | | | *
 */
int merge_layout;
+   /*
+* The number of columns added to the graph by the current commit. For
+* 2-way and octopus merges, this is is usually one less than the
+* number of parents:
+*
+*  | | |   | |\
+*  | * |   | *---. \
+*  | |\ \  | |\ \ \ \
+*  | | | | | | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 1  edges_added: 3
+*
+* For left-skewed merges, the first parent fuses with its neighbor and
+* so one less column is added:
+*
+*  | | |   | |  \
+*  | * |   | *-. \
+*  |/| |   |/|\ \ \
+*  | | |   | | | | |
+*
+*  num_parents: 2  num_parents: 4
+*  edges_added: 0 

[PATCH v3 04/13] graph: reduce duplication in `graph_insert_into_new_columns()`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

I will shortly be making some changes to this function and so am trying
to simplify it. It currently contains some duplicated logic; both
branches the function can take assign the commit's column index into
the `mapping` array and increment `mapping_index`.

Here I change the function so that the only conditional behaviour is
that it appends the commit to `new_columns` if it's not present. All
manipulation of `mapping` now happens on a single code path.

Signed-off-by: James Coglan 
---
 graph.c | 20 +++-
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/graph.c b/graph.c
index c9646d9e00..512ae16535 100644
--- a/graph.c
+++ b/graph.c
@@ -478,23 +478,17 @@ static void graph_insert_into_new_columns(struct 
git_graph *graph,
int i = graph_find_new_column_by_commit(graph, commit);
 
/*
-* If the commit is already in the new_columns list, we don't need to
-* add it.  Just update the mapping correctly.
+* If the commit is not already in the new_columns array, then add it
+* and record it as being in the final column.
 */
-   if (i >= 0) {
-   graph->mapping[*mapping_index] = i;
-   *mapping_index += 2;
-   return;
+   if (i < 0) {
+   i = graph->num_new_columns++;
+   graph->new_columns[i].commit = commit;
+   graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   /*
-* This commit isn't already in new_columns.  Add it.
-*/
-   graph->new_columns[graph->num_new_columns].commit = commit;
-   graph->new_columns[graph->num_new_columns].color = 
graph_find_commit_color(graph, commit);
-   graph->mapping[*mapping_index] = graph->num_new_columns;
+   graph->mapping[*mapping_index] = i;
*mapping_index += 2;
-   graph->num_new_columns++;
 }
 
 static void graph_update_width(struct git_graph *graph,
-- 
gitgitgadget



[PATCH v3 01/13] graph: automatically track display width of graph lines

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

All the output functions called by `graph_next_line()` currently keep
track of how many printable chars they've written to the buffer, before
calling `graph_pad_horizontally()` to pad the line with spaces. Some
functions do this by incrementing a counter whenever they write to the
buffer, and others do it by encoding an assumption about how many chars
are written, as in:

graph_pad_horizontally(graph, sb, graph->num_columns * 2);

This adds a fair amount of noise to the functions' logic and is easily
broken if one forgets to increment the right counter or update the
calculations used for padding.

To make this easier to use, I'm introducing a new struct called
`graph_line` that wraps a `strbuf` and keeps count of its display width
implicitly. `graph_next_line()` wraps this around the `struct strbuf *`
it's given and passes a `struct graph_line *` to the output functions,
which use its interface.

The `graph_line` interface wraps the `strbuf_addch()`,
`strbuf_addchars()` and `strbuf_addstr()` functions, and adds the
`graph_line_write_column()` function for adding a single character with
color formatting. The `graph_pad_horizontally()` function can then use
the `width` field from the struct rather than taking a character count
as a parameter.

Signed-off-by: James Coglan 
---
 graph.c | 194 +---
 1 file changed, 99 insertions(+), 95 deletions(-)

diff --git a/graph.c b/graph.c
index f53135485f..2f81a5d23d 100644
--- a/graph.c
+++ b/graph.c
@@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short 
color)
return column_colors[color];
 }
 
-static void strbuf_write_column(struct strbuf *sb, const struct column *c,
-   char col_char)
+struct graph_line {
+   struct strbuf *buf;
+   size_t width;
+};
+
+static inline void graph_line_addch(struct graph_line *line, int c)
+{
+   strbuf_addch(line->buf, c);
+   line->width++;
+}
+
+static inline void graph_line_addchars(struct graph_line *line, int c, size_t 
n)
+{
+   strbuf_addchars(line->buf, c, n);
+   line->width += n;
+}
+
+static inline void graph_line_addstr(struct graph_line *line, const char *s)
+{
+   strbuf_addstr(line->buf, s);
+   line->width += strlen(s);
+}
+
+static inline void graph_line_addcolor(struct graph_line *line, unsigned short 
color)
+{
+   strbuf_addstr(line->buf, column_get_color_code(color));
+}
+
+static void graph_line_write_column(struct graph_line *line, const struct 
column *c,
+   char col_char)
 {
if (c->color < column_colors_max)
-   strbuf_addstr(sb, column_get_color_code(c->color));
-   strbuf_addch(sb, col_char);
+   graph_line_addcolor(line, c->color);
+   graph_line_addch(line, col_char);
if (c->color < column_colors_max)
-   strbuf_addstr(sb, column_get_color_code(column_colors_max));
+   graph_line_addcolor(line, column_colors_max);
 }
 
 struct git_graph {
@@ -686,8 +714,7 @@ static int graph_is_mapping_correct(struct git_graph *graph)
return 1;
 }
 
-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
-  int chars_written)
+static void graph_pad_horizontally(struct git_graph *graph, struct graph_line 
*line)
 {
/*
 * Add additional spaces to the end of the strbuf, so that all
@@ -696,12 +723,12 @@ static void graph_pad_horizontally(struct git_graph 
*graph, struct strbuf *sb,
 * This way, fields printed to the right of the graph will remain
 * aligned for the entire commit.
 */
-   if (chars_written < graph->width)
-   strbuf_addchars(sb, ' ', graph->width - chars_written);
+   if (line->width < graph->width)
+   graph_line_addchars(line, ' ', graph->width - line->width);
 }
 
 static void graph_output_padding_line(struct git_graph *graph,
- struct strbuf *sb)
+ struct graph_line *line)
 {
int i;
 
@@ -719,11 +746,11 @@ static void graph_output_padding_line(struct git_graph 
*graph,
 * Output a padding row, that leaves all branch lines unchanged
 */
for (i = 0; i < graph->num_new_columns; i++) {
-   strbuf_write_column(sb, &graph->new_columns[i], '|');
-   strbuf_addch(sb, ' ');
+   graph_line_write_column(line, &graph->new_columns[i], '|');
+   graph_line_addch(line, ' ');
}
 
-   graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
+   graph_pad_horizontally(graph, line);
 }
 
 
@@ -733,14 +760,14 @@ int graph_width(struct git_graph *graph)
 }
 
 
-static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
+static void graph_output_skip_line(struct git_graph *graph, struct graph_line 
*line)
 {
/*
 * O

[PATCH v3 10/13] graph: rename `new_mapping` to `old_mapping`

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

The change I'm about to make requires being able to inspect the mapping
array that was used to render the last GRAPH_COLLAPSING line while
rendering a GRAPH_COMMIT line. The `new_mapping` array currently exists
as a pre-allocated space for computing the next `mapping` array during
`graph_output_collapsing_line()`, but we can repurpose it to let us see
the previous `mapping` state.

To support this use it will make more sense if this array is named
`old_mapping`, as it will contain the mapping data for the previous line
we rendered, at the point we're rendering a commit line.

Signed-off-by: James Coglan 
---
 graph.c | 54 +++---
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/graph.c b/graph.c
index 21edad8085..2315f3604d 100644
--- a/graph.c
+++ b/graph.c
@@ -259,7 +259,7 @@ struct git_graph {
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
-* that can be stored in the mapping and new_mapping arrays.
+* that can be stored in the mapping and old_mapping arrays.
 */
int column_capacity;
/*
@@ -302,7 +302,7 @@ struct git_graph {
 * of the git_graph simply so we don't have to allocate a new
 * temporary array each time we have to output a collapsing line.
 */
-   int *new_mapping;
+   int *old_mapping;
/*
 * The current default column color being used.  This is
 * stored as an index into the array column_colors.
@@ -388,7 +388,7 @@ struct git_graph *graph_init(struct rev_info *opt)
ALLOC_ARRAY(graph->columns, graph->column_capacity);
ALLOC_ARRAY(graph->new_columns, graph->column_capacity);
ALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);
-   ALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity);
+   ALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);
 
/*
 * The diff output prefix callback, with this we can make
@@ -418,7 +418,7 @@ static void graph_ensure_capacity(struct git_graph *graph, 
int num_columns)
REALLOC_ARRAY(graph->columns, graph->column_capacity);
REALLOC_ARRAY(graph->new_columns, graph->column_capacity);
REALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);
-   REALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2);
+   REALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);
 }
 
 /*
@@ -1116,13 +1116,18 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
int horizontal_edge_target = -1;
 
/*
-* Clear out the new_mapping array
+* Swap the mapping and old_mapping arrays
+*/
+   SWAP(graph->mapping, graph->old_mapping);
+
+   /*
+* Clear out the mapping array
 */
for (i = 0; i < graph->mapping_size; i++)
-   graph->new_mapping[i] = -1;
+   graph->mapping[i] = -1;
 
for (i = 0; i < graph->mapping_size; i++) {
-   int target = graph->mapping[i];
+   int target = graph->old_mapping[i];
if (target < 0)
continue;
 
@@ -1143,14 +1148,14 @@ static void graph_output_collapsing_line(struct 
git_graph *graph, struct graph_l
 * This column is already in the
 * correct place
 */
-   assert(graph->new_mapping[i] == -1);
-   graph->new_mapping[i] = target;
-   } else if (graph->new_mapping[i - 1] < 0) {
+   assert(graph->mapping[i] == -1);
+   graph->mapping[i] = target;
+   } else if (graph->mapping[i - 1] < 0) {
/*
 * Nothing is to the left.
 * Move to the left by one
 */
-   graph->new_mapping[i - 1] = target;
+   graph->mapping[i - 1] = target;
/*
 * If there isn't already an edge moving horizontally
 * select this one.
@@ -1166,9 +1171,9 @@ static void graph_output_collapsing_line(struct git_graph 
*graph, struct graph_l
 * line.
 */
for (j = (target * 2)+3; j < (i - 2); j += 2)
-   graph->new_mapping[j] = target;
+   graph->mapping[j] = target;
}
-   } else if (graph->new_mapping[i - 1] == target) {
+   } else if (graph->mapping[i - 1] == target) {
/*
 * There is a branch line to our left
 * already, and it is our target.  We
@@ -1176,7 +

[PATCH v3 13/13] graph: fix coloring of octopus dashes

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

In 04005834ed ("log: fix coloring of certain octopus merge shapes",
2018-09-01) there is a fix for the coloring of dashes following an
octopus merge. It makes a distinction between the case where all parents
introduce a new column, versus the case where the first parent collapses
into an existing column:

| *-.   | *-.
| |\ \  | |\ \
| | | | |/ / /

The latter case means that the columns for the merge parents begin one
place to the left in the `new_columns` array compared to the former
case.

However, the implementation only works if the commit's parents are kept
in order as they map onto the visual columns, as we get the colors by
iterating over `new_columns` as we print the dashes. In general, the
commit's parents can arbitrarily merge with existing columns, and change
their ordering in the process.

For example, in the following diagram, the number of each column
indicates which commit parent appears in each column.

| | *---.
| | |\ \ \
| | |/ / /
| |/| | /
| |_|_|/
|/| | |
3 1 0 2

If the columns are colored (red, green, yellow, blue), then the dashes
will currently be colored yellow and blue, whereas they should be blue
and red.

To fix this, we need to look up each column in the `mapping` array,
which before the `GRAPH_COLLAPSING` state indicates which logical column
is displayed in each visual column. This implementation is simpler as it
doesn't have any edge cases, and it also handles how left-skewed first
parents are now displayed:

| *-.
|/|\ \
| | | |
0 1 2 3

The color of the first dashes is always the color found in `mapping` two
columns to the right of the commit symbol. Because commits are displayed
after all edges have been collapsed together and the visual columns
match the logical ones, we can find the visual offset of the commit
symbol using `commit_index`.

Signed-off-by: James Coglan 
---
 graph.c  | 71 +++-
 t/t4214-log-graph-octopus.sh | 10 ++---
 2 files changed, 42 insertions(+), 39 deletions(-)

diff --git a/graph.c b/graph.c
index 80db74aee6..e3fd0ea5f8 100644
--- a/graph.c
+++ b/graph.c
@@ -684,6 +684,11 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping_size--;
 }
 
+static int graph_num_dashed_parents(struct git_graph *graph)
+{
+   return graph->num_parents + graph->merge_layout - 3;
+}
+
 static int graph_num_expansion_rows(struct git_graph *graph)
 {
/*
@@ -706,7 +711,7 @@ static int graph_num_expansion_rows(struct git_graph *graph)
 *  | * \
 *  |/|\ \
 */
-   return (graph->num_parents + graph->merge_layout - 3) * 2;
+   return graph_num_dashed_parents(graph) * 2;
 }
 
 static int graph_needs_pre_commit_line(struct git_graph *graph)
@@ -934,47 +939,45 @@ static void graph_output_commit_char(struct git_graph 
*graph, struct graph_line
 static void graph_draw_octopus_merge(struct git_graph *graph, struct 
graph_line *line)
 {
/*
-* Here dashless_parents represents the number of parents which don't
-* need to have dashes (the edges labeled "0" and "1").  And
-* dashful_parents are the remaining ones.
+* The parents of a merge commit can be arbitrarily reordered as they
+* are mapped onto display columns, for example this is a valid merge:
 *
-* | *---.
-* | |\ \ \
-* | | | | |
-* x 0 1 2 3
+*  | | *---.
+*  | | |\ \ \
+*  | | |/ / /
+*  | |/| | /
+*  | |_|_|/
+*  |/| | |
+*  3 1 0 2
 *
-*/
-   const int dashless_parents = 3 - graph->merge_layout;
-   int dashful_parents = graph->num_parents - dashless_parents;
-
-   /*
-* Usually, we add one new column for each parent (like the diagram
-* above) but sometimes the first parent goes into an existing column,
-* like this:
+* The numbers denote which parent of the merge each visual column
+* corresponds to; we can't assume that the parents will initially
+* display in the order given by new_columns.
 *
-* | *-.
-* |/|\ \
-* | | | |
-* x 0 1 2
+* To find the right color for each dash, we need to consult the
+* mapping array, starting from the column 2 places to the right of the
+* merge commit, and use that to find out which logical column each
+* edge will collapse to.
 *
-* In which case the number of parents will be one greater than the
-* number of added columns.
+* Commits are rendered once all edges have collapsed to their correct
+* logcial column, so commit_index gives us the right visual offset for
+* the merge commit.
 */
-   int adde

[PATCH v3 12/13] graph: flatten edges that fuse with their right neighbor

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

When a merge commit is printed and its final parent is the same commit
that occupies the column to the right of the merge, this results in a
kink in the displayed edges:

* |
|\ \
| |/
| *

Graphs containing these shapes can be hard to read, as the expansion to
the right followed immediately by collapsing back to the left creates a
lot of zig-zagging edges, especially when many columns are present.

We can improve this by eliminating the zig-zag and having the merge's
final parent edge fuse immediately with its neighbor:

* |
|\|
| *

This reduces the horizontal width for the current commit by 2, and
requires one less row, making the graph display more compact. Taken in
combination with other graph-smoothing enhancements, it greatly
compresses the space needed to display certain histories:

*
|\
| *   *
| |\  |\
| | * | *
| | | | |\
| |  \| | *
| *-. \   | * |
| |\ \ \=>|/|\|
|/ / / /  | | *
| | | /   | * |
| | |/| |/
| | * * /
| * | |/
| |/  *
* |
|/
*

One of the test cases here cannot be correctly rendered in Git v2.23.0;
it produces this output following commit E:

| | *-. \   5_E
| | |\ \ \
| |/ / / /
| | | / _
| |_|/
|/| |

The new implementation makes sure that the rightmost edge in this
history is not left dangling as above.

Signed-off-by: James Coglan 
---
 graph.c| 34 +
 t/t4215-log-skewed-merges.sh   | 56 ++
 t/t6016-rev-list-graph-simplify-history.sh | 30 +---
 3 files changed, 86 insertions(+), 34 deletions(-)

diff --git a/graph.c b/graph.c
index 63f8d18baa..80db74aee6 100644
--- a/graph.c
+++ b/graph.c
@@ -557,8 +557,24 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
shift = (dist > 1) ? 2 * dist - 3 : 1;
 
graph->merge_layout = (dist > 0) ? 0 : 1;
+   graph->edges_added = graph->num_parents + graph->merge_layout  
- 2;
+
mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
graph->width += 2 * graph->merge_layout;
+
+   } else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 
2]) {
+   /*
+* If some columns have been added by a merge, but this commit
+* was found in the last existing column, then adjust the
+* numbers so that the two edges immediately join, i.e.:
+*
+*  * | * |
+*  |\ \=>  |\|
+*  | |/| *
+*  | *
+*/
+   mapping_idx = graph->width - 2;
+   graph->edges_added = -1;
} else {
mapping_idx = graph->width;
graph->width += 2;
@@ -604,6 +620,8 @@ static void graph_update_columns(struct git_graph *graph)
graph->mapping[i] = -1;
 
graph->width = 0;
+   graph->prev_edges_added = graph->edges_added;
+   graph->edges_added = 0;
 
/*
 * Populate graph->new_columns and graph->mapping
@@ -731,9 +749,6 @@ void graph_update(struct git_graph *graph, struct commit 
*commit)
 */
graph_update_columns(graph);
 
-   graph->prev_edges_added = graph->edges_added;
-   graph->edges_added = graph->num_parents + graph->merge_layout - 2;
-
graph->expansion_row = 0;
 
/*
@@ -1041,7 +1056,7 @@ const char merge_chars[] = {'/', '|', '\\'};
 static void graph_output_post_merge_line(struct git_graph *graph, struct 
graph_line *line)
 {
int seen_this = 0;
-   int i;
+   int i, j;
 
struct commit_list *first_parent = first_interesting_parent(graph);
int seen_parent = 0;
@@ -1073,16 +1088,19 @@ static void graph_output_post_merge_line(struct 
git_graph *graph, struct graph_l
char c;
seen_this = 1;
 
-   for (; parents; parents = 
next_interesting_parent(graph, parents)) {
+   for (j = 0; j < graph->num_parents; j++) {
par_column = 
graph_find_new_column_by_commit(graph, parents->item);
assert(par_column >= 0);
 
c = merge_chars[idx];
graph_line_write_column(line, 
&graph->new_columns[par_column], c);
-   if (idx == 2)
-  

[PATCH v3 08/13] graph: tidy up display of left-skewed merges

2019-10-15 Thread James Coglan via GitGitGadget
From: James Coglan 

Currently, when we display a merge whose first parent is already present
in a column to the left of the merge commit, we display the first parent
as a vertical pipe `|` in the GRAPH_POST_MERGE line and then immediately
enter the GRAPH_COLLAPSING state. The first-parent line tracks to the
left and all the other parent lines follow it; this creates a "kink" in
those lines:

| *---.
| |\ \ \
|/ / / /
| | | *

This change tidies the display of such commits such that if the first
parent appears to the left of the merge, we render it as a `/` and the
second parent as a `|`. This reduces the horizontal and vertical space
needed to render the merge, and makes the resulting lines easier to
read.

| *-.
|/|\ \
| | | *

If the first parent is separated from the merge by several columns, a
horizontal line is drawn in a similar manner to how the GRAPH_COLLAPSING
state displays the line.

| | | *-.
| |_|/|\ \
|/| | | | *

This effect is applied to both "normal" two-parent merges, and to
octopus merges. It also reduces the vertical space needed for pre-commit
lines, as the merge occupies one less column than usual.

Before: After:

| * | *
| |\| |\
| | \   | * \
| |  \  |/|\ \
| *-. \
| |\ \ \

Signed-off-by: James Coglan 
---
 graph.c  | 125 +++
 t/t4214-log-graph-octopus.sh |  20 +++---
 t/t4215-log-skewed-merges.sh |  45 +++--
 3 files changed, 144 insertions(+), 46 deletions(-)

diff --git a/graph.c b/graph.c
index bd7403065e..e37127f5ab 100644
--- a/graph.c
+++ b/graph.c
@@ -202,6 +202,20 @@ struct git_graph {
 * previous commit.
 */
int prev_commit_index;
+   /*
+* Which layout variant to use to display merge commits. If the
+* commit's first parent is known to be in a column to the left of the
+* merge, then this value is 0 and we use the layout on the left.
+* Otherwise, the value is 1 and the layout on the right is used. This
+* field tells us how many columns the first parent occupies.
+*
+*  0)  1)
+*
+*  | | | *-.   | | *---.
+*  | |_|/|\ \  | | |\ \ \
+*  |/| | | | | | | | | | *
+*/
+   int merge_layout;
/*
 * The maximum number of columns that can be stored in the columns
 * and new_columns arrays.  This is also half the number of entries
@@ -313,6 +327,7 @@ struct git_graph *graph_init(struct rev_info *opt)
graph->prev_state = GRAPH_PADDING;
graph->commit_index = 0;
graph->prev_commit_index = 0;
+   graph->merge_layout = 0;
graph->num_columns = 0;
graph->num_new_columns = 0;
graph->mapping_size = 0;
@@ -472,9 +487,11 @@ static int graph_find_new_column_by_commit(struct 
git_graph *graph,
 }
 
 static void graph_insert_into_new_columns(struct git_graph *graph,
- struct commit *commit)
+ struct commit *commit,
+ int idx)
 {
int i = graph_find_new_column_by_commit(graph, commit);
+   int mapping_idx;
 
/*
 * If the commit is not already in the new_columns array, then add it
@@ -486,8 +503,26 @@ static void graph_insert_into_new_columns(struct git_graph 
*graph,
graph->new_columns[i].color = graph_find_commit_color(graph, 
commit);
}
 
-   graph->mapping[graph->width] = i;
-   graph->width += 2;
+   if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {
+   /*
+* If this is the first parent of a merge, choose a layout for
+* the merge line based on whether the parent appears in a
+* column to the left of the merge
+*/
+   int dist, shift;
+
+   dist = idx - i;
+   shift = (dist > 1) ? 2 * dist - 3 : 1;
+
+   graph->merge_layout = (dist > 0) ? 0 : 1;
+   mapping_idx = graph->width + (graph->merge_layout - 1) * shift;
+   graph->width += 2 * graph->merge_layout;
+   } else {
+   mapping_idx = graph->width;
+   graph->width += 2;
+   }
+
+   graph->mapping[mapping_idx] = i;
 }
 
 static void graph_update_columns(struct git_graph *graph)
@@ -553,6 +588,7 @@ static void graph_update_columns(struct git_graph *graph)
if (col_commit == graph->commit) {
seen_this = 1;
graph->commit_index = i;
+   graph->merge_layout = -1;
for (parent = first_interesting_parent(graph);