* [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns
@ 2026-03-16 13:34 Pablo Sabater
2026-03-16 17:04 ` Karthik Nayak
2026-03-17 22:09 ` [GSoC RFC PATCH v2] graph: add --max-columns " Pablo Sabater
0 siblings, 2 replies; 39+ messages in thread
From: Pablo Sabater @ 2026-03-16 13:34 UTC (permalink / raw)
To: git
Cc: christian.couder, karthik.188, jltobler, ayu.chandekar,
siddharthasthana31, chandrapratap3519, Pablo Sabater
When there are multiple branches, --graph-max modifies the maximum
amount of columns that will be displayed.
Add "--graph-max=<n>" option to cap how many columns will be shown,
columns after the limit are replaced with a single '.'. Changes only
the output rendering.
Define MINIMUM_GRAPH_COLUMNS constant to validate the option value.
The commit character '*' is always shown no matter what the limit is.
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
This addresses the TODO at graph.c:
TODO:
- Limit the number of columns, similar to the way gitk does.
If we reach more than a specified number of columns, omit
sections of some columns.
About the design of how this would have to be:
- Should '--graph-max' by itself be enough to implicitly work like '--graph' so
'git log --graph-max=3' works without needing to write '--graph'?
- graph_max_columns by default is set to 0, meaning no limit, and any other
positive value becomes a limit. Is this a good design? it cannot be negative,
shouldn't it be a uint32_t instead, I left it as a int because of the other
variables like this that are int. like skip_count, max_count, etc.
- Is '--graph-max' a good name?
- Is '.' a good char for truncation?
- Should '/' to outside branches be shown?
- What should it be done when a commit is in a column that is truncated?
known limitations:
- Post merge lines have some trouble with the padding.
I added two tests for example, but I will add better test coverage as design
choices are more clear. testing on the Git repo itself is a good example also.
graph.c | 52 +++++++++++++++++++++++++++------
graph.h | 2 ++
revision.c | 7 +++++
revision.h | 1 +
t/t4215-log-skewed-merges.sh | 56 ++++++++++++++++++++++++++++++++++++
5 files changed, 109 insertions(+), 9 deletions(-)
diff --git a/graph.c b/graph.c
index 26f6fbf000..7ae0ab61b7 100644
--- a/graph.c
+++ b/graph.c
@@ -42,14 +42,6 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
static void graph_show_strbuf(struct git_graph *graph,
FILE *file,
struct strbuf const *sb);
-
-/*
- * TODO:
- * - Limit the number of columns, similar to the way gitk does.
- * If we reach more than a specified number of columns, omit
- * sections of some columns.
- */
-
struct column {
/*
* The parent commit of this column.
@@ -317,6 +309,12 @@ struct git_graph {
struct strbuf prefix_buf;
};
+static int graph_is_truncated(struct git_graph *graph, int col)
+{
+ int max = graph->revs->graph_max_columns;
+ return max > 0 && col >= max;
+}
+
static const char *diff_output_prefix_callback(struct diff_options *opt, void *data)
{
struct git_graph *graph = data;
@@ -846,6 +844,10 @@ 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++) {
+ if (graph_is_truncated(graph, i)) {
+ graph_line_addstr(line, ". ");
+ break;
+ }
graph_line_write_column(line, &graph->new_columns[i], '|');
graph_line_addch(line, ' ');
}
@@ -903,6 +905,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
seen_this = 1;
graph_line_write_column(line, col, '|');
graph_line_addchars(line, ' ', graph->expansion_row);
+ } else if (seen_this && graph_is_truncated(graph, i)) {
+ graph_line_addstr(line, ". ");
+ break;
} else if (seen_this && (graph->expansion_row == 0)) {
/*
* This is the first line of the pre-commit output.
@@ -1013,6 +1018,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
* children that we have already processed.)
*/
seen_this = 0;
+
for (i = 0; i <= graph->num_columns; i++) {
struct column *col = &graph->columns[i];
struct commit *col_commit;
@@ -1028,8 +1034,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
seen_this = 1;
graph_output_commit_char(graph, line);
+ if (graph_is_truncated(graph, i))
+ break;
+
if (graph->num_parents > 2)
graph_draw_octopus_merge(graph, line);
+ } else if (seen_this && graph_is_truncated(graph, i)) {
+ graph_line_addstr(line, ". ");
+ break;
} else if (seen_this && (graph->edges_added > 1)) {
graph_line_write_column(line, col, '\\');
} else if (seen_this && (graph->edges_added == 1)) {
@@ -1109,9 +1121,15 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
int par_column;
int idx = graph->merge_layout;
char c;
+ int truncated = 0;
seen_this = 1;
for (j = 0; j < graph->num_parents; j++) {
+ if (graph_is_truncated(graph, i + j)) {
+ graph_line_addstr(line, ". ");
+ truncated = 1;
+ break;
+ }
par_column = graph_find_new_column_by_commit(graph, parents->item);
assert(par_column >= 0);
@@ -1125,10 +1143,15 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
}
parents = next_interesting_parent(graph, parents);
}
+ if (truncated)
+ break;
if (graph->edges_added == 0)
graph_line_addch(line, ' ');
-
} else if (seen_this) {
+ if (graph_is_truncated(graph, i)) {
+ graph_line_addstr(line, ". ");
+ break;
+ }
if (graph->edges_added > 0)
graph_line_write_column(line, col, '\\');
else
@@ -1279,6 +1302,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
*/
for (i = 0; i < graph->mapping_size; i++) {
int target = graph->mapping[i];
+
+ if (graph_is_truncated(graph, i / 2)) {
+ graph_line_addstr(line, ". ");
+ break;
+ }
+
if (target < 0)
graph_line_addch(line, ' ');
else if (target * 2 == i)
@@ -1372,6 +1401,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
for (i = 0; i < graph->num_columns; i++) {
struct column *col = &graph->columns[i];
+ if (graph_is_truncated(graph, i)) {
+ graph_line_addch(&line, '.');
+ break;
+ }
+
graph_line_write_column(&line, col, '|');
if (col->commit == graph->commit && graph->num_parents > 2) {
diff --git a/graph.h b/graph.h
index 3fd1dcb2e9..9a4551dd29 100644
--- a/graph.h
+++ b/graph.h
@@ -262,4 +262,6 @@ void graph_show_commit_msg(struct git_graph *graph,
FILE *file,
struct strbuf const *sb);
+#define MINIMUM_GRAPH_COLUMNS 1
+
#endif /* GRAPH_H */
diff --git a/revision.c b/revision.c
index 31808e3df0..ba5088be14 100644
--- a/revision.c
+++ b/revision.c
@@ -2605,6 +2605,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--no-graph")) {
graph_clear(revs->graph);
revs->graph = NULL;
+ } else if (skip_prefix(arg, "--graph-max=", &optarg)) {
+ revs->graph_max_columns = strtoul(optarg, NULL, 10);
+ if (revs->graph_max_columns < MINIMUM_GRAPH_COLUMNS) {
+ die(_("minimum columns is %d, unable to set below %d"),
+ MINIMUM_GRAPH_COLUMNS,
+ revs->graph_max_columns);
+ }
} else if (!strcmp(arg, "--encode-email-headers")) {
revs->encode_email_headers = 1;
} else if (!strcmp(arg, "--no-encode-email-headers")) {
diff --git a/revision.h b/revision.h
index 69242ecb18..6442129c14 100644
--- a/revision.h
+++ b/revision.h
@@ -304,6 +304,7 @@ struct rev_info {
/* Display history graph */
struct git_graph *graph;
+ int graph_max_columns;
/* special limits */
int skip_count;
diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh
index 28d0779a8c..6266de4e2b 100755
--- a/t/t4215-log-skewed-merges.sh
+++ b/t/t4215-log-skewed-merges.sh
@@ -370,4 +370,60 @@ test_expect_success 'log --graph with multiple tips' '
EOF
'
+test_expect_success 'log --graph --graph-max=2 only two columns' '
+ check_graph --graph-max=2 M_7 <<-\EOF
+ *-. 7_M4
+ |\ .
+ | | * 7_G
+ | | * 7_F
+ | * . 7_E
+ | * . 7_D
+ * | . 7_C
+ | |/
+ |/|
+ * | 7_B
+ |/
+ * 7_A
+ EOF
+'
+
+test_expect_success 'log --graph --graph-max=3 only three columns' '
+ check_graph --graph-max=3 M_1 M_3 M_5 M_7 <<-\EOF
+ * 7_M1
+ |\
+ | | * 7_M2
+ | | |.
+ | | | * 7_H
+ | | | | * 7_M3
+ | | | | .
+ | | | | | * 7_J
+ | | | | * 7_I
+ | | | | | | * 7_M4
+ | |_|_|_|_|.
+ |/| | .
+ | | |_.
+ | |/|_.
+ | |/|_.
+ | |/| .
+ | | |/.
+ | | * . 7_G
+ | | | .
+ | | |/.
+ | | |/.
+ | | * . 7_F
+ | * | . 7_E
+ | | |/.
+ | |/| .
+ | * | . 7_D
+ | | |/
+ | |/|
+ * | | 7_C
+ | |/
+ |/|
+ * | 7_B
+ |/
+ * 7_A
+ EOF
+'
+
test_done
--
2.43.0
^ permalink raw reply related [flat|nested] 39+ messages in thread* Re: [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns 2026-03-16 13:34 [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns Pablo Sabater @ 2026-03-16 17:04 ` Karthik Nayak 2026-03-16 19:48 ` Pablo 2026-03-17 22:09 ` [GSoC RFC PATCH v2] graph: add --max-columns " Pablo Sabater 1 sibling, 1 reply; 39+ messages in thread From: Karthik Nayak @ 2026-03-16 17:04 UTC (permalink / raw) To: Pablo Sabater, git Cc: christian.couder, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519 [-- Attachment #1: Type: text/plain, Size: 13550 bytes --] Pablo Sabater <pabloosabaterr@gmail.com> writes: > When there are multiple branches, --graph-max modifies the maximum > amount of columns that will be displayed. > > Add "--graph-max=<n>" option to cap how many columns will be shown, > columns after the limit are replaced with a single '.'. Changes only > the output rendering. > The first sentence seems to talk about the option like it already exists, when the second para introduces it. It would be nice if the first para explained the problem we're trying to solve and why and the second para then dove into the solution space. Do you think '--graph-max' signifies that we're talking about the maximum columns to display? > Define MINIMUM_GRAPH_COLUMNS constant to validate the option value. What does this mean? Validate how? > The commit character '*' is always shown no matter what the limit is. I think overall a little more explanation in the commit message makes it easier to understand the context and also helps reviewers! Shouldn't we also talk about the todo and the commit (c12172d2ea (Add history graph API, 2008-05-04)) in which it was added? > > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> > --- > > This addresses the TODO at graph.c: > > TODO: > - Limit the number of columns, similar to the way gitk does. > If we reach more than a specified number of columns, omit > sections of some columns. > One question to ask is, is this even needed anymore and does it really make sense to add it? The TODO was added back in 2008, that's ~16 years ago and was not touched till now. So perhaps no one needs it? If so, maybe the smarter option is to simply remove the TODO? Or do you see a usecase where this is useful? If so, it would be nice to talk about that in the commit message. > About the design of how this would have to be: > > - Should '--graph-max' by itself be enough to implicitly work like '--graph' so > 'git log --graph-max=3' works without needing to write '--graph'? My preference would be not to have implicit behavior but also at the same time guide the user in the right path, so: $ git log --graph-max=4 fatal: --graph-max used without --graph > - graph_max_columns by default is set to 0, meaning no limit, and any other > positive value becomes a limit. Is this a good design? it cannot be negative, > shouldn't it be a uint32_t instead, I left it as a int because of the other > variables like this that are int. like skip_count, max_count, etc. > - Is '--graph-max' a good name? I would argue against it. Perhaps '--graph-col-limit'? It's a bit handy though. > - Is '.' a good char for truncation? Trying it out: $ git log --graph --oneline --graph-max=3 | * ba1c21d343 odb: split `struct odb_source` into separate header | * b1af291b4a Merge branch 'ps/object-info-bits-cleanup' into ps/odb-sources | |\ | * \ 703c97519d Merge branch 'ps/odb-for-each-object' into ps/odb-sources | |\ \ * | \ . d0413b31dd Merge branch 'hn/status-compare-with-push' |\ \ \ . | * | . 68791d7506 status: clarify how status.compareBranches deduplicates | * | . 3ea95ac9c5 (gitster/hn/status-compare-with-push) status: add status.compareBranches config for multiple branch comparisons | * | . 04f47265c1 refactor format_branch_comparison in preparation | * | . 2aa9b75b43 Merge branch 'jk/remote-tracking-ref-leakfix' into hn/status-compare-with-push | |\ \ . * | \ . 03161747b4 Merge branch 'ds/for-each-repo-w-worktree' |\ \ \ . | * | . e87493b9b4 for-each-repo: simplify passing of parameters | * | . 2ef539bcee for-each-repo: work correctly in a worktree | * | . 5f031fe4f1 run-command: extract sanitize_repo_env helper | * | . c5e62e1aa0 for-each-repo: test outside of repo context * | | . 67006b9db8 The 15th batch * | | . 99da934835 Merge branch 'sp/send-email-validate-charset' |\ \ \ . | * | . c52f085a47 (gitster/sp/send-email-validate-charset) send-email: validate charset name in 8bit encoding prompt vs $ git log --graph --oneline | * ba1c21d343 odb: split `struct odb_source` into separate header | * b1af291b4a Merge branch 'ps/object-info-bits-cleanup' into ps/odb-sources | |\ | * \ 703c97519d Merge branch 'ps/odb-for-each-object' into ps/odb-sources | |\ \ * | \ \ d0413b31dd Merge branch 'hn/status-compare-with-push' |\ \ \ \ | * | | | 68791d7506 status: clarify how status.compareBranches deduplicates | * | | | 3ea95ac9c5 (gitster/hn/status-compare-with-push) status: add status.compareBranches config for multiple branch comparisons | * | | | 04f47265c1 refactor format_branch_comparison in preparation | * | | | 2aa9b75b43 Merge branch 'jk/remote-tracking-ref-leakfix' into hn/status-compare-with-push | |\ \ \ \ * | \ \ \ \ 03161747b4 Merge branch 'ds/for-each-repo-w-worktree' |\ \ \ \ \ \ | * | | | | | e87493b9b4 for-each-repo: simplify passing of parameters | * | | | | | 2ef539bcee for-each-repo: work correctly in a worktree | * | | | | | 5f031fe4f1 run-command: extract sanitize_repo_env helper | * | | | | | c5e62e1aa0 for-each-repo: test outside of repo context * | | | | | | 67006b9db8 The 15th batch * | | | | | | 99da934835 Merge branch 'sp/send-email-validate-charset' |\ \ \ \ \ \ \ | * | | | | | | c52f085a47 (gitster/sp/send-email-validate-charset) send-email: validate charset name in 8bit encoding prompt So we still keep the spaces, but only remove the column indicator > - Should '/' to outside branches be shown? > - What should it be done when a commit is in a column that is truncated? > > known limitations: > > - Post merge lines have some trouble with the padding. > > I added two tests for example, but I will add better test coverage as design > choices are more clear. testing on the Git repo itself is a good example also. > > graph.c | 52 +++++++++++++++++++++++++++------ > graph.h | 2 ++ > revision.c | 7 +++++ > revision.h | 1 + > t/t4215-log-skewed-merges.sh | 56 ++++++++++++++++++++++++++++++++++++ > 5 files changed, 109 insertions(+), 9 deletions(-) > > diff --git a/graph.c b/graph.c > index 26f6fbf000..7ae0ab61b7 100644 > --- a/graph.c > +++ b/graph.c > @@ -42,14 +42,6 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb); > static void graph_show_strbuf(struct git_graph *graph, > FILE *file, > struct strbuf const *sb); > - > -/* > - * TODO: > - * - Limit the number of columns, similar to the way gitk does. > - * If we reach more than a specified number of columns, omit > - * sections of some columns. > - */ > - > struct column { > /* > * The parent commit of this column. > @@ -317,6 +309,12 @@ struct git_graph { > struct strbuf prefix_buf; > }; > > +static int graph_is_truncated(struct git_graph *graph, int col) Isn't this more of `graphs_needs_truncation()`? > +{ > + int max = graph->revs->graph_max_columns; > + return max > 0 && col >= max; > +} > + > static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) > { > struct git_graph *graph = data; > @@ -846,6 +844,10 @@ 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++) { > + if (graph_is_truncated(graph, i)) { > + graph_line_addstr(line, ". "); > + break; > + } > graph_line_write_column(line, &graph->new_columns[i], '|'); > graph_line_addch(line, ' '); > } > @@ -903,6 +905,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, > seen_this = 1; > graph_line_write_column(line, col, '|'); > graph_line_addchars(line, ' ', graph->expansion_row); > + } else if (seen_this && graph_is_truncated(graph, i)) { > + graph_line_addstr(line, ". "); > + break; > } else if (seen_this && (graph->expansion_row == 0)) { > /* > * This is the first line of the pre-commit output. > @@ -1013,6 +1018,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line > * children that we have already processed.) > */ > seen_this = 0; > + > for (i = 0; i <= graph->num_columns; i++) { > struct column *col = &graph->columns[i]; > struct commit *col_commit; > @@ -1028,8 +1034,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line > seen_this = 1; > graph_output_commit_char(graph, line); > > + if (graph_is_truncated(graph, i)) > + break; > + > if (graph->num_parents > 2) > graph_draw_octopus_merge(graph, line); > + } else if (seen_this && graph_is_truncated(graph, i)) { > + graph_line_addstr(line, ". "); > + break; > } else if (seen_this && (graph->edges_added > 1)) { > graph_line_write_column(line, col, '\\'); > } else if (seen_this && (graph->edges_added == 1)) { > @@ -1109,9 +1121,15 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l > int par_column; > int idx = graph->merge_layout; > char c; > + int truncated = 0; > seen_this = 1; > > for (j = 0; j < graph->num_parents; j++) { > + if (graph_is_truncated(graph, i + j)) { > + graph_line_addstr(line, ". "); > + truncated = 1; > + break; > + } > par_column = graph_find_new_column_by_commit(graph, parents->item); > assert(par_column >= 0); > > @@ -1125,10 +1143,15 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l > } > parents = next_interesting_parent(graph, parents); > } > + if (truncated) > + break; > if (graph->edges_added == 0) > graph_line_addch(line, ' '); > - > } else if (seen_this) { > + if (graph_is_truncated(graph, i)) { > + graph_line_addstr(line, ". "); > + break; > + } > if (graph->edges_added > 0) > graph_line_write_column(line, col, '\\'); > else > @@ -1279,6 +1302,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l > */ > for (i = 0; i < graph->mapping_size; i++) { > int target = graph->mapping[i]; > + > + if (graph_is_truncated(graph, i / 2)) { > + graph_line_addstr(line, ". "); > + break; > + } > + > if (target < 0) > graph_line_addch(line, ' '); > else if (target * 2 == i) > @@ -1372,6 +1401,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) > for (i = 0; i < graph->num_columns; i++) { > struct column *col = &graph->columns[i]; > > + if (graph_is_truncated(graph, i)) { > + graph_line_addch(&line, '.'); > + break; > + } > + > graph_line_write_column(&line, col, '|'); > > if (col->commit == graph->commit && graph->num_parents > 2) { > diff --git a/graph.h b/graph.h > index 3fd1dcb2e9..9a4551dd29 100644 > --- a/graph.h > +++ b/graph.h > @@ -262,4 +262,6 @@ void graph_show_commit_msg(struct git_graph *graph, > FILE *file, > struct strbuf const *sb); > > +#define MINIMUM_GRAPH_COLUMNS 1 > + > #endif /* GRAPH_H */ > diff --git a/revision.c b/revision.c > index 31808e3df0..ba5088be14 100644 > --- a/revision.c > +++ b/revision.c > @@ -2605,6 +2605,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg > } else if (!strcmp(arg, "--no-graph")) { > graph_clear(revs->graph); > revs->graph = NULL; > + } else if (skip_prefix(arg, "--graph-max=", &optarg)) { > + revs->graph_max_columns = strtoul(optarg, NULL, 10); > + if (revs->graph_max_columns < MINIMUM_GRAPH_COLUMNS) { > + die(_("minimum columns is %d, unable to set below %d"), > + MINIMUM_GRAPH_COLUMNS, > + revs->graph_max_columns); Shouldn't we allow users to set 0? That combined with an unsigned int would: 1. remove the need for MINIMUM_GRAPH_COLUMNS 2. allow users to specify that they do not want a column limit > + } > } else if (!strcmp(arg, "--encode-email-headers")) { > revs->encode_email_headers = 1; > } else if (!strcmp(arg, "--no-encode-email-headers")) { > diff --git a/revision.h b/revision.h > index 69242ecb18..6442129c14 100644 > --- a/revision.h > +++ b/revision.h > @@ -304,6 +304,7 @@ struct rev_info { > > /* Display history graph */ > struct git_graph *graph; > + int graph_max_columns; > I think it makes sense to make this an unsigned int. > /* special limits */ > int skip_count; > diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh > index 28d0779a8c..6266de4e2b 100755 > --- a/t/t4215-log-skewed-merges.sh > +++ b/t/t4215-log-skewed-merges.sh > @@ -370,4 +370,60 @@ test_expect_success 'log --graph with multiple tips' ' > EOF > ' > > +test_expect_success 'log --graph --graph-max=2 only two columns' ' > + check_graph --graph-max=2 M_7 <<-\EOF > + *-. 7_M4 > + |\ . > + | | * 7_G > + | | * 7_F > + | * . 7_E > + | * . 7_D > + * | . 7_C > + | |/ > + |/| > + * | 7_B > + |/ > + * 7_A > + EOF > +' > + > +test_expect_success 'log --graph --graph-max=3 only three columns' ' > + check_graph --graph-max=3 M_1 M_3 M_5 M_7 <<-\EOF > + * 7_M1 > + |\ > + | | * 7_M2 > + | | |. > + | | | * 7_H > + | | | | * 7_M3 > + | | | | . > + | | | | | * 7_J > + | | | | * 7_I > + | | | | | | * 7_M4 > + | |_|_|_|_|. > + |/| | . > + | | |_. > + | |/|_. > + | |/|_. > + | |/| . > + | | |/. > + | | * . 7_G > + | | | . > + | | |/. > + | | |/. > + | | * . 7_F > + | * | . 7_E > + | | |/. > + | |/| . > + | * | . 7_D > + | | |/ > + | |/| > + * | | 7_C > + | |/ > + |/| > + * | 7_B > + |/ > + * 7_A > + EOF > +' > + > test_done > -- > 2.43.0 v [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 690 bytes --] ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns 2026-03-16 17:04 ` Karthik Nayak @ 2026-03-16 19:48 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-16 19:48 UTC (permalink / raw) To: Karthik Nayak Cc: git, christian.couder, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519 Thanks for the feedback, > Do you think '--graph-max' signifies that we're talking about the > maximum columns to display? Yeah, doesn't seem clear enough, but --graph-col-limit seems too verbose. What do you think about --graph-max-cols ? It's just one char less but I feel it is more readable. > One question to ask is, is this even needed anymore and does it really > make sense to add it? > The TODO was added back in 2008, that's ~16 years ago and was not > touched till now. So perhaps no one needs it? If so, maybe the smarter > option is to simply remove the TODO? > Or do you see a usecase where this is useful? If so, it would be nice to > talk about that in the commit message. I've run 'git log --graph --all' on the Git repo itself on 'next' and just scrolling a bit down up to March 5 there are already +35 branches, which isn't very readable. It's been 16 years but I believe that is still a good thing to add, even if a lot of graph viewing happens with third parties, adding this makes Git a bit more self sufficient. This is even more useful in a case where the third party is not an option. That said, if this still doesn't seem useful I would remove the TODO to avoid further confusion in the future. > My preference would be not to have implicit behavior but also at the > same time guide the user in the right path, so: > $ git log --graph-max=4 > fatal: --graph-max used without --graph Ok, will add this to v2. > Trying it out: > ... > So we still keep the spaces, but only remove the column indicator Padding needs to be adjusted to the columns truncated. Will add it to v2. > Isn't this more of `graphs_needs_truncation()`? Yes, I'll rename it in v2. > Shouldn't we allow users to set 0? That combined with an unsigned int > would: > 1. remove the need for MINIMUM_GRAPH_COLUMNS > 2. allow users to specify that they do not want a column limit I think it's better not to let 0 be a good input, it's the same as not using it making it redundant. Is set to 0 by default from the memset(). Other "max" options I've tried that allow 0, don't share the "no limit" behaviour. git log --graph --all --max-parents=0 git log --max-count=0 That's why I wouldn't let it be valid. The unsigned int is better for clarity. > I think overall a little more explanation in the commit message makes it > easier to understand the context and also helps reviewers! > > Shouldn't we also talk about the todo and the commit (c12172d2ea (Add > history graph API, 2008-05-04)) in which it was added? > The first sentence seems to talk about the option like it already > exists, when the second para introduces it. It would be nice if the > first para explained the problem we're trying to solve and why and the > second para then dove into the solution space. My bad, I'll improve the commit msg on v2 to be more clear about the problem, what it does and where it comes from. > What does this mean? Validate how? It validated user input to avoid them to set 0. In case of letting them set 0 (no limit) this would be removed. I'll wait for the decision about whether this TODO from 2008 is worth doing before a v2. Thanks, Pablo ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC RFC PATCH v2] graph: add --max-columns option to limit displayed columns 2026-03-16 13:34 [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns Pablo Sabater 2026-03-16 17:04 ` Karthik Nayak @ 2026-03-17 22:09 ` Pablo Sabater 2026-03-18 16:05 ` Junio C Hamano 2026-03-22 19:54 ` [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option Pablo Sabater 1 sibling, 2 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-17 22:09 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, Pablo Sabater Repositories that have many active branches produce very wide outputs with 'git log --graph --all', makes it difficult to read. Add '--max-columns=<n>' to limit the columns shown. Columns over the limit are replaced with a '.' truncation indicator. This only affects the visual rendering. The commit mark '*' is only shown in two cases: - The commit is in a branch inside the limit. - The commit is in the first hidden branch, in this case '.' is replaced by '*'. Commits on deeper hidden branches do not show '*' but the commit subject is still shown, so no information is lost. The original idea to limit columns was noted as a TODO in c12172d2ea (Add history graph API, 2008-05-04), which mentions gitk's behavior. This does not implement gitk-style column rearrangement; it only truncates the visual output. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- > I'll wait for the decision about whether this TODO from 2008 is worth > doing before a v2. I believe that it is better to send an improved version instead of waiting to show more clearly what I want to do, sorry for the confusion. > It's been 16 years but I believe that is still a good thing to add, even if > a lot of graph viewing happens with third parties, adding this makes > Git a bit more self sufficient. This is even more useful in a case where the > third party is not an option. > That said, if this still doesn't seem useful I would remove the TODO > to avoid further confusion in the future. I revert what I've said here. This doesn't actually reflects gitk behaviour, it only truncates the visual output and prettifies it. To actually fullfill the TODO, it should rearrange the columns what would mean to change the whole rendering algorithm of --graph. This prob why it has been a TODO for so long. I don't think the TODO should be removed with what I bring here. Therefore, I wont't remove it. The actual TODO seems still a good project and a good improvement. example: git log --oneline --graph --all * b81d099 (M_1) 7_M1 |\ | | * 2d96ba0 (M_3) 7_M2 | | |\ | | | * 993fbcd (7_4) 7_H | | | | * d5b56db (M_5) 7_M3 | | | | |\ | | | | | * b45d68d (7_6) 7_J | | | | * | 3f9ab79 (7_5) 7_I | | | | | | * f89bbdc (HEAD -> M_7) 7_M4 | |_|_|_|_|/|\ |/| | | | |/ / | | |_|_|/| / | |/| | | |/ | | | |_|/| | | |/| | | | | * | | | f5cbb18 (7_3) 7_G | | | |_|/ | | |/| | | | * | | 6ee728a 7_F | * | | | d0c5a67 (7_2) 7_E | | |/ / | |/| | | * | | c3bffce 7_D | | |/ | |/| * | | df1a24f (7_1) 7_C | |/ |/| * | 5d3f777 7_B |/ * d309d64 7_A vs git log --graph --all --oneline --max-columns=2 * b81d099 (M_1) 7_M1 |\ | | * 2d96ba0 (M_3) 7_M2 | | . | | . 993fbcd (7_4) 7_H | | . d5b56db (M_5) 7_M3 | | . | | . b45d68d (7_6) 7_J | | . 3f9ab79 (7_5) 7_I | | . f89bbdc (HEAD -> M_7) 7_M4 | |_. |/| . | | . | |/. | |/. | |/. | | . | | * f5cbb18 (7_3) 7_G | | . | | . | | . | | * 6ee728a 7_F | * . d0c5a67 (7_2) 7_E | | . | |/. | * . c3bffce 7_D | | . | |/. * | . df1a24f (7_1) 7_C | |/ |/| * | 5d3f777 7_B |/ * d309d64 7_A Changes since v1: - Renamed option from --graph-max to --max-columns - Require --graph for --max-columns, die without it - Fixed padding so commit text aligns correctly after truncation - Added pre-commit truncation so lines before the commit column are also truncated when they exceed the limit - Fixed post-merge line spacing inconsistency - Used parse_long_opt/parse_count for input validation, matching existing revision.c patterns - Reject negative and zero values with clear error messages - Renamed graph_is_truncated to graph_needs_truncation RFC : - I think --max-columns is a good name, but it does not have --graph prefix, bcs it can only be on --graph I think it's clear enough. I added checks in setup_revisions(). - Is '.' fine as delimiter ? other options could be "~" or "-". graph.c | 57 +++++++++++++++++++++++++++++++++++- graph.h | 2 ++ revision.c | 11 +++++++ revision.h | 1 + t/t4215-log-skewed-merges.sh | 56 +++++++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/graph.c b/graph.c index 26f6fbf000..6227c4f22f 100644 --- a/graph.c +++ b/graph.c @@ -317,6 +317,12 @@ struct git_graph { struct strbuf prefix_buf; }; +static int graph_needs_truncation(struct git_graph *graph, int col) +{ + int max = graph->revs->graph_max_columns; + return max > 0 && col >= max; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; @@ -696,6 +702,15 @@ static void graph_update_columns(struct git_graph *graph) } } + /* + * If graph_max_columns is set, cap the padding from the branches + */ + if (graph->revs->graph_max_columns > 0) { + int truncation = graph->revs->graph_max_columns * 2 + 2; + if (graph->width > truncation) + graph->width = truncation; + } + /* * Shrink mapping_size to be the minimum necessary */ @@ -846,6 +861,10 @@ 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++) { + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -903,6 +922,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -1013,6 +1035,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line * children that we have already processed.) */ seen_this = 0; + for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -1028,8 +1051,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(graph, i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1109,9 +1141,17 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { + if (graph_needs_truncation(graph, i + j)) { + if (j > 0) + graph_line_addch(line, ' '); + graph_line_addstr(line, ". "); + truncated = 1; + break; + } par_column = graph_find_new_column_by_commit(graph, parents->item); assert(par_column >= 0); @@ -1125,9 +1165,13 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); @@ -1279,6 +1323,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; + + if (graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, ". "); + break; + } + if (target < 0) graph_line_addch(line, ' '); else if (target * 2 == i) @@ -1372,6 +1422,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(graph, i)) { + graph_line_addch(&line, '.'); + break; + } + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { diff --git a/graph.h b/graph.h index 3fd1dcb2e9..9a4551dd29 100644 --- a/graph.h +++ b/graph.h @@ -262,4 +262,6 @@ void graph_show_commit_msg(struct git_graph *graph, FILE *file, struct strbuf const *sb); +#define MINIMUM_GRAPH_COLUMNS 1 + #endif /* GRAPH_H */ diff --git a/revision.c b/revision.c index 31808e3df0..e8d38cb2a1 100644 --- a/revision.c +++ b/revision.c @@ -2605,6 +2605,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if ((argcount = parse_long_opt("max-columns", argv, &optarg))) { + int val = parse_count(optarg); + if (val < MINIMUM_GRAPH_COLUMNS) + die(_("minimum columns is %d, cannot be set to %d"), + MINIMUM_GRAPH_COLUMNS, val); + revs->graph_max_columns = val; + return argcount; } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -3172,6 +3179,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_columns > 0 && !revs->graph) + die(_("option '%s' requires '%s'"), "--max-columns", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); diff --git a/revision.h b/revision.h index 69242ecb18..f15b390289 100644 --- a/revision.h +++ b/revision.h @@ -304,6 +304,7 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + unsigned int graph_max_columns; /* special limits */ int skip_count; diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..ca2b224cc4 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -370,4 +370,60 @@ test_expect_success 'log --graph with multiple tips' ' EOF ' +test_expect_success 'log --graph --max-columns=2 only two columns' ' + check_graph --max-columns=2 M_7 <<-\EOF + *-. 7_M4 + |\ . + | | * 7_G + | | * 7_F + | * . 7_E + | * . 7_D + * | . 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --max-columns=3 only three columns' ' + check_graph --max-columns=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | | . + | | | * 7_H + | | | . 7_M3 + | | | . + | | | . 7_J + | | | . 7_I + | | | . 7_M4 + | |_|_. + |/| | . + | | |_. + | |/|_. + | |/|_. + | |/| . + | | |/. + | | * . 7_G + | | | . + | | |/. + | | |/. + | | * . 7_F + | * | . 7_E + | | |/. + | |/| . + | * | . 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC RFC PATCH v2] graph: add --max-columns option to limit displayed columns 2026-03-17 22:09 ` [GSoC RFC PATCH v2] graph: add --max-columns " Pablo Sabater @ 2026-03-18 16:05 ` Junio C Hamano 2026-03-18 18:20 ` Pablo 2026-03-22 19:54 ` [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option Pablo Sabater 1 sibling, 1 reply; 39+ messages in thread From: Junio C Hamano @ 2026-03-18 16:05 UTC (permalink / raw) To: Pablo Sabater Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519 Pablo Sabater <pabloosabaterr@gmail.com> writes: > Repositories that have many active branches produce very wide > outputs with 'git log --graph --all', makes it difficult to read. "making it difficult"? > Add '--max-columns=<n>' to limit the columns shown. Columns over > the limit are replaced with a '.' truncation indicator. This only > affects the visual rendering. Being an option to "git log", I expect that readers would naturally take "--max-columns=<n>" to refer to the total display width consumed by the entire output including the log messages and possibly patches when the command is run with the "-p" option, whether the ancestry graph is shown or not. But somehow I suspect that it is not what is going on here. If this <n> refers to the width of graph part only, the option name should hint that fact somehow. Perhaps include the word "graph" in it, or something. As you use the verb "limit" below, perhaps "--limit-graph-columns=<n>"? > The commit mark '*' is only shown in two cases: > - The commit is in a branch inside the limit. > - The commit is in the first hidden branch, in this case '.' > is replaced by '*'. > Commits on deeper hidden branches do not show '*' but the commit > subject is still shown, so no information is lost. Isn't "no information is lost" a huge exaggeration? We are losing the ancestry information by not drawing graph lines, aren't we? > The original idea to limit columns was noted as a TODO in > c12172d2ea (Add history graph API, 2008-05-04), which mentions > gitk's behavior. This does not implement gitk-style column > rearrangement; it only truncates the visual output. True. IIRC, Gitk also allows you to click the chopped arrow-head to jump to the other end of the omitted ancestry line, which is very useful but is hard to do on a terminal output that is not interactive. > git log --graph --all --oneline --max-columns=2 > * b81d099 (M_1) 7_M1 > |\ > | | * 2d96ba0 (M_3) 7_M2 > | | . I would call this output consuming 5 columns for graph part, not 2. For end users, being able to specify 2, i.e., being able to say "I tolerate wasting display columns to show up to two ancestry lines" (or "two lanes of ancestry information"), is indeed a lot more intuitive than having to say "You are allowed to use up to 5 display columns", so I do not object to an option that takes "2" as its value and produces the above output, but I am not sure if we want to have "columns" in the name of such an option; "--limit-graph-lanes=2"? > - I think --max-columns is a good name, but it does not have --graph > prefix, bcs it can only be on --graph I think it's clear enough. If "git log --max-columns=77" ignores the option because "--graph" is not given, it would be confusing to the users. > + /* > + * If graph_max_columns is set, cap the padding from the branches > + */ > + if (graph->revs->graph_max_columns > 0) { > + int truncation = graph->revs->graph_max_columns * 2 + 2; This needs a bit more commenting to explain where these magic numbers come from; they are of the same value 2 but have different meanings, right? Like this (only to illustrate the shape, not suggesting what the contents should read): /* * Each ancestry "lane" occupies 2 columns, and * we leave two columns before drawing the commit * title and log message part. */ int max_column_width = graph->revs->graph_limit_lanes * 2 + 2; > + > + if (revs->graph_max_columns > 0 && !revs->graph) > + die(_("option '%s' requires '%s'"), "--max-columns", "--graph"); The naming is so selfish. Among "git log" options that exists and that will be added in the future, this design decision declares that "--graph" is and will remain to be the only one that may want to specify the maximum number of columns to spend. If the option is named clearly to be related to the "--graph" feature, another way to go is to make it imply "--graph". If the user says "I want to limit the graph output to consume no more than 10 leftmost columns", it is clear that the user expects the graph to be shown. ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC RFC PATCH v2] graph: add --max-columns option to limit displayed columns 2026-03-18 16:05 ` Junio C Hamano @ 2026-03-18 18:20 ` Pablo 2026-03-19 7:07 ` Johannes Sixt 0 siblings, 1 reply; 39+ messages in thread From: Pablo @ 2026-03-18 18:20 UTC (permalink / raw) To: Junio C Hamano Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519 Thanks for the feedback! Junio C Hamano (<gitster@pobox.com>) writes: > "making it difficult"? I'll correct the typo > If this <n> refers to the width of graph part only, the option name > should hint that fact somehow. Perhaps include the word "graph" in > it, or something. As you use the verb "limit" below, perhaps > "--limit-graph-columns=<n>"? > I would call this output consuming 5 columns for graph part, not 2. > For end users, being able to specify 2, i.e., being able to say "I > tolerate wasting display columns to show up to two ancestry lines" > (or "two lanes of ancestry information"), is indeed a lot more > intuitive than having to say "You are allowed to use up to 5 display > columns", so I do not object to an option that takes "2" as its > value and produces the above output, but I am not sure if we want to > have "columns" in the name of such an option; "--limit-graph-lanes=2"? Yes, it only affects the graph rendering, I'll change the name to be more clear about it is for the graph only and that what is being modified are the lanes, "|" + " " not actual single char columns. What about --graph-limit-lanes and make it imply --graph as you said if it's clear enough? > Isn't "no information is lost" a huge exaggeration? We are losing > the ancestry information by not drawing graph lines, aren't we? Yeah, I meant that most of the commit information is not lost, only where they come from if it's a deeper hidden lane. But what I wanted to say is that internally I'm not removing any information about the graph and that every commit will show no matter the lane limit. > IIRC, Gitk also allows you to click the chopped arrow-head to jump > to the other end of the omitted ancestry line, which is very useful > but is hard to do on a terminal output that is not interactive. Yes, that's why it's been sooo long without being done prob. But I think this approach is still useful, it can be extended later for more customization like limiting the lines from the left side or ranges. For lane rearrangement I would need to study graph.c more and look forward to refactoring a lot in multiple patches. > If "git log --max-columns=77" ignores the option because "--graph" > is not given, it would be confusing to the users. With the check on 'setup_revision()' users won't be able to 'max-columns=77' if there's no '--graph' > This needs a bit more commenting to explain where these magic > numbers come from; they are of the same value 2 but have different > meanings, right? Like this (only to illustrate the shape, not > suggesting what the contents should read): > > /* > * Each ancestry "lane" occupies 2 columns, and > * we leave two columns before drawing the commit > * title and log message part. > */ > int max_column_width = > graph->revs->graph_limit_lanes * 2 + 2; the magic numbers are because, a lane is two columns '|' + ' ', and the +2 comes from the truncation mark '.' + ' ', so for a 3 lanes limit, the padding from the graph should be 3 * 2 + 2 = 8. I'll add a comment to doc the magic numbers. > > + > > + if (revs->graph_max_columns > 0 && !revs->graph) > > + die(_("option '%s' requires '%s'"), "--max-columns", "--graph"); > > The naming is so selfish. Among "git log" options that exists and > that will be added in the future, this design decision declares that > "--graph" is and will remain to be the only one that may want to > specify the maximum number of columns to spend. > > If the option is named clearly to be related to the "--graph" > feature, another way to go is to make it imply "--graph". If the > user says "I want to limit the graph output to consume no more than > 10 leftmost columns", it is clear that the user expects the graph to > be shown. I'll make the name clearer about what it does and less selfish. I like the idea about --graph-limit-lanes to imply --graph directly and not force it to be explicit. Making --graph-limit-lanes imply --graph removes the check at revision_setup() I'll send a v3 ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC RFC PATCH v2] graph: add --max-columns option to limit displayed columns 2026-03-18 18:20 ` Pablo @ 2026-03-19 7:07 ` Johannes Sixt 0 siblings, 0 replies; 39+ messages in thread From: Johannes Sixt @ 2026-03-19 7:07 UTC (permalink / raw) To: Pablo Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, Junio C Hamano Am 18.03.26 um 19:20 schrieb Pablo: > Junio C Hamano (<gitster@pobox.com>) writes:>> If the option is named clearly to be related to the "--graph" >> feature, another way to go is to make it imply "--graph". If the >> user says "I want to limit the graph output to consume no more than >> 10 leftmost columns", it is clear that the user expects the graph to >> be shown. > > I'll make the name clearer about what it does and less selfish. I like > the idea about --graph-limit-lanes to imply --graph directly and not > force it to be explicit. Don't let this option imply --graph. It specifies a parameter that could also reasonably be specified via a configuration. But we don't want that the existence of the hypothetical configuration implies --graph. How about --edge-limit or --lane-limit? -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option 2026-03-17 22:09 ` [GSoC RFC PATCH v2] graph: add --max-columns " Pablo Sabater 2026-03-18 16:05 ` Junio C Hamano @ 2026-03-22 19:54 ` Pablo Sabater 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater 1 sibling, 2 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-22 19:54 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater When viewing the history of a repository that has many branches, the graph output can become very wide easily, making it difficult to read and sometimes pushing the commit messages to the right and breaking the format. Introduce --graph-lane-limit option to truncate the graph at n + 1/2 lanes (n lanes plus the column between the lane and the truncation mark). Lanes over the limit are replaced with '.' keeping the graph readable and shows all the information about the visible lanes. commit mark '*' is shown in two cases: - When the commit lives on a visible lane. - When the commit lives on the first hidden lane (n+1) this case instead of '.' it will show '*' to show that there is a commit in the first hidden lane. Any commit on deeper branches won't show any mark, but the commit subject will still be shown. Coming from graph_output_commit_line, merge-lanes with no relevant information on the visible lanes (neither commit or parent lives on a visible lane) are skipped making the graph more compact. The original idea to limit columns was noted as a TODO in c12172d2ea (Add history graph API, 2008-05-04), which mentions gitk's behavior. This does not implement gitk-style column rearrangement; it only truncates the visual output. RFC and WIP: 1. On lane-limited graphs, there are collapsing, merge and padding lanes that don't add any information to the graph. - graph_output_collapsing_line mixes state handling and graph rendering, skipping this function entirely, as is done with post-merge, would corrupt the mapping so the function needs to be run but returning an empty buffer is not expected by the callers and produces blank lanes. - Suppressing redundant lanes like merges or collapses that come from graph_next_line(). graph_show_remainder() and graph_show_commit_msg() emits newlines hoping that all lines will have content to print, returning an empty buffer to them results in blank lines because of newlines being duplicated. graph_show_remainder would be called from two places with different newlines needs, to make this work graph_show_remainder() needs to be refactored. Doing this will result in shorter graphs with less redundant lines on lane-limited graphs. Given that the graph.c code is 17 years old is this desired ? 2. To keep the most information rendered on lane-limited graphs, collapsing and merges are rendered as usual but when the collapse or merge to hidden lanes, the information about from where and to where it goes it lost, are they still useful ? if not, it could be desirable to remove them like in point 1. Pablo Sabater (3): graph: add --graph-lane-limit option graph: truncate graph visual output graph: add documentation and testing about --graph-lane-limit Documentation/rev-list-options.adoc | 5 ++ graph.c | 130 ++++++++++++++++++++++++---- graph.h | 2 + revision.c | 11 +++ revision.h | 1 + t/t4215-log-skewed-merges.sh | 53 ++++++++++++ 6 files changed, 185 insertions(+), 17 deletions(-) base-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1 -- 2.43.0 ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option 2026-03-22 19:54 ` [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option Pablo Sabater @ 2026-03-22 20:37 ` Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 2/3] graph: truncate graph visual output Pablo Sabater ` (2 more replies) 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater 1 sibling, 3 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-22 20:37 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Repositories that have many active branches produce very wide outputs with 'git log --graph --all' making it difficult to read. Add MINIMUM_GRAPH_COLUMNS = 1 Add '--graph-lane-limit=<n>' to the revision options, this option needs --graph explicitly and rejects values under MINIMUM_GRAPH_COLUMNS. Add graph_max_lanes to rev_info and store what the user set Add graph_needs_truncation() and teach it to know when a column is over the limit following graph_max_lanes, if the limit is 0, treat it like no limit. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 6 ++++++ graph.h | 2 ++ revision.c | 11 +++++++++++ revision.h | 1 + 4 files changed, 20 insertions(+) diff --git a/graph.c b/graph.c index 26f6fbf000..a95c0a9a73 100644 --- a/graph.c +++ b/graph.c @@ -317,6 +317,12 @@ struct git_graph { struct strbuf prefix_buf; }; +static int graph_needs_truncation(struct git_graph *graph, int col) +{ + int max = graph->revs->graph_max_lanes; + return max > 0 && col >= max; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; diff --git a/graph.h b/graph.h index 3fd1dcb2e9..9a4551dd29 100644 --- a/graph.h +++ b/graph.h @@ -262,4 +262,6 @@ void graph_show_commit_msg(struct git_graph *graph, FILE *file, struct strbuf const *sb); +#define MINIMUM_GRAPH_COLUMNS 1 + #endif /* GRAPH_H */ diff --git a/revision.c b/revision.c index 31808e3df0..aeddf2d166 100644 --- a/revision.c +++ b/revision.c @@ -2605,6 +2605,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if ((argcount = parse_long_opt("graph-lane-limit", argv, &optarg))) { + int max_lanes = parse_count(optarg); + if (max_lanes < MINIMUM_GRAPH_COLUMNS) + die(_("minimum lanes is %d, cannot be set to %d"), + MINIMUM_GRAPH_COLUMNS, max_lanes); + revs->graph_max_lanes = max_lanes; + return argcount; } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -3172,6 +3179,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_lanes > 0 && !revs->graph) + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); diff --git a/revision.h b/revision.h index 69242ecb18..597116f885 100644 --- a/revision.h +++ b/revision.h @@ -304,6 +304,7 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + unsigned int graph_max_lanes; /* special limits */ int skip_count; -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [GSoC PATCH WIP RFC v3 2/3] graph: truncate graph visual output 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater @ 2026-03-22 20:38 ` Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 3/3] graph: add documentation and testing about --graph-lane-limit Pablo Sabater 2026-03-22 22:09 ` [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option Junio C Hamano 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-22 20:38 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Teach graph statuses to stop rendering and add the truncation mark '.' once they are writing over the lane limit, following graph_needs_truncation(). Teach graph_output_commit_line to skip the POST_MERGE status when neither the commit nor the parent is on a visible lane but keep it when either commit or parent lives in a visible lane. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 17 deletions(-) diff --git a/graph.c b/graph.c index a95c0a9a73..ab0a008af5 100644 --- a/graph.c +++ b/graph.c @@ -702,6 +702,20 @@ static void graph_update_columns(struct git_graph *graph) } } + /* + * If graph_max_lanes is set, cap the padding from the branches + */ + if (graph->revs->graph_max_lanes > 0) { + /* + * Get the maximum width by multiplying the maximum number of + * lanes by the size of the lane "| " and adds the truncation + * mark ". " + */ + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; + if (graph->width > max_columns_width) + graph->width = max_columns_width; + } + /* * Shrink mapping_size to be the minimum necessary */ @@ -852,6 +866,10 @@ 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++) { + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -909,6 +927,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -1019,6 +1040,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line * children that we have already processed.) */ seen_this = 0; + for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -1034,8 +1056,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(graph, i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1071,10 +1102,32 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line /* * Update graph->state - */ - if (graph->num_parents > 1) - graph_update_state(graph, GRAPH_POST_MERGE); - else if (graph_is_mapping_correct(graph)) + * + * If the commit is a merge and the first parent is in a visible lane, + * then the GRAPH_POST_MERGE is needed to draw the merge lane. + * + * If the commit is over the truncation limit, but the first parent is on + * a visible lane, then we still need the merge lane but truncated. + * + * If both commit and first parent are over the truncation limit, then + * there's no need to draw the merge lane because it would work as a + * padding lane. + */ + if (graph->num_parents > 1) { + if (!graph_needs_truncation(graph, graph->commit_index)) { + graph_update_state(graph, GRAPH_POST_MERGE); + } else { + struct commit_list *first_parent = first_interesting_parent(graph); + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); + + if (!graph_needs_truncation(graph, first_parent_col)) + graph_update_state(graph, GRAPH_POST_MERGE); + else if (graph_is_mapping_correct(graph)) + graph_update_state(graph, GRAPH_PADDING); + else + graph_update_state(graph, GRAPH_COLLAPSING); + } + } else if (graph_is_mapping_correct(graph)) graph_update_state(graph, GRAPH_PADDING); else graph_update_state(graph, GRAPH_COLLAPSING); @@ -1115,14 +1168,28 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { + unsigned int truncation_max = i + (j > 1 ? j - 1 : 0); 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 (j >= 2) + truncation_max -= 1; + + if (graph_needs_truncation(graph, truncation_max)) { + if (j > 0 && !(graph->edges_added > 0)) + graph_line_addch(line, ' '); + graph_line_addstr(line, ". "); + truncated = 1; + break; + } + if (idx == 2) { if (graph->edges_added > 0 || j < graph->num_parents - 1) graph_line_addch(line, ' '); @@ -1131,15 +1198,24 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); else graph_line_write_column(line, col, '|'); - graph_line_addch(line, ' '); + /* + * If it's between two lanes and next would be truncated, + * don't add space padding. + */ + if (!graph_needs_truncation(graph, i + 1)) + graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); if (graph->merge_layout != 0 || i != graph->commit_index - 1) { @@ -1170,6 +1246,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l short used_horizontal = 0; int horizontal_edge = -1; int horizontal_edge_target = -1; + int truncated = 0; /* * Swap the mapping and old_mapping arrays @@ -1285,12 +1362,20 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (target < 0) - graph_line_addch(line, ' '); - else if (target * 2 == i) - graph_line_write_column(line, &graph->new_columns[target], '|'); - else if (target == horizontal_edge_target && - i != horizontal_edge - 1) { + + if (!truncated && graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, ". "); + truncated = 1; + } + + if (target < 0) { + if (!truncated) + graph_line_addch(line, ' '); + } else if (target * 2 == i) { + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '|'); + } else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { /* * Set the mappings for all but the * first segment to -1 so that they @@ -1298,13 +1383,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ if (i != (target * 2)+3) graph->mapping[i] = -1; - used_horizontal = 1; - graph_line_write_column(line, &graph->new_columns[target], '_'); + used_horizontal = 1; + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->mapping[i] = -1; - graph_line_write_column(line, &graph->new_columns[target], '/'); - + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '/'); } } @@ -1353,7 +1439,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) graph_output_collapsing_line(graph, &line); break; } - graph_pad_horizontally(graph, &line); return shown_commit_line; } @@ -1378,6 +1463,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(graph, i)) { + graph_line_addch(&line, '.'); + break; + } + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [GSoC PATCH WIP RFC v3 3/3] graph: add documentation and testing about --graph-lane-limit 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 2/3] graph: truncate graph visual output Pablo Sabater @ 2026-03-22 20:38 ` Pablo Sabater 2026-03-22 22:09 ` [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option Junio C Hamano 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-22 20:38 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Document --graph-lane-limit option in rev-list-options.adoc with --graph option. Add two tests in t4215 reusing last test graph structure. - --graph-lane-limit=2 on one tip showing only two rendered lanes and the rest replaced with the truncation marker. - --graph-lane-limit=3 with multiple tips, showing only three rendered lanes. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/rev-list-options.adoc | 5 +++ t/t4215-log-skewed-merges.sh | 53 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1474..1819228b60 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the in between them in that case. If _<barrier>_ is specified, it is the string that will be shown instead of the default one. +`graph-lane-limit=<n>`:: + When `--graph` is used, limit the number of graph lanes to be shown. + Lanes over the limit are replaced with a truncation mark '.'. By default + there is no limit. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..657e3ff2a5 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -370,4 +370,57 @@ test_expect_success 'log --graph with multiple tips' ' EOF ' +test_expect_success 'log --graph --graph-lane-limit=2 limited to two columns' ' + check_graph --graph-lane-limit=2 M_7 <<-\EOF + *-. 7_M4 + |\ \ + | | * 7_G + | | * 7_F + | * . 7_E + | * . 7_D + * | . 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=3 limited to three columns' ' + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | . 7_M3 + | | | . 7_J + | | | . 7_I + | | | . 7_M4 + | |_|_. + |/| | . + | | |_. + | |/| . + | | | . + | | |/. + | | * . 7_G + | | | . + | | |/. + | | * . 7_F + | * | . 7_E + | | |/. + | |/| . + | * | . 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 2/3] graph: truncate graph visual output Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 3/3] graph: add documentation and testing about --graph-lane-limit Pablo Sabater @ 2026-03-22 22:09 ` Junio C Hamano 2026-03-23 2:33 ` Pablo 2 siblings, 1 reply; 39+ messages in thread From: Junio C Hamano @ 2026-03-22 22:09 UTC (permalink / raw) To: Pablo Sabater Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t Pablo Sabater <pabloosabaterr@gmail.com> writes: > Repositories that have many active branches produce very wide outputs > with 'git log --graph --all' making it difficult to read. If you have active branches, whether they are merged to some very small number of integration branches or they are left updating without getting merged for a long time, you'd end up getting very wide output from "log --all --graph"? Or is this a problem only when these active branches are merged and having to show merge commits create the need for wider output? I cannot quite see which from the above description. > > Add MINIMUM_GRAPH_COLUMNS = 1 This unfinished sentence looks a bit out of place. It is unclear how that variable about "columns" related to "lane" that is introduced in the next paratraph. > Add '--graph-lane-limit=<n>' to the revision options, this option > needs --graph explicitly and rejects values under MINIMUM_GRAPH_COLUMNS. "and rejects values under ..." -> "has to be at least 1". And the previous paragraph that consists of a single unfinished sentence can be discarded. The implementation detail that you happened to choose a C proprocessor macro instead of hardcoded constatnt to write the lower limit is not something readers of the log message needs to know. They can see that in the "log -p" output easily. What is more helpful for readers to know is what you mean by "graph-lane", what you are counting, and why you want its lower limit to 1 (instead of 0 or 2). These reasoning behind the design is much more important to record to help future developers who want to fix bugs in this code or who want to extend the feature this code adds, without violating the underlying assumption and design goals of the original author (i.e., you). > Add graph_max_lanes to rev_info and store what the user set > > Add graph_needs_truncation() and teach it to know when a column is > over the limit following graph_max_lanes, if the limit is 0, treat it like > no limit. > > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> > --- > graph.c | 6 ++++++ > graph.h | 2 ++ > revision.c | 11 +++++++++++ > revision.h | 1 + > 4 files changed, 20 insertions(+) > > diff --git a/graph.c b/graph.c > index 26f6fbf000..a95c0a9a73 100644 > --- a/graph.c > +++ b/graph.c > @@ -317,6 +317,12 @@ struct git_graph { > struct strbuf prefix_buf; > }; > > +static int graph_needs_truncation(struct git_graph *graph, int col) > +{ > + int max = graph->revs->graph_max_lanes; > + return max > 0 && col >= max; > +} > + > static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) > { > struct git_graph *graph = data; > diff --git a/graph.h b/graph.h > index 3fd1dcb2e9..9a4551dd29 100644 > --- a/graph.h > +++ b/graph.h > @@ -262,4 +262,6 @@ void graph_show_commit_msg(struct git_graph *graph, > FILE *file, > struct strbuf const *sb); > > +#define MINIMUM_GRAPH_COLUMNS 1 > + > #endif /* GRAPH_H */ > diff --git a/revision.c b/revision.c > index 31808e3df0..aeddf2d166 100644 > --- a/revision.c > +++ b/revision.c > @@ -2605,6 +2605,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg > } else if (!strcmp(arg, "--no-graph")) { > graph_clear(revs->graph); > revs->graph = NULL; > + } else if ((argcount = parse_long_opt("graph-lane-limit", argv, &optarg))) { > + int max_lanes = parse_count(optarg); > + if (max_lanes < MINIMUM_GRAPH_COLUMNS) > + die(_("minimum lanes is %d, cannot be set to %d"), > + MINIMUM_GRAPH_COLUMNS, max_lanes); > + revs->graph_max_lanes = max_lanes; > + return argcount; > } else if (!strcmp(arg, "--encode-email-headers")) { > revs->encode_email_headers = 1; > } else if (!strcmp(arg, "--no-encode-email-headers")) { > @@ -3172,6 +3179,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s > > if (revs->no_walk && revs->graph) > die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); > + > + if (revs->graph_max_lanes > 0 && !revs->graph) > + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); > + > if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) > die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); > > diff --git a/revision.h b/revision.h > index 69242ecb18..597116f885 100644 > --- a/revision.h > +++ b/revision.h > @@ -304,6 +304,7 @@ struct rev_info { > > /* Display history graph */ > struct git_graph *graph; > + unsigned int graph_max_lanes; This is "unsigned int"; don't we want the other places (like the on-stack local variable handle_revision_opt() uses to parse the value from the command line) and the parameter used in graph_needs_truncation() helper function all consistently use the same type? > /* special limits */ > int skip_count; ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option 2026-03-22 22:09 ` [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option Junio C Hamano @ 2026-03-23 2:33 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-23 2:33 UTC (permalink / raw) To: Junio C Hamano Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t Junio C Hamano (<gitster@pobox.com>) writes: > If you have active branches, whether they are merged to some very > small number of integration branches or they are left updating > without getting merged for a long time, you'd end up getting very > wide output from "log --all --graph"? Or is this a problem only > when these active branches are merged and having to show merge > commits create the need for wider output? I cannot quite see which > from the above description. The graph gets wide based on the branches active at the same time, each one occupies two columns, merges don't create new lanes. I'll improve the problem description. > The implementation detail that you happened to choose a C > proprocessor macro instead of hardcoded constatnt to write the lower > limit is not something readers of the log message needs to know. > They can see that in the "log -p" output easily. > > What is more helpful for readers to know is what you mean by > "graph-lane", what you are counting, and why you want its lower > limit to 1 (instead of 0 or 2). These reasoning behind the design > is much more important to record to help future developers who want > to fix bugs in this code or who want to extend the feature this code > adds, without violating the underlying assumption and design goals > of the original author (i.e., you). Ok, I'll focus more on why, rather than how. the minimum is set to 1 to have at least 1 visible lane, even though it could accept 0 it's the same as not using this option, 0 it's treated as no limit and I found it better to not give the users the option to place it because no other option seems to behave like this, so I thought the best would be to force the input to be >= 1 to be valid. in v4 I'll make sure that this is clear for others. > This is "unsigned int"; don't we want the other places (like the > on-stack local variable handle_revision_opt() uses to parse the > value from the command line) and the parameter used in > graph_needs_truncation() helper function all consistently use the > same type? I saw that other options like max_count, min/max_parents use int instead of unsigned int, so yes the most consistent would be to have it as int also, but it would make no sense to have -1 visible lanes. I thought it would be a good idea to keep it explicit that it can't be negative. this examples, max_count, etc were the closest examples I saw, but parse_count does return an int so i can't cast it to unsigned without checking if its neg that's why this > int max_lanes = parse_count(optarg); > if (max_lanes < MINIMUM_GRAPH_COLUMNS) > die(_("minimum lanes is %d, cannot be set to %d"), > MINIMUM_GRAPH_COLUMNS, max_lanes); > revs->graph_max_lanes = max_lanes; where it checks if it's < 1, now max_lanes has to be > 1 and it fits in an unsigned int. But i do understand to keep the consistency and the coding guidelines, i'll make it an int. Thanks for the feedback! ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option 2026-03-22 19:54 ` [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater @ 2026-03-23 21:59 ` Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater ` (4 more replies) 1 sibling, 5 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-23 21:59 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the space padding, each branch takes a lane in the graph and there is no way to limit how many can be shown. Add '--graph-lane-limit=<n>' option that caps the visible lanes to n. Lanes over this limit are replaced with '.' truncation mark. The '*' commit mark is visible when it lives on a visible lane or the first hidden lane, any deeper lane doesn't show the commit mark but keeps the commit message visible. Merges where neither the commit nor its parents live on a visible lane are skipped because they dont carry any visible information. The original idea to limit columns was noted as a TODO in c12172d2ea (Add history graph API, 2008-05-04). This does not implement gitk-style column rearrangement, it only truncates the visual output. Possible future improvements: - When all branches involved in collapsing or padding are over the limit, the truncated lane doesn't show any information, this lane could be removed to make the graph more compact. Currently these lanes still appear because graph_output_collapsing_line() mixes state handling with rendering, so it can't be skipped but callers always expect a non empty buffer. Fixing it would need to refactor the callers to handle empty buffers instead of expecting them to always have content. - Collapsing and merges lanes that start on visible lanes but end on hidden ones are kept to maintain the most information possible on the visible lanes, but the information about where they go is lost. They can be kept, removed or think of a way to show that information without showing the lanes. Changes since v3: - Rewrote commit messages to focus on reasoning rather than the implementation details. - Changed graph_max_lanes to int for consistency with other rev_info fields like max_count. - Zero and negative values are now accepted and silently treated as "no limit", following what --max-parents does with negative values. - Removed the MINIMUM_GRAPH_COLUMNS macro. - Fixed missing "--" prefix in the documentation. Pablo Sabater (3): graph: add --graph-lane-limit option graph: truncate graph visual output graph: add documentation and tests about --graph-lane-limit Documentation/rev-list-options.adoc | 5 ++ graph.c | 131 ++++++++++++++++++++++++---- revision.c | 6 ++ revision.h | 1 + t/t4215-log-skewed-merges.sh | 53 +++++++++++ 5 files changed, 180 insertions(+), 16 deletions(-) base-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1 -- 2.43.0 ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v4 1/3] graph: add --graph-lane-limit option 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater @ 2026-03-23 21:59 ` Pablo Sabater 2026-03-25 7:02 ` SZEDER Gábor 2026-03-25 10:03 ` Johannes Sixt 2026-03-23 21:59 ` [GSoC PATCH v4 2/3] graph: truncate graph visual output Pablo Sabater ` (3 subsequent siblings) 4 siblings, 2 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-23 21:59 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the space padding, each branch takes a lane in the graph and there is no way to limit how many can be shown. Add '--graph-lane-limit=<n>' revision option that caps the number of visible lanes to n. This option requires '--graph', without it a limit to the graph has no meaning, in this case error out. Zero and negative values are valid inputs but silently ignored treating them as "no limit", the same as not using the option. This follows what '--max-parents' does with negative values. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 9 +++++++++ revision.c | 6 ++++++ revision.h | 1 + 3 files changed, 16 insertions(+) diff --git a/graph.c b/graph.c index 26f6fbf000..e7c1151ac0 100644 --- a/graph.c +++ b/graph.c @@ -317,6 +317,15 @@ struct git_graph { struct strbuf prefix_buf; }; +static int graph_needs_truncation(struct git_graph *graph, int lane) +{ + int max = graph->revs->graph_max_lanes; + /* + * Ignore values <= 0, meaning no limit. + */ + return max > 0 && lane >= max; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; diff --git a/revision.c b/revision.c index 31808e3df0..952edb031e 100644 --- a/revision.c +++ b/revision.c @@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { + revs->graph_max_lanes = parse_count(optarg); } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_lanes > 0 && !revs->graph) + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); diff --git a/revision.h b/revision.h index 69242ecb18..874ccce625 100644 --- a/revision.h +++ b/revision.h @@ -304,6 +304,7 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + int graph_max_lanes; /* special limits */ int skip_count; -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 1/3] graph: add --graph-lane-limit option 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater @ 2026-03-25 7:02 ` SZEDER Gábor 2026-03-25 10:03 ` Johannes Sixt 1 sibling, 0 replies; 39+ messages in thread From: SZEDER Gábor @ 2026-03-25 7:02 UTC (permalink / raw) To: Pablo Sabater Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t On Mon, Mar 23, 2026 at 10:59:33PM +0100, Pablo Sabater wrote: > Repositories that have many active branches at the same time produce > wide graphs. A lane consists of two columns, the edge and the space > padding, each branch takes a lane in the graph and there is no way > to limit how many can be shown. > > Add '--graph-lane-limit=<n>' revision option that caps the number > of visible lanes to n. This option requires '--graph', without it > a limit to the graph has no meaning, in this case error out. > > Zero and negative values are valid inputs but silently ignored > treating them as "no limit", the same as not using the option. > This follows what '--max-parents' does with negative values. > > Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> > --- > graph.c | 9 +++++++++ > revision.c | 6 ++++++ > revision.h | 1 + > 3 files changed, 16 insertions(+) > > diff --git a/graph.c b/graph.c > index 26f6fbf000..e7c1151ac0 100644 > --- a/graph.c > +++ b/graph.c > @@ -317,6 +317,15 @@ struct git_graph { > struct strbuf prefix_buf; > }; > > +static int graph_needs_truncation(struct git_graph *graph, int lane) > +{ > + int max = graph->revs->graph_max_lanes; > + /* > + * Ignore values <= 0, meaning no limit. > + */ > + return max > 0 && lane >= max; > +} This patch adds this static function, but it doesn't add any callers. This breaks the build with DEVELOPER=1: $ make DEVELOPER=1 graph.o CC graph.o graph.c:320:12: error: ‘graph_needs_truncation’ defined but not used [-Werror=unused-function] 320 | static int graph_needs_truncation(struct git_graph *graph, int lane) | ^~~~~~~~~~~~~~~~~~~~~~ cc1: all warnings being treated as errors make: *** [Makefile:2923: graph.o] Error 1 > static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) > { > struct git_graph *graph = data; > diff --git a/revision.c b/revision.c > index 31808e3df0..952edb031e 100644 > --- a/revision.c > +++ b/revision.c > @@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg > } else if (!strcmp(arg, "--no-graph")) { > graph_clear(revs->graph); > revs->graph = NULL; > + } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { > + revs->graph_max_lanes = parse_count(optarg); > } else if (!strcmp(arg, "--encode-email-headers")) { > revs->encode_email_headers = 1; > } else if (!strcmp(arg, "--no-encode-email-headers")) { > @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s > > if (revs->no_walk && revs->graph) > die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); > + > + if (revs->graph_max_lanes > 0 && !revs->graph) > + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); > + > if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) > die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); > > diff --git a/revision.h b/revision.h > index 69242ecb18..874ccce625 100644 > --- a/revision.h > +++ b/revision.h > @@ -304,6 +304,7 @@ struct rev_info { > > /* Display history graph */ > struct git_graph *graph; > + int graph_max_lanes; > > /* special limits */ > int skip_count; > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 1/3] graph: add --graph-lane-limit option 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater 2026-03-25 7:02 ` SZEDER Gábor @ 2026-03-25 10:03 ` Johannes Sixt 2026-03-25 12:29 ` Pablo 1 sibling, 1 reply; 39+ messages in thread From: Johannes Sixt @ 2026-03-25 10:03 UTC (permalink / raw) To: Pablo Sabater Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Am 23.03.26 um 22:59 schrieb Pablo Sabater: > @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s > > if (revs->no_walk && revs->graph) > die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); > + > + if (revs->graph_max_lanes > 0 && !revs->graph) > + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); > + > if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) > die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); You help translators if you make the new error message format string exactly identical to the one that we see in the post-context. -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 1/3] graph: add --graph-lane-limit option 2026-03-25 10:03 ` Johannes Sixt @ 2026-03-25 12:29 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-25 12:29 UTC (permalink / raw) To: Johannes Sixt Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Johannes Sixt (<j6t@kdbg.org>) writes: > > Am 23.03.26 um 22:59 schrieb Pablo Sabater: > > @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s > > > > if (revs->no_walk && revs->graph) > > die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); > > + > > + if (revs->graph_max_lanes > 0 && !revs->graph) > > + die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); > > + > > if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) > > die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); > > You help translators if you make the new error message format string > exactly identical to the one that we see in the post-context. > True, I'll make the messages the same in v5. > -- Hannes > ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v4 2/3] graph: truncate graph visual output 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater @ 2026-03-23 21:59 ` Pablo Sabater 2026-03-25 10:04 ` Johannes Sixt 2026-03-23 21:59 ` [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit Pablo Sabater ` (2 subsequent siblings) 4 siblings, 1 reply; 39+ messages in thread From: Pablo Sabater @ 2026-03-23 21:59 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater When '--graph-lane-limit' is set, lanes over the limit should not be drawn. Teach each graph state to stop rendering at the lane limit and print a '.' truncation mark, so users know that there are hidden lanes. On the commit line, if the commit lives on a visible lane, show the normal commit mark and truncate after it, if the commit lives on the first hidden lane (the truncation mark lane) show the "*" instead of the truncation mark so it is known that this commit sits on the first hidden lane. Commits on deeper lanes don't leave a mark. For merges, the post-merge lane is only needed when the commit or the first parent lives on a visible lane (to draw the connection between them), when both are on hidden lanes, post-merge carries no useful information, skip it and go to collapsing or padding state. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/graph.c b/graph.c index e7c1151ac0..886ef7cede 100644 --- a/graph.c +++ b/graph.c @@ -705,6 +705,20 @@ static void graph_update_columns(struct git_graph *graph) } } + /* + * If graph_max_lanes is set, cap the padding from the branches + */ + if (graph->revs->graph_max_lanes > 0) { + /* + * Get the maximum width by multiplying the maximum number of + * lanes by the size of the lane "| " and adds the truncation + * mark ". " + */ + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; + if (graph->width > max_columns_width) + graph->width = max_columns_width; + } + /* * Shrink mapping_size to be the minimum necessary */ @@ -855,6 +869,10 @@ 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++) { + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -912,6 +930,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -1022,6 +1043,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line * children that we have already processed.) */ seen_this = 0; + for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -1037,8 +1059,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(graph, i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1074,10 +1105,32 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line /* * Update graph->state + * + * If the commit is a merge and the first parent is in a visible lane, + * then the GRAPH_POST_MERGE is needed to draw the merge lane. + * + * If the commit is over the truncation limit, but the first parent is on + * a visible lane, then we still need the merge lane but truncated. + * + * If both commit and first parent are over the truncation limit, then + * there's no need to draw the merge lane because it would work as a + * padding lane. */ - if (graph->num_parents > 1) - graph_update_state(graph, GRAPH_POST_MERGE); - else if (graph_is_mapping_correct(graph)) + if (graph->num_parents > 1) { + if (!graph_needs_truncation(graph, graph->commit_index)) { + graph_update_state(graph, GRAPH_POST_MERGE); + } else { + struct commit_list *first_parent = first_interesting_parent(graph); + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); + + if (!graph_needs_truncation(graph, first_parent_col)) + graph_update_state(graph, GRAPH_POST_MERGE); + else if (graph_is_mapping_correct(graph)) + graph_update_state(graph, GRAPH_PADDING); + else + graph_update_state(graph, GRAPH_COLLAPSING); + } + } else if (graph_is_mapping_correct(graph)) graph_update_state(graph, GRAPH_PADDING); else graph_update_state(graph, GRAPH_COLLAPSING); @@ -1118,14 +1171,28 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { + unsigned int truncation_max = i + (j > 1 ? j - 1 : 0); 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 (j >= 2) + truncation_max -= 1; + + if (graph_needs_truncation(graph, truncation_max)) { + if (j > 0 && !(graph->edges_added > 0)) + graph_line_addch(line, ' '); + graph_line_addstr(line, ". "); + truncated = 1; + break; + } + if (idx == 2) { if (graph->edges_added > 0 || j < graph->num_parents - 1) graph_line_addch(line, ' '); @@ -1134,15 +1201,24 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, ". "); + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); else graph_line_write_column(line, col, '|'); - graph_line_addch(line, ' '); + /* + * If it's between two lanes and next would be truncated, + * don't add space padding. + */ + if (!graph_needs_truncation(graph, i + 1)) + graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); if (graph->merge_layout != 0 || i != graph->commit_index - 1) { @@ -1173,6 +1249,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l short used_horizontal = 0; int horizontal_edge = -1; int horizontal_edge_target = -1; + int truncated = 0; /* * Swap the mapping and old_mapping arrays @@ -1288,12 +1365,20 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (target < 0) - graph_line_addch(line, ' '); - else if (target * 2 == i) - graph_line_write_column(line, &graph->new_columns[target], '|'); - else if (target == horizontal_edge_target && - i != horizontal_edge - 1) { + + if (!truncated && graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, ". "); + truncated = 1; + } + + if (target < 0) { + if (!truncated) + graph_line_addch(line, ' '); + } else if (target * 2 == i) { + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '|'); + } else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { /* * Set the mappings for all but the * first segment to -1 so that they @@ -1301,13 +1386,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ if (i != (target * 2)+3) graph->mapping[i] = -1; - used_horizontal = 1; - graph_line_write_column(line, &graph->new_columns[target], '_'); + used_horizontal = 1; + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->mapping[i] = -1; - graph_line_write_column(line, &graph->new_columns[target], '/'); - + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '/'); } } @@ -1356,7 +1442,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) graph_output_collapsing_line(graph, &line); break; } - graph_pad_horizontally(graph, &line); return shown_commit_line; } @@ -1381,6 +1466,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(graph, i)) { + graph_line_addch(&line, '.'); + break; + } + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 2/3] graph: truncate graph visual output 2026-03-23 21:59 ` [GSoC PATCH v4 2/3] graph: truncate graph visual output Pablo Sabater @ 2026-03-25 10:04 ` Johannes Sixt 2026-03-25 11:19 ` Pablo 0 siblings, 1 reply; 39+ messages in thread From: Johannes Sixt @ 2026-03-25 10:04 UTC (permalink / raw) To: Pablo Sabater Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Am 23.03.26 um 22:59 schrieb Pablo Sabater: > + /* > + * If graph_max_lanes is set, cap the padding from the branches > + */ > + if (graph->revs->graph_max_lanes > 0) { > + /* > + * Get the maximum width by multiplying the maximum number of > + * lanes by the size of the lane "| " and adds the truncation > + * mark ". " > + */ > + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; Please be to the point in the code comments. That there is a multiplication and an addition, we can see in the code. Perhaps: /* width of "| " per lane plus truncation mark ". " */ > + if (graph->width > max_columns_width) > + graph->width = max_columns_width; > + } > + > /* > * Shrink mapping_size to be the minimum necessary > */ > @@ -1022,6 +1043,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line > * children that we have already processed.) > */ > seen_this = 0; > + > for (i = 0; i <= graph->num_columns; i++) { > struct column *col = &graph->columns[i]; > struct commit *col_commit; Is this empty line really needed? > + if (j >= 2) > + truncation_max -= 1; I think it is more idiomatic way to write this as truncation_max--. > @@ -1288,12 +1365,20 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l > */ > for (i = 0; i < graph->mapping_size; i++) { > int target = graph->mapping[i]; > - if (target < 0) > - graph_line_addch(line, ' '); > - else if (target * 2 == i) > - graph_line_write_column(line, &graph->new_columns[target], '|'); > - else if (target == horizontal_edge_target && > - i != horizontal_edge - 1) { > + > + if (!truncated && graph_needs_truncation(graph, i / 2)) { > + graph_line_addstr(line, ". "); > + truncated = 1; > + } > + > + if (target < 0) { > + if (!truncated) > + graph_line_addch(line, ' '); > + } else if (target * 2 == i) { > + if (!truncated) > + graph_line_write_column(line, &graph->new_columns[target], '|'); > + } else if (target == horizontal_edge_target && > + i != horizontal_edge - 1) { > /* > * Set the mappings for all but the > * first segment to -1 so that they > @@ -1301,13 +1386,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l > */ > if (i != (target * 2)+3) > graph->mapping[i] = -1; > - used_horizontal = 1; > - graph_line_write_column(line, &graph->new_columns[target], '_'); > + used_horizontal = 1; > + if (!truncated) > + graph_line_write_column(line, &graph->new_columns[target], '_'); Huh? The indentation of "used_horizontal..." changed. The reason is that this whole if-branch is indented too far by one tab. Perhaps an initial clean-up commit that only fixes this indentation? > } else { > if (used_horizontal && i < horizontal_edge) > graph->mapping[i] = -1; > - graph_line_write_column(line, &graph->new_columns[target], '/'); > - > + if (!truncated) > + graph_line_write_column(line, &graph->new_columns[target], '/'); > } > } > > @@ -1356,7 +1442,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) > graph_output_collapsing_line(graph, &line); > break; > } > - > graph_pad_horizontally(graph, &line); > return shown_commit_line; > } This removal of an empty line isn't warranted, I think. -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 2/3] graph: truncate graph visual output 2026-03-25 10:04 ` Johannes Sixt @ 2026-03-25 11:19 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-25 11:19 UTC (permalink / raw) To: Johannes Sixt Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Johannes Sixt (<j6t@kdbg.org>) writes: > > Am 23.03.26 um 22:59 schrieb Pablo Sabater: > > + /* > > + * If graph_max_lanes is set, cap the padding from the branches > > + */ > > + if (graph->revs->graph_max_lanes > 0) { > > + /* > > + * Get the maximum width by multiplying the maximum number of > > + * lanes by the size of the lane "| " and adds the truncation > > + * mark ". " > > + */ > > + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; > > Please be to the point in the code comments. That there is a > multiplication and an addition, we can see in the code. Perhaps: > > /* width of "| " per lane plus truncation mark ". " */ I'll use this one instead, thanks. > > + if (graph->width > max_columns_width) > > + graph->width = max_columns_width; > > + } > > + > > /* > > * Shrink mapping_size to be the minimum necessary > > */ > > > @@ -1022,6 +1043,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line > > * children that we have already processed.) > > */ > > seen_this = 0; > > + > > for (i = 0; i <= graph->num_columns; i++) { > > struct column *col = &graph->columns[i]; > > struct commit *col_commit; > > Is this empty line really needed? No, I'll drop it. > > > + if (j >= 2) > > + truncation_max -= 1; > > I think it is more idiomatic way to write this as truncation_max--. I'll change it. > > > @@ -1288,12 +1365,20 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l > > */ > > for (i = 0; i < graph->mapping_size; i++) { > > int target = graph->mapping[i]; > > - if (target < 0) > > - graph_line_addch(line, ' '); > > - else if (target * 2 == i) > > - graph_line_write_column(line, &graph->new_columns[target], '|'); > > - else if (target == horizontal_edge_target && > > - i != horizontal_edge - 1) { > > + > > + if (!truncated && graph_needs_truncation(graph, i / 2)) { > > + graph_line_addstr(line, ". "); > > + truncated = 1; > > + } > > + > > + if (target < 0) { > > + if (!truncated) > > + graph_line_addch(line, ' '); > > + } else if (target * 2 == i) { > > + if (!truncated) > > + graph_line_write_column(line, &graph->new_columns[target], '|'); > > + } else if (target == horizontal_edge_target && > > + i != horizontal_edge - 1) { > > /* > > * Set the mappings for all but the > > * first segment to -1 so that they > > @@ -1301,13 +1386,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l > > */ > > if (i != (target * 2)+3) > > graph->mapping[i] = -1; > > - used_horizontal = 1; > > - graph_line_write_column(line, &graph->new_columns[target], '_'); > > + used_horizontal = 1; > > + if (!truncated) > > + graph_line_write_column(line, &graph->new_columns[target], '_'); > > Huh? The indentation of "used_horizontal..." changed. The reason is that > this whole if-branch is indented too far by one tab. Perhaps an initial > clean-up commit that only fixes this indentation? I'll fix it, but I think it will be better to have it on the patch with the truncation logic because it is a tiny cleanup and doesn't really justify a whole commit for it. > > } else { > > if (used_horizontal && i < horizontal_edge) > > graph->mapping[i] = -1; > > - graph_line_write_column(line, &graph->new_columns[target], '/'); > > - > > + if (!truncated) > > + graph_line_write_column(line, &graph->new_columns[target], '/'); > > } > > } > > > > @@ -1356,7 +1442,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) > > graph_output_collapsing_line(graph, &line); > > break; > > } > > - > > graph_pad_horizontally(graph, &line); > > return shown_commit_line; > > } > > This removal of an empty line isn't warranted, I think. No, I'll revert that. > > -- Hannes > ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 2/3] graph: truncate graph visual output Pablo Sabater @ 2026-03-23 21:59 ` Pablo Sabater 2026-03-25 10:07 ` Johannes Sixt 2026-03-25 10:02 ` [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option Johannes Sixt 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater 4 siblings, 1 reply; 39+ messages in thread From: Pablo Sabater @ 2026-03-23 21:59 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, Pablo Sabater Document --graph-lane-limit option in rev-list-options.adoc with --graph option. Add two tests in t4215 reusing existing graphs. The first test limits to two lanes on a single tip and the second one limits to three lanes on multiple tips. Both tests check that everything is truncated correctly. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/rev-list-options.adoc | 5 +++ t/t4215-log-skewed-merges.sh | 53 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1474..2b5a1794cd 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the in between them in that case. If _<barrier>_ is specified, it is the string that will be shown instead of the default one. +`--graph-lane-limit=<n>`:: + When `--graph` is used, limit the number of graph lanes to be shown. + Lanes over the limit are replaced with a truncation mark '.'. By default + there is no limit. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..650701df42 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -370,4 +370,57 @@ test_expect_success 'log --graph with multiple tips' ' EOF ' +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' + check_graph --graph-lane-limit=2 M_7 <<-\EOF + *-. 7_M4 + |\ \ + | | * 7_G + | | * 7_F + | * . 7_E + | * . 7_D + * | . 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | . 7_M3 + | | | . 7_J + | | | . 7_I + | | | . 7_M4 + | |_|_. + |/| | . + | | |_. + | |/| . + | | | . + | | |/. + | | * . 7_G + | | | . + | | |/. + | | * . 7_F + | * | . 7_E + | | |/. + | |/| . + | * | . 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit 2026-03-23 21:59 ` [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit Pablo Sabater @ 2026-03-25 10:07 ` Johannes Sixt 2026-03-25 11:49 ` Pablo 0 siblings, 1 reply; 39+ messages in thread From: Johannes Sixt @ 2026-03-25 10:07 UTC (permalink / raw) To: Pablo Sabater, git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster Am 23.03.26 um 22:59 schrieb Pablo Sabater: > @@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the > in between them in that case. If _<barrier>_ is specified, it > is the string that will be shown instead of the default one. > > +`--graph-lane-limit=<n>`:: > + When `--graph` is used, limit the number of graph lanes to be shown. > + Lanes over the limit are replaced with a truncation mark '.'. By default > + there is no limit. This should probably mention that 0 means no limit. > + > ifdef::git-rev-list[] > `--count`:: > Print a number stating how many commits would have been > diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh > index 28d0779a8c..650701df42 100755 > --- a/t/t4215-log-skewed-merges.sh > +++ b/t/t4215-log-skewed-merges.sh > @@ -370,4 +370,57 @@ test_expect_success 'log --graph with multiple tips' ' > EOF > ' > > +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' > + check_graph --graph-lane-limit=2 M_7 <<-\EOF > + *-. 7_M4 > + |\ \ > + | | * 7_G > + | | * 7_F > + | * . 7_E > + | * . 7_D > + * | . 7_C > + | |/ > + |/| > + * | 7_B > + |/ > + * 7_A I'm confused. If the lane limit is 2, why do we have actually have 3 lanes? > +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' > + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF > + * 7_M1 > + |\ > + | | * 7_M2 > + | | |\ > + | | | * 7_H > + | | | . 7_M3 > + | | | . 7_J > + | | | . 7_I > + | | | . 7_M4 > + | |_|_. > + |/| | . > + | | |_. > + | |/| . > + | | | . > + | | |/. > + | | * . 7_G > + | | | . > + | | |/. > + | | * . 7_F > + | * | . 7_E > + | | |/. > + | |/| . > + | * | . 7_D > + | | |/ > + | |/| > + * | | 7_C > + | |/ > + |/| > + * | 7_B > + |/ > + * 7_A Same here. Why is there a fourth lane? Oh! "Truncation" here does not mean that the vertial lines are cut off and are supposed to continue sometime later in the chart. It literally means that the *line* is truncated and just some stuff *on that line* is omitted. Ouch! That was not what I was expecting. I thought that truncation means that when the eye follows a line vertically, it finds the truncation point of the line at some point, and then the continuation of that line is again some time later down the chart. The only clue which lanes are the same would be the color, which would have to be remedied somehow. I don't know what to make of it. I have to reconsider. -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit 2026-03-25 10:07 ` Johannes Sixt @ 2026-03-25 11:49 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-25 11:49 UTC (permalink / raw) To: Johannes Sixt Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster Johannes Sixt (<j6t@kdbg.org>) writes: > > Am 23.03.26 um 22:59 schrieb Pablo Sabater: > > @@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the > > in between them in that case. If _<barrier>_ is specified, it > > is the string that will be shown instead of the default one. > > > > +`--graph-lane-limit=<n>`:: > > + When `--graph` is used, limit the number of graph lanes to be shown. > > + Lanes over the limit are replaced with a truncation mark '.'. By default > > + there is no limit. > > This should probably mention that 0 means no limit. I'll add that, it is for zero or any negative number. > > > + > > ifdef::git-rev-list[] > > `--count`:: > > Print a number stating how many commits would have been > > diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh > > index 28d0779a8c..650701df42 100755 > > --- a/t/t4215-log-skewed-merges.sh > > +++ b/t/t4215-log-skewed-merges.sh > > @@ -370,4 +370,57 @@ test_expect_success 'log --graph with multiple tips' ' > > EOF > > ' > > > > +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' > > + check_graph --graph-lane-limit=2 M_7 <<-\EOF > > + *-. 7_M4 > > + |\ \ > > + | | * 7_G > > + | | * 7_F > > + | * . 7_E > > + | * . 7_D > > + * | . 7_C > > + | |/ > > + |/| > > + * | 7_B > > + |/ > > + * 7_A > > I'm confused. If the lane limit is 2, why do we have actually have 3 lanes? > > > +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' > > + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF > > + * 7_M1 > > + |\ > > + | | * 7_M2 > > + | | |\ > > + | | | * 7_H > > + | | | . 7_M3 > > + | | | . 7_J > > + | | | . 7_I > > + | | | . 7_M4 > > + | |_|_. > > + |/| | . > > + | | |_. > > + | |/| . > > + | | | . > > + | | |/. > > + | | * . 7_G > > + | | | . > > + | | |/. > > + | | * . 7_F > > + | * | . 7_E > > + | | |/. > > + | |/| . > > + | * | . 7_D > > + | | |/ > > + | |/| > > + * | | 7_C > > + | |/ > > + |/| > > + * | 7_B > > + |/ > > + * 7_A > > Same here. Why is there a fourth lane? The extra column is the truncation marker that shows in every lane that had to be truncated horizontally, not an actual lane, even tho that by keeping the right side edge might seem confusing. > > Oh! "Truncation" here does not mean that the vertical lines are cut off > and are supposed to continue sometime later in the chart. It literally > means that the *line* is truncated and just some stuff *on that line* is > omitted. > > Ouch! That was not what I was expecting. I thought that truncation means > that when the eye follows a line vertically, it finds the truncation > point of the line at some point, and then the continuation of that line > is again some time later down the chart. The only clue which lanes are > the same would be the color, which would have to be remedied somehow. > > I don't know what to make of it. I have to reconsider. Yes, the truncation is horizontal, each lane is cut at the lane limit. I went with this because it is more accessible starting to work at the graph and understanding it (it is a 17 years old code). Vertical truncation needs rearranging which lanes are visible and which ones come and go, similar to gitk (This is the TODO idea that is present on the graph.c code) I think it will be hard to do it even if there is a way of not having to rewrite the whole graph rendering code. prob if this is done it won't replace the actual --graph but actually become something like --rgraph as a different graph rendering option. > > -- Hannes > I hope I a made my intentions with horizontal truncation more clear. I'll send a v5 with all the changes talked about. Thanks for the feedback throughout all the patches! Pablo ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater ` (2 preceding siblings ...) 2026-03-23 21:59 ` [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit Pablo Sabater @ 2026-03-25 10:02 ` Johannes Sixt 2026-03-25 12:28 ` Pablo 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater 4 siblings, 1 reply; 39+ messages in thread From: Johannes Sixt @ 2026-03-25 10:02 UTC (permalink / raw) To: Pablo Sabater Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Am 23.03.26 um 22:59 schrieb Pablo Sabater: > Repositories that have many active branches at the same time produce > wide graphs. A lane consists of two columns, the edge and the space > padding, each branch takes a lane in the graph and there is no way > to limit how many can be shown. Generally, I like the goal of this patch series. However, the way in which it is presented and justified can be improved substantially, IMO. It begins with the statement of what this patch series wants to achieve. It is "limit the width of the graph", isn't it? It is not "add --graph-lane-limit"; that is just a tool to achieve the goal. To help reviewers, you should present an example chart in the cover letter that shows the before- and after-state (with and without the user of the new option). As far as the separation into patches is concerned, I see a few problems. With the current separation is difficult to justify the patches. For example, the first patch adds prerequisites for a later patch, but it is unclear how these are used. The answer to the question "Why do we need this?" is simply "because the next patch uses them", but this is a very weak justification, because the next questions are "how are they used and why didn't you squash this into the next patch?" Let me suggest a different separation. 1. The first patch limits the graph width with a hard-coded limit, say 15 lanes. It limits the graph *always*. Choose a limit that is large enough to pass all tests. 2. The next patch adds --graph-lane-limit and its documentation. Let it do its thing. Revert to the default limit value 0, i.e., unlimited. 3. Next, add additional eye-candy. I am alluding to the line that marks where a graph lane was truncated. (4. If more detailed document is warranted, e.g., an example chart, do this as a separate patch that can now show all bells and whistles that the earlier commits have implemented. Whether this makes sense as a separate step, or whether documentation grows with the earlier patches, is a judgement call.) As far as commit messages are concerned, always, always provide an answer to "Why?" for every detail. - Why do we want to limit the graph width? - Why is the hard-coded limit 15? (because it lets tests pass and is still a useful limit; we'll make it dynamic later.) - Why do we always limit the graph width? (Because it makes this patch simpler; we'll fix this later.) - Why does 0 mean unlimited? (Consistency with --max-parents.) - Why is the truncation marked with a fullstop "."? (...) I'll also look over the patches, but I don't do C code, so I can provide only superficial comments, if any. -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option 2026-03-25 10:02 ` [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option Johannes Sixt @ 2026-03-25 12:28 ` Pablo 2026-03-25 17:44 ` Johannes Sixt 0 siblings, 1 reply; 39+ messages in thread From: Pablo @ 2026-03-25 12:28 UTC (permalink / raw) To: Johannes Sixt Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Johannes Sixt (<j6t@kdbg.org>) writes: > Generally, I like the goal of this patch series. However, the way in > which it is presented and justified can be improved substantially, IMO. > > It begins with the statement of what this patch series wants to achieve. > It is "limit the width of the graph", isn't it? It is not "add > --graph-lane-limit"; that is just a tool to achieve the goal. > > To help reviewers, you should present an example chart in the cover > letter that shows the before- and after-state (with and without the user > of the new option). True, I'll do that. Because it seems on your last review that I didn't explain myself correctly, the idea was to: Without --graph-lane-limit: | | | | | | | * commit message | | | | | |/ With --graph-lane-limit=3: | | | . commit message | | | . It truncates the lanes horizontally at the lane limit, the "." replaces everything over the limit (n+1). > As far as the separation into patches is concerned, I see a few > problems. With the current separation is difficult to justify the > patches. For example, the first patch adds prerequisites for a later > patch, but it is unclear how these are used. The answer to the question > "Why do we need this?" is simply "because the next patch uses them", but > this is a very weak justification, because the next questions are "how > are they used and why didn't you squash this into the next patch?" > > Let me suggest a different separation. I'll merge 1st and 2nd patch together into a single one, adding the option together with the actual logic that does it. This fixes what SZEDER said about the first patch alone breaking the build. And the documentation + tests on a separate commit. > 1. The first patch limits the graph width with a hard-coded limit, say > 15 lanes. It limits the graph *always*. Choose a limit that is large > enough to pass all tests. > > 2. The next patch adds --graph-lane-limit and its documentation. Let it > do its thing. Revert to the default limit value 0, i.e., unlimited. > > 3. Next, add additional eye-candy. I am alluding to the line that marks > where a graph lane was truncated. > > (4. If more detailed document is warranted, e.g., an example chart, do > this as a separate patch that can now show all bells and whistles that > the earlier commits have implemented. Whether this makes sense as a > separate step, or whether documentation grows with the earlier patches, > is a judgement call.) > > As far as commit messages are concerned, always, always provide an > answer to "Why?" for every detail. > > - Why do we want to limit the graph width? > - Why is the hard-coded limit 15? (because it lets tests pass and is > still a useful limit; we'll make it dynamic later.) > - Why do we always limit the graph width? (Because it makes this patch > simpler; we'll fix this later.) > - Why does 0 mean unlimited? (Consistency with --max-parents.) > - Why is the truncation marked with a fullstop "."? (...) Ok, I'll make sure to answer clearly all the WHY's on the commits. > I'll also look over the patches, but I don't do C code, so I can provide > only superficial comments, if any. > > -- Hannes > ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option 2026-03-25 12:28 ` Pablo @ 2026-03-25 17:44 ` Johannes Sixt 2026-03-25 17:58 ` Pablo 0 siblings, 1 reply; 39+ messages in thread From: Johannes Sixt @ 2026-03-25 17:44 UTC (permalink / raw) To: Pablo Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Am 25.03.26 um 13:28 schrieb Pablo: > Johannes Sixt (<j6t@kdbg.org>) writes: >> Let me suggest a different separation. > > I'll merge 1st and 2nd patch together into a single one, adding the option > together with the actual logic that does it. This fixes what SZEDER said about > the first patch alone breaking the build. > > And the documentation + tests on a separate commit. It is better to add documentation and tests in the same commit that add the feature, because both serve as a specification what the code is supposed to do. This way reviewers can decide whether the code does indeed work as designed. On top of that, when the code has to be inspected later, the commit that introduced the code shows immediately whether a certain behavior was intentional or not. So, you would end up with a single patch. But to make reviewing easier, I proposed a different split: >> 1. The first patch limits the graph width with a hard-coded limit, say >> 15 lanes. It limits the graph *always*. Choose a limit that is large >> enough to pass all tests. This change will touch the graphing engine, but almost nothing else. >> 2. The next patch adds --graph-lane-limit and its documentation. Let it >> do its thing. Revert to the default limit value 0, i.e., unlimited. This change now introduces all the plumbing that passes the user's option through to the engine. >> 3. Next, add additional eye-candy. I am alluding to the line that marks >> where a graph lane was truncated. If possible, this change provides final touches that can reasonably be left out from the first patch without compromising its basic functionality. >> (4. If more detailed document is warranted, e.g., an example chart, do >> this as a separate patch that can now show all bells and whistles that >> the earlier commits have implemented. Whether this makes sense as a >> separate step, or whether documentation grows with the earlier patches, >> is a judgement call.) This could be a new paragraph in the manuals with example charts if doing so makes sense. -- Hannes ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option 2026-03-25 17:44 ` Johannes Sixt @ 2026-03-25 17:58 ` Pablo 0 siblings, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-25 17:58 UTC (permalink / raw) To: Johannes Sixt Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, git Johannes Sixt (<j6t@kdbg.org>) writes: > > Am 25.03.26 um 13:28 schrieb Pablo: > > Johannes Sixt (<j6t@kdbg.org>) writes: > >> Let me suggest a different separation. > > > > I'll merge 1st and 2nd patch together into a single one, adding the option > > together with the actual logic that does it. This fixes what SZEDER said about > > the first patch alone breaking the build. > > > > And the documentation + tests on a separate commit. > > It is better to add documentation and tests in the same commit that add > the feature, because both serve as a specification what the code is > supposed to do. This way reviewers can decide whether the code does > indeed work as designed. On top of that, when the code has to be > inspected later, the commit that introduced the code shows immediately > whether a certain behavior was intentional or not. > > So, you would end up with a single patch. > > But to make reviewing easier, I proposed a different split: > > >> 1. The first patch limits the graph width with a hard-coded limit, say > >> 15 lanes. It limits the graph *always*. Choose a limit that is large > >> enough to pass all tests. > > This change will touch the graphing engine, but almost nothing else. > > >> 2. The next patch adds --graph-lane-limit and its documentation. Let it > >> do its thing. Revert to the default limit value 0, i.e., unlimited. > > This change now introduces all the plumbing that passes the user's > option through to the engine. > > >> 3. Next, add additional eye-candy. I am alluding to the line that marks > >> where a graph lane was truncated. > > If possible, this change provides final touches that can reasonably be > left out from the first patch without compromising its basic functionality. > > >> (4. If more detailed document is warranted, e.g., an example chart, do > >> this as a separate patch that can now show all bells and whistles that > >> the earlier commits have implemented. Whether this makes sense as a > >> separate step, or whether documentation grows with the earlier patches, > >> is a judgement call.) > > This could be a new paragraph in the manuals with example charts if > doing so makes sense. Ok, I'll do that to make it better for reviewing, thanks. I've just sent the v5 at the same time with this morning's feedback so it has the 2 patch split I talked about but I'll do a v6 following this. > > -- Hannes > Thanks for explaining everything, the patience and the feedback. Pablo ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v5 0/2] graph: add --graph-lane-limit option 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater ` (3 preceding siblings ...) 2026-03-25 10:02 ` [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option Johannes Sixt @ 2026-03-25 17:43 ` Pablo Sabater 2026-03-25 17:44 ` [GSoC PATCH v5 1/2] " Pablo Sabater ` (2 more replies) 4 siblings, 3 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-25 17:43 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the space padding, each branch takes a lane in the graph and there is no way to limit how many can be shown. The limit is a horizontal truncation, each lane is cut at the lane limit: Without --graph-lane-limit: * 7_M1 |\ | * 7_E * | 7_C | | * 7_M2 | | |\ | | | * 7_H | | |/ | |/| | * | 7_D | | * 7_G | | * 7_F | |/ |/| * | 7_B |/ * 7_A With --graph-lane-limit=1: * 7_M1 |\ | * 7_E * ~ 7_C | ~ 7_M2 | ~ 7_H | ~ | ~ | * 7_D | ~ 7_G | ~ 7_F | ~ |/~ * ~ 7_B |/ * 7_A The '~' is the truncation mark, not an actual lane. It was chosen because '.' is already used in octopus merges and '~' is not used elsewhere in the graph. Yet the edges between the last visible lane and the truncation mark are still conserved. The '*' commit mark is visible when it lives on a visible lane or the first hidden lane, any deeper lane doesn't show the commit mark but keeps the commit message visible. Merges where neither the commit nor its parents live on a visible lane are skipped because they don't carry any visible information. The original idea to limit columns was noted as a TODO in c12172d2ea (Add history graph API, 2008-05-04). This does not implement gitk-style column rearrangement, it only truncates the visual output. Possible future improvements: - When all branches involved in collapsing or padding are over the limit, the truncated lane doesn't show any information, this lane could be removed to make the graph more compact. Currently these lanes still appear because graph_output_collapsing_line() mixes state handling with rendering, so it can't be skipped but callers always expect a non empty buffer. Fixing it would need to refactor the callers to handle empty buffers instead of expecting them to always have content. - Collapsing and merges lanes that start on visible lanes but end on hidden ones are kept to maintain the most information possible on the visible lanes, but the information about where they go is lost. They can be kept, removed or think of a way to show that information without showing the lanes. Changes since v4: - Merged the option parsing and the truncation logic into a single patch, fixing the DEVELOPER=1 build break when the first patch was alone. - Added before/after example to clarify horizontal truncation. - Fixed error message to match existing format strings. - Shortened code comment. - Changed truncation_max -= 1 to truncation_max--. - Fixed pre-existing indentation in graph_output_collapsing_line(). - Fixed unnecessary blank line changes. - Added that zero and negative values mean no limit in the docs. - Changed truncation mark from '.' to '~' because '.' is already used in octopus merges. - Added more tests. Pablo Sabater (2): graph: add --graph-lane-limit option graph: add documentation and tests about --graph-lane-limit Documentation/rev-list-options.adoc | 6 ++ graph.c | 149 +++++++++++++++++++++++----- revision.c | 6 ++ revision.h | 1 + t/t4215-log-skewed-merges.sh | 144 +++++++++++++++++++++++++++ 5 files changed, 283 insertions(+), 23 deletions(-) base-commit: ce74208c2fa13943fffa58f168ac27a76d0eb789 -- 2.43.0 ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v5 1/2] graph: add --graph-lane-limit option 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater @ 2026-03-25 17:44 ` Pablo Sabater 2026-03-25 22:11 ` Junio C Hamano 2026-03-25 17:44 ` [GSoC PATCH v5 2/2] graph: add documentation and tests about --graph-lane-limit Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater 2 siblings, 1 reply; 39+ messages in thread From: Pablo Sabater @ 2026-03-25 17:44 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the space padding, each branch takes a lane in the graph and there is no way to limit how many can be shown. Add '--graph-lane-limit=<n>' revision option that caps the number of visible lanes to n. This option requires '--graph', without it a limit to the graph has no meaning, in this case error out. Zero and negative values are valid inputs but silently ignored treating them as "no limit", the same as not using the option. This follows what '--max-parents' does with negative values. When the limit is set, lanes over the limit are not drawn. Teach each graph state to stop rendering at the lane limit and print a "~" truncation mark, so users know that there are hidden lanes. The "~" was chosen because it was not used elsewhere in the graph and it is discrete. On the commit line, if the commit lives on a visible lane, show the normal commit mark and truncate after it, if the commit lives on the first hidden lane show the "*" instead of the truncation mark so it is known that this commit sits on the first hidden lane. Commits on deeper lanes don't leave a mark. For merges, the post-merge lane is only needed when the commit or the first parent lives on a visible lane (to draw the connection between them), when both are on hidden lanes, post-merge carries no useful information, skip it and go to collapsing or padding state. Also fix a pre-existing indentation issue. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 150 +++++++++++++++++++++++++++++++++++++++++++++-------- revision.c | 6 +++ revision.h | 1 + 3 files changed, 134 insertions(+), 23 deletions(-) diff --git a/graph.c b/graph.c index 26f6fbf000..2218f00c40 100644 --- a/graph.c +++ b/graph.c @@ -317,6 +317,15 @@ struct git_graph { struct strbuf prefix_buf; }; +static int graph_needs_truncation(struct git_graph *graph, int lane) +{ + int max = graph->revs->graph_max_lanes; + /* + * Ignore values <= 0, meaning no limit. + */ + return max > 0 && lane >= max; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; @@ -696,6 +705,18 @@ static void graph_update_columns(struct git_graph *graph) } } + /* + * If graph_max_lanes is set, cap the padding from the branches + */ + if (graph->revs->graph_max_lanes > 0) { + /* + * width of "| " per lanes plus truncation mark "~ ". + */ + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; + if (graph->width > max_columns_width) + graph->width = max_columns_width; + } + /* * Shrink mapping_size to be the minimum necessary */ @@ -846,6 +867,10 @@ 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++) { + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -903,6 +928,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -994,6 +1022,12 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line col = &graph->new_columns[j]; graph_line_write_column(line, col, '-'); + + if (graph_needs_truncation(graph, j / 2 + i)) { + graph_line_addstr(line, "~ "); + break; + } + graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); } @@ -1028,8 +1062,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(graph, i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1065,10 +1108,32 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line /* * Update graph->state + * + * If the commit is a merge and the first parent is in a visible lane, + * then the GRAPH_POST_MERGE is needed to draw the merge lane. + * + * If the commit is over the truncation limit, but the first parent is on + * a visible lane, then we still need the merge lane but truncated. + * + * If both commit and first parent are over the truncation limit, then + * there's no need to draw the merge lane because it would work as a + * padding lane. */ - if (graph->num_parents > 1) - graph_update_state(graph, GRAPH_POST_MERGE); - else if (graph_is_mapping_correct(graph)) + if (graph->num_parents > 1) { + if (!graph_needs_truncation(graph, graph->commit_index)) { + graph_update_state(graph, GRAPH_POST_MERGE); + } else { + struct commit_list *first_parent = first_interesting_parent(graph); + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); + + if (!graph_needs_truncation(graph, first_parent_col)) + graph_update_state(graph, GRAPH_POST_MERGE); + else if (graph_is_mapping_correct(graph)) + graph_update_state(graph, GRAPH_PADDING); + else + graph_update_state(graph, GRAPH_COLLAPSING); + } + } else if (graph_is_mapping_correct(graph)) graph_update_state(graph, GRAPH_PADDING); else graph_update_state(graph, GRAPH_COLLAPSING); @@ -1109,6 +1174,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { @@ -1117,23 +1183,46 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l c = merge_chars[idx]; graph_line_write_column(line, &graph->new_columns[par_column], c); + if (graph_needs_truncation(graph, j / 2 + i) && + j / 2 + i <= graph->num_columns) { + if ((j + i * 2) % 2 != 0) + graph_line_addch(line, ' '); + graph_line_addstr(line, "~ "); + truncated = 1; + break; + } + if (idx == 2) { - if (graph->edges_added > 0 || j < graph->num_parents - 1) + if (graph_needs_truncation(graph, (j + 1) / 2 + i) && + j < graph->num_parents - 1) { + graph_line_addstr(line, "~ "); + truncated = 1; + break; + } else if (graph->edges_added > 0 || j < graph->num_parents - 1) graph_line_addch(line, ' '); } else { idx++; } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); else graph_line_write_column(line, col, '|'); - graph_line_addch(line, ' '); + /* + * If it's between two lanes and next would be truncated, + * don't add space padding. + */ + if (!graph_needs_truncation(graph, i + 1)) + graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); if (graph->merge_layout != 0 || i != graph->commit_index - 1) { @@ -1164,6 +1253,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l short used_horizontal = 0; int horizontal_edge = -1; int horizontal_edge_target = -1; + int truncated = 0; /* * Swap the mapping and old_mapping arrays @@ -1279,26 +1369,35 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (target < 0) - graph_line_addch(line, ' '); - else if (target * 2 == i) - graph_line_write_column(line, &graph->new_columns[target], '|'); - else if (target == horizontal_edge_target && - i != horizontal_edge - 1) { - /* - * Set the mappings for all but the - * first segment to -1 so that they - * won't continue into the next line. - */ - if (i != (target * 2)+3) - graph->mapping[i] = -1; - used_horizontal = 1; - graph_line_write_column(line, &graph->new_columns[target], '_'); + + if (!truncated && graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, "~ "); + truncated = 1; + } + + if (target < 0) { + if (!truncated) + graph_line_addch(line, ' '); + } else if (target * 2 == i) { + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '|'); + } else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { + /* + * Set the mappings for all but the + * first segment to -1 so that they + * won't continue into the next line. + */ + if (i != (target * 2)+3) + graph->mapping[i] = -1; + used_horizontal = 1; + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->mapping[i] = -1; - graph_line_write_column(line, &graph->new_columns[target], '/'); - + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '/'); } } @@ -1372,6 +1471,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(graph, i)) { + graph_line_addch(&line, '~'); + break; + } + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { diff --git a/revision.c b/revision.c index 31808e3df0..81b67682a8 100644 --- a/revision.c +++ b/revision.c @@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { + revs->graph_max_lanes = parse_count(optarg); } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_lanes > 0 && !revs->graph) + die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); diff --git a/revision.h b/revision.h index 69242ecb18..874ccce625 100644 --- a/revision.h +++ b/revision.h @@ -304,6 +304,7 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + int graph_max_lanes; /* special limits */ int skip_count; base-commit: ce74208c2fa13943fffa58f168ac27a76d0eb789 -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v5 1/2] graph: add --graph-lane-limit option 2026-03-25 17:44 ` [GSoC PATCH v5 1/2] " Pablo Sabater @ 2026-03-25 22:11 ` Junio C Hamano 2026-03-27 14:22 ` Pablo 0 siblings, 1 reply; 39+ messages in thread From: Junio C Hamano @ 2026-03-25 22:11 UTC (permalink / raw) To: Pablo Sabater Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t, szeder.dev Pablo Sabater <pabloosabaterr@gmail.com> writes: > +static int graph_needs_truncation(struct git_graph *graph, int lane) > +{ > + int max = graph->revs->graph_max_lanes; > + /* > + * Ignore values <= 0, meaning no limit. > + */ > + return max > 0 && lane >= max; > +} Make a mental note that this helper function works on number of lanes, not display columns (which is roughly twice the number of lanes). > @@ -696,6 +705,18 @@ static void graph_update_columns(struct git_graph *graph) > } > } > > + /* > + * If graph_max_lanes is set, cap the padding from the branches > + */ > + if (graph->revs->graph_max_lanes > 0) { > + /* > + * width of "| " per lanes plus truncation mark "~ ". > + */ > + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; > + if (graph->width > max_columns_width) > + graph->width = max_columns_width; > + } > + > /* > * Shrink mapping_size to be the minimum necessary > */ > @@ -846,6 +867,10 @@ 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++) { > + if (graph_needs_truncation(graph, i)) { > + graph_line_addstr(line, "~ "); > + break; > + } And that mental note helps to convince us this loop makes sense, as it increments 'i' one by one ;-) > @@ -903,6 +928,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, > seen_this = 1; > graph_line_write_column(line, col, '|'); > graph_line_addchars(line, ' ', graph->expansion_row); > + } else if (seen_this && graph_needs_truncation(graph, i)) { > + graph_line_addstr(line, "~ "); > + break; > } else if (seen_this && (graph->expansion_row == 0)) { > /* > * This is the first line of the pre-commit output. > @@ -994,6 +1022,12 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line > col = &graph->new_columns[j]; > > graph_line_write_column(line, col, '-'); And here, 'j' comes from graph->mapping[] array. Does that count in display columns or lanes? > + if (graph_needs_truncation(graph, j / 2 + i)) { This makes it look as if 'j' counts in columns and needs to be divided by 2 to make it comparable to lanes. > + graph_line_addstr(line, "~ "); > + break; > + } > + > graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); > } > > + if (graph->num_parents > 1) { > + if (!graph_needs_truncation(graph, graph->commit_index)) { > + graph_update_state(graph, GRAPH_POST_MERGE); > + } else { > + struct commit_list *first_parent = first_interesting_parent(graph); > + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); Are we sure that first_interesting_parent() will always give us a non-NULL pointer? Can we use a bit shorter identifier names to deal with these overly long lines? The lifetime of these two variables is very short so they do not have to be so descriptive. struct commit *p = first_interesting_parent(graph)->item; int lane = graph_find_new_column_by_commit(graph, p); > + if (!graph_needs_truncation(graph, first_parent_col)) > + graph_update_state(graph, GRAPH_POST_MERGE); > + else if (graph_is_mapping_correct(graph)) > + graph_update_state(graph, GRAPH_PADDING); > + else > + graph_update_state(graph, GRAPH_COLLAPSING); > + } > + } else if (graph_is_mapping_correct(graph)) ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v5 1/2] graph: add --graph-lane-limit option 2026-03-25 22:11 ` Junio C Hamano @ 2026-03-27 14:22 ` Pablo 2026-03-27 16:07 ` Pablo 2026-03-27 16:34 ` Junio C Hamano 0 siblings, 2 replies; 39+ messages in thread From: Pablo @ 2026-03-27 14:22 UTC (permalink / raw) To: Junio C Hamano Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t, szeder.dev Junio C Hamano (<gitster@pobox.com>) writes: > > Pablo Sabater <pabloosabaterr@gmail.com> writes: > > > +static int graph_needs_truncation(struct git_graph *graph, int lane) > > +{ > > + int max = graph->revs->graph_max_lanes; > > + /* > > + * Ignore values <= 0, meaning no limit. > > + */ > > + return max > 0 && lane >= max; > > +} > > Make a mental note that this helper function works on number of > lanes, not display columns (which is roughly twice the number of > lanes). > > > @@ -696,6 +705,18 @@ static void graph_update_columns(struct git_graph *graph) > > } > > } > > > > + /* > > + * If graph_max_lanes is set, cap the padding from the branches > > + */ > > + if (graph->revs->graph_max_lanes > 0) { > > + /* > > + * width of "| " per lanes plus truncation mark "~ ". > > + */ > > + int max_columns_width = graph->revs->graph_max_lanes * 2 + 2; > > + if (graph->width > max_columns_width) > > + graph->width = max_columns_width; > > + } > > + > > /* > > * Shrink mapping_size to be the minimum necessary > > */ > > @@ -846,6 +867,10 @@ 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++) { > > + if (graph_needs_truncation(graph, i)) { > > + graph_line_addstr(line, "~ "); > > + break; > > + } > > And that mental note helps to convince us this loop makes sense, as > it increments 'i' one by one ;-) Ok, I'll add the note to graph_needs_truncation() and any other places that might need to be more clear about if it handles columns or lanes. > > > @@ -903,6 +928,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph, > > seen_this = 1; > > graph_line_write_column(line, col, '|'); > > graph_line_addchars(line, ' ', graph->expansion_row); > > + } else if (seen_this && graph_needs_truncation(graph, i)) { > > + graph_line_addstr(line, "~ "); > > + break; > > } else if (seen_this && (graph->expansion_row == 0)) { > > /* > > * This is the first line of the pre-commit output. > > @@ -994,6 +1022,12 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line > > col = &graph->new_columns[j]; > > > > graph_line_write_column(line, col, '-'); > > And here, 'j' comes from graph->mapping[] array. Does that count in > display columns or lanes? > > > + if (graph_needs_truncation(graph, j / 2 + i)) { > > This makes it look as if 'j' counts in columns and needs to be > divided by 2 to make it comparable to lanes. Actually, no, because there are other parts like graph_output_post_merge_line handling i and j like that and it is a more mechanical thing that logical I didn't double checked it, it should be something like commit_index + 1 + i similar to what j is, imma check to be sure and add another test for this to be sure because current ones pass this and that's why I thought it was ok in the first place. > > > + graph_line_addstr(line, "~ "); > > + break; > > + } > > + > > graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); > > } > > > > > + if (graph->num_parents > 1) { > > + if (!graph_needs_truncation(graph, graph->commit_index)) { > > + graph_update_state(graph, GRAPH_POST_MERGE); > > + } else { > > + struct commit_list *first_parent = first_interesting_parent(graph); > > + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); > > Are we sure that first_interesting_parent() will always give us a > non-NULL pointer? my bad, first_interestign_parent() can be a NULL, will add a check for that > > Can we use a bit shorter identifier names to deal with these overly > long lines? The lifetime of these two variables is very short so they > do not have to be so descriptive. > > struct commit *p = first_interesting_parent(graph)->item; > int lane = graph_find_new_column_by_commit(graph, p); > > > + if (!graph_needs_truncation(graph, first_parent_col)) > > + graph_update_state(graph, GRAPH_POST_MERGE); > > + else if (graph_is_mapping_correct(graph)) > > + graph_update_state(graph, GRAPH_PADDING); > > + else > > + graph_update_state(graph, GRAPH_COLLAPSING); > > + } > > + } else if (graph_is_mapping_correct(graph)) > sure Thanks for the feedback I'll start with the v6, Pablo. ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v5 1/2] graph: add --graph-lane-limit option 2026-03-27 14:22 ` Pablo @ 2026-03-27 16:07 ` Pablo 2026-03-27 16:34 ` Junio C Hamano 1 sibling, 0 replies; 39+ messages in thread From: Pablo @ 2026-03-27 16:07 UTC (permalink / raw) To: Junio C Hamano Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t, szeder.dev Pablo (<pabloosabaterr@gmail.com>) writes: > > > + graph_line_addstr(line, "~ "); > > > + break; > > > + } > > > + > > > graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); > > > } > > > > > > > > + if (graph->num_parents > 1) { > > > + if (!graph_needs_truncation(graph, graph->commit_index)) { > > > + graph_update_state(graph, GRAPH_POST_MERGE); > > > + } else { > > > + struct commit_list *first_parent = first_interesting_parent(graph); > > > + int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item); > > > > Are we sure that first_interesting_parent() will always give us a > > non-NULL pointer? > > my bad, first_interestign_parent() can be a NULL, will add a check for that Actually, I've been looking and first_interesting_parent() can't return NULL here because: num_parents is counted using first_interesting_parent()/next_interesting_parent() so if num_parents > 1 it guarantees that first_interesting_parent() is non-NULL. I'll add a BUG() to have it reflected on the code. Pablo ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [GSoC PATCH v5 1/2] graph: add --graph-lane-limit option 2026-03-27 14:22 ` Pablo 2026-03-27 16:07 ` Pablo @ 2026-03-27 16:34 ` Junio C Hamano 1 sibling, 0 replies; 39+ messages in thread From: Junio C Hamano @ 2026-03-27 16:34 UTC (permalink / raw) To: Pablo Cc: git, christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, j6t, szeder.dev Pablo <pabloosabaterr@gmail.com> writes: >> Make a mental note that this helper function works on number of >> lanes, not display columns (which is roughly twice the number of >> lanes). >> ... >> And that mental note helps to convince us this loop makes sense, as >> it increments 'i' one by one ;-) > > Ok, I'll add the note to graph_needs_truncation() and any other places > that might need to be more clear about if it handles columns or lanes. Sorry, I should have taken into account that you are new around here. My "mental note" comment wasn't meant to suggest adding extra comments in the code. Rather, it is "readers would make a mental note here after reading this piece of code---and then what they later see this other piece of code, what it does is consistent with what they remember from the earlier piece code did, which is good" (if they are inconsistent, you'd see a similar "make a mental note here" followed later by "but this contradicts what we saw earlier. what is going on?!?!" instead). ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v5 2/2] graph: add documentation and tests about --graph-lane-limit 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater 2026-03-25 17:44 ` [GSoC PATCH v5 1/2] " Pablo Sabater @ 2026-03-25 17:44 ` Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-25 17:44 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Document --graph-lane-limit option in rev-list-options.adoc with --graph option. Add multiple tests in t4215 reusing existing last graph, test for different scenarios. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/rev-list-options.adoc | 6 ++ t/t4215-log-skewed-merges.sh | 144 ++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1474..56590f4e95 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1259,6 +1259,12 @@ This implies the `--topo-order` option by default, but the in between them in that case. If _<barrier>_ is specified, it is the string that will be shown instead of the default one. +`--graph-lane-limit=<n>`:: + When `--graph` is used, limit the number of graph lanes to be shown. + Lanes over the limit are replaced with a truncation mark '~'. By default + it is set to 0 (no limit), zero and negative values are ignored and + treated as no limit. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..1612f05f1b 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -370,4 +370,148 @@ test_expect_success 'log --graph with multiple tips' ' EOF ' +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' + check_graph --graph-lane-limit=2 M_7 <<-\EOF + *-. 7_M4 + |\ \ + | | * 7_G + | | * 7_F + | * ~ 7_E + | * ~ 7_D + * | ~ 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' ' + check_graph --graph-lane-limit=1 M_7 <<-\EOF + *-~ 7_M4 + |\~ + | ~ 7_G + | ~ 7_F + | * 7_E + | * 7_D + * ~ 7_C + | ~ + |/~ + * ~ 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | ~ 7_M3 + | | | ~ 7_J + | | | ~ 7_I + | | | ~ 7_M4 + | |_|_~ + |/| | ~ + | | |_~ + | |/| ~ + | | | ~ + | | |/~ + | | * ~ 7_G + | | | ~ + | | |/~ + | | * ~ 7_F + | * | ~ 7_E + | | |/~ + | |/| ~ + | * | ~ 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' ' + check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/~ + |/| | | | |/~ + | | |_|_|/| ~ + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' ' + check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/|\ + |/| | | | |/ / + | | |_|_|/| / + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater 2026-03-25 17:44 ` [GSoC PATCH v5 1/2] " Pablo Sabater 2026-03-25 17:44 ` [GSoC PATCH v5 2/2] graph: add documentation and tests about --graph-lane-limit Pablo Sabater @ 2026-03-28 0:11 ` Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 1/3] graph: limit the graph width to a hard-coded max Pablo Sabater ` (2 more replies) 2 siblings, 3 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-28 0:11 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the space padding, each branch takes a lane in the graph and there is no way to limit how many can be shown. The limit is a horizontal truncation, each lane is cut at the lane limit: Without --graph-lane-limit: * 7_M1 |\ | * 7_E * | 7_C | | * 7_M2 | | |\ | | | * 7_H | | |/ | |/| | * | 7_D | | * 7_G | | * 7_F | |/ |/| * | 7_B |/ * 7_A With --graph-lane-limit=1: * 7_M1 |\ | * 7_E * ~ 7_C | ~ 7_M2 | ~ 7_H | ~ | ~ | * 7_D | ~ 7_G | ~ 7_F | ~ |/~ * ~ 7_B |/ * 7_A The '~' is the truncation mark, not an actual lane. It was chosen because '.' is already used in octopus merges and '~' is not used elsewhere in the graph. Yet the edges between the last visible lane and the truncation mark are still conserved. The '*' commit mark is visible when it lives on a visible lane or the first hidden lane, any deeper lane doesn't show the commit mark but keeps the commit message visible. Merges where neither the commit nor its parents live on a visible lane are skipped because they don't carry any visible information. The original idea to limit columns was noted as a TODO in c12172d2ea (Add history graph API, 2008-05-04). This does not implement gitk-style column rearrangement, it only truncates the visual output. Possible future improvements: - When all branches involved in collapsing or padding are over the limit, the truncated lane doesn't show any information, this lane could be removed to make the graph more compact. Currently these lanes still appear because graph_output_collapsing_line() mixes state handling with rendering, so it can't be skipped but callers always expect a non empty buffer. Fixing it would need to refactor the callers to handle empty buffers instead of expecting them to always have content. - Collapsing and merges lanes that start on visible lanes but end on hidden ones are kept to maintain the most information possible on the visible lanes, but the information about where they go or come from is lost. They can be kept, removed or think of a way to show that information without showing the lanes. Changes since v5: - Changed patch structure - Fixed octopus merge truncation check to commit_index + 1 + i. - Added check for first_interesting_parent() NULL check. - Shortened variable names. - Added clarifications when converting between lanes and columns. Pablo Sabater (3): graph: limit the graph width to a hard-coded max graph: add --graph-lane-limit option graph: add truncation mark to capped lanes Documentation/rev-list-options.adoc | 6 + graph.c | 178 ++++++++++++++++++++++++---- revision.c | 6 + revision.h | 1 + t/t4215-log-skewed-merges.sh | 144 ++++++++++++++++++++++ 5 files changed, 311 insertions(+), 24 deletions(-) base-commit: 5361983c075154725be47b65cca9a2421789e410 -- 2.43.0 ^ permalink raw reply [flat|nested] 39+ messages in thread
* [GSoC PATCH v6 1/3] graph: limit the graph width to a hard-coded max 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater @ 2026-03-28 0:11 ` Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 2/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 3/3] graph: add truncation mark to capped lanes Pablo Sabater 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-28 0:11 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Repositories that have many active branches at the same time produce wide graphs. A lane consists of two columns, the edge and the padding (or another edge), each branch takes a lane in the graph and there is no way to limit how many can be shown. Limit the graph engine to draw at most 15 lanes. Lanes over the limit are not rendered. On the commit line, if the commit lives on a visible lane, show the normal commit mark and stop rendering. If the commit lives on the first hidden lane, show the "*" commit mark so it is known that this commit lives in the first hidden lane. Commits on deeper lanes aren't rendered, but the commit subject will always remain. For merges, the post-merge lane is only needed when the commit or the first parent lives on a visible lane (to draw the connection between them), when both are on hidden lanes, post-merge carries no useful information, skip it and go to collapsing or padding state. Also fix a pre-existing indentation issue. The hard-coded limit will be replaced by a user-facing option on a subsequent commit. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- graph.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 25 deletions(-) diff --git a/graph.c b/graph.c index 26f6fbf000..70458cf323 100644 --- a/graph.c +++ b/graph.c @@ -82,6 +82,8 @@ static void graph_show_line_prefix(const struct diff_options *diffopt) static const char **column_colors; static unsigned short column_colors_max; +static unsigned int max_lanes = 15; + static void parse_graph_colors_config(struct strvec *colors, const char *string) { const char *end, *start; @@ -317,6 +319,11 @@ struct git_graph { struct strbuf prefix_buf; }; +static inline int graph_needs_truncation(int lane) +{ + return lane >= max_lanes; +} + static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) { struct git_graph *graph = data; @@ -607,7 +614,7 @@ static void graph_update_columns(struct git_graph *graph) { struct commit_list *parent; int max_new_columns; - int i, seen_this, is_commit_in_columns; + int i, seen_this, is_commit_in_columns, max; /* * Swap graph->columns with graph->new_columns @@ -696,6 +703,14 @@ static void graph_update_columns(struct git_graph *graph) } } + /* + * Cap to the hard-coded limit. + * Allow commits from merges to align to the merged lane. + */ + max = max_lanes * 2 + 2; + if (graph->width > max) + graph->width = max; + /* * Shrink mapping_size to be the minimum necessary */ @@ -846,6 +861,8 @@ 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++) { + if (graph_needs_truncation(i)) + break; graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -903,6 +920,8 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); + } else if (seen_this && graph_needs_truncation(i)) { + break; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -994,6 +1013,14 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line col = &graph->new_columns[j]; graph_line_write_column(line, col, '-'); + + /* + * Commit is at commit_index, each iteration move one lane to + * the right from the commit. + */ + if (graph_needs_truncation(graph->commit_index + 1 + i)) + break; + graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); } @@ -1028,8 +1055,16 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); + if (graph_needs_truncation(i)) { + graph_line_addch(line, ' '); + break; + } + if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); + } else if (graph_needs_truncation(i)) { + seen_this = 1; + break; } else if (seen_this && (graph->edges_added > 1)) { graph_line_write_column(line, col, '\\'); } else if (seen_this && (graph->edges_added == 1)) { @@ -1065,13 +1100,46 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line /* * Update graph->state + * + * If the commit is a merge and the first parent is in a visible lane, + * then the GRAPH_POST_MERGE is needed to draw the merge lane. + * + * If the commit is over the truncation limit, but the first parent is on + * a visible lane, then we still need the merge lane but truncated. + * + * If both commit and first parent are over the truncation limit, then + * there's no need to draw the merge lane because it would work as a + * padding lane. */ - if (graph->num_parents > 1) - graph_update_state(graph, GRAPH_POST_MERGE); - else if (graph_is_mapping_correct(graph)) + if (graph->num_parents > 1) { + if (!graph_needs_truncation(graph->commit_index)) { + graph_update_state(graph, GRAPH_POST_MERGE); + } else { + struct commit_list *p = first_interesting_parent(graph); + int lane; + + /* + * graph->num_parents are found using first_interesting_parent + * and next_interesting_parent so it can't be a scenario + * where num_parents > 1 and there are no interesting parents + */ + if (!p) + BUG("num_parents > 1 but no interesting parent"); + + lane = graph_find_new_column_by_commit(graph, p->item); + + if (!graph_needs_truncation(lane)) + graph_update_state(graph, GRAPH_POST_MERGE); + else if (graph_is_mapping_correct(graph)) + graph_update_state(graph, GRAPH_PADDING); + else + graph_update_state(graph, GRAPH_COLLAPSING); + } + } else if (graph_is_mapping_correct(graph)) { graph_update_state(graph, GRAPH_PADDING); - else + } else { graph_update_state(graph, GRAPH_COLLAPSING); + } } static const char merge_chars[] = {'/', '|', '\\'}; @@ -1109,6 +1177,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l int par_column; int idx = graph->merge_layout; char c; + int truncated = 0; seen_this = 1; for (j = 0; j < graph->num_parents; j++) { @@ -1117,23 +1186,53 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l c = merge_chars[idx]; graph_line_write_column(line, &graph->new_columns[par_column], c); + + /* + * j counts parents, it needs to be halved to be + * comparable with i. Don't truncate if there are + * no more lanes to print (end of the lane) + */ + if (graph_needs_truncation(j / 2 + i) && + j / 2 + i <= graph->num_columns) { + if ((j + i * 2) % 2 != 0) + graph_line_addch(line, ' '); + truncated = 1; + break; + } + if (idx == 2) { - if (graph->edges_added > 0 || j < graph->num_parents - 1) + /* + * Check if the next lane needs truncation + * to avoid having the padding doubled + */ + if (graph_needs_truncation((j + 1) / 2 + i) && + j < graph->num_parents - 1) { + truncated = 1; + break; + } else if (graph->edges_added > 0 || j < graph->num_parents - 1) graph_line_addch(line, ' '); } else { idx++; } parents = next_interesting_parent(graph, parents); } + if (truncated) + break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - + } else if (graph_needs_truncation(i)) { + break; } else if (seen_this) { if (graph->edges_added > 0) graph_line_write_column(line, col, '\\'); else graph_line_write_column(line, col, '|'); - graph_line_addch(line, ' '); + /* + * If it's between two lanes and next would be truncated, + * don't add space padding. + */ + if (!graph_needs_truncation(i + 1)) + graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); if (graph->merge_layout != 0 || i != graph->commit_index - 1) { @@ -1164,6 +1263,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l short used_horizontal = 0; int horizontal_edge = -1; int horizontal_edge_target = -1; + int truncated = 0; /* * Swap the mapping and old_mapping arrays @@ -1279,26 +1379,34 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l */ for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (target < 0) - graph_line_addch(line, ' '); - else if (target * 2 == i) - graph_line_write_column(line, &graph->new_columns[target], '|'); - else if (target == horizontal_edge_target && - i != horizontal_edge - 1) { - /* - * Set the mappings for all but the - * first segment to -1 so that they - * won't continue into the next line. - */ - if (i != (target * 2)+3) - graph->mapping[i] = -1; - used_horizontal = 1; - graph_line_write_column(line, &graph->new_columns[target], '_'); + + if (!truncated && graph_needs_truncation(i / 2)) { + truncated = 1; + } + + if (target < 0) { + if (!truncated) + graph_line_addch(line, ' '); + } else if (target * 2 == i) { + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '|'); + } else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { + /* + * Set the mappings for all but the + * first segment to -1 so that they + * won't continue into the next line. + */ + if (i != (target * 2)+3) + graph->mapping[i] = -1; + used_horizontal = 1; + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) graph->mapping[i] = -1; - graph_line_write_column(line, &graph->new_columns[target], '/'); - + if (!truncated) + graph_line_write_column(line, &graph->new_columns[target], '/'); } } @@ -1372,6 +1480,9 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; + if (graph_needs_truncation(i)) + break; + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [GSoC PATCH v6 2/3] graph: add --graph-lane-limit option 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 1/3] graph: limit the graph width to a hard-coded max Pablo Sabater @ 2026-03-28 0:11 ` Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 3/3] graph: add truncation mark to capped lanes Pablo Sabater 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-28 0:11 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater Replace the hard-coded lane limit with a user-facing option '--graph-lane-limit=<n>'. It caps the number of visible lanes to n. This option requires '--graph', without it, limiting the graph has no meaning, in this case error out. Zero and negative values are valid inputs but silently ignored treating them as "no limit", the same as not using the option. This follows what '--max-parents' does with negative values. The default is 0, same as not being used. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/rev-list-options.adoc | 5 + graph.c | 53 +++++----- revision.c | 6 ++ revision.h | 1 + t/t4215-log-skewed-merges.sh | 144 ++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 23 deletions(-) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 2d195a1474..1b6ea89a63 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the in between them in that case. If _<barrier>_ is specified, it is the string that will be shown instead of the default one. +`--graph-lane-limit=<n>`:: + When `--graph` is used, limit the number of graph lanes to be shown. + Lanes over the limit are not shown. By default it is set to 0 + (no limit), zero and negative values are ignored and treated as no limit. + ifdef::git-rev-list[] `--count`:: Print a number stating how many commits would have been diff --git a/graph.c b/graph.c index 70458cf323..ee1f9e2d2d 100644 --- a/graph.c +++ b/graph.c @@ -82,8 +82,6 @@ static void graph_show_line_prefix(const struct diff_options *diffopt) static const char **column_colors; static unsigned short column_colors_max; -static unsigned int max_lanes = 15; - static void parse_graph_colors_config(struct strvec *colors, const char *string) { const char *end, *start; @@ -319,9 +317,13 @@ struct git_graph { struct strbuf prefix_buf; }; -static inline int graph_needs_truncation(int lane) +static inline int graph_needs_truncation(struct git_graph *graph, int lane) { - return lane >= max_lanes; + int max = graph->revs->graph_max_lanes; + /* + * Ignore values <= 0, meaning no limit. + */ + return max > 0 && lane >= max; } static const char *diff_output_prefix_callback(struct diff_options *opt, void *data) @@ -614,7 +616,7 @@ static void graph_update_columns(struct git_graph *graph) { struct commit_list *parent; int max_new_columns; - int i, seen_this, is_commit_in_columns, max; + int i, seen_this, is_commit_in_columns; /* * Swap graph->columns with graph->new_columns @@ -704,12 +706,17 @@ static void graph_update_columns(struct git_graph *graph) } /* - * Cap to the hard-coded limit. - * Allow commits from merges to align to the merged lane. + * If graph_max_lanes is set, cap the width */ - max = max_lanes * 2 + 2; - if (graph->width > max) - graph->width = max; + if (graph->revs->graph_max_lanes > 0) { + /* + * Width is column index while a lane is half that. + * Allow commits from merges to align to the merged lane. + */ + int max_width = graph->revs->graph_max_lanes * 2 + 2; + if (graph->width > max_width) + graph->width = max_width; + } /* * Shrink mapping_size to be the minimum necessary @@ -861,7 +868,7 @@ 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++) { - if (graph_needs_truncation(i)) + if (graph_needs_truncation(graph, i)) break; graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); @@ -920,7 +927,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph, seen_this = 1; graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); - } else if (seen_this && graph_needs_truncation(i)) { + } else if (seen_this && graph_needs_truncation(graph, i)) { break; } else if (seen_this && (graph->expansion_row == 0)) { /* @@ -1018,7 +1025,7 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line * Commit is at commit_index, each iteration move one lane to * the right from the commit. */ - if (graph_needs_truncation(graph->commit_index + 1 + i)) + if (graph_needs_truncation(graph, graph->commit_index + 1 + i)) break; graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); @@ -1055,14 +1062,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line seen_this = 1; graph_output_commit_char(graph, line); - if (graph_needs_truncation(i)) { + if (graph_needs_truncation(graph, i)) { graph_line_addch(line, ' '); break; } if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); - } else if (graph_needs_truncation(i)) { + } else if (graph_needs_truncation(graph, i)) { seen_this = 1; break; } else if (seen_this && (graph->edges_added > 1)) { @@ -1112,7 +1119,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line * padding lane. */ if (graph->num_parents > 1) { - if (!graph_needs_truncation(graph->commit_index)) { + if (!graph_needs_truncation(graph, graph->commit_index)) { graph_update_state(graph, GRAPH_POST_MERGE); } else { struct commit_list *p = first_interesting_parent(graph); @@ -1128,7 +1135,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line lane = graph_find_new_column_by_commit(graph, p->item); - if (!graph_needs_truncation(lane)) + if (!graph_needs_truncation(graph, lane)) graph_update_state(graph, GRAPH_POST_MERGE); else if (graph_is_mapping_correct(graph)) graph_update_state(graph, GRAPH_PADDING); @@ -1192,7 +1199,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l * comparable with i. Don't truncate if there are * no more lanes to print (end of the lane) */ - if (graph_needs_truncation(j / 2 + i) && + if (graph_needs_truncation(graph, j / 2 + i) && j / 2 + i <= graph->num_columns) { if ((j + i * 2) % 2 != 0) graph_line_addch(line, ' '); @@ -1205,7 +1212,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l * Check if the next lane needs truncation * to avoid having the padding doubled */ - if (graph_needs_truncation((j + 1) / 2 + i) && + if (graph_needs_truncation(graph, (j + 1) / 2 + i) && j < graph->num_parents - 1) { truncated = 1; break; @@ -1220,7 +1227,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l break; if (graph->edges_added == 0) graph_line_addch(line, ' '); - } else if (graph_needs_truncation(i)) { + } else if (graph_needs_truncation(graph, i)) { break; } else if (seen_this) { if (graph->edges_added > 0) @@ -1231,7 +1238,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l * If it's between two lanes and next would be truncated, * don't add space padding. */ - if (!graph_needs_truncation(i + 1)) + if (!graph_needs_truncation(graph, i + 1)) graph_line_addch(line, ' '); } else { graph_line_write_column(line, col, '|'); @@ -1380,7 +1387,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l for (i = 0; i < graph->mapping_size; i++) { int target = graph->mapping[i]; - if (!truncated && graph_needs_truncation(i / 2)) { + if (!truncated && graph_needs_truncation(graph, i / 2)) { truncated = 1; } @@ -1480,7 +1487,7 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; - if (graph_needs_truncation(i)) + if (graph_needs_truncation(graph, i)) break; graph_line_write_column(&line, col, '|'); diff --git a/revision.c b/revision.c index 31808e3df0..81b67682a8 100644 --- a/revision.c +++ b/revision.c @@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--no-graph")) { graph_clear(revs->graph); revs->graph = NULL; + } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) { + revs->graph_max_lanes = parse_count(optarg); } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -3172,6 +3174,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); + + if (revs->graph_max_lanes > 0 && !revs->graph) + die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph"); + if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs"); diff --git a/revision.h b/revision.h index 69242ecb18..874ccce625 100644 --- a/revision.h +++ b/revision.h @@ -304,6 +304,7 @@ struct rev_info { /* Display history graph */ struct git_graph *graph; + int graph_max_lanes; /* special limits */ int skip_count; diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..d7524e9366 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -370,4 +370,148 @@ test_expect_success 'log --graph with multiple tips' ' EOF ' +test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' + check_graph --graph-lane-limit=2 M_7 <<-\EOF + *-. 7_M4 + |\ \ + | | * 7_G + | | * 7_F + | * 7_E + | * 7_D + * | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' ' + check_graph --graph-lane-limit=1 M_7 <<-\EOF + *- 7_M4 + |\ + | 7_G + | 7_F + | * 7_E + | * 7_D + * 7_C + | + |/ + * 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' + check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | 7_M3 + | | | 7_J + | | | 7_I + | | | 7_M4 + | |_|_ + |/| | + | | |_ + | |/| + | | | + | | |/ + | | * 7_G + | | | + | | |/ + | | * 7_F + | * | 7_E + | | |/ + | |/| + | * | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' ' + check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/ + |/| | | | |/ + | | |_|_|/| + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + +test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' ' + check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF + * 7_M1 + |\ + | | * 7_M2 + | | |\ + | | | * 7_H + | | | | * 7_M3 + | | | | |\ + | | | | | * 7_J + | | | | * | 7_I + | | | | | | * 7_M4 + | |_|_|_|_|/|\ + |/| | | | |/ / + | | |_|_|/| / + | |/| | | |/ + | | | |_|/| + | | |/| | | + | | * | | | 7_G + | | | |_|/ + | | |/| | + | | * | | 7_F + | * | | | 7_E + | | |/ / + | |/| | + | * | | 7_D + | | |/ + | |/| + * | | 7_C + | |/ + |/| + * | 7_B + |/ + * 7_A + EOF +' + test_done -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [GSoC PATCH v6 3/3] graph: add truncation mark to capped lanes 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 1/3] graph: limit the graph width to a hard-coded max Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 2/3] graph: add --graph-lane-limit option Pablo Sabater @ 2026-03-28 0:11 ` Pablo Sabater 2 siblings, 0 replies; 39+ messages in thread From: Pablo Sabater @ 2026-03-28 0:11 UTC (permalink / raw) To: git Cc: christian.couder, karthik.188, jltobler, ayu.chandekar, siddharthasthana31, chandrapratap3519, gitster, j6t, szeder.dev, Pablo Sabater When lanes are hidden by --graph-lane-limit, show a "~" truncation mark, so users know that there are lanes being truncated. The "~" is chosen because it is not used elsewhere in the graph and it is discrete. Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com> --- Documentation/rev-list-options.adoc | 5 ++- graph.c | 22 +++++++--- t/t4215-log-skewed-merges.sh | 64 ++++++++++++++--------------- 3 files changed, 52 insertions(+), 39 deletions(-) diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 1b6ea89a63..937ffc6195 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -1261,8 +1261,9 @@ This implies the `--topo-order` option by default, but the `--graph-lane-limit=<n>`:: When `--graph` is used, limit the number of graph lanes to be shown. - Lanes over the limit are not shown. By default it is set to 0 - (no limit), zero and negative values are ignored and treated as no limit. + Lanes over the limit are replaced with a truncation mark '~'. + By default it is set to 0 (no limit), zero and negative values + are ignored and treated as no limit. ifdef::git-rev-list[] `--count`:: diff --git a/graph.c b/graph.c index ee1f9e2d2d..842282685f 100644 --- a/graph.c +++ b/graph.c @@ -706,11 +706,11 @@ static void graph_update_columns(struct git_graph *graph) } /* - * If graph_max_lanes is set, cap the width + * If graph_max_lanes is set, cap the width */ if (graph->revs->graph_max_lanes > 0) { /* - * Width is column index while a lane is half that. + * width of "| " per lanes plus truncation mark "~ ". * Allow commits from merges to align to the merged lane. */ int max_width = graph->revs->graph_max_lanes * 2 + 2; @@ -868,8 +868,10 @@ 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++) { - if (graph_needs_truncation(graph, i)) + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); break; + } graph_line_write_column(line, &graph->new_columns[i], '|'); graph_line_addch(line, ' '); } @@ -928,6 +930,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph, graph_line_write_column(line, col, '|'); graph_line_addchars(line, ' ', graph->expansion_row); } else if (seen_this && graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); break; } else if (seen_this && (graph->expansion_row == 0)) { /* @@ -1025,8 +1028,10 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line * Commit is at commit_index, each iteration move one lane to * the right from the commit. */ - if (graph_needs_truncation(graph, graph->commit_index + 1 + i)) + if (graph_needs_truncation(graph, graph->commit_index + 1 + i)) { + graph_line_addstr(line, "~ "); break; + } graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); } @@ -1070,6 +1075,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line if (graph->num_parents > 2) graph_draw_octopus_merge(graph, line); } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); seen_this = 1; break; } else if (seen_this && (graph->edges_added > 1)) { @@ -1203,6 +1209,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l j / 2 + i <= graph->num_columns) { if ((j + i * 2) % 2 != 0) graph_line_addch(line, ' '); + graph_line_addstr(line, "~ "); truncated = 1; break; } @@ -1214,6 +1221,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l */ if (graph_needs_truncation(graph, (j + 1) / 2 + i) && j < graph->num_parents - 1) { + graph_line_addstr(line, "~ "); truncated = 1; break; } else if (graph->edges_added > 0 || j < graph->num_parents - 1) @@ -1228,6 +1236,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l if (graph->edges_added == 0) graph_line_addch(line, ' '); } else if (graph_needs_truncation(graph, i)) { + graph_line_addstr(line, "~ "); break; } else if (seen_this) { if (graph->edges_added > 0) @@ -1388,6 +1397,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l int target = graph->mapping[i]; if (!truncated && graph_needs_truncation(graph, i / 2)) { + graph_line_addstr(line, "~ "); truncated = 1; } @@ -1487,8 +1497,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; - if (graph_needs_truncation(graph, i)) + if (graph_needs_truncation(graph, i)) { + graph_line_addstr(&line, "~ "); break; + } graph_line_write_column(&line, col, '|'); diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index d7524e9366..1612f05f1b 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -376,9 +376,9 @@ test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' |\ \ | | * 7_G | | * 7_F - | * 7_E - | * 7_D - * | 7_C + | * ~ 7_E + | * ~ 7_D + * | ~ 7_C | |/ |/| * | 7_B @@ -389,16 +389,16 @@ test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' ' check_graph --graph-lane-limit=1 M_7 <<-\EOF - *- 7_M4 - |\ - | 7_G - | 7_F + *-~ 7_M4 + |\~ + | ~ 7_G + | ~ 7_F | * 7_E | * 7_D - * 7_C - | - |/ - * 7_B + * ~ 7_C + | ~ + |/~ + * ~ 7_B |/ * 7_A EOF @@ -411,24 +411,24 @@ test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' | | * 7_M2 | | |\ | | | * 7_H - | | | 7_M3 - | | | 7_J - | | | 7_I - | | | 7_M4 - | |_|_ - |/| | - | | |_ - | |/| - | | | - | | |/ - | | * 7_G - | | | - | | |/ - | | * 7_F - | * | 7_E - | | |/ - | |/| - | * | 7_D + | | | ~ 7_M3 + | | | ~ 7_J + | | | ~ 7_I + | | | ~ 7_M4 + | |_|_~ + |/| | ~ + | | |_~ + | |/| ~ + | | | ~ + | | |/~ + | | * ~ 7_G + | | | ~ + | | |/~ + | | * ~ 7_F + | * | ~ 7_E + | | |/~ + | |/| ~ + | * | ~ 7_D | | |/ | |/| * | | 7_C @@ -452,9 +452,9 @@ test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows fir | | | | | * 7_J | | | | * | 7_I | | | | | | * 7_M4 - | |_|_|_|_|/ - |/| | | | |/ - | | |_|_|/| + | |_|_|_|_|/~ + |/| | | | |/~ + | | |_|_|/| ~ | |/| | | |/ | | | |_|/| | | |/| | | -- 2.43.0 ^ permalink raw reply related [flat|nested] 39+ messages in thread
end of thread, other threads:[~2026-03-28 0:11 UTC | newest] Thread overview: 39+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-16 13:34 [GSoC RFC PATCH] graph: add --graph-max option to limit displayed columns Pablo Sabater 2026-03-16 17:04 ` Karthik Nayak 2026-03-16 19:48 ` Pablo 2026-03-17 22:09 ` [GSoC RFC PATCH v2] graph: add --max-columns " Pablo Sabater 2026-03-18 16:05 ` Junio C Hamano 2026-03-18 18:20 ` Pablo 2026-03-19 7:07 ` Johannes Sixt 2026-03-22 19:54 ` [GSoC PATCH WIP RFC v3 0/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-22 20:37 ` [GSoC PATCH WIP RFC v3 1/3] " Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 2/3] graph: truncate graph visual output Pablo Sabater 2026-03-22 20:38 ` [GSoC PATCH WIP RFC v3 3/3] graph: add documentation and testing about --graph-lane-limit Pablo Sabater 2026-03-22 22:09 ` [GSoC PATCH WIP RFC v3 1/3] graph: add --graph-lane-limit option Junio C Hamano 2026-03-23 2:33 ` Pablo 2026-03-23 21:59 ` [GSoC PATCH v4 0/3] " Pablo Sabater 2026-03-23 21:59 ` [GSoC PATCH v4 1/3] " Pablo Sabater 2026-03-25 7:02 ` SZEDER Gábor 2026-03-25 10:03 ` Johannes Sixt 2026-03-25 12:29 ` Pablo 2026-03-23 21:59 ` [GSoC PATCH v4 2/3] graph: truncate graph visual output Pablo Sabater 2026-03-25 10:04 ` Johannes Sixt 2026-03-25 11:19 ` Pablo 2026-03-23 21:59 ` [GSoC PATCH v4 3/3] graph: add documentation and tests about --graph-lane-limit Pablo Sabater 2026-03-25 10:07 ` Johannes Sixt 2026-03-25 11:49 ` Pablo 2026-03-25 10:02 ` [GSoC PATCH v4 0/3] graph: add --graph-lane-limit option Johannes Sixt 2026-03-25 12:28 ` Pablo 2026-03-25 17:44 ` Johannes Sixt 2026-03-25 17:58 ` Pablo 2026-03-25 17:43 ` [GSoC PATCH v5 0/2] " Pablo Sabater 2026-03-25 17:44 ` [GSoC PATCH v5 1/2] " Pablo Sabater 2026-03-25 22:11 ` Junio C Hamano 2026-03-27 14:22 ` Pablo 2026-03-27 16:07 ` Pablo 2026-03-27 16:34 ` Junio C Hamano 2026-03-25 17:44 ` [GSoC PATCH v5 2/2] graph: add documentation and tests about --graph-lane-limit Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 0/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 1/3] graph: limit the graph width to a hard-coded max Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 2/3] graph: add --graph-lane-limit option Pablo Sabater 2026-03-28 0:11 ` [GSoC PATCH v6 3/3] graph: add truncation mark to capped lanes Pablo Sabater
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox