Skip to content

Commit eed08de

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 truncation mark to capped lanes graph: add --graph-lane-limit option graph: limit the graph width to a hard-coded max
2 parents a544e50 + 9bab3ce commit eed08de

File tree

5 files changed

+311
-24
lines changed

5 files changed

+311
-24
lines changed

Documentation/rev-list-options.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,12 @@ 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 '~'.
1265+
By default it is set to 0 (no limit), zero and negative values
1266+
are ignored and treated as no limit.
1267+
12621268
ifdef::git-rev-list[]
12631269
`--count`::
12641270
Print a number stating how many commits would have been

graph.c

Lines changed: 154 additions & 24 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 inline 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,19 @@ static void graph_update_columns(struct git_graph *graph)
696705
}
697706
}
698707

708+
/*
709+
* If graph_max_lanes is set, cap the width
710+
*/
711+
if (graph->revs->graph_max_lanes > 0) {
712+
/*
713+
* width of "| " per lanes plus truncation mark "~ ".
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+
699721
/*
700722
* Shrink mapping_size to be the minimum necessary
701723
*/
@@ -846,6 +868,10 @@ static void graph_output_padding_line(struct git_graph *graph,
846868
* Output a padding row, that leaves all branch lines unchanged
847869
*/
848870
for (i = 0; i < graph->num_new_columns; i++) {
871+
if (graph_needs_truncation(graph, i)) {
872+
graph_line_addstr(line, "~ ");
873+
break;
874+
}
849875
graph_line_write_column(line, &graph->new_columns[i], '|');
850876
graph_line_addch(line, ' ');
851877
}
@@ -903,6 +929,9 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
903929
seen_this = 1;
904930
graph_line_write_column(line, col, '|');
905931
graph_line_addchars(line, ' ', graph->expansion_row);
932+
} else if (seen_this && graph_needs_truncation(graph, i)) {
933+
graph_line_addstr(line, "~ ");
934+
break;
906935
} else if (seen_this && (graph->expansion_row == 0)) {
907936
/*
908937
* This is the first line of the pre-commit output.
@@ -994,6 +1023,16 @@ static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line
9941023
col = &graph->new_columns[j];
9951024

9961025
graph_line_write_column(line, col, '-');
1026+
1027+
/*
1028+
* Commit is at commit_index, each iteration move one lane to
1029+
* the right from the commit.
1030+
*/
1031+
if (graph_needs_truncation(graph, graph->commit_index + 1 + i)) {
1032+
graph_line_addstr(line, "~ ");
1033+
break;
1034+
}
1035+
9971036
graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-');
9981037
}
9991038

@@ -1028,8 +1067,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
10281067
seen_this = 1;
10291068
graph_output_commit_char(graph, line);
10301069

1070+
if (graph_needs_truncation(graph, i)) {
1071+
graph_line_addch(line, ' ');
1072+
break;
1073+
}
1074+
10311075
if (graph->num_parents > 2)
10321076
graph_draw_octopus_merge(graph, line);
1077+
} else if (graph_needs_truncation(graph, i)) {
1078+
graph_line_addstr(line, "~ ");
1079+
seen_this = 1;
1080+
break;
10331081
} else if (seen_this && (graph->edges_added > 1)) {
10341082
graph_line_write_column(line, col, '\\');
10351083
} else if (seen_this && (graph->edges_added == 1)) {
@@ -1065,13 +1113,46 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
10651113

10661114
/*
10671115
* Update graph->state
1116+
*
1117+
* If the commit is a merge and the first parent is in a visible lane,
1118+
* then the GRAPH_POST_MERGE is needed to draw the merge lane.
1119+
*
1120+
* If the commit is over the truncation limit, but the first parent is on
1121+
* a visible lane, then we still need the merge lane but truncated.
1122+
*
1123+
* If both commit and first parent are over the truncation limit, then
1124+
* there's no need to draw the merge lane because it would work as a
1125+
* padding lane.
10681126
*/
1069-
if (graph->num_parents > 1)
1070-
graph_update_state(graph, GRAPH_POST_MERGE);
1071-
else if (graph_is_mapping_correct(graph))
1127+
if (graph->num_parents > 1) {
1128+
if (!graph_needs_truncation(graph, graph->commit_index)) {
1129+
graph_update_state(graph, GRAPH_POST_MERGE);
1130+
} else {
1131+
struct commit_list *p = first_interesting_parent(graph);
1132+
int lane;
1133+
1134+
/*
1135+
* graph->num_parents are found using first_interesting_parent
1136+
* and next_interesting_parent so it can't be a scenario
1137+
* where num_parents > 1 and there are no interesting parents
1138+
*/
1139+
if (!p)
1140+
BUG("num_parents > 1 but no interesting parent");
1141+
1142+
lane = graph_find_new_column_by_commit(graph, p->item);
1143+
1144+
if (!graph_needs_truncation(graph, lane))
1145+
graph_update_state(graph, GRAPH_POST_MERGE);
1146+
else if (graph_is_mapping_correct(graph))
1147+
graph_update_state(graph, GRAPH_PADDING);
1148+
else
1149+
graph_update_state(graph, GRAPH_COLLAPSING);
1150+
}
1151+
} else if (graph_is_mapping_correct(graph)) {
10721152
graph_update_state(graph, GRAPH_PADDING);
1073-
else
1153+
} else {
10741154
graph_update_state(graph, GRAPH_COLLAPSING);
1155+
}
10751156
}
10761157

10771158
static const char merge_chars[] = {'/', '|', '\\'};
@@ -1109,6 +1190,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
11091190
int par_column;
11101191
int idx = graph->merge_layout;
11111192
char c;
1193+
int truncated = 0;
11121194
seen_this = 1;
11131195

11141196
for (j = 0; j < graph->num_parents; j++) {
@@ -1117,23 +1199,56 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct graph_l
11171199

11181200
c = merge_chars[idx];
11191201
graph_line_write_column(line, &graph->new_columns[par_column], c);
1202+
1203+
/*
1204+
* j counts parents, it needs to be halved to be
1205+
* comparable with i. Don't truncate if there are
1206+
* no more lanes to print (end of the lane)
1207+
*/
1208+
if (graph_needs_truncation(graph, j / 2 + i) &&
1209+
j / 2 + i <= graph->num_columns) {
1210+
if ((j + i * 2) % 2 != 0)
1211+
graph_line_addch(line, ' ');
1212+
graph_line_addstr(line, "~ ");
1213+
truncated = 1;
1214+
break;
1215+
}
1216+
11201217
if (idx == 2) {
1121-
if (graph->edges_added > 0 || j < graph->num_parents - 1)
1218+
/*
1219+
* Check if the next lane needs truncation
1220+
* to avoid having the padding doubled
1221+
*/
1222+
if (graph_needs_truncation(graph, (j + 1) / 2 + i) &&
1223+
j < graph->num_parents - 1) {
1224+
graph_line_addstr(line, "~ ");
1225+
truncated = 1;
1226+
break;
1227+
} else if (graph->edges_added > 0 || j < graph->num_parents - 1)
11221228
graph_line_addch(line, ' ');
11231229
} else {
11241230
idx++;
11251231
}
11261232
parents = next_interesting_parent(graph, parents);
11271233
}
1234+
if (truncated)
1235+
break;
11281236
if (graph->edges_added == 0)
11291237
graph_line_addch(line, ' ');
1130-
1238+
} else if (graph_needs_truncation(graph, i)) {
1239+
graph_line_addstr(line, "~ ");
1240+
break;
11311241
} else if (seen_this) {
11321242
if (graph->edges_added > 0)
11331243
graph_line_write_column(line, col, '\\');
11341244
else
11351245
graph_line_write_column(line, col, '|');
1136-
graph_line_addch(line, ' ');
1246+
/*
1247+
* If it's between two lanes and next would be truncated,
1248+
* don't add space padding.
1249+
*/
1250+
if (!graph_needs_truncation(graph, i + 1))
1251+
graph_line_addch(line, ' ');
11371252
} else {
11381253
graph_line_write_column(line, col, '|');
11391254
if (graph->merge_layout != 0 || i != graph->commit_index - 1) {
@@ -1164,6 +1279,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
11641279
short used_horizontal = 0;
11651280
int horizontal_edge = -1;
11661281
int horizontal_edge_target = -1;
1282+
int truncated = 0;
11671283

11681284
/*
11691285
* Swap the mapping and old_mapping arrays
@@ -1279,26 +1395,35 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
12791395
*/
12801396
for (i = 0; i < graph->mapping_size; i++) {
12811397
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) {
1288-
/*
1289-
* Set the mappings for all but the
1290-
* first segment to -1 so that they
1291-
* won't continue into the next line.
1292-
*/
1293-
if (i != (target * 2)+3)
1294-
graph->mapping[i] = -1;
1295-
used_horizontal = 1;
1296-
graph_line_write_column(line, &graph->new_columns[target], '_');
1398+
1399+
if (!truncated && graph_needs_truncation(graph, i / 2)) {
1400+
graph_line_addstr(line, "~ ");
1401+
truncated = 1;
1402+
}
1403+
1404+
if (target < 0) {
1405+
if (!truncated)
1406+
graph_line_addch(line, ' ');
1407+
} else if (target * 2 == i) {
1408+
if (!truncated)
1409+
graph_line_write_column(line, &graph->new_columns[target], '|');
1410+
} else if (target == horizontal_edge_target &&
1411+
i != horizontal_edge - 1) {
1412+
/*
1413+
* Set the mappings for all but the
1414+
* first segment to -1 so that they
1415+
* won't continue into the next line.
1416+
*/
1417+
if (i != (target * 2)+3)
1418+
graph->mapping[i] = -1;
1419+
used_horizontal = 1;
1420+
if (!truncated)
1421+
graph_line_write_column(line, &graph->new_columns[target], '_');
12971422
} else {
12981423
if (used_horizontal && i < horizontal_edge)
12991424
graph->mapping[i] = -1;
1300-
graph_line_write_column(line, &graph->new_columns[target], '/');
1301-
1425+
if (!truncated)
1426+
graph_line_write_column(line, &graph->new_columns[target], '/');
13021427
}
13031428
}
13041429

@@ -1372,6 +1497,11 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
13721497
for (i = 0; i < graph->num_columns; i++) {
13731498
struct column *col = &graph->columns[i];
13741499

1500+
if (graph_needs_truncation(graph, i)) {
1501+
graph_line_addstr(&line, "~ ");
1502+
break;
1503+
}
1504+
13751505
graph_line_write_column(&line, col, '|');
13761506

13771507
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
@@ -2606,6 +2606,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
26062606
} else if (!strcmp(arg, "--no-graph")) {
26072607
graph_clear(revs->graph);
26082608
revs->graph = NULL;
2609+
} else if (skip_prefix(arg, "--graph-lane-limit=", &optarg)) {
2610+
revs->graph_max_lanes = parse_count(optarg);
26092611
} else if (!strcmp(arg, "--encode-email-headers")) {
26102612
revs->encode_email_headers = 1;
26112613
} else if (!strcmp(arg, "--no-encode-email-headers")) {
@@ -3175,6 +3177,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
31753177

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

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;

0 commit comments

Comments
 (0)