graph: add --graph-lane-limit option
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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Pablo Sabater committed
Mar 28, 2026 at 01:11 UTC
f756a3c78d4d88af1701996394650e6df6f66170
5 files changed
+186
-23
Documentation/rev-list-options.adoc
+5
@@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the
1259
in between them in that case. If _<barrier>_ is specified, it
1260
is the string that will be shown instead of the default one.
1261
1262
+`--graph-lane-limit=<n>`::
1263
+ When `--graph` is used, limit the number of graph lanes to be shown.
1264
+ Lanes over the limit are not shown. By default it is set to 0
1265
+ (no limit), zero and negative values are ignored and treated as no limit.
1266
+
1267
ifdef::git-rev-list[]
1268
`--count`::
1269
Print a number stating how many commits would have been
graph.c
+30
-23
@@ -82,8 +82,6 @@ static void graph_show_line_prefix(const struct diff_options *diffopt)
82
static const char **column_colors;
83
static unsigned short column_colors_max;
84
85
-static unsigned int max_lanes = 15;
86
-
85
static void parse_graph_colors_config(struct strvec *colors, const char *string)
86
{
87
const char *end, *start;
@@ -319,9 +317,13 @@ struct git_graph {
317
struct strbuf prefix_buf;
318
};
319
322
-static inline int graph_needs_truncation(int lane)
320
+static inline int graph_needs_truncation(struct git_graph *graph, int lane)
321
{
324
- return lane >= max_lanes;
322
+ int max = graph->revs->graph_max_lanes;
323
+ /*
324
+ * Ignore values <= 0, meaning no limit.
325
+ */
326
+ return max > 0 && lane >= max;
327
}
328
329
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)
616
{
617
struct commit_list *parent;
618
int max_new_columns;
617
- int i, seen_this, is_commit_in_columns, max;
619
+ int i, seen_this, is_commit_in_columns;
620
621
/*
622
* Swap graph->columns with graph->new_columns
@@ -704,12 +706,17 @@ static void graph_update_columns(struct git_graph *graph)
706
}
707
708
/*
707
- * Cap to the hard-coded limit.
708
- * Allow commits from merges to align to the merged lane.
709
+ * If graph_max_lanes is set, cap the width
710
*/
710
- max = max_lanes * 2 + 2;
711
- if (graph->width > max)
712
- graph->width = max;
711
+ if (graph->revs->graph_max_lanes > 0) {
712
+ /*
713
+ * Width is column index while a lane is half that.
714
+ * Allow commits from merges to align to the merged lane.
715
+ */
716
+ int max_width = graph->revs->graph_max_lanes * 2 + 2;
717
+ if (graph->width > max_width)
718
+ graph->width = max_width;
719
+ }
720
721
/*
722
* Shrink mapping_size to be the minimum necessary
@@ -861,7 +868,7 @@ static void graph_output_padding_line(struct git_graph *graph,
868
* Output a padding row, that leaves all branch lines unchanged
869
*/
870
for (i = 0; i < graph->num_new_columns; i++) {
864
- if (graph_needs_truncation(i))
871
+ if (graph_needs_truncation(graph, i))
872
break;
873
graph_line_write_column(line, &graph->new_columns[i], '|');
874
graph_line_addch(line, ' ');
@@ -920,7 +927,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
927
seen_this = 1;
928
graph_line_write_column(line, col, '|');
929
graph_line_addchars(line, ' ', graph->expansion_row);
923
- } else if (seen_this && graph_needs_truncation(i)) {
930
+ } else if (seen_this && graph_needs_truncation(graph, i)) {
931
break;
932
} else if (seen_this && (graph->expansion_row == 0)) {
933
/*
@@ -1018,7 +1025,7 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line
1025
* Commit is at commit_index, each iteration move one lane to
1026
* the right from the commit.
1027
*/
1021
- if (graph_needs_truncation(graph->commit_index + 1 + i))
1028
+ if (graph_needs_truncation(graph, graph->commit_index + 1 + i))
1029
break;
1030
1031
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
1062
seen_this = 1;
1063
graph_output_commit_char(graph, line);
1064
1058
- if (graph_needs_truncation(i)) {
1065
+ if (graph_needs_truncation(graph, i)) {
1066
graph_line_addch(line, ' ');
1067
break;
1068
}
1069
1070
if (graph->num_parents > 2)
1071
graph_draw_octopus_merge(graph, line);
1065
- } else if (graph_needs_truncation(i)) {
1072
+ } else if (graph_needs_truncation(graph, i)) {
1073
seen_this = 1;
1074
break;
1075
} 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
1119
* padding lane.
1120
*/
1121
if (graph->num_parents > 1) {
1115
- if (!graph_needs_truncation(graph->commit_index)) {
1122
+ if (!graph_needs_truncation(graph, graph->commit_index)) {
1123
graph_update_state(graph, GRAPH_POST_MERGE);
1124
} else {
1125
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
1135
1136
lane = graph_find_new_column_by_commit(graph, p->item);
1137
1131
- if (!graph_needs_truncation(lane))
1138
+ if (!graph_needs_truncation(graph, lane))
1139
graph_update_state(graph, GRAPH_POST_MERGE);
1140
else if (graph_is_mapping_correct(graph))
1141
graph_update_state(graph, GRAPH_PADDING);
@@ -1192,7 +1199,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
1199
* comparable with i. Don't truncate if there are
1200
* no more lanes to print (end of the lane)
1201
*/
1195
- if (graph_needs_truncation(j / 2 + i) &&
1202
+ if (graph_needs_truncation(graph, j / 2 + i) &&
1203
j / 2 + i <= graph->num_columns) {
1204
if ((j + i * 2) % 2 != 0)
1205
graph_line_addch(line, ' ');
@@ -1205,7 +1212,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
1212
* Check if the next lane needs truncation
1213
* to avoid having the padding doubled
1214
*/
1208
- if (graph_needs_truncation((j + 1) / 2 + i) &&
1215
+ if (graph_needs_truncation(graph, (j + 1) / 2 + i) &&
1216
j < graph->num_parents - 1) {
1217
truncated = 1;
1218
break;
@@ -1220,7 +1227,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
1227
break;
1228
if (graph->edges_added == 0)
1229
graph_line_addch(line, ' ');
1223
- } else if (graph_needs_truncation(i)) {
1230
+ } else if (graph_needs_truncation(graph, i)) {
1231
break;
1232
} else if (seen_this) {
1233
if (graph->edges_added > 0)
@@ -1231,7 +1238,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
1238
* If it's between two lanes and next would be truncated,
1239
* don't add space padding.
1240
*/
1234
- if (!graph_needs_truncation(i + 1))
1241
+ if (!graph_needs_truncation(graph, i + 1))
1242
graph_line_addch(line, ' ');
1243
} else {
1244
graph_line_write_column(line, col, '|');
@@ -1380,7 +1387,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
1387
for (i = 0; i < graph->mapping_size; i++) {
1388
int target = graph->mapping[i];
1389
1383
- if (!truncated && graph_needs_truncation(i / 2)) {
1390
+ if (!truncated && graph_needs_truncation(graph, i / 2)) {
1391
truncated = 1;
1392
}
1393
@@ -1480,7 +1487,7 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
1487
for (i = 0; i < graph->num_columns; i++) {
1488
struct column *col = &graph->columns[i];
1489
1483
- if (graph_needs_truncation(i))
1490
+ if (graph_needs_truncation(graph, i))
1491
break;
1492
1493
graph_line_write_column(&line, col, '|');
revision.c
+6
@@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
2605
} else if (!strcmp(arg, "--no-graph")) {
2606
graph_clear(revs->graph);
2607
revs->graph = NULL;
2608
+ } else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) {
2609
+ revs->graph_max_lanes = parse_count(optarg);
2610
} else if (!strcmp(arg, "--encode-email-headers")) {
2611
revs->encode_email_headers = 1;
2612
} 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
3174
3175
if (revs->no_walk && revs->graph)
3176
die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph");
3177
+
3178
+ if (revs->graph_max_lanes > 0 && !revs->graph)
3179
+ die(_("the option '%s' requires '%s'"), "--graph-lane-limit", "--graph");
3180
+
3181
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
3182
die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs");
3183
revision.h
+1
@@ -304,6 +304,7 @@ struct rev_info {
304
305
/* Display history graph */
306
struct git_graph *graph;
307
+ int graph_max_lanes;
308
309
/* special limits */
310
int skip_count;
t/t4215-log-skewed-merges.sh
+144
@@ -370,4 +370,148 @@ test_expect_success 'log --graph with multiple tips' '
370
EOF
371
'
372
373
+test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' '
374
+ check_graph --graph-lane-limit=2 M_7 <<-\EOF
375
+ *-. 7_M4
376
+ |\ \
377
+ | | * 7_G
378
+ | | * 7_F
379
+ | * 7_E
380
+ | * 7_D
381
+ * | 7_C
382
+ | |/
383
+ |/|
384
+ * | 7_B
385
+ |/
386
+ * 7_A
387
+ EOF
388
+'
389
+
390
+test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' '
391
+ check_graph --graph-lane-limit=1 M_7 <<-\EOF
392
+ *- 7_M4
393
+ |\
394
+ | 7_G
395
+ | 7_F
396
+ | * 7_E
397
+ | * 7_D
398
+ * 7_C
399
+ |
400
+ |/
401
+ * 7_B
402
+ |/
403
+ * 7_A
404
+ EOF
405
+'
406
+
407
+test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' '
408
+ check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
409
+ * 7_M1
410
+ |\
411
+ | | * 7_M2
412
+ | | |\
413
+ | | | * 7_H
414
+ | | | 7_M3
415
+ | | | 7_J
416
+ | | | 7_I
417
+ | | | 7_M4
418
+ | |_|_
419
+ |/| |
420
+ | | |_
421
+ | |/|
422
+ | | |
423
+ | | |/
424
+ | | * 7_G
425
+ | | |
426
+ | | |/
427
+ | | * 7_F
428
+ | * | 7_E
429
+ | | |/
430
+ | |/|
431
+ | * | 7_D
432
+ | | |/
433
+ | |/|
434
+ * | | 7_C
435
+ | |/
436
+ |/|
437
+ * | 7_B
438
+ |/
439
+ * 7_A
440
+ EOF
441
+'
442
+
443
+test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' '
444
+ check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF
445
+ * 7_M1
446
+ |\
447
+ | | * 7_M2
448
+ | | |\
449
+ | | | * 7_H
450
+ | | | | * 7_M3
451
+ | | | | |\
452
+ | | | | | * 7_J
453
+ | | | | * | 7_I
454
+ | | | | | | * 7_M4
455
+ | |_|_|_|_|/
456
+ |/| | | | |/
457
+ | | |_|_|/|
458
+ | |/| | | |/
459
+ | | | |_|/|
460
+ | | |/| | |
461
+ | | * | | | 7_G
462
+ | | | |_|/
463
+ | | |/| |
464
+ | | * | | 7_F
465
+ | * | | | 7_E
466
+ | | |/ /
467
+ | |/| |
468
+ | * | | 7_D
469
+ | | |/
470
+ | |/|
471
+ * | | 7_C
472
+ | |/
473
+ |/|
474
+ * | 7_B
475
+ |/
476
+ * 7_A
477
+ EOF
478
+'
479
+
480
+test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' '
481
+ check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF
482
+ * 7_M1
483
+ |\
484
+ | | * 7_M2
485
+ | | |\
486
+ | | | * 7_H
487
+ | | | | * 7_M3
488
+ | | | | |\
489
+ | | | | | * 7_J
490
+ | | | | * | 7_I
491
+ | | | | | | * 7_M4
492
+ | |_|_|_|_|/|\
493
+ |/| | | | |/ /
494
+ | | |_|_|/| /
495
+ | |/| | | |/
496
+ | | | |_|/|
497
+ | | |/| | |
498
+ | | * | | | 7_G
499
+ | | | |_|/
500
+ | | |/| |
501
+ | | * | | 7_F
502
+ | * | | | 7_E
503
+ | | |/ /
504
+ | |/| |
505
+ | * | | 7_D
506
+ | | |/
507
+ | |/|
508
+ * | | 7_C
509
+ | |/
510
+ |/|
511
+ * | 7_B
512
+ |/
513
+ * 7_A
514
+ EOF
515
+'
516
+
517
test_done