Skip to content

Commit 9d9de14

Browse files
committed
Merge branch 'ps/graph-lane-limit' into seen
The graph output from commands like "git log --graph" can now be limited to a specified number of lanes, preventing overly wide output in repositories with many branches. * ps/graph-lane-limit: graph: add documentation and tests about --graph-lane-limit graph: truncate graph visual output graph: add --graph-lane-limit option
2 parents ff42d0c + a05fd94 commit 9d9de14

5 files changed

Lines changed: 180 additions & 16 deletions

File tree

Documentation/rev-list-options.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,11 @@ This implies the `--topo-order` option by default, but the
12591259
in between them in that case. If _<barrier>_ is specified, it
12601260
is the string that will be shown instead of the default one.
12611261

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 replaced with a truncation mark '.'. By default
1265+
there is no limit.
1266+
12621267
ifdef::git-rev-list[]
12631268
`--count`::
12641269
Print a number stating how many commits would have been

graph.c

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,15 @@ struct git_graph {
317317
struct strbuf prefix_buf;
318318
};
319319

320+
static int graph_needs_truncation(struct git_graph *graph, int lane)
321+
{
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+
320329
static const char *diff_output_prefix_callback(struct diff_options *opt, void *data)
321330
{
322331
struct git_graph *graph = data;
@@ -696,6 +705,20 @@ static void graph_update_columns(struct git_graph *graph)
696705
}
697706
}
698707

708+
/*
709+
* If graph_max_lanes is set, cap the padding from the branches
710+
*/
711+
if (graph->revs->graph_max_lanes > 0) {
712+
/*
713+
* Get the maximum width by multiplying the maximum number of
714+
* lanes by the size of the lane "| " and adds the truncation
715+
* mark ". "
716+
*/
717+
int max_columns_width = graph->revs->graph_max_lanes * 2 + 2;
718+
if (graph->width > max_columns_width)
719+
graph->width = max_columns_width;
720+
}
721+
699722
/*
700723
* Shrink mapping_size to be the minimum necessary
701724
*/
@@ -846,6 +869,10 @@ static void graph_output_padding_line(struct git_graph *graph,
846869
* Output a padding row, that leaves all branch lines unchanged
847870
*/
848871
for (i = 0; i < graph->num_new_columns; i++) {
872+
if (graph_needs_truncation(graph, i)) {
873+
graph_line_addstr(line, ". ");
874+
break;
875+
}
849876
graph_line_write_column(line, &graph->new_columns[i], '|');
850877
graph_line_addch(line, ' ');
851878
}
@@ -903,6 +930,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
903930
seen_this = 1;
904931
graph_line_write_column(line, col, '|');
905932
graph_line_addchars(line, ' ', graph->expansion_row);
933+
} else if (seen_this && graph_needs_truncation(graph, i)) {
934+
graph_line_addstr(line, ". ");
935+
break;
906936
} else if (seen_this && (graph->expansion_row == 0)) {
907937
/*
908938
* This is the first line of the pre-commit output.
@@ -1013,6 +1043,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
10131043
* children that we have already processed.)
10141044
*/
10151045
seen_this = 0;
1046+
10161047
for (i = 0; i <= graph->num_columns; i++) {
10171048
struct column *col = &graph->columns[i];
10181049
struct commit *col_commit;
@@ -1028,8 +1059,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
10281059
seen_this = 1;
10291060
graph_output_commit_char(graph, line);
10301061

1062+
if (graph_needs_truncation(graph, i)) {
1063+
graph_line_addch(line, ' ');
1064+
break;
1065+
}
1066+
10311067
if (graph->num_parents > 2)
10321068
graph_draw_octopus_merge(graph, line);
1069+
} else if (graph_needs_truncation(graph, i)) {
1070+
graph_line_addstr(line, ". ");
1071+
seen_this = 1;
1072+
break;
10331073
} else if (seen_this && (graph->edges_added > 1)) {
10341074
graph_line_write_column(line, col, '\\');
10351075
} else if (seen_this && (graph->edges_added == 1)) {
@@ -1065,10 +1105,32 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
10651105

10661106
/*
10671107
* Update graph->state
1108+
*
1109+
* If the commit is a merge and the first parent is in a visible lane,
1110+
* then the GRAPH_POST_MERGE is needed to draw the merge lane.
1111+
*
1112+
* If the commit is over the truncation limit, but the first parent is on
1113+
* a visible lane, then we still need the merge lane but truncated.
1114+
*
1115+
* If both commit and first parent are over the truncation limit, then
1116+
* there's no need to draw the merge lane because it would work as a
1117+
* padding lane.
10681118
*/
1069-
if (graph->num_parents > 1)
1070-
graph_update_state(graph, GRAPH_POST_MERGE);
1071-
else if (graph_is_mapping_correct(graph))
1119+
if (graph->num_parents > 1) {
1120+
if (!graph_needs_truncation(graph, graph->commit_index)) {
1121+
graph_update_state(graph, GRAPH_POST_MERGE);
1122+
} else {
1123+
struct commit_list *first_parent = first_interesting_parent(graph);
1124+
int first_parent_col = graph_find_new_column_by_commit(graph, first_parent->item);
1125+
1126+
if (!graph_needs_truncation(graph, first_parent_col))
1127+
graph_update_state(graph, GRAPH_POST_MERGE);
1128+
else if (graph_is_mapping_correct(graph))
1129+
graph_update_state(graph, GRAPH_PADDING);
1130+
else
1131+
graph_update_state(graph, GRAPH_COLLAPSING);
1132+
}
1133+
} else if (graph_is_mapping_correct(graph))
10721134
graph_update_state(graph, GRAPH_PADDING);
10731135
else
10741136
graph_update_state(graph, GRAPH_COLLAPSING);
@@ -1109,14 +1171,28 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
11091171
int par_column;
11101172
int idx = graph->merge_layout;
11111173
char c;
1174+
int truncated = 0;
11121175
seen_this = 1;
11131176

11141177
for (j = 0; j < graph->num_parents; j++) {
1178+
unsigned int truncation_max = i + (j > 1 ? j - 1 : 0);
11151179
par_column = graph_find_new_column_by_commit(graph, parents->item);
11161180
assert(par_column >= 0);
11171181

11181182
c = merge_chars[idx];
11191183
graph_line_write_column(line, &graph->new_columns[par_column], c);
1184+
1185+
if (j >= 2)
1186+
truncation_max -= 1;
1187+
1188+
if (graph_needs_truncation(graph, truncation_max)) {
1189+
if (j > 0 && !(graph->edges_added > 0))
1190+
graph_line_addch(line, ' ');
1191+
graph_line_addstr(line, ". ");
1192+
truncated = 1;
1193+
break;
1194+
}
1195+
11201196
if (idx == 2) {
11211197
if (graph->edges_added > 0 || j < graph->num_parents - 1)
11221198
graph_line_addch(line, ' ');
@@ -1125,15 +1201,24 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
11251201
}
11261202
parents = next_interesting_parent(graph, parents);
11271203
}
1204+
if (truncated)
1205+
break;
11281206
if (graph->edges_added == 0)
11291207
graph_line_addch(line, ' ');
1130-
1208+
} else if (graph_needs_truncation(graph, i)) {
1209+
graph_line_addstr(line, ". ");
1210+
break;
11311211
} else if (seen_this) {
11321212
if (graph->edges_added > 0)
11331213
graph_line_write_column(line, col, '\\');
11341214
else
11351215
graph_line_write_column(line, col, '|');
1136-
graph_line_addch(line, ' ');
1216+
/*
1217+
* If it's between two lanes and next would be truncated,
1218+
* don't add space padding.
1219+
*/
1220+
if (!graph_needs_truncation(graph, i + 1))
1221+
graph_line_addch(line, ' ');
11371222
} else {
11381223
graph_line_write_column(line, col, '|');
11391224
if (graph->merge_layout != 0 || i != graph->commit_index - 1) {
@@ -1164,6 +1249,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
11641249
short used_horizontal = 0;
11651250
int horizontal_edge = -1;
11661251
int horizontal_edge_target = -1;
1252+
int truncated = 0;
11671253

11681254
/*
11691255
* Swap the mapping and old_mapping arrays
@@ -1279,26 +1365,35 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
12791365
*/
12801366
for (i = 0; i < graph->mapping_size; i++) {
12811367
int target = graph->mapping[i];
1282-
if (target < 0)
1283-
graph_line_addch(line, ' ');
1284-
else if (target * 2 == i)
1285-
graph_line_write_column(line, &graph->new_columns[target], '|');
1286-
else if (target == horizontal_edge_target &&
1287-
i != horizontal_edge - 1) {
1368+
1369+
if (!truncated && graph_needs_truncation(graph, i / 2)) {
1370+
graph_line_addstr(line, ". ");
1371+
truncated = 1;
1372+
}
1373+
1374+
if (target < 0) {
1375+
if (!truncated)
1376+
graph_line_addch(line, ' ');
1377+
} else if (target * 2 == i) {
1378+
if (!truncated)
1379+
graph_line_write_column(line, &graph->new_columns[target], '|');
1380+
} else if (target == horizontal_edge_target &&
1381+
i != horizontal_edge - 1) {
12881382
/*
12891383
* Set the mappings for all but the
12901384
* first segment to -1 so that they
12911385
* won't continue into the next line.
12921386
*/
12931387
if (i != (target * 2)+3)
12941388
graph->mapping[i] = -1;
1295-
used_horizontal = 1;
1296-
graph_line_write_column(line, &graph->new_columns[target], '_');
1389+
used_horizontal = 1;
1390+
if (!truncated)
1391+
graph_line_write_column(line, &graph->new_columns[target], '_');
12971392
} else {
12981393
if (used_horizontal && i < horizontal_edge)
12991394
graph->mapping[i] = -1;
1300-
graph_line_write_column(line, &graph->new_columns[target], '/');
1301-
1395+
if (!truncated)
1396+
graph_line_write_column(line, &graph->new_columns[target], '/');
13021397
}
13031398
}
13041399

@@ -1347,7 +1442,6 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
13471442
graph_output_collapsing_line(graph, &line);
13481443
break;
13491444
}
1350-
13511445
graph_pad_horizontally(graph, &line);
13521446
return shown_commit_line;
13531447
}
@@ -1372,6 +1466,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
13721466
for (i = 0; i < graph->num_columns; i++) {
13731467
struct column *col = &graph->columns[i];
13741468

1469+
if (graph_needs_truncation(graph, i)) {
1470+
graph_line_addch(&line, '.');
1471+
break;
1472+
}
1473+
13751474
graph_line_write_column(&line, col, '|');
13761475

13771476
if (col->commit == graph->commit && graph->num_parents > 2) {

revision.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
26052605
} else if (!strcmp(arg, "--no-graph")) {
26062606
graph_clear(revs->graph);
26072607
revs->graph = NULL;
2608+
} else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) {
2609+
revs->graph_max_lanes = parse_count(optarg);
26082610
} else if (!strcmp(arg, "--encode-email-headers")) {
26092611
revs->encode_email_headers = 1;
26102612
} else if (!strcmp(arg, "--no-encode-email-headers")) {
@@ -3174,6 +3176,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
31743176

31753177
if (revs->no_walk && revs->graph)
31763178
die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph");
3179+
3180+
if (revs->graph_max_lanes > 0 && !revs->graph)
3181+
die(_("option '%s' requires '%s'"), "--graph-lane-limit", "--graph");
3182+
31773183
if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
31783184
die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs");
31793185

revision.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ struct rev_info {
305305

306306
/* Display history graph */
307307
struct git_graph *graph;
308+
int graph_max_lanes;
308309

309310
/* special limits */
310311
int skip_count;

t/t4215-log-skewed-merges.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,57 @@ test_expect_success 'log --graph with multiple tips' '
370370
EOF
371371
'
372372

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=3 limited to three lanes' '
391+
check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
392+
* 7_M1
393+
|\
394+
| | * 7_M2
395+
| | |\
396+
| | | * 7_H
397+
| | | . 7_M3
398+
| | | . 7_J
399+
| | | . 7_I
400+
| | | . 7_M4
401+
| |_|_.
402+
|/| | .
403+
| | |_.
404+
| |/| .
405+
| | | .
406+
| | |/.
407+
| | * . 7_G
408+
| | | .
409+
| | |/.
410+
| | * . 7_F
411+
| * | . 7_E
412+
| | |/.
413+
| |/| .
414+
| * | . 7_D
415+
| | |/
416+
| |/|
417+
* | | 7_C
418+
| |/
419+
|/|
420+
* | 7_B
421+
|/
422+
* 7_A
423+
EOF
424+
'
425+
373426
test_done

0 commit comments

Comments
 (0)