git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
James Coglan <jcoglan@gmail.com>
Date
Oct 11, 2019, 17:08 UTC
Message-ID
<16547350-2997-d250-d48b-43c6ae49b3c3@gmail.com>
In-Reply-To
<nycvar.QRO.7.76.6.1910102303330.46@tvgsbejvaqbjf.bet>
Hi Johannes,
On 10/10/2019 22:07, Johannes Schindelin wrote:
Show 26 quoted lines
> Hi James,
> 
> On Thu, 10 Oct 2019, James Coglan via GitGitGadget wrote:
> 
>> From: James Coglan <jcoglan@gmail.com>
>>
>> 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.
> 
> This is a big heavy-handed: adding a `width` field to `struct strbuf`
> and maintaining it _just_ for the purpose of `graph.c` puts an
> unnecssary load on every other `strbuf` user (of which there are a
> _lot_).
I was anticipating there might be objections to modifying a widely used struct. There are other idea I had for solving this that I can share -- I'll post an alternative to this patch shortly that does not involve changing `strbuf`.
 
> So my obvious question is: what makes `width` different from `len`?
> Since we exclusively use ASCII characters for the graph part, we should
> be able to use the already-existing `len`, for free, no?
`len` counts the number of bytes in the buffer, which may include non-printing bytes used to apply color formatting. For padding graph lines, we need to know the number of display columns the buffer will use, and that's what `width` was added for.
> I could imagine that the `strbuf` might receive more than one line, but
> then we still would only need to remember the offset of the last newline
> character in that `strbuf`, no?
As far as I know, it does not contain multiple lines when used in graph.c, but I agree the implementation I've submitted here is obviously wrong if you wanted to use it to get the width of multi-line text. Putting code in strbuf that only works in graph.c and not more broadly was probably a mistake on my part.
Show 332 quoted lines
> 
>> 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 <jcoglan@gmail.com>
>> ---
>>  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 graph_output_pre_commit_line(struct git_graph *graph,
>>  				strbuf_write_column(sb, col, '\\');
>>  			else
>>  				strbuf_write_column(sb, col, '|');
>> -			chars_written++;
>>  		} else if (seen_this && (graph->expansion_row > 0)) {
>>  			strbuf_write_column(sb, col, '\\');
>> -			chars_written++;
>>  		} else {
>>  			strbuf_write_column(sb, col, '|');
>> -			chars_written++;
>>  		}
>>  		strbuf_addch(sb, ' ');
>> -		chars_written++;
>>  	}
>>
>> -	graph_pad_horizontally(graph, sb, chars_written);
>> +	graph_pad_horizontally(graph, sb);
>>
>>  	/*
>>  	 * Increment graph->expansion_row,
>> @@ -842,11 +843,9 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
>>  }
>>
>>  /*
>> - * Draw the horizontal dashes of an octopus merge and return the number of
>> - * characters written.
>> + * Draw the horizontal dashes of an octopus merge.
>>   */
>> -static int graph_draw_octopus_merge(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
>> @@ -890,13 +889,12 @@ static int graph_draw_octopus_merge(struct git_graph *graph,
>>  		strbuf_write_column(sb, &graph->new_columns[i+first_col],
>>  				    i == dashful_parents-1 ? '.' : '-');
>>  	}
>> -	return 2 * dashful_parents;
>>  }
>>
>>  static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
>>  {
>>  	int seen_this = 0;
>> -	int i, chars_written;
>> +	int i;
>>
>>  	/*
>>  	 * Output the row containing this commit
>> @@ -906,7 +904,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
>>  	 * children that we have already processed.)
>>  	 */
>>  	seen_this = 0;
>> -	chars_written = 0;
>>  	for (i = 0; i <= graph->num_columns; i++) {
>>  		struct column *col = &graph->columns[i];
>>  		struct commit *col_commit;
>> @@ -921,14 +918,11 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
>>  		if (col_commit == graph->commit) {
>>  			seen_this = 1;
>>  			graph_output_commit_char(graph, sb);
>> -			chars_written++;
>>
>>  			if (graph->num_parents > 2)
>> -				chars_written += graph_draw_octopus_merge(graph,
>> -									  sb);
>> +				graph_draw_octopus_merge(graph, sb);
>>  		} else if (seen_this && (graph->num_parents > 2)) {
>>  			strbuf_write_column(sb, col, '\\');
>> -			chars_written++;
>>  		} else if (seen_this && (graph->num_parents == 2)) {
>>  			/*
>>  			 * This is a 2-way merge commit.
>> @@ -948,16 +942,13 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
>>  				strbuf_write_column(sb, col, '\\');
>>  			else
>>  				strbuf_write_column(sb, col, '|');
>> -			chars_written++;
>>  		} else {
>>  			strbuf_write_column(sb, col, '|');
>> -			chars_written++;
>>  		}
>>  		strbuf_addch(sb, ' ');
>> -		chars_written++;
>>  	}
>>
>> -	graph_pad_horizontally(graph, sb, chars_written);
>> +	graph_pad_horizontally(graph, sb);
>>
>>  	/*
>>  	 * Update graph->state
>> @@ -984,12 +975,11 @@ static struct column *find_new_column_by_commit(struct git_graph *graph,
>>  static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
>>  {
>>  	int seen_this = 0;
>> -	int i, j, chars_written;
>> +	int i, j;
>>
>>  	/*
>>  	 * Output the post-merge row
>>  	 */
>> -	chars_written = 0;
>>  	for (i = 0; i <= graph->num_columns; i++) {
>>  		struct column *col = &graph->columns[i];
>>  		struct commit *col_commit;
>> @@ -1017,7 +1007,6 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf
>>  			assert(par_column);
>>
>>  			strbuf_write_column(sb, par_column, '|');
>> -			chars_written++;
>>  			for (j = 0; j < graph->num_parents - 1; j++) {
>>  				parents = next_interesting_parent(graph, parents);
>>  				assert(parents);
>> @@ -1026,19 +1015,16 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf
>>  				strbuf_write_column(sb, par_column, '\\');
>>  				strbuf_addch(sb, ' ');
>>  			}
>> -			chars_written += j * 2;
>>  		} else if (seen_this) {
>>  			strbuf_write_column(sb, col, '\\');
>>  			strbuf_addch(sb, ' ');
>> -			chars_written += 2;
>>  		} else {
>>  			strbuf_write_column(sb, col, '|');
>>  			strbuf_addch(sb, ' ');
>> -			chars_written += 2;
>>  		}
>>  	}
>>
>> -	graph_pad_horizontally(graph, sb, chars_written);
>> +	graph_pad_horizontally(graph, sb);
>>
>>  	/*
>>  	 * Update graph->state
>> @@ -1181,7 +1167,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
>>  		}
>>  	}
>>
>> -	graph_pad_horizontally(graph, sb, graph->mapping_size);
>> +	graph_pad_horizontally(graph, sb);
>>
>>  	/*
>>  	 * Swap mapping and new_mapping
>> @@ -1199,6 +1185,8 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
>>
>>  int graph_next_line(struct git_graph *graph, struct strbuf *sb)
>>  {
>> +	sb->width = 0;
>> +
>>  	switch (graph->state) {
>>  	case GRAPH_PADDING:
>>  		graph_output_padding_line(graph, sb);
>> @@ -1227,7 +1215,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
>>  static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
>>  {
>>  	int i;
>> -	int chars_written = 0;
>>
>>  	if (graph->state != GRAPH_COMMIT) {
>>  		graph_next_line(graph, sb);
>> @@ -1245,19 +1232,16 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
>>  		struct column *col = &graph->columns[i];
>>
>>  		strbuf_write_column(sb, col, '|');
>> -		chars_written++;
>>
>>  		if (col->commit == graph->commit && graph->num_parents > 2) {
>>  			int len = (graph->num_parents - 2) * 2;
>>  			strbuf_addchars(sb, ' ', len);
>> -			chars_written += len;
>>  		} else {
>>  			strbuf_addch(sb, ' ');
>> -			chars_written++;
>>  		}
>>  	}
>>
>> -	graph_pad_horizontally(graph, sb, chars_written);
>> +	graph_pad_horizontally(graph, sb);
>>
>>  	/*
>>  	 * Update graph->prev_state since we have output a padding line
>> diff --git a/strbuf.h b/strbuf.h
>> index f62278a0be..3a98147321 100644
>> --- a/strbuf.h
>> +++ b/strbuf.h
>> @@ -66,11 +66,12 @@ struct string_list;
>>  struct strbuf {
>>  	size_t alloc;
>>  	size_t len;
>> +	size_t width;
>>  	char *buf;
>>  };
>>
>>  extern char strbuf_slopbuf[];
>> -#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }
>> +#define STRBUF_INIT  { .alloc = 0, .len = 0, .width = 0, .buf = strbuf_slopbuf }
>>
>>  /*
>>   * Predeclare this here, since cache.h includes this file before it defines the
>> @@ -161,6 +162,10 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len)
>>  {
>>  	if (len > (sb->alloc ? sb->alloc - 1 : 0))
>>  		die("BUG: strbuf_setlen() beyond buffer");
>> +	if (len > sb->len)
>> +		sb->width += len - sb->len;
>> +	else
>> +		sb->width = len;
>>  	sb->len = len;
>>  	if (sb->buf != strbuf_slopbuf)
>>  		sb->buf[len] = '\0';
>> @@ -231,6 +236,7 @@ static inline void strbuf_addch(struct strbuf *sb, int c)
>>  		strbuf_grow(sb, 1);
>>  	sb->buf[sb->len++] = c;
>>  	sb->buf[sb->len] = '\0';
>> +	sb->width++;
>>  }
>>
>>  /**
>> --
>> gitgitgadget
>>
>>
Previous: Junio C HamanoNext: James Coglan via GitGitGadget
Message 16 of 83 in “Improve the readability of log --graph output”
  1. 00/11 Improve the readability of log --graph outputJames Coglan via GitGitGadget, Oct 10, 2019
  2. 01/11 graph: automatically track visible width of `strbuf`James Coglan via GitGitGadget, Oct 10, 2019
  3. Johannes SchindelinOct 10, 2019
  4. Denton LiuOct 10, 2019
  5. Derrick StoleeOct 11, 2019
  6. Junio C HamanoOct 11, 2019
  7. Denton LiuOct 11, 2019
  8. Johannes SchindelinOct 11, 2019
  9. James CoglanOct 11, 2019
  10. Junio C HamanoOct 12, 2019
  11. Johannes SchindelinOct 12, 2019
  12. James CoglanOct 14, 2019
  13. James CoglanOct 14, 2019
  14. Junio C HamanoOct 16, 2019
  15. Junio C HamanoOct 11, 2019
  16. James CoglanOct 11, 2019
  17. 03/11 graph: reduce duplication in `graph_insert_into_new_columns()`James Coglan via GitGitGadget, Oct 10, 2019
  18. 02/11 graph: reuse `find_new_column_by_commit()`James Coglan via GitGitGadget, Oct 10, 2019
  19. 04/11 graph: remove `mapping_idx` and `graph_update_width()`James Coglan via GitGitGadget, Oct 10, 2019
  20. 05/11 graph: extract logic for moving to GRAPH_PRE_COMMIT stateJames Coglan via GitGitGadget, Oct 10, 2019
  21. 09/11 graph: smooth appearance of collapsing edges on commit linesJames Coglan via GitGitGadget, Oct 10, 2019
  22. 08/11 graph: rename `new_mapping` to `old_mapping`James Coglan via GitGitGadget, Oct 10, 2019
  23. 11/11 graph: fix coloring of octopus dashesJames Coglan via GitGitGadget, Oct 10, 2019
  24. Denton LiuOct 10, 2019
  25. Denton LiuOct 10, 2019
  26. Jeff KingOct 13, 2019
  27. 07/11 graph: commit and post-merge lines for left-skewed mergesJames Coglan via GitGitGadget, Oct 10, 2019
  28. Derrick StoleeOct 10, 2019
  29. James CoglanOct 11, 2019
  30. Jeff KingOct 13, 2019
  31. James CoglanOct 14, 2019
  32. Derrick StoleeOct 14, 2019
  33. Johannes SchindelinOct 14, 2019
  34. 06/11 graph: tidy up display of left-skewed mergesJames Coglan via GitGitGadget, Oct 10, 2019
  35. Derrick StoleeOct 10, 2019
  36. James CoglanOct 11, 2019
  37. Derrick StoleeOct 12, 2019
  38. James CoglanOct 14, 2019
  39. 10/11 graph: flatten edges that join to their right neighborJames Coglan via GitGitGadget, Oct 10, 2019
  40. Derrick StoleeOct 10, 2019
  41. Jeff KingOct 13, 2019
  42. James CoglanOct 14, 2019
  43. 00/13 Improve the readability of log --graph outputJames Coglan via GitGitGadget, Oct 15, 2019
  44. 03/13 graph: reuse `find_new_column_by_commit()`James Coglan via GitGitGadget, Oct 15, 2019
  45. 02/13 graph: handle line padding in `graph_next_line()`James Coglan via GitGitGadget, Oct 15, 2019
  46. 05/13 graph: remove `mapping_idx` and `graph_update_width()`James Coglan via GitGitGadget, Oct 15, 2019
  47. 01/13 graph: automatically track display width of graph linesJames Coglan via GitGitGadget, Oct 15, 2019
  48. 04/13 graph: reduce duplication in `graph_insert_into_new_columns()`James Coglan via GitGitGadget, Oct 15, 2019
  49. 06/13 graph: extract logic for moving to GRAPH_PRE_COMMIT stateJames Coglan via GitGitGadget, Oct 15, 2019
  50. 09/13 graph: commit and post-merge lines for left-skewed mergesJames Coglan via GitGitGadget, Oct 15, 2019
  51. 07/13 graph: example of graph output that can be simplifiedJames Coglan via GitGitGadget, Oct 15, 2019
  52. 11/13 graph: smooth appearance of collapsing edges on commit linesJames Coglan via GitGitGadget, Oct 15, 2019
  53. 08/13 graph: tidy up display of left-skewed mergesJames Coglan via GitGitGadget, Oct 15, 2019
  54. 12/13 graph: flatten edges that fuse with their right neighborJames Coglan via GitGitGadget, Oct 15, 2019
  55. 10/13 graph: rename `new_mapping` to `old_mapping`James Coglan via GitGitGadget, Oct 15, 2019
  56. 13/13 graph: fix coloring of octopus dashesJames Coglan via GitGitGadget, Oct 15, 2019
  57. 00/13 Improve the readability of log --graph outputJames Coglan via GitGitGadget, Oct 15, 2019
  58. 03/13 graph: reuse `find_new_column_by_commit()`James Coglan via GitGitGadget, Oct 15, 2019
  59. 02/13 graph: handle line padding in `graph_next_line()`James Coglan via GitGitGadget, Oct 15, 2019
  60. Junio C HamanoOct 16, 2019
  61. 01/13 graph: automatically track display width of graph linesJames Coglan via GitGitGadget, Oct 15, 2019
  62. Junio C HamanoOct 16, 2019
  63. Junio C HamanoOct 16, 2019
  64. 04/13 graph: reduce duplication in `graph_insert_into_new_columns()`James Coglan via GitGitGadget, Oct 15, 2019
  65. 06/13 graph: extract logic for moving to GRAPH_PRE_COMMIT stateJames Coglan via GitGitGadget, Oct 15, 2019
  66. 05/13 graph: remove `mapping_idx` and `graph_update_width()`James Coglan via GitGitGadget, Oct 15, 2019
  67. 07/13 graph: example of graph output that can be simplifiedJames Coglan via GitGitGadget, Oct 15, 2019
  68. Derrick StoleeOct 17, 2019
  69. SZEDER GáborOct 18, 2019
  70. t4215: don't put git commands upstream of pipeDenton Liu, Nov 12, 2019
  71. Junio C HamanoNov 12, 2019
  72. SZEDER GáborNov 12, 2019
  73. t4215: use helper function to check outputDenton Liu, Nov 12, 2019
  74. Junio C HamanoNov 13, 2019
  75. 09/13 graph: commit and post-merge lines for left-skewed mergesJames Coglan via GitGitGadget, Oct 15, 2019
  76. 13/13 graph: fix coloring of octopus dashesJames Coglan via GitGitGadget, Oct 15, 2019
  77. 08/13 graph: tidy up display of left-skewed mergesJames Coglan via GitGitGadget, Oct 15, 2019
  78. Junio C HamanoOct 16, 2019
  79. Derrick StoleeOct 17, 2019
  80. Junio C HamanoOct 18, 2019
  81. 10/13 graph: rename `new_mapping` to `old_mapping`James Coglan via GitGitGadget, Oct 15, 2019
  82. 12/13 graph: flatten edges that fuse with their right neighborJames Coglan via GitGitGadget, Oct 15, 2019
  83. 11/13 graph: smooth appearance of collapsing edges on commit linesJames Coglan via GitGitGadget, Oct 15, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.