Skip to contentSkip to conversation

log: support 256 colors with --graph=256colors

PATCH In review Nguyễn Thái Ngọc Duy 3 participants 30 messages Dec 20, 2016 – Jan 28, 2017 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 log: support 256 colors with --graph=256colors

---
I got mad after tracing two consecutive red history lines in git log --graph --oneline back to their merge points, far far away. Yeah probably should fire up tig, or gitk or something.

This may sound like a good thing to add, but I don't know how good it is compared to the good old 16 color palette, yet as I haven't tried it for long since it's just written.

BTW anyone interested in bringing this type [1] of --graph to git? I tried the unicode box characters, but the vertical lines do not join with diagonal ones, making the graph a bit ugly (even though it's still better than ascii version)

[1] https://github.com/magit/magit/issues/495#issuecomment-17480757

 Documentation/rev-list-options.txt |  5 ++++-
 graph.c                            | 31 ++++++++++++++++++++++++++++++-
 graph.h                            |  8 +++++++-
 revision.c                         |  4 ++++
 4 files changed, 45 insertions(+), 3 deletions(-)
Documentation/rev-list-options.txt +4 -1
index 5da7cf5..0a0c2f3 100644
@@ -825,7 +825,7 @@ you would get an output like this:
 	-xxxxxxx... 1st on a
 -----------------------------------------------------------------------
 
---graph::
+--graph[=<options>]::
 	Draw a text-based graphical representation of the commit history
 	on the left hand side of the output.  This may cause extra lines
 	to be printed in between commits, in order for the graph history
@@ -836,6 +836,9 @@ This enables parent rewriting, see 'History Simplification' below.
 +
 This implies the `--topo-order` option by default, but the
 `--date-order` option may also be specified.
++
+The only supported option is `256colors` which allows more than 16
+colors for drawing the commit history.
 
 --show-linear-break[=<barrier>]::
 	When --graph is not used, all history branches are flattened
graph.c +30 -1
index d4e8519..75375a1 100644
@@ -78,6 +78,7 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 
 static const char **column_colors;
 static unsigned short column_colors_max;
+static int column_colors_step;
 
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
@@ -234,10 +235,24 @@ void graph_setup_line_prefix(struct diff_options *diffopt)
 }
 
 
-struct git_graph *graph_init(struct rev_info *opt)
+struct git_graph *graph_init_with_options(struct rev_info *opt, const char *arg)
 {
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
+	if (arg && !strcmp(arg, "256colors")) {
+		int i, start = 17, stop = 232;
+		column_colors_max = stop - start;
+		column_colors =
+			xmalloc((column_colors_max + 1) * sizeof(*column_colors));
+		for (i = start; i < stop; i++) {
+			struct strbuf sb = STRBUF_INIT;
+			strbuf_addf(&sb, "\033[38;5;%dm", i);
+			column_colors[i - start] = strbuf_detach(&sb, NULL);
+		}
+		column_colors[column_colors_max] = xstrdup(GIT_COLOR_RESET);
+		/* ignore the closet 16 colors on either side for the next line */
+		column_colors_step = 16;
+	}
 	if (!column_colors)
 		graph_set_column_colors(column_colors_ansi,
 					column_colors_ansi_max);
@@ -382,6 +397,20 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra
  */
 static void graph_increment_column_color(struct git_graph *graph)
 {
+	if (column_colors_step) {
+		static int random_initialized;
+		int v;
+
+		if (!random_initialized) {
+			srand((unsigned int)getpid());
+			random_initialized = 1;
+		}
+		v = rand() % (column_colors_max - column_colors_step * 2);
+		graph->default_column_color += column_colors_step + v;
+		graph->default_column_color %= column_colors_max;
+		return;
+	}
+
 	graph->default_column_color = (graph->default_column_color + 1) %
 		column_colors_max;
 }
graph.h +7 -1
index af62339..8069aa4 100644
@@ -40,7 +40,13 @@ void graph_set_column_colors(const char **colors, unsigned short colors_max);
 /*
  * Create a new struct git_graph.
  */
-struct git_graph *graph_init(struct rev_info *opt);
+struct git_graph *graph_init_with_options(struct rev_info *opt, const char *arg);
+
+static inline struct git_graph *graph_init(struct rev_info *opt)
+{
+	return graph_init_with_options(opt, NULL);
+}
+
 
 /*
  * Update a git_graph with a new commit.
revision.c +4 -0
index b37dbec..07bea54 100644
@@ -1933,6 +1933,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->topo_order = 1;
 		revs->rewrite_parents = 1;
 		revs->graph = graph_init(revs);
+	} else if (skip_prefix(arg, "--graph=", &arg)) {
+		revs->topo_order = 1;
+		revs->rewrite_parents = 1;
+		revs->graph = graph_init_with_options(revs, arg);
 	} else if (!strcmp(arg, "--root")) {
 		revs->show_root_diff = 1;
 	} else if (!strcmp(arg, "--no-commit-id")) {
Signature · 1 line
2.8.2.524.g6ff3d78
#1 lore raw
Quoted 9 lines · Nguyễn Thái Ngọc Duy

---
I got mad after tracing two consecutive red history lines in git log --graph --oneline back to their merge points, far far away. Yeah probably should fire up tig, or gitk or something.

This may sound like a good thing to add, but I don't know how good it is compared to the good old 16 color palette, yet as I haven't tried it for long since it's just written.

Hmm. At some point the colors become too close together to be easily distinguishable. In your code you have:

Quoted 4 lines
graph.c +4 -0
+	if (arg && !strcmp(arg, "256colors")) {
+		int i, start = 17, stop = 232;
+		column_colors_max = stop - start;
+		column_colors =

graph.c void graph_setup_line_prefix(struct diff_options *diffopt) · line 255

Quoted 10 lines
graph.c +10 -0
+			xmalloc((column_colors_max + 1) * sizeof(*column_colors));
+		for (i = start; i < stop; i++) {
+			struct strbuf sb = STRBUF_INIT;
+			strbuf_addf(&sb, "\033[38;5;%dm", i);
+			column_colors[i - start] = strbuf_detach(&sb, NULL);
+		}
+		column_colors[column_colors_max] = xstrdup(GIT_COLOR_RESET);
+		/* ignore the closet 16 colors on either side for the next line */
+		column_colors_step = 16;
+	}

So you step by 16, over a set of 215 colors. That seems to give only 13 colors, versus the original 16. :)

I know that is a simplification. If you wrap around, then you get your 13 colors, and then another 13 colors that aren't _quite_ the same, and so on, until you've used all 256. I'm just not sure if the 1st and 14th color would be visually different enough for it to matter (I admit I didn't do any experiments, though).

Documentation/rev-list-options.txt you would get an output like this: · line 831

Quoted 5 lines

---graph::
+--graph[=<options>]::

Documentation/rev-list-options.txt +0 -0
 	Draw a text-based graphical representation of the commit history
 	on the left hand side of the output.  This may cause extra lines
 	to be printed in between commits, in order for the graph history

I wonder if we would ever want another use for "--graph=foo". I guess any such thing could fall under the name of "graph options", and we'd end up with "--graph=256colors,unicode" or something like that.

I do suspect people would want a config option for this, though. I.e., you'd want to enable it all the time if you have a terminal which can handle 256 colors, not just for a particular invocation.

-Peff

author
#2 lore raw
Quoted 30 lines · Jeff King
Quoted 9 lines · Nguyễn Thái Ngọc Duy

---
I got mad after tracing two consecutive red history lines in git log --graph --oneline back to their merge points, far far away. Yeah probably should fire up tig, or gitk or something.

This may sound like a good thing to add, but I don't know how good it is compared to the good old 16 color palette, yet as I haven't tried it for long since it's just written.

Hmm. At some point the colors become too close together to be easily distinguishable. In your code you have:

Quoted 4 lines
graph.c +4 -0
+     if (arg && !strcmp(arg, "256colors")) {
+             int i, start = 17, stop = 232;
+             column_colors_max = stop - start;
+             column_colors =

graph.c void graph_setup_line_prefix(struct diff_options *diffopt) · line 255

Quoted 10 lines
graph.c +10 -0
+                     xmalloc((column_colors_max + 1) * sizeof(*column_colors));
+             for (i = start; i < stop; i++) {
+                     struct strbuf sb = STRBUF_INIT;
+                     strbuf_addf(&sb, "\033[38;5;%dm", i);
+                     column_colors[i - start] = strbuf_detach(&sb, NULL);
+             }
+             column_colors[column_colors_max] = xstrdup(GIT_COLOR_RESET);
+             /* ignore the closet 16 colors on either side for the next line */
+             column_colors_step = 16;
+     }
Quoted 8 lines

So you step by 16, over a set of 215 colors. That seems to give only 13 colors, versus the original 16. :)

I know that is a simplification. If you wrap around, then you get your 13 colors, and then another 13 colors that aren't _quite_ the same, and so on, until you've used all 256. I'm just not sure if the 1st and 14th color would be visually different enough for it to matter (I admit I didn't do any experiments, though).

Yep. If the jump sequence is a random one, we're less likely to run into this. But I think Junio's "run git-log in 2 terminals with the same coloring" convinces me randomization here is not the best thing.

The best solution would be select colors per text line, so we can pick different colors. But I think that's a lot of computation (and probably an NP problem too). The second best option is have a good, predefined color palette. We don't need a red of all shades, we need something that look distinct enough from the rest. I googled for this first and failed. But I think I could approach it a different way:
collect colors that have names. That reduces the number of colors so we can go back to "step 1 at a time" and still don't run into two similar colors often.

Quoted 7 lines
Quoted 5 lines

---graph::
+--graph[=<options>]::
Draw a text-based graphical representation of the commit history on the left hand side of the output. This may cause extra lines to be printed in between commits, in order for the graph history

I wonder if we would ever want another use for "--graph=foo"

I do. See the screenshot in [1] from the original mail. I have to stare at --graph so often lately that it might get my attention before other things.

Quoted 2 lines

I guess any such thing could fall under the name of "graph options", and we'd end up with "--graph=256colors,unicode" or something like that.

Exactly.

Quoted 3 lines

I do suspect people would want a config option for this, though. I.e., you'd want to enable it all the time if you have a terminal which can handle 256 colors, not just for a particular invocation.

Yeah. That also means we need the ability to override/negate config options, perhaps something like --graph=-256colors.

Signature · 1 line
Duy
#3 lore raw
Quoted 4 lines · Duy Nguyen

.... But I think I could approach it a different way:
collect colors that have names. That reduces the number of colors so we can go back to "step 1 at a time" and still don't run into two similar colors often.

I suspect that there is a "cultural" bias that makes the idea unworkable. I haven't found a definitive source, but I think there are a lot more hues and shades of red that have names than hues and shades of blue, for example.

If I were doing this, I'd just prepare a table with 32 color slots or so [*1*], start at a random spot (say 017:00005f) of

https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg,

and pick spots by jumping southeast like a chess knight (i.e. 017->030->043->086->...) until the table is filled, wrapping around at the edge of that color chart as necessary.

[Footnote]

*1* ...because I do not think the thin graph lines painted in too many colours on the screen would be easily distinguishable from each other anyway.

author
#4 lore raw
Quoted 8 lines · Junio C Hamano

If I were doing this, I'd just prepare a table with 32 color slots or so [*1*], start at a random spot (say 017:00005f) of

https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg,

and pick spots by jumping southeast like a chess knight (i.e. 017->030->043->086->...) until the table is filled, wrapping around at the edge of that color chart as necessary.

If you want to play with that, [1] may help, which sorts colors in hsv space, and you can select a few colors to see how they look, either manually or by calculation. Yeah we probably get maybe 32 (or 48 if you stretch it a bit) distinct colors. Not sure if it's any better with true color terminals, probably not.

[1] https://gist.github.com/pclouds/616b67f0b5a9b20d74373286574cd0ac

Signature · 1 line
Duy
#5 lore raw
In reply to Nguyễn Thái Ngọc Duy
Quoted 25 lines · Nguyễn Thái Ngọc Duy
graph.c +6 -1
index d4e8519..75375a1 100644
@@ -78,6 +78,7 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 
 static const char **column_colors;
 static unsigned short column_colors_max;
+static int column_colors_step;
 
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
@@ -234,10 +235,24 @@ void graph_setup_line_prefix(struct diff_options *diffopt)
 }
 
 
-struct git_graph *graph_init(struct rev_info *opt)
+struct git_graph *graph_init_with_options(struct rev_info *opt, const char *arg)
 {
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
+	if (arg && !strcmp(arg, "256colors")) {
+		int i, start = 17, stop = 232;
+		column_colors_max = stop - start;
+		column_colors =

graph.c void graph_setup_line_prefix(struct diff_options *diffopt) · line 255

Quoted 10 lines
graph.c +10 -0
+			xmalloc((column_colors_max + 1) * sizeof(*column_colors));
+		for (i = start; i < stop; i++) {
+			struct strbuf sb = STRBUF_INIT;
+			strbuf_addf(&sb, "\033[38;5;%dm", i);
+			column_colors[i - start] = strbuf_detach(&sb, NULL);
+		}
+		column_colors[column_colors_max] = xstrdup(GIT_COLOR_RESET);
+		/* ignore the closet 16 colors on either side for the next line */
+		column_colors_step = 16;
+	}

So you pre-fill a table of colors with 232-17=215 slots. Is the idea that it is a co-prime with column_colors_step which is set to 16 so that going over the table with wraparound will cover all its elements?

Quoted 11 lines
graph.c +7 -0
@@ -382,6 +397,20 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra
  */
 static void graph_increment_column_color(struct git_graph *graph)
 {
+	if (column_colors_step) {
+		static int random_initialized;
+		int v;
+
+		if (!random_initialized) {
+			srand((unsigned int)getpid());
+			random_initialized = 1;

graph.c static unsigned short graph_get_current_column_color(const struct git_graph *gra · line 416

Quoted 10 lines
graph.c +7 -0
+		}
+		v = rand() % (column_colors_max - column_colors_step * 2);
+		graph->default_column_color += column_colors_step + v;
+		graph->default_column_color %= column_colors_max;
+		return;
+	}
+
 	graph->default_column_color = (graph->default_column_color + 1) %
 		column_colors_max;
 }

This is too ugly to live as-is for two reasons.

- Do you really need rand()? Doesn't this frustrate somebody who runs the same "git log" in two terminals in order to view an overly tall graph, expecting both commands that were started with the same set of arguments to paint the same line in the same color?

- Even if you needed rand(), you should be able to factor out computation of V so that the function does not need an early return that hints totally different processing for two codepaths.

        static void graph_increment_column_color(struct git_graph *g)
        {
                int next;

                if (! 256-color in use) {
                        next = 1;
                } else {
                        do whatever to compute your v;
                        next = v + column_colors_step;
                }
                g->default_column_color =
                (g->default_column_color + v) / column_colors_max;
        }

Or something like that, perhaps?

author
#6 lore raw
Quoted 34 lines · Junio C Hamano
Quoted 32 lines · Nguyễn Thái Ngọc Duy
graph.c +13 -1
index d4e8519..75375a1 100644
@@ -78,6 +78,7 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 
 static const char **column_colors;
 static unsigned short column_colors_max;
+static int column_colors_step;
 
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
@@ -234,10 +235,24 @@ void graph_setup_line_prefix(struct diff_options *diffopt)
 }
 
 
-struct git_graph *graph_init(struct rev_info *opt)
+struct git_graph *graph_init_with_options(struct rev_info *opt, const char *arg)
 {
      struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
+     if (arg && !strcmp(arg, "256colors")) {
+             int i, start = 17, stop = 232;
+             column_colors_max = stop - start;
+             column_colors =
+                     xmalloc((column_colors_max + 1) * sizeof(*column_colors));
+             for (i = start; i < stop; i++) {
+                     struct strbuf sb = STRBUF_INIT;
+                     strbuf_addf(&sb, "\033[38;5;%dm", i);
+                     column_colors[i - start] = strbuf_detach(&sb, NULL);
+             }
+             column_colors[column_colors_max] = xstrdup(GIT_COLOR_RESET);
Quoted 8 lines

graph.c void graph_setup_line_prefix(struct diff_options *diffopt) · line 255

Quoted 3 lines
graph.c +3 -0
+             /* ignore the closet 16 colors on either side for the next line */
+             column_colors_step = 16;
+     }

So you pre-fill a table of colors with 232-17=215 slots. Is the idea that it is a co-prime with column_colors_step which is set to 16 so that going over the table with wraparound will cover all its elements?

Originally yes (because the next color would be more or less the same, maybe brighter or darker a bit), then I went fancy with the rand() thing...

Quoted 25 lines
Quoted 12 lines
graph.c +7 -0
@@ -382,6 +397,20 @@ static unsigned short graph_get_current_column_color(const struct git_graph *gra
  */
 static void graph_increment_column_color(struct git_graph *graph)
 {
+     if (column_colors_step) {
+             static int random_initialized;
+             int v;
+
+             if (!random_initialized) {
+                     srand((unsigned int)getpid());
+                     random_initialized = 1;

graph.c static unsigned short graph_get_current_column_color(const struct git_graph *gra · line 416

Quoted 10 lines
graph.c +7 -0
+             }
+             v = rand() % (column_colors_max - column_colors_step * 2);
+             graph->default_column_color += column_colors_step + v;
+             graph->default_column_color %= column_colors_max;
+             return;
+     }
+
      graph->default_column_color = (graph->default_column_color + 1) %
              column_colors_max;
 }

This is too ugly to live as-is for two reasons.

Quoted 5 lines

- Do you really need rand()? Doesn't this frustrate somebody who runs the same "git log" in two terminals in order to view an overly tall graph, expecting both commands that were started with the same set of arguments to paint the same line in the same color?

No we probably don't need rand(). The thinking was.. now that we have a lot more colors to choose from, let's add some randomness, maybe it'll reduce the chance of showing the same colors in the same line.

There was another concern with a fixed number of steps too, that we could get into a stable jump sequence and never use all the colors (e.g. step 3 with 6 colors total, to simplify). But I verify that we'll use all the colors (at least until we allow people to customize step and the number colors)

Signature · 1 line
Duy
#7 lore raw

PATCH v2 1/1 log --graph: customize the graph lines with config log.graphColors

In reply to Nguyễn Thái Ngọc Duy

If you have a 256 colors terminal (or one with true color support), then the predefined 12 colors seem limited. On the other hand, you don't want to draw graph lines with every single color in this mode because the two colors could look extremely similar. This option allows you to hand pick the colors you want.

Even with standard terminal, if your background color is neither black or white, then the graph line may match your background and become hidden. You can exclude your background color (or simply the colors you hate) with this.

---
Sounds like the good first step should be something like this instead of jumping straight to generating a new color palette automatically.

It's not hard to create a script that generate this config value based on some jump calculation, if you don't want to manually picking colors.

 Documentation/config.txt |  4 ++++
 graph.c                  | 36 ++++++++++++++++++++++++++++++++++--
 2 files changed, 38 insertions(+), 2 deletions(-)
Documentation/config.txt +4 -0
index d51182a..4f26c2a 100644
@@ -2033,6 +2033,10 @@ log.follow::
 	i.e. it cannot be used to follow multiple files and does not work well
 	on non-linear history.
 
+log.graphColors::
+	A list of colors, separated by commas, that can be used to draw
+	history lines in `git log --graph`.
+
 log.showRoot::
 	If true, the initial commit will be shown as a big creation event.
 	This is equivalent to a diff against an empty tree.
graph.c +34 -2
index d4e8519..9c58fd1 100644
@@ -79,6 +79,39 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 static const char **column_colors;
 static unsigned short column_colors_max;
 
+static void set_column_colors_by_config(void)
+{
+	static char **colors;
+	static int colors_max, colors_alloc;
+	char *string = NULL;
+	const char *end, *start;
+
+	if (git_config_get_string("log.graphcolors", &string)) {
+		graph_set_column_colors(column_colors_ansi,
+					column_colors_ansi_max);
+		return;
+	}
+
+	start = string;
+	end = string + strlen(string);
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];
+
+		if (!color_parse_mem(start, comma - start, color)) {
+			ALLOC_GROW(colors, colors_max + 1, colors_alloc);
+			colors[colors_max++] = xstrdup(color);
+		} else
+			warning(_("ignore invalid color '%.*s'"),
+				(int)(comma - start), start);
+		start = comma + 1;
+	}
+	free(string);
+	ALLOC_GROW(colors, colors_max + 1, colors_alloc);
+	colors[colors_max] = xstrdup(GIT_COLOR_RESET);
+	graph_set_column_colors((const char **)colors, colors_max);
+}
+
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
 	column_colors = colors;
@@ -239,8 +272,7 @@ struct git_graph *graph_init(struct rev_info *opt)
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
 	if (!column_colors)
-		graph_set_column_colors(column_colors_ansi,
-					column_colors_ansi_max);
+		set_column_colors_by_config();
 
 	graph->commit = NULL;
 	graph->revs = opt;
Signature · 1 line
2.8.2.524.g6ff3d78
#8 lore raw

Re: [PATCH v2] log --graph: customize the graph lines with config log.graphColors

Quoted 2 lines · Nguyễn Thái Ngọc Duy

Sounds like the good first step should be something like this instead of jumping straight to generating a new color palette automatically.

I like this not merely as a good first step but potentially a good endgame.

graph.c static void graph_show_line_prefix(const struct diff_options *diffopt) · line 85

Quoted 12 lines
graph.c +4 -0
index d4e8519..9c58fd1 100644
@@ -79,6 +79,39 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
 static const char **column_colors;
 static unsigned short column_colors_max;
 
+static void set_column_colors_by_config(void)
+{
+	static char **colors;
+	static int colors_max, colors_alloc;

I doubt you want these to be static (and allow multiple instances of the same configuration variable to accumulate). As you defined the value to be comma-separated colors, users would want the usual "last one wins" rule to override previous settings, no?

graph.c static void graph_show_line_prefix(const struct diff_options *diffopt) · line 91

Quoted 6 lines
graph.c +6 -0
+	char *string = NULL;
+	const char *end, *start;
+
+	if (git_config_get_string("log.graphcolors", &string)) {
+		graph_set_column_colors(column_colors_ansi,
+					column_colors_ansi_max);

On the other hand, if you do want cumulative semantics, then you'd need to free the previous set you held in the statics here, I would think.

Quoted 5 lines
graph.c +5 -0
+		return;
+	}
+
+	start = string;
+	end = string + strlen(string);

graph.c static void graph_show_line_prefix(const struct diff_options *diffopt) · line 106

Quoted 10 lines
graph.c +10 -0
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];
+
+		if (!color_parse_mem(start, comma - start, color)) {
+			ALLOC_GROW(colors, colors_max + 1, colors_alloc);
+			colors[colors_max++] = xstrdup(color);
+		} else
+			warning(_("ignore invalid color '%.*s'"),
+				(int)(comma - start), start);

"ignore invalid color"? Who is giving the command to ignore to whom?

Quoted 12 lines
graph.c +8 -0
+		start = comma + 1;
+	}
+	free(string);
+	ALLOC_GROW(colors, colors_max + 1, colors_alloc);
+	colors[colors_max] = xstrdup(GIT_COLOR_RESET);
+	graph_set_column_colors((const char **)colors, colors_max);
+}
+
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
 	column_colors = colors;

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 275

Quoted 6 lines
graph.c +1 -2
@@ -239,8 +272,7 @@ struct git_graph *graph_init(struct rev_info *opt)
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
 	if (!column_colors)
-		graph_set_column_colors(column_colors_ansi,
-					column_colors_ansi_max);
+		set_column_colors_by_config();

"by" in the function name somehow sounds funny, at least to me.

#9 lore raw

PATCH v3 1/1 log --graph: customize the graph lines with config log.graphColors

If you have a 256 colors terminal (or one with true color support), then the predefined 12 colors seem limited. On the other hand, you don't want to draw graph lines with every single color in this mode because the two colors could look extremely similar. This option allows you to hand pick the colors you want.

Even with standard terminal, if your background color is neither black or white, then the graph line may match your background and become hidden. You can exclude your background color (or simply the colors you hate) with this.

---
Compared to v2:

* set_column_colors_by_config() is renamed to set_column_colors()
* no memory leak if the function is called the second time
* proper space trimming since color_parse_mem expects so
* fixed the warning message, giving some context
* at least one test to exercise the code
* I'm not going with the cumulative behavior because I think that's just harder to manage colors, and we would need a way to remove colors from the config too.

 Documentation/config.txt |  4 ++++
 graph.c                  | 46 ++++++++++++++++++++++++++++++++++++++++++++--
 t/t4202-log.sh           | 22 ++++++++++++++++++++++
 3 files changed, 70 insertions(+), 2 deletions(-)
Documentation/config.txt +4 -0
index 0bcb679..33a007b 100644
@@ -2003,6 +2003,10 @@ log.follow::
 	i.e. it cannot be used to follow multiple files and does not work well
 	on non-linear history.
 
+log.graphColors::
+	A list of colors, separated by commas, that can be used to draw
+	history lines in `git log --graph`.
+
 log.showRoot::
 	If true, the initial commit will be shown as a big creation event.
 	This is equivalent to a diff against an empty tree.
graph.c +44 -2

58 lines not on this page · raw message

t/t4202-log.sh +22 -0
index e2db47c..afe0715 100755
@@ -313,6 +313,28 @@ test_expect_success 'log --graph with merge' '
 	test_cmp expect actual
 '
 
+cat > expect.colors <<\EOF
+*   Merge branch 'side'
+<BLUE>|<RESET><CYAN>\<RESET>
+<BLUE>|<RESET> * side-2
+<BLUE>|<RESET> * side-1
+* <CYAN>|<RESET> Second
+* <CYAN>|<RESET> sixth
+* <CYAN>|<RESET> fifth
+* <CYAN>|<RESET> fourth
+<CYAN>|<RESET><CYAN>/<RESET>
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge with log.graphColors' '
+	test_config log.graphColors " blue , cyan , red " &&
+	git log --color=always --graph --date-order --pretty=tformat:%s |
+		test_decode_color | sed "s/ *\$//" >actual &&
+	test_cmp expect.colors actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
 	git log --raw --graph --oneline -m master | head -n 500 >actual &&
 	grep "initial" actual
Signature · 1 line
2.8.2.524.g6ff3d78
#10 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

graph.c enum graph_state { · line 65

Quoted 9 lines · Nguyễn Thái Ngọc Duy
graph.c +1 -0
index dd17201..048f5cb 100644
@@ -62,6 +62,49 @@ enum graph_state {
 static const char **column_colors;
 static unsigned short column_colors_max;
 
+static void set_column_colors(void)

When I said "'by config' sounds funny", I meant "'from config' may be more natural". Perhaps name this read_graph_colors_config(), as that (i.e. reading "log.graphColors") is what it does.

graph.c enum graph_state { · line 77

Quoted 12 lines
graph.c +12 -0
+{
+	static char **colors;
+	static int colors_max, colors_alloc;
+	char *string = NULL;
+	const char *end, *start;
+	int i;
+
+	for (i = 0; i < colors_max; i++)
+		free(colors[i]);
+	if (colors)
+		free(colors[colors_max]);
+	colors_max = 0;

The correctness of the first loop relies on the fact that colors is non-null when colors_max is not zero, and then the freeing of the colors relies on something else. It is not wrong per-se, but it will reduce the "Huh?" factor if you wrote it like so:

	if (colors) {
        	/* 
                 * Reinitialize, but keep the colors[] array.
		 * Note that the last entry is allocated for
		 * reset but colors_max does not count it, hence
		 * "i <= colors_max", not "i < colors_max".
		 */
		int i;
		for (i = 0; i <= colors_max; i++)
			free(colors[i]);
		colors_max = 0;
	}
#11 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

Quoted 18 lines · Junio C Hamano

graph.c enum graph_state { · line 77

Quoted 12 lines
graph.c +12 -0
+{
+	static char **colors;
+	static int colors_max, colors_alloc;
+	char *string = NULL;
+	const char *end, *start;
+	int i;
+
+	for (i = 0; i < colors_max; i++)
+		free(colors[i]);
+	if (colors)
+		free(colors[colors_max]);
+	colors_max = 0;

The correctness of the first loop relies on the fact that colors is non-null when colors_max is not zero, and then the freeing of the colors relies on something else. It is not wrong per-se, but it will reduce the "Huh?" factor if you wrote it like so:

Quoted 12 lines
	if (colors) {
        	/* 
                 * Reinitialize, but keep the colors[] array.
		 * Note that the last entry is allocated for
		 * reset but colors_max does not count it, hence
		 * "i <= colors_max", not "i < colors_max".
		 */
		int i;
		for (i = 0; i <= colors_max; i++)
			free(colors[i]);
		colors_max = 0;
	}

Yeah, I agree that what you've written here is less confusing. Less confusing still would be to keep colors_nr, and deal separately with the "max" interface to graph_set_column_colors().

I also wonder if it is worth just using argv_array. We do not care about NULL-terminating the list here, but it actually works pretty well as a generic string-array class (and keeping a NULL at the end of any array-of-pointers is a reasonable defensive measure). Then the function becomes:

  argv_array_clear(&colors);
  ...
  if (!color_parse_mem(..., color))
          argv_array_push(&colors, color);
  ...
  argv_array_push(&colors, GIT_COLOR_RESET);
  /* graph_set_column_colors takes a max-index, not a count */
  graph_set_column_colors(colors.argv, colors.argc - 1);

It is not much shorter than ALLOC_GROW(), but IMHO it is easier to read.

-Peff

author
#12 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

Quoted 15 lines · Jeff King

I also wonder if it is worth just using argv_array. We do not care about NULL-terminating the list here, but it actually works pretty well as a generic string-array class (and keeping a NULL at the end of any array-of-pointers is a reasonable defensive measure). Then the function becomes:

  argv_array_clear(&colors);
  ...
  if (!color_parse_mem(..., color))
          argv_array_push(&colors, color);
  ...
  argv_array_push(&colors, GIT_COLOR_RESET);
  /* graph_set_column_colors takes a max-index, not a count */
  graph_set_column_colors(colors.argv, colors.argc - 1);
Quoted 1 line

It is not much shorter than ALLOC_GROW(), but IMHO it is easier to read.

Indeed. My only complaint is it's "argv_array_" and not "string_array_" but we could think about renaming it later when we see another use like this.

Signature · 1 line
Duy
#13 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

Quoted 8 lines · Duy Nguyen
Quoted 3 lines · Jeff King

I also wonder if it is worth just using argv_array. We do not care about
...
It is not much shorter than ALLOC_GROW(), but IMHO it is easier to read.

Indeed. My only complaint is it's "argv_array_" and not "string_array_" but we could think about renaming it later when we see another use like this.

Yup, when Peff said "argv-array", it took me a few breaths, until I realized that argv-array was merely an array of strings, to convince myself that the data structure can be used here. If we are going to use it in more places, we may want to rename it somehow.

#14 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

In reply to Nguyễn Thái Ngọc Duy
Quoted 10 lines · Nguyễn Thái Ngọc Duy

If you have a 256 colors terminal (or one with true color support), then the predefined 12 colors seem limited. On the other hand, you don't want to draw graph lines with every single color in this mode because the two colors could look extremely similar. This option allows you to hand pick the colors you want.

Even with standard terminal, if your background color is neither black or white, then the graph line may match your background and become hidden. You can exclude your background color (or simply the colors you hate) with this.

I like this approach much more than the 256-color option.

Quoted 3 lines

* I'm not going with the cumulative behavior because I think that's just harder to manage colors, and we would need a way to remove colors from the config too.

Yeah, figuring out the list semantics would be a pain. This makes it hard to exclude a single color, but I think it's more likely somebody would want to replace the whole set with something that works well against their background.

t/t4202-log.sh test_expect_success 'log --graph with merge' ' · line 332

Quoted 2 lines
t/t4202-log.sh +2 -0
+test_expect_success 'log --graph with merge with log.graphColors' '
+	test_config log.graphColors " blue , cyan , red " &&

This funny syntax isn't required, right? It should work with the more natural:

test_config log.graphColors "blue, cyan, red"

-Peff

author
#15 lore raw

Re: [PATCH v3] log --graph: customize the graph lines with config log.graphColors

Quoted 7 lines · Jeff King

t/t4202-log.sh test_expect_success 'log --graph with merge' ' · line 332

Quoted 2 lines
t/t4202-log.sh +2 -0
+test_expect_success 'log --graph with merge with log.graphColors' '
+     test_config log.graphColors " blue , cyan , red " &&

This funny syntax isn't required, right? It should work with the more natural:

test_config log.graphColors "blue, cyan, red"

Yeah spaces are optional. I just wanted to put extra spaces there because I hit a bug with them.

Signature · 1 line
Duy
#16 lore raw

PATCH v4 1/1 log --graph: customize the graph lines with config log.graphColors

In reply to Nguyễn Thái Ngọc Duy

If you have a 256 colors terminal (or one with true color support), then the predefined 12 colors seem limited. On the other hand, you don't want to draw graph lines with every single color in this mode because the two colors could look extremely similar. This option allows you to hand pick the colors you want.

Even with standard terminal, if your background color is neither black or white, then the graph line may match your background and become hidden. You can exclude your background color (or simply the colors you hate) with this.

---
v4:

* rename the function again (and it reads better)
* use argv_array
* fix a bug with two consecutive commas (after spaces are trimmed)

 Documentation/config.txt |  4 ++++
 graph.c                  | 43 +++++++++++++++++++++++++++++++++++++++++--
 t/t4202-log.sh           | 22 ++++++++++++++++++++++
 3 files changed, 67 insertions(+), 2 deletions(-)
Documentation/config.txt +4 -0
index 0bcb679..33a007b 100644
@@ -2003,6 +2003,10 @@ log.follow::
 	i.e. it cannot be used to follow multiple files and does not work well
 	on non-linear history.
 
+log.graphColors::
+	A list of colors, separated by commas, that can be used to draw
+	history lines in `git log --graph`.
+
 log.showRoot::
 	If true, the initial commit will be shown as a big creation event.
 	This is equivalent to a diff against an empty tree.
graph.c +41 -2

61 lines not on this page · raw message

t/t4202-log.sh +22 -0
index e2db47c..0aeabed 100755
@@ -313,6 +313,28 @@ test_expect_success 'log --graph with merge' '
 	test_cmp expect actual
 '
 
+cat > expect.colors <<\EOF
+*   Merge branch 'side'
+<BLUE>|<RESET><CYAN>\<RESET>
+<BLUE>|<RESET> * side-2
+<BLUE>|<RESET> * side-1
+* <CYAN>|<RESET> Second
+* <CYAN>|<RESET> sixth
+* <CYAN>|<RESET> fifth
+* <CYAN>|<RESET> fourth
+<CYAN>|<RESET><CYAN>/<RESET>
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge with log.graphColors' '
+	test_config log.graphColors ",, blue,invalid-color, cyan, red  , " &&
+	git log --color=always --graph --date-order --pretty=tformat:%s |
+		test_decode_color | sed "s/ *\$//" >actual &&
+	test_cmp expect.colors actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
 	git log --raw --graph --oneline -m master | head -n 500 >actual &&
 	grep "initial" actual
Signature · 1 line
2.8.2.524.g6ff3d78
#17 lore raw

Re: [PATCH v4] log --graph: customize the graph lines with config log.graphColors

graph.c enum graph_state { · line 92

Quoted 13 lines · Nguyễn Thái Ngọc Duy
graph.c +13 -0
+	end = string + strlen(string);
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];
+
+		while (start < comma && isspace(*start))
+			start++;
+		if (start == comma) {
+			start = comma + 1;
+			continue;
+		}
+
+		if (!color_parse_mem(start, comma - start, color))

So you skip the leading blanks but let color_parse_mem() trim the trailing blanks? It would work once the control reaches the loop, but wouldn't that miss

	git -c log.graphColors=' reset , blue, red' log --graph 

as "reset" is not understood by parse_color() and is understood by color_parse_mem() only when the length is given correctly?

I am undecided, but leaning towards declaring that it is a bug in color_parse_mem() not in this caller.

Quoted 16 lines
graph.c +12 -0
+			argv_array_push(&colors, color);
+		else
+			warning(_("ignore invalid color '%.*s' in log.graphColors"),
+				(int)(comma - start), start);
+		start = comma + 1;
+	}
+	free(string);
+	argv_array_push(&colors, GIT_COLOR_RESET);
+	/* graph_set_column_colors takes a max-index, not a count */
+	graph_set_column_colors(colors.argv, colors.argc - 1);
+}
+
 void graph_set_column_colors(const char **colors, unsigned short colors_max)
 {
 	column_colors = colors;

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 251

Quoted 6 lines
graph.c +1 -2
@@ -208,8 +248,7 @@ struct git_graph *graph_init(struct rev_info *opt)
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
 	if (!column_colors)
-		graph_set_column_colors(column_colors_ansi,
-					column_colors_ansi_max);
+		read_graph_colors_config();

Now that the helper is renamed to be about "reading the configuration to figure out the graph colors", the division of labor between the caller and the callee we see here is suboptimal for readability, I would think.

We would want to see the caller to either be

	if (!column_colors) {
		if (read_graph_colors_config())
			; /* ok */
		else                        
			graph_set_column_colors(ansi, ansi_max);
	}

or better yet, something like:

	if (!column_colors) {
		const char **colors = column_colors_ansi;
		int colors_max = column_colors_ansi_max;

		read_graph_colors_config(&colors, &colors_max);
		graph_set_collumn_colors(colors, colors_max);
	}

The last one would make it clear that by default we use ansi but override that default by calling that function whose purpose is to read the values that override the default from the configuration.

I haven't thought things thru regarding memory leakages etc., so there may be valid reasons why the last one is infeasible.

author
#18 lore raw

Re: [PATCH v4] log --graph: customize the graph lines with config log.graphColors

Just FYI. The broken internet cables in Vietnam seem to hit my ISP really hard. It's nearly impossible to make a TCP connection. So I'm basically off the grid, hopefully not longer than two weeks.

Quoted 23 lines · Junio C Hamano

graph.c enum graph_state { · line 92

Quoted 13 lines · Nguyễn Thái Ngọc Duy
graph.c +13 -0
+	end = string + strlen(string);
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];
+
+		while (start < comma && isspace(*start))
+			start++;
+		if (start == comma) {
+			start = comma + 1;
+			continue;
+		}
+
+		if (!color_parse_mem(start, comma - start, color))

So you skip the leading blanks but let color_parse_mem() trim the trailing blanks? It would work once the control reaches the loop, but wouldn't that miss

	git -c log.graphColors=' reset , blue, red' log --graph

as "reset" is not

#19 lore raw

v5 cover nd/log-graph-configurable-colors

In reply to Nguyễn Thái Ngọc Duy

v5 moves space trimming to color_parse_mem() from read_graph_colors_config, which is renamed to parse_graph... because the config reading is moved back to graph_init.

I think it looks better, but we may be pushing the limits of argv_array's abuse.

Nguyễn Thái Ngọc Duy (3):
color.c: fix color_parse_mem() with value_len == 0 color.c: trim leading spaces in color_parse_mem()
log --graph: customize the graph lines with config log.graphColors

 Documentation/config.txt |  4 ++++
 color.c                  | 10 +++++++++-
 graph.c                  | 42 +++++++++++++++++++++++++++++++++++++++---
 t/t4202-log.sh           | 22 ++++++++++++++++++++++
 4 files changed, 74 insertions(+), 4 deletions(-)
Signature · 1 line
2.8.2.524.g6ff3d78
#20 lore raw

PATCH v5 1/3 color.c: fix color_parse_mem() with value_len == 0

In this code we want to match the word "reset". If len is zero, strncasecmp() will return zero and we incorrectly assume it's "reset" as a result.

---
 color.c | 3 +++
 1 file changed, 3 insertions(+)
color.c +3 -0
index 81c2676..a9eadd1 100644
@@ -207,6 +207,9 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 	struct color fg = { COLOR_UNSPECIFIED };
 	struct color bg = { COLOR_UNSPECIFIED };
 
+	if (!len)
+		return -1;
+
 	if (!strncasecmp(value, "reset", len)) {
 		xsnprintf(dst, end - dst, GIT_COLOR_RESET);
 		return 0;
Signature · 1 line
2.8.2.524.g6ff3d78
#21 lore raw

Re: [PATCH v5 1/3] color.c: fix color_parse_mem() with value_len == 0

Quoted 3 lines · Nguyễn Thái Ngọc Duy

In this code we want to match the word "reset". If len is zero, strncasecmp() will return zero and we incorrectly assume it's "reset" as a result.

This is probably a good idea. This _is_ user-visible, so it's possible somebody was using empty config as a synonym for "reset". But since it was never documented, I feel like relying on that is somewhat crazy.

-Peff

#22 lore raw

Re: [PATCH v5 1/3] color.c: fix color_parse_mem() with value_len == 0

Quoted 9 lines · Jeff King
Quoted 3 lines · Nguyễn Thái Ngọc Duy

In this code we want to match the word "reset". If len is zero, strncasecmp() will return zero and we incorrectly assume it's "reset" as a result.

This is probably a good idea. This _is_ user-visible, so it's possible somebody was using empty config as a synonym for "reset". But since it was never documented, I feel like relying on that is somewhat crazy.

Hrm. This seems to break the add--interactive script if you do not have color.diff.plain set:

  $ GIT_TRACE=1 git add -p
  ...
  22:58:12.568990 [pid=11401] git.c:387  trace: built-in: git 'config' '--get-color' 'color.diff.plain' ''
  fatal: unable to parse default color value
  config --get-color color.diff.plain : command returned error: 128

As you can see, the default value the empty string, which is now an error.

The default in the C code for that value is GIT_COLOR_NORMAL, which really is the empty string. So I think the old code was buggy to choose "reset", but the new one is worse because it fails entirely. :)

We probably want something like this instead:

color.c +4 -2
index 190b2da96..dee61557e 100644
@@ -212,8 +212,10 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 		len--;
 	}
 
-	if (!len)
-		return -1;
+	if (!len) {
+		dst[0] = '\0';
+		return 0;
+	}
 
 	if (!strncasecmp(ptr, "reset", len)) {
 		xsnprintf(dst, end - dst, GIT_COLOR_RESET);

The breakage is in 'next' (it looks like it went out a few days ago; I'm surprised I didn't notice until now).

-Peff

#23 lore raw

PATCH v5 2/3 color.c: trim leading spaces in color_parse_mem()

In reply to Nguyễn Thái Ngọc Duy

Normally color_parse_mem() is called from config parser which trims the leading spaces already. The new caller in the next patch won't. Let's be tidy and trim leading spaces too (we already trim trailing spaces before comma).

---
 color.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
color.c +6 -1
index a9eadd1..7bb4a96 100644
@@ -207,10 +207,15 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 	struct color fg = { COLOR_UNSPECIFIED };
 	struct color bg = { COLOR_UNSPECIFIED };
 
+	while (len > 0 && isspace(*ptr)) {
+		ptr++;
+		len--;
+	}
+
 	if (!len)
 		return -1;
 
-	if (!strncasecmp(value, "reset", len)) {
+	if (!strncasecmp(ptr, "reset", len)) {
 		xsnprintf(dst, end - dst, GIT_COLOR_RESET);
 		return 0;
 	}
Signature · 1 line
2.8.2.524.g6ff3d78
#24 lore raw

Re: [PATCH v5 2/3] color.c: trim leading spaces in color_parse_mem()

Quoted 4 lines · Nguyễn Thái Ngọc Duy

Normally color_parse_mem() is called from config parser which trims the leading spaces already. The new caller in the next patch won't. Let's be tidy and trim leading spaces too (we already trim trailing spaces before comma).

What comma? I don't think that exists until the next patch. :)

I think just trimming from the front is OK, though, because color_parse_mem() trims trailing whitespace after a word. So either you have a word and we will trim after it, or you do not (in which case this will trim everything and hit the !len case you added).

So maybe a better commit message is just:

Normally color_parse_mem() is called from config parser which trims the leading spaces already. The new caller in the next patch won't. Let's be tidy and trim leading spaces too (we already trim trailing spaces after a word).

-Peff

#25 lore raw

Re: [PATCH v5 2/3] color.c: trim leading spaces in color_parse_mem()

Quoted 14 lines · Jeff King
Quoted 4 lines · Nguyễn Thái Ngọc Duy

Normally color_parse_mem() is called from config parser which trims the leading spaces already. The new caller in the next patch won't. Let's be tidy and trim leading spaces too (we already trim trailing spaces before comma).

What comma? I don't think that exists until the next patch. :)

I think just trimming from the front is OK, though, because color_parse_mem() trims trailing whitespace after a word. So either you have a word and we will trim after it, or you do not (in which case this will trim everything and hit the !len case you added).

Quoted 8 lines

So maybe a better commit message is just:

Normally color_parse_mem() is called from config parser which trims the leading spaces already. The new caller in the next patch won't. Let's be tidy and trim leading spaces too (we already trim trailing spaces after a word).

-Peff

Yeah, my reading stuttered at the same place in the original, and your rewrite looks a lot more sensible.

#26 lore raw

PATCH v5 3/3 log --graph: customize the graph lines with config log.graphColors

In reply to Nguyễn Thái Ngọc Duy

If you have a 256 colors terminal (or one with true color support), then the predefined 12 colors seem limited. On the other hand, you don't want to draw graph lines with every single color in this mode because the two colors could look extremely similar. This option allows you to hand pick the colors you want.

Even with standard terminal, if your background color is neither black or white, then the graph line may match your background and become hidden. You can exclude your background color (or simply the colors you hate) with this.

---
 Documentation/config.txt |  4 ++++
 graph.c                  | 42 +++++++++++++++++++++++++++++++++++++++---
 t/t4202-log.sh           | 22 ++++++++++++++++++++++
 3 files changed, 65 insertions(+), 3 deletions(-)
Documentation/config.txt +4 -0
index 0bcb679..33a007b 100644
@@ -2003,6 +2003,10 @@ log.follow::
 	i.e. it cannot be used to follow multiple files and does not work well
 	on non-linear history.
 
+log.graphColors::
+	A list of colors, separated by commas, that can be used to draw
+	history lines in `git log --graph`.
+
 log.showRoot::
 	If true, the initial commit will be shown as a big creation event.
 	This is equivalent to a diff against an empty tree.
graph.c +39 -3

60 lines not on this page · raw message

t/t4202-log.sh +22 -0
index e2db47c..0aeabed 100755
@@ -313,6 +313,28 @@ test_expect_success 'log --graph with merge' '
 	test_cmp expect actual
 '
 
+cat > expect.colors <<\EOF
+*   Merge branch 'side'
+<BLUE>|<RESET><CYAN>\<RESET>
+<BLUE>|<RESET> * side-2
+<BLUE>|<RESET> * side-1
+* <CYAN>|<RESET> Second
+* <CYAN>|<RESET> sixth
+* <CYAN>|<RESET> fifth
+* <CYAN>|<RESET> fourth
+<CYAN>|<RESET><CYAN>/<RESET>
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge with log.graphColors' '
+	test_config log.graphColors ",, blue,invalid-color, cyan, red  , " &&
+	git log --color=always --graph --date-order --pretty=tformat:%s |
+		test_decode_color | sed "s/ *\$//" >actual &&
+	test_cmp expect.colors actual
+'
+
 test_expect_success 'log --raw --graph -m with merge' '
 	git log --raw --graph --oneline -m master | head -n 500 >actual &&
 	grep "initial" actual
Signature · 1 line
2.8.2.524.g6ff3d78
#27 lore raw

Re: [PATCH v5 3/3] log --graph: customize the graph lines with config log.graphColors

Quoted 9 lines · Nguyễn Thái Ngọc Duy
graph.c +9 -0
+static void parse_graph_colors_config(struct argv_array *colors, const char *string)
+{
+	const char *end, *start;
+
+	start = string;
+	end = string + strlen(string);
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];

graph.c enum graph_state { · line 84

Quoted 10 lines
graph.c +10 -0
+
+		if (!color_parse_mem(start, comma - start, color))
+			argv_array_push(colors, color);
+		else
+			warning(_("ignore invalid color '%.*s' in log.graphColors"),
+				(int)(comma - start), start);
+		start = comma + 1;
+	}
+	argv_array_push(colors, GIT_COLOR_RESET);
+}

This looks good.

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 235

Quoted 12 lines
graph.c +5 -3
@@ -207,9 +228,24 @@ struct git_graph *graph_init(struct rev_info *opt)
 {
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
-	if (!column_colors)
-		graph_set_column_colors(column_colors_ansi,
-					column_colors_ansi_max);
+	if (!column_colors) {
+		struct argv_array ansi_colors = {
+			column_colors_ansi,
+			column_colors_ansi_max + 1
+		};

Hrm. At first I thought this would cause memory corrution, because your argv_array_clear() would try to free() the non-heap array you've stuffed inside. But you only clear the custom_colors array which actually is dynamically allocated. This outer one is just here to give uniform access:

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 248

Quoted 13 lines
graph.c +13 -0
+		struct argv_array *colors = &ansi_colors;
+		char *string;
+
+		if (!git_config_get_string("log.graphcolors", &string)) {
+			static struct argv_array custom_colors = ARGV_ARRAY_INIT;
+			argv_array_clear(&custom_colors);
+			parse_graph_colors_config(&custom_colors, string);
+			free(string);
+			colors = &custom_colors;
+		}
+		/* graph_set_column_colors takes a max-index, not a count */
+		graph_set_column_colors(colors->argv, colors->argc - 1);
+	}

Since there's only one line that cares about the result of "colors", maybe it would be less confusing to do:

  if (!git_config_get-string("log.graphcolors", &string)) {
	... parse, etc ...
	graph_set_column_colors(colors.argv, colors.argc - 1);
  } else {
	graph_set_column_colors(column_colors_ansi,
	                        column_colors_ansi_max);

}

-Peff

#28 lore raw

Re: [PATCH v5 3/3] log --graph: customize the graph lines with config log.graphColors

Quoted 60 lines · Jeff King
Quoted 9 lines · Nguyễn Thái Ngọc Duy
graph.c +9 -0
+static void parse_graph_colors_config(struct argv_array *colors, const char *string)
+{
+	const char *end, *start;
+
+	start = string;
+	end = string + strlen(string);
+	while (start < end) {
+		const char *comma = strchrnul(start, ',');
+		char color[COLOR_MAXLEN];

graph.c enum graph_state { · line 84

Quoted 10 lines
graph.c +10 -0
+
+		if (!color_parse_mem(start, comma - start, color))
+			argv_array_push(colors, color);
+		else
+			warning(_("ignore invalid color '%.*s' in log.graphColors"),
+				(int)(comma - start), start);
+		start = comma + 1;
+	}
+	argv_array_push(colors, GIT_COLOR_RESET);
+}

This looks good.

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 235

Quoted 12 lines
graph.c +5 -3
@@ -207,9 +228,24 @@ struct git_graph *graph_init(struct rev_info *opt)
 {
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
-	if (!column_colors)
-		graph_set_column_colors(column_colors_ansi,
-					column_colors_ansi_max);
+	if (!column_colors) {
+		struct argv_array ansi_colors = {
+			column_colors_ansi,
+			column_colors_ansi_max + 1
+		};

Hrm. At first I thought this would cause memory corrution, because your argv_array_clear() would try to free() the non-heap array you've stuffed inside. But you only clear the custom_colors array which actually is dynamically allocated. This outer one is just here to give uniform access:

graph.c struct git_graph *graph_init(struct rev_info *opt) · line 248

Quoted 13 lines
graph.c +13 -0
+		struct argv_array *colors = &ansi_colors;
+		char *string;
+
+		if (!git_config_get_string("log.graphcolors", &string)) {
+			static struct argv_array custom_colors = ARGV_ARRAY_INIT;
+			argv_array_clear(&custom_colors);
+			parse_graph_colors_config(&custom_colors, string);
+			free(string);
+			colors = &custom_colors;
+		}
+		/* graph_set_column_colors takes a max-index, not a count */
+		graph_set_column_colors(colors->argv, colors->argc - 1);
+	}

Since there's only one line that cares about the result of "colors", maybe it would be less confusing to do:

Quoted 7 lines
  if (!git_config_get-string("log.graphcolors", &string)) {
	... parse, etc ...
	graph_set_column_colors(colors.argv, colors.argc - 1);
  } else {
	graph_set_column_colors(column_colors_ansi,
	                        column_colors_ansi_max);

}

Yes, that would be much much less confusing. By doing so, the cover letter can lose "pushing the limits of abuse", too ;-).

#29 lore raw

Re: [PATCH v5 3/3] log --graph: customize the graph lines with config log.graphColors

Quoted 11 lines · Junio C Hamano
Quoted 10 lines

Since there's only one line that cares about the result of "colors", maybe it would be less confusing to do:

  if (!git_config_get-string("log.graphcolors", &string)) {
	... parse, etc ...
	graph_set_column_colors(colors.argv, colors.argc - 1);
  } else {
	graph_set_column_colors(column_colors_ansi,
	                        column_colors_ansi_max);

}

Quoted 2 lines

Yes, that would be much much less confusing. By doing so, the cover letter can lose "pushing the limits of abuse", too ;-).

Will queue with a squashable change.

Thanks.

 graph.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)
graph.c +8 -10
index 822d40ae20..00aeee36d8 100644
@@ -229,22 +229,20 @@ struct git_graph *graph_init(struct rev_info *opt)
 	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
 
 	if (!column_colors) {
-		struct argv_array ansi_colors = {
-			column_colors_ansi,
-			column_colors_ansi_max + 1
-		};
-		struct argv_array *colors = &ansi_colors;
 		char *string;
-
-		if (!git_config_get_string("log.graphcolors", &string)) {
+		if (git_config_get_string("log.graphcolors", &string)) {
+			/* not configured -- use default */
+			graph_set_column_colors(column_colors_ansi,
+						column_colors_ansi_max);
+		} else {
 			static struct argv_array custom_colors = ARGV_ARRAY_INIT;
 			argv_array_clear(&custom_colors);
 			parse_graph_colors_config(&custom_colors, string);
 			free(string);
-			colors = &custom_colors;
+			/* graph_set_column_colors takes a max-index, not a count */
+			graph_set_column_colors(custom_colors.argv,
+						custom_colors.argc - 1);
 		}
-		/* graph_set_column_colors takes a max-index, not a count */
-		graph_set_column_colors(colors->argv, colors->argc - 1);
 	}
 
 	graph->commit = NULL;