diff --git a/graph.c b/graph.c index 842282685f..7263aa6283 100644 --- a/graph.c +++ b/graph.c @@ -60,12 +60,23 @@ struct column { * index into column_colors. */ unsigned short color; + /* + * Marks if a commit is a non-first parent of a merge. These columns are + * already visually connected to the merge commit and do not need + * indentation. + * + * The first parent is the one that inherits the column and it can need + * indentation if turns out to be a visual root and there's still + * commits to render. + */ + unsigned is_merge_parent:1; }; enum graph_state { GRAPH_PADDING, GRAPH_SKIP, GRAPH_PRE_COMMIT, + GRAPH_PRE_ROOT, GRAPH_COMMIT, GRAPH_POST_MERGE, GRAPH_COLLAPSING @@ -315,6 +326,48 @@ struct git_graph { * diff_output_prefix_callback(). */ struct strbuf prefix_buf; + + /* + * If a commit is a visual root, we need to indent it to prevent + * unrelated commits from being vertically adjacent to it. + */ + unsigned is_visual_root:1; + + /* + * Indentation increases for each visual root adjacent to another visual + * root, making visual root commits indentation cascade. + */ + unsigned int visual_root_depth; + + /* + * When a visual root is adjacent to other visual roots, the first one + * can avoid indentation and the rest cascades, increasing the indentation + * for each one. + */ + unsigned visual_root_cascade:1; + + /* + * Set when the current commit was already present in graph->columns + * before being processed. + */ + unsigned commit_in_columns:1; +}; + +struct graph_lookahead_flags { + /* + * Set when there will be a commit after the current one that will be + * rendered. + */ + unsigned int is_next_visible:1; + /* + * Set when the next visible commit is candidate to be a visual root. + */ + unsigned int is_next_visual_root:1; + /* + * Set when the next visible commit will be rendered under the current + * commit. + */ + unsigned int next_has_column:1; }; static inline int graph_needs_truncation(struct git_graph *graph, int lane) @@ -388,6 +441,8 @@ struct git_graph *graph_init(struct rev_info *opt) graph->num_columns = 0; graph->num_new_columns = 0; graph->mapping_size = 0; + graph->visual_root_depth = 0; + graph->visual_root_cascade = 0; /* * Start the column color at the maximum value, since we'll * always increment it for the first commit we output. @@ -561,6 +616,11 @@ static void graph_insert_into_new_columns(struct git_graph *graph, struct commit *commit, int idx) { + /* + * Get the initial merge_layout before it's modified to know if this + * is a merge. + */ + int initial_merge_layout = graph->merge_layout; int i = graph_find_new_column_by_commit(graph, commit); int mapping_idx; @@ -572,6 +632,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph, i = graph->num_new_columns++; graph->new_columns[i].commit = commit; graph->new_columns[i].color = graph_find_commit_color(graph, commit); + graph->new_columns[i].is_merge_parent = 0; } if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) { @@ -610,6 +671,12 @@ static void graph_insert_into_new_columns(struct git_graph *graph, } graph->mapping[mapping_idx] = i; + + /* + * Mark non-first parents of a merge. + */ + if (graph->num_parents > 1 && initial_merge_layout >= 0 && idx > -1) + graph->new_columns[i].is_merge_parent = 1; } static void graph_update_columns(struct git_graph *graph) @@ -701,10 +768,20 @@ static void graph_update_columns(struct git_graph *graph) if (graph->num_parents == 0) graph->width += 2; } else { + int j; graph_insert_into_new_columns(graph, col_commit, -1); + /* + * This column is not the current commit, but we need to + * propagate the flag until the commit is processed. + */ + j = graph_find_new_column_by_commit(graph, col_commit); + if (j >= 0 && graph->columns[i].is_merge_parent) + graph->new_columns[j].is_merge_parent = 1; } } + graph->commit_in_columns = is_commit_in_columns; + /* * If graph_max_lanes is set, cap the width */ @@ -763,9 +840,144 @@ static int graph_needs_pre_commit_line(struct git_graph *graph) graph->expansion_row < graph_num_expansion_rows(graph); } +/* + * A commit can be a visual root when: + * - It has no parents. + * + * - It has parents but they are all filtered out and + * commit->parents arrives NULL. + * + * - It is not a boundary commit. Boundary commits also have no visible + * parents, but they are not selected as visual roots because they cannot + *. cause the ambiguity of being vertically adjacent because: + * + * 1. A boundary only appears because an included commit is its child. + * Children are always above, and the renderer draws an edge down to + * the boundary from that child. Rather than starting a column like a + * visual root would do, it inherits its child column. + * + * 2. Included commits cannot appear below a boundary. Boundaries are + * ancestors of the exclusion point; if an included commit were an + * ancestor of the boundary it would be excluded and not rendered. + * Boundaries therefore always sink to the bottom. + */ +static int graph_is_visual_root_candidate(struct commit *c) +{ + return c->parents == NULL && !(c->object.flags & BOUNDARY); +} + +static int graph_is_visual_root(struct git_graph *graph, + struct graph_lookahead_flags *flags) +{ + /* + * This must be only called for the current commit as graph contains + * the state for the current commit only. + * + * To check if a commit is a visual root, call graph_is_visual_root_candidate() + * but we won't know if it is really a visual root until we get to the + * next commit state. + * + * The current commit is an actual visual root if it is a candidate and + * the commit is not a non-first parent of a merge. + * + * * + * |\ + * | * <- it is a visual root candidate but it shouldn't be indented + * * because it is already connected by an edge. + * ^ if commit_in_columns && is_merge_parent means the commit + * | was put by a merge and is connected. + * | + * `-------- if !is_next_visible means we're on the last commit, avoid + * indentation unless the one before is a visual root, then + * we need to differentiate from the one above. + * + * If next_has_columns means that the next commit has + * already a column, so it will not be rendered below, the + * current commit has to act as the last commit and omit + * indentation. + */ + return graph_is_visual_root_candidate(graph->commit) && + !(graph->commit_in_columns && + graph->columns[graph->commit_index].is_merge_parent) && + flags->is_next_visible && + (!flags->next_has_column || graph->visual_root_depth > 0); +} + +/* + * Iterates the commits queue searching for the next visible commit, once found + * sets visibleness and visual-root flags. + * Knowing if the next commit is also a visual root avoids redundant indentations + * + * NEEDSWORK: There are two main limitations to predict if the next commit will be + * a visual root candidate: + * + * 1. The peek only gives us the next entry reliably, we cannot see past it + * reliably in order. + * + * 2. Even if we could peek past in order, its parents might not have been + * simplified yet, so a future commit that will become a visual root is + * not detected as a visual root in peek-time. + * + * This results in a redundant indentation. + * + * See 'test_expect_failure' at t4218-log-graph-indentation.sh. + */ +static void graph_peek_next_visible(struct git_graph *graph, + struct graph_lookahead_flags *flags) +{ + struct commit *next; + + flags->is_next_visible = 0; + flags->is_next_visual_root = 0; + flags->next_has_column = 0; + + next = revision_peek_next_commit(graph->revs); + if (!next) + return; + + if (get_commit_action(graph->revs, next) != commit_show) { + /* + * next commit won't be shown but there could be a visible + * commit still. + */ + if (revision_has_commits_after(graph->revs, 1)) + flags->is_next_visible = 1; + return; + } + + flags->is_next_visible = 1; + flags->next_has_column = graph_find_new_column_by_commit(graph, next) >= 0; + + if (!graph_is_visual_root_candidate(next)) + return; + /* + * Next commit is a visual root candidate but we don't want the last + * commit to get indented, check if its not the last visible commit + * + * We do not need graph->commit_in_columns or is_merge_parent, + * because we only need to know whether the next one might be a + * visual root, affecting the current commit where the cascade + * would have to be set and the first visual root not indented. + * + * It will set next_is_visual_root to true for merge parents that + * graph_is_visual_root() would return false, but if the next is + * a merge parent, the current commit is the child and cannot + * be a visual root and therefore having no effect. + */ + if (revision_has_commits_after(graph->revs, 2)) + flags->is_next_visual_root = 1; +} + +static int graph_needs_pre_root_line(struct git_graph *graph) +{ + return graph->commit_in_columns && graph->is_visual_root && + graph->num_columns > 0 && !graph->visual_root_cascade; +} + void graph_update(struct git_graph *graph, struct commit *commit) { struct commit_list *parent; + struct graph_lookahead_flags flags; /* * Set the new commit @@ -796,6 +1008,23 @@ void graph_update(struct git_graph *graph, struct commit *commit) */ graph_update_columns(graph); + graph_peek_next_visible(graph, &flags); + + graph->is_visual_root = graph_is_visual_root(graph, &flags); + + if (graph->is_visual_root) { + /* + * If next is a visual root we can omit the indent for the first + * visual root and start cascading. + */ + if (!graph->visual_root_depth && flags.is_next_visual_root) + graph->visual_root_cascade = 1; + graph->visual_root_depth++; + } else { + graph->visual_root_depth = 0; + graph->visual_root_cascade = 0; + } + graph->expansion_row = 0; /* @@ -813,11 +1042,16 @@ void graph_update(struct git_graph *graph, struct commit *commit) * room for it. We need to do this only if there is a branch row * (or more) to the right of this commit. * + * If it is a visual root, we need to print an extra row to + * connect the indentation. + * * If there are less than 3 parents, we can immediately print the * commit line. */ if (graph->state != GRAPH_PADDING) graph->state = GRAPH_SKIP; + else if (graph_needs_pre_root_line(graph)) + graph->state = GRAPH_PRE_ROOT; else if (graph_needs_pre_commit_line(graph)) graph->state = GRAPH_PRE_COMMIT; else @@ -1065,6 +1299,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line if (col_commit == graph->commit) { seen_this = 1; + if (graph->is_visual_root) { + int depth = graph->visual_root_depth; + /* + * Each visual column is 2 characters wide. + * Omit the indentation for the first visual + * root in cascade mode. + */ + int padding = (depth - graph->visual_root_cascade) * 2; + graph_line_addchars(line, ' ', padding); + graph->width += padding; + } graph_output_commit_char(graph, line); if (graph_needs_truncation(graph, i)) { @@ -1436,6 +1681,29 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l graph_update_state(graph, GRAPH_PADDING); } +static void graph_output_pre_root_line(struct git_graph *graph, struct graph_line *line) +{ + /* + * This function adds a row before a visual root, to connect the + * branch to the indented commit. It should only be called on a + * visual root. + */ + assert(graph->is_visual_root); + + for (size_t i = 0; i < graph->num_columns; i++) { + struct column *col = &graph->columns[i]; + if (col->commit == graph->commit) { + graph_line_addch(line, ' '); + graph_line_write_column(line, col, '\\'); + } else { + graph_line_write_column(line, col, '|'); + } + graph_line_addch(line, ' '); + } + + graph_update_state(graph, GRAPH_COMMIT); +} + int graph_next_line(struct git_graph *graph, struct strbuf *sb) { int shown_commit_line = 0; @@ -1461,6 +1729,9 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb) case GRAPH_PRE_COMMIT: graph_output_pre_commit_line(graph, &line); break; + case GRAPH_PRE_ROOT: + graph_output_pre_root_line(graph, &line); + break; case GRAPH_COMMIT: graph_output_commit_line(graph, &line); shown_commit_line = 1; diff --git a/revision.c b/revision.c index 0c95edef59..9ab22be122 100644 --- a/revision.c +++ b/revision.c @@ -3710,6 +3710,44 @@ static unsigned int count_explore_walked; static unsigned int count_indegree_walked; static unsigned int count_topo_walked; +struct commit *revision_peek_next_commit (struct rev_info *revs) +{ + struct topo_walk_info *info = revs->topo_walk_info; + + if (info) + return prio_queue_peek(&info->topo_queue); + if (revs->commits) + return revs->commits->item; + + return NULL; +} + +int revision_has_commits_after (struct rev_info *revs, int n) +{ + struct topo_walk_info *info = revs->topo_walk_info; + + if (info) { + int visible = 0; + for (size_t i = 0; i < info->topo_queue.nr && visible < n; i++) { + struct commit *c = info->topo_queue.array[i].data; + if (get_commit_action(revs, c) == commit_show) + visible++; + } + return visible > n-1; + } + if (revs->commits) { + struct commit_list *cl; + int visible = 0; + for (cl = revs->commits; cl && visible < n; cl = cl->next) { + if (get_commit_action(revs, cl->item) == commit_show) + visible++; + } + return visible > n-1; + } + + return 0; +} + static void trace2_topo_walk_statistics_atexit(void) { struct json_writer jw = JSON_WRITER_INIT; diff --git a/revision.h b/revision.h index 569b3fa1cb..7fa8ae4bc8 100644 --- a/revision.h +++ b/revision.h @@ -576,4 +576,14 @@ int rewrite_parents(struct rev_info *revs, */ struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit); +/* + * Peek into revision's next commit without consuming it. + */ +struct commit *revision_peek_next_commit(struct rev_info *revs); + +/* + * Check if there are n more commits to be shown yet. + */ +int revision_has_commits_after(struct rev_info *revs, int n); + #endif diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh index bf952ef920..1eae8f60c2 100644 --- a/t/lib-log-graph.sh +++ b/t/lib-log-graph.sh @@ -26,3 +26,8 @@ lib_test_cmp_colored_graph () { test_decode_color output.colors && test_cmp expect.colors output.colors } + +lib_test_check_graph () { + cat >expect && + lib_test_cmp_graph --format=%s "$@" +} diff --git a/t/meson.build b/t/meson.build index 0796dba2c4..960836383c 100644 --- a/t/meson.build +++ b/t/meson.build @@ -581,6 +581,7 @@ integration_tests = [ 't4215-log-skewed-merges.sh', 't4216-log-bloom.sh', 't4217-log-limit.sh', + 't4218-log-graph-indentation.sh', 't4219-log-follow-merge.sh', 't4252-am-options.sh', 't4253-am-keep-cr-dos.sh', diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 1612f05f1b..eebab71039 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -5,11 +5,6 @@ test_description='git log --graph of skewed merges' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh -check_graph () { - cat >expect && - lib_test_cmp_graph --format=%s "$@" -} - test_expect_success 'log --graph with merge fusing with its left and right neighbors' ' git checkout --orphan _p && test_commit A && @@ -21,7 +16,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh git checkout _p && git merge --no-ff _r -m G && git checkout @^^ && git merge --no-ff _p -m H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * H |\ | * G @@ -49,7 +44,7 @@ test_expect_success 'log --graph with left-skewed merge' ' git checkout 0_p && git merge --no-ff 0_s -m 0_G && git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF *-----. 0_H |\ \ \ \ | | | | * 0_G @@ -83,7 +78,7 @@ test_expect_success 'log --graph with nested left-skewed merge' ' git checkout 1_p && git merge --no-ff 1_r -m 1_G && git checkout @^^ && git merge --no-ff 1_p -m 1_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 1_H |\ | * 1_G @@ -115,7 +110,7 @@ test_expect_success 'log --graph with nested left-skewed merge following normal git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J && git checkout 2_p && git merge --no-ff 2_s -m 2_K && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 2_K |\ | * 2_J @@ -151,7 +146,7 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s git checkout 3_p && git merge --no-ff 3_r -m 3_H && git checkout @^^ && git merge --no-ff 3_p -m 3_J && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 3_J |\ | * 3_H @@ -182,7 +177,7 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed git merge --no-ff 4_p -m 4_G && git checkout @^^ && git merge --no-ff 4_s -m 4_H && - check_graph --date-order <<-\EOF + lib_test_check_graph --date-order <<-\EOF * 4_H |\ | * 4_G @@ -218,7 +213,7 @@ test_expect_success 'log --graph with octopus merge with column joining its penu git checkout 5_r && git merge --no-ff 5_s -m 5_H && - check_graph <<-\EOF + lib_test_check_graph <<-\EOF * 5_H |\ | *-. 5_G @@ -257,7 +252,7 @@ test_expect_success 'log --graph with multiple tips' ' git checkout 6_1 && git merge --no-ff 6_2 -m 6_I && - check_graph 6_1 6_3 6_5 <<-\EOF + lib_test_check_graph 6_1 6_3 6_5 <<-\EOF * 6_I |\ | | * 6_H @@ -334,7 +329,7 @@ test_expect_success 'log --graph with multiple tips' ' git checkout -b M_7 7_1 && git merge --no-ff 7_2 7_3 -m 7_M4 && - check_graph M_1 M_3 M_5 M_7 <<-\EOF + lib_test_check_graph M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -371,7 +366,7 @@ test_expect_success 'log --graph with multiple tips' ' ' test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' ' - check_graph --graph-lane-limit=2 M_7 <<-\EOF + lib_test_check_graph --graph-lane-limit=2 M_7 <<-\EOF *-. 7_M4 |\ \ | | * 7_G @@ -388,7 +383,7 @@ 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 + lib_test_check_graph --graph-lane-limit=1 M_7 <<-\EOF *-~ 7_M4 |\~ | ~ 7_G @@ -405,7 +400,7 @@ test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge ' 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 + lib_test_check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -441,7 +436,7 @@ test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' ' ' 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 + lib_test_check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 @@ -478,7 +473,7 @@ test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows fir ' 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 + lib_test_check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF * 7_M1 |\ | | * 7_M2 diff --git a/t/t4218-log-graph-indentation.sh b/t/t4218-log-graph-indentation.sh new file mode 100755 index 0000000000..005ad7c30c --- /dev/null +++ b/t/t4218-log-graph-indentation.sh @@ -0,0 +1,468 @@ +#!/bin/sh + +test_description='git log --graph visual root indentations' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-log-graph.sh + +check_graph_with_description () { + cat >expect && + lib_test_cmp_graph --format="%s%ndescription%nsecond-line" "$@" +} + +create_orphan () { + git checkout --orphan "$1" && + { git rm -rf . || true; } +} + +# disable commit-graph topo order to have the graph to render in different +# ways (used in --first-parent tests to have multiple visual roots while a +# column is active at the same time). +unset_commit_graph() { + sane_unset GIT_TEST_COMMIT_GRAPH && + rm -f .git/objects/info/commit-graph && + rm -rf .git/objects/info/commit-graphs +} + +test_expect_success 'single root commit is not indented' ' + create_orphan _1 && test_commit 1_A && + lib_test_check_graph _1 <<-\EOF + * 1_A + EOF +' + +test_expect_success 'visual root indented before unrelated branch' ' + create_orphan _2 && test_commit 2_A && test_commit 2_B && + create_orphan _3 && test_commit 3_A && + lib_test_check_graph _2 _3 <<-\EOF + * 3_A + * 2_B + * 2_A + EOF +' + +test_expect_success 'visual root indentation with --left-right' ' + lib_test_check_graph --left-right _2..._3 <<-\EOF + > 3_A + < 2_B + < 2_A + EOF +' + +# A better case of why indentation is still needed with '--left-right' flag is +# that unrelated branches can be on the same side, so it's needed to +# differentiate visual roots on the same side. +test_expect_success 'visual root indentation with --left-right having unrelated commits on the same side' ' + lib_test_check_graph --left-right _2..._3 _1 <<-\EOF + > 3_A + < 2_B + \ + < 2_A + > 1_A + EOF +' + +test_expect_success 'visual root indents the description also' ' + check_graph_with_description _2 _3 <<-\EOF + * 3_A + description + second-line + * 2_B + | description + | second-line + * 2_A + description + second-line + EOF +' + +test_expect_success 'indented visual root parent gets connected to its child' ' + create_orphan _4 && test_commit 4_A && test_commit 4_B && + create_orphan _5 && test_commit 5_A && test_commit 5_B && + lib_test_check_graph _4 _5<<-\EOF + * 5_B + \ + * 5_A + * 4_B + * 4_A + EOF +' + +test_expect_success 'indented visual root parent gets connected to its child with description' ' + check_graph_with_description _4 _5 <<-\EOF + * 5_B + | description + | second-line + \ + * 5_A + description + second-line + * 4_B + | description + | second-line + * 4_A + description + second-line + EOF +' + +test_expect_success 'visual roots cascade and last root does not' ' + create_orphan _7 && test_commit 7_A && test_commit 7_B && + create_orphan _8 && test_commit 8_A && + create_orphan _9 && test_commit 9_A && + create_orphan _10 && test_commit 10_A && + lib_test_check_graph _7 _8 _9 _10 <<-\EOF + * 10_A + * 9_A + * 8_A + * 7_B + * 7_A + EOF +' + +test_expect_success 'last root does not cascade' ' + lib_test_check_graph _8 _9 _10 <<-\EOF + * 10_A + * 9_A + * 8_A + EOF +' + +test_expect_success 'merge parents are roots between them but they do not indent' ' + create_orphan _11 && test_commit 11_A && + create_orphan _12 && test_commit 12_A && + create_orphan _13 && test_commit 13_A && + git checkout _11 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _11 -p _12 -p _13 -m 11_octopus) && + git reset --hard $MERGE && + lib_test_check_graph _11 <<-\EOF + *-. 11_octopus + |\ \ + | | * 13_A + | * 12_A + * 11_A + EOF +' + +# The last parent of a merge can be indented if nothing related to it needs to +# be rendered after, if it's another visual root, merge parent must not get +# indented but rather activate cascading. +test_expect_success 'merge then unrelated visual root and unrelated branch' ' + create_orphan _16 && test_commit 16_A && test_commit 16_B && + create_orphan _17 && test_commit 17_A && + create_orphan _18 && test_commit 18_A && + create_orphan _19 && test_commit 19_A && + create_orphan _20 && test_commit 20_A && + git checkout _18 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _18 -p _19 -p _20 -m 18_octopus) && + git reset --hard $MERGE && + lib_test_check_graph _18 _17 _16 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + * 18_A + * 17_A + * 16_B + * 16_A + EOF +' + +# The last commit root does not get indented, if the next thing after the root +# merge parent is the last commit, indent the merge parent. +test_expect_success 'merge then unrelated root indents merge parent' ' + lib_test_check_graph _18 _17 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + \ + * 18_A + * 17_A + EOF +' + +test_expect_success 'merge then unrelated branch indents merge parent' ' + lib_test_check_graph _18 _16 <<-\EOF + *-. 18_octopus + |\ \ + | | * 20_A + | * 19_A + \ + * 18_A + * 16_B + * 16_A + EOF +' + +test_expect_success 'two-parent merge of orphans' ' + create_orphan _21 && test_commit 21_A && + create_orphan _22 && test_commit 22_A && + git checkout _21 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _21 -p _22 -m 21_merge) && + git reset --hard $MERGE && + lib_test_check_graph _21 <<-\EOF + * 21_merge + |\ + | * 22_A + * 21_A + EOF +' + +test_expect_success 'commit with filtered parent becomes a visual root' ' + create_orphan _23 && + echo test >other.txt && + git add other.txt && + git commit -m "23_A" && + echo test >foo.txt && + git add foo.txt && + git commit -m "23_B" && + create_orphan _24 && + echo test >foo.txt && + git add foo.txt && + git commit -m "24_A" && + lib_test_check_graph _23 _24 -- foo.txt <<-\EOF + * 23_B + * 24_A + EOF +' + +# When a commit's parent will be filtered, the lookahead cannot reliably predict +# if the next commit will be shown because the filtering has not happened yet at +# peek-time. This makes cascade to not be set causing an extra indentation. +# +# Expected: +# +# A +# B +# D +# +# Output: +# +# A +# B +# D +# +# This will happen for any case where we find ourselves with the next commit +# being a unrelated child of a parent that will be filtered. +# +# instead of the expected: +test_expect_failure 'filtered parent cascading edge case' ' + create_orphan _25 && + echo test >other.txt && + git add other.txt && + git commit -m "C-filtered" && + + echo test >foo.txt && + git add foo.txt && + git commit -m "B (child of filtered)" && + + create_orphan _26 && + echo test >foo.txt && + git add foo.txt && + git commit -m "A (visual root)" && + + create_orphan _27 && + echo test >foo.txt && + git add foo.txt && + git commit -m "D (last)" && + + lib_test_check_graph _25 _26 _27 -- foo.txt <<-\EOF + * B (child of filtered) + * A (visual root) + * D (last) + EOF +' + +test_expect_failure 'multiple filtered parents in sequence' ' + create_orphan _44 && + echo a >other.txt && git add other.txt && git commit -m "44_F" && + echo b >foo.txt && git add foo.txt && git commit -m "44_C" && + + create_orphan _45 && + echo c >other.txt && git add other.txt && git commit -m "45_F" && + echo d >foo.txt && git add foo.txt && git commit -m "45_C" && + + create_orphan _46 && + echo e >foo.txt && git add foo.txt && git commit -m "46_A" && + + lib_test_check_graph _44 _45 _46 -- foo.txt <<-\EOF + * 44_C + * 45_C + * 46_A + EOF +' + +# This tests prove why there is no need to have indentation for boundary +# commits. +# +# Boundary commits rather than starting a column they 'inherit' the one of +# its child so there will always be an edge that connects it removing the +# ambiguity. +test_expect_success 'unrelated boundaries are not ambiguous' ' + create_orphan _28 && test_commit 28_A && test_commit 28_B && + test_commit 28_C && + create_orphan _29 && test_commit 29_A && test_commit 29_B && + lib_test_check_graph --boundary 28_A.._28 29_A.._29 <<-\EOF + * 29_B + | * 28_C + | * 28_B + | o 28_A + o 29_A + EOF +' + +# Same structure as t6016 +test_expect_success 'boundary commits big test' ' + # 3 commits on branch _30 + create_orphan _30 && + test_commit 30_A && + test_commit 30_B && + test_commit 30_C && + + # 2 commits on branch _31, started from 30_A + git checkout -b _31 30_A && + test_commit 31_A && + test_commit 31_B && + + # 2 commits on branch _32, started from 30_B + git checkout -b _32 30_B && + test_commit 32_A && + test_commit 32_B && + + # Octopus merge _31 and _32 into -30 + git checkout _30 && + git merge _31 _32 -m 30_D && + git tag 30_D && + test_commit 30_E && + + # More commits on _32, then merge _32 into _30 + git checkout _32 && + test_commit 32_C && + test_commit 32_D && + git checkout _30 && + git merge -s ours _32 -m 30_F && + git tag 30_F && + test_commit 30_G && + lib_test_check_graph --boundary _30 _31 _32 ^32_C <<-\EOF + * 30_G + * 30_F + |\ + | * 32_D + * | 30_E + | | + | \ + *-. \ 30_D + |\ \ \ + | * | | 31_B + | * | | 31_A + * | | | 30_C + o | | | 30_B + |/ / / + o / / 30_A + / / + | o 32_C + |/ + o 32_B + EOF +' + +# Filter by --first-parent and then forcing the filtered parents to be shown. +test_expect_success '--first-parent flag with the filtered parents' ' + ( + unset_commit_graph && + create_orphan _35 && test_commit 35_A && test_commit 35_B && + create_orphan _36 && test_commit 36_A && + create_orphan _37 && test_commit 37_A && + git checkout _35 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _35 -p _36 -p _37 -m 35_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _35 _36 _37 <<-\EOF + * 35_octopus + | * 37_A + | * 36_A + * 35_B + * 35_A + EOF + ) +' + +test_expect_success '--first-parent with filtered parents but one has a child' ' + ( + unset_commit_graph && + create_orphan _38 && test_commit 38_A && test_commit 38_B && + create_orphan _39 && test_commit 39_A && + create_orphan _40 && test_commit 40_A && test_commit 40_B && + git checkout _38 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _38 -p _39 -p _40 -m 38_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _38 _39 _40 <<-\EOF + * 38_octopus + | * 40_B + | * 40_A + | * 39_A + * 38_B + * 38_A + EOF + ) +' + +test_expect_success '--first-parent with filtered parents but both have childs' ' + ( + unset_commit_graph && + create_orphan _41 && test_commit 41_A && test_commit 41_B && + create_orphan _42 && test_commit 42_A && test_commit 42_B && + create_orphan _43 && test_commit 43_A && test_commit 43_B && + git checkout _41 && + TREE=$(git write-tree) && + MERGE=$(git commit-tree $TREE -p _41 -p _42 -p _43 -m 41_octopus) && + git reset --hard $MERGE && + lib_test_check_graph --first-parent _41 _42 _43 <<-\EOF + * 41_octopus + | * 43_B + | \ + | * 43_A + | * 42_B + | * 42_A + * 41_B + * 41_A + EOF + ) +' + +test_expect_success 'two unrelated merges' ' + create_orphan _50 && test_commit 50_A && + git checkout -b _51 && + test_commit 51_A && test_commit 51_B && + git checkout _50 && + git merge --no-ff _51 -m 50_B && + + create_orphan _52 && test_commit 52_A && + git checkout -b _53 && + test_commit 53_A && test_commit 53_B && + git checkout _52 && + git merge --no-ff _53 -m 52_B && + + lib_test_check_graph _52 _50 <<-\EOF + * 52_B + |\ + | * 53_B + | * 53_A + |/ + \ + * 52_A + * 50_B + |\ + | * 51_B + | * 51_A + |/ + * 50_A + EOF +' + +test_done diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh index 54b0a6f5f8..e0d9c3c1ac 100755 --- a/t/t6016-rev-list-graph-simplify-history.sh +++ b/t/t6016-rev-list-graph-simplify-history.sh @@ -13,11 +13,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh -check_graph () { - cat >expect && - lib_test_cmp_graph --format=%s "$@" -} - test_expect_success 'set up rev-list --graph test' ' # 3 commits on branch A test_commit A1 foo.txt && @@ -54,7 +49,7 @@ test_expect_success 'set up rev-list --graph test' ' ' test_expect_success '--graph --all' ' - check_graph --all <<-\EOF + lib_test_check_graph --all <<-\EOF * A7 * A6 |\ @@ -82,7 +77,7 @@ test_expect_success '--graph --all' ' # that undecorated merges are interesting, even with --simplify-by-decoration test_expect_success '--graph --simplify-by-decoration' ' git tag -d A4 && - check_graph --all --simplify-by-decoration <<-\EOF + lib_test_check_graph --all --simplify-by-decoration <<-\EOF * A7 * A6 |\ @@ -114,7 +109,7 @@ test_expect_success 'setup: get rid of decorations on B' ' # Graph with branch B simplified away test_expect_success '--graph --simplify-by-decoration prune branch B' ' - check_graph --simplify-by-decoration --all <<-\EOF + lib_test_check_graph --simplify-by-decoration --all <<-\EOF * A7 * A6 |\ @@ -133,7 +128,7 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' ' ' test_expect_success '--graph --full-history -- bar.txt' ' - check_graph --full-history --all -- bar.txt <<-\EOF + lib_test_check_graph --full-history --all -- bar.txt <<-\EOF * A7 * A6 |\ @@ -148,7 +143,7 @@ test_expect_success '--graph --full-history -- bar.txt' ' ' test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' - check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF + lib_test_check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF * A7 * A6 |\ @@ -161,7 +156,7 @@ test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' ' test_expect_success '--graph -- bar.txt' ' - check_graph --all -- bar.txt <<-\EOF + lib_test_check_graph --all -- bar.txt <<-\EOF * A7 * A5 * A3 @@ -172,7 +167,7 @@ test_expect_success '--graph -- bar.txt' ' ' test_expect_success '--graph --sparse -- bar.txt' ' - check_graph --sparse --all -- bar.txt <<-\EOF + lib_test_check_graph --sparse --all -- bar.txt <<-\EOF * A7 * A6 * A5 @@ -189,7 +184,7 @@ test_expect_success '--graph --sparse -- bar.txt' ' ' test_expect_success '--graph ^C4' ' - check_graph --all ^C4 <<-\EOF + lib_test_check_graph --all ^C4 <<-\EOF * A7 * A6 * A5 @@ -202,7 +197,7 @@ test_expect_success '--graph ^C4' ' ' test_expect_success '--graph ^C3' ' - check_graph --all ^C3 <<-\EOF + lib_test_check_graph --all ^C3 <<-\EOF * A7 * A6 |\ @@ -220,7 +215,7 @@ test_expect_success '--graph ^C3' ' # that important, but this test depends on it. If the ordering ever changes # in the code, we'll need to update this test. test_expect_success '--graph --boundary ^C3' ' - check_graph --boundary --all ^C3 <<-\EOF + lib_test_check_graph --boundary --all ^C3 <<-\EOF * A7 * A6 |\