1#include "cache.h" 2#include "commit.h" 3#include "color.h" 4#include "graph.h" 5#include "diff.h" 6#include "revision.h" 7 8/* Internal API */ 9 10/* 11 * Output a padding line in the graph. 12 * This is similar to graph_next_line(). However, it is guaranteed to 13 * never print the current commit line. Instead, if the commit line is 14 * next, it will simply output a line of vertical padding, extending the 15 * branch lines downwards, but leaving them otherwise unchanged. 16 */ 17static void graph_padding_line(struct git_graph *graph, struct strbuf *sb); 18 19/* 20 * Print a strbuf to stdout. If the graph is non-NULL, all lines but the 21 * first will be prefixed with the graph output. 22 * 23 * If the strbuf ends with a newline, the output will end after this 24 * newline. A new graph line will not be printed after the final newline. 25 * If the strbuf is empty, no output will be printed. 26 * 27 * Since the first line will not include the graph output, the caller is 28 * responsible for printing this line's graph (perhaps via 29 * graph_show_commit() or graph_show_oneline()) before calling 30 * graph_show_strbuf(). 31 */ 32static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb); 33 34/* 35 * TODO: 36 * - Limit the number of columns, similar to the way gitk does. 37 * If we reach more than a specified number of columns, omit 38 * sections of some columns. 39 */ 40 41struct column { 42 /* 43 * The parent commit of this column. 44 */ 45 struct commit *commit; 46 /* 47 * The color to (optionally) print this column in. This is an 48 * index into column_colors. 49 */ 50 unsigned short color; 51}; 52 53enum graph_state { 54 GRAPH_PADDING, 55 GRAPH_SKIP, 56 GRAPH_PRE_COMMIT, 57 GRAPH_COMMIT, 58 GRAPH_POST_MERGE, 59 GRAPH_COLLAPSING 60}; 61 62/* 63 * The list of available column colors. 64 */ 65static char column_colors[][COLOR_MAXLEN] = { 66 GIT_COLOR_RED, 67 GIT_COLOR_GREEN, 68 GIT_COLOR_YELLOW, 69 GIT_COLOR_BLUE, 70 GIT_COLOR_MAGENTA, 71 GIT_COLOR_CYAN, 72 GIT_COLOR_BOLD_RED, 73 GIT_COLOR_BOLD_GREEN, 74 GIT_COLOR_BOLD_YELLOW, 75 GIT_COLOR_BOLD_BLUE, 76 GIT_COLOR_BOLD_MAGENTA, 77 GIT_COLOR_BOLD_CYAN, 78}; 79 80#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) 81 82static const char *column_get_color_code(const struct column *c) 83{ 84 return column_colors[c->color]; 85} 86 87static void strbuf_write_column(struct strbuf *sb, const struct column *c, 88 char col_char) 89{ 90 if (c->color < COLUMN_COLORS_MAX) 91 strbuf_addstr(sb, column_get_color_code(c)); 92 strbuf_addch(sb, col_char); 93 if (c->color < COLUMN_COLORS_MAX) 94 strbuf_addstr(sb, GIT_COLOR_RESET); 95} 96 97struct git_graph { 98 /* 99 * The commit currently being processed 100 */ 101 struct commit *commit; 102 /* The rev-info used for the current traversal */ 103 struct rev_info *revs; 104 /* 105 * The number of interesting parents that this commit has. 106 * 107 * Note that this is not the same as the actual number of parents. 108 * This count excludes parents that won't be printed in the graph 109 * output, as determined by graph_is_interesting(). 110 */ 111 int num_parents; 112 /* 113 * The width of the graph output for this commit. 114 * All rows for this commit are padded to this width, so that 115 * messages printed after the graph output are aligned. 116 */ 117 int width; 118 /* 119 * The next expansion row to print 120 * when state is GRAPH_PRE_COMMIT 121 */ 122 int expansion_row; 123 /* 124 * The current output state. 125 * This tells us what kind of line graph_next_line() should output. 126 */ 127 enum graph_state state; 128 /* 129 * The output state for the previous line of output. 130 * This is primarily used to determine how the first merge line 131 * should appear, based on the last line of the previous commit. 132 */ 133 enum graph_state prev_state; 134 /* 135 * The index of the column that refers to this commit. 136 * 137 * If none of the incoming columns refer to this commit, 138 * this will be equal to num_columns. 139 */ 140 int commit_index; 141 /* 142 * The commit_index for the previously displayed commit. 143 * 144 * This is used to determine how the first line of a merge 145 * graph output should appear, based on the last line of the 146 * previous commit. 147 */ 148 int prev_commit_index; 149 /* 150 * The maximum number of columns that can be stored in the columns 151 * and new_columns arrays. This is also half the number of entries 152 * that can be stored in the mapping and new_mapping arrays. 153 */ 154 int column_capacity; 155 /* 156 * The number of columns (also called "branch lines" in some places) 157 */ 158 int num_columns; 159 /* 160 * The number of columns in the new_columns array 161 */ 162 int num_new_columns; 163 /* 164 * The number of entries in the mapping array 165 */ 166 int mapping_size; 167 /* 168 * The column state before we output the current commit. 169 */ 170 struct column *columns; 171 /* 172 * The new column state after we output the current commit. 173 * Only valid when state is GRAPH_COLLAPSING. 174 */ 175 struct column *new_columns; 176 /* 177 * An array that tracks the current state of each 178 * character in the output line during state GRAPH_COLLAPSING. 179 * Each entry is -1 if this character is empty, or a non-negative 180 * integer if the character contains a branch line. The value of 181 * the integer indicates the target position for this branch line. 182 * (I.e., this array maps the current column positions to their 183 * desired positions.) 184 * 185 * The maximum capacity of this array is always 186 * sizeof(int) * 2 * column_capacity. 187 */ 188 int *mapping; 189 /* 190 * A temporary array for computing the next mapping state 191 * while we are outputting a mapping line. This is stored as part 192 * of the git_graph simply so we don't have to allocate a new 193 * temporary array each time we have to output a collapsing line. 194 */ 195 int *new_mapping; 196 /* 197 * The current default column color being used. This is 198 * stored as an index into the array column_colors. 199 */ 200 unsigned short default_column_color; 201}; 202 203static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void *data) 204{ 205 struct git_graph *graph = data; 206 static struct strbuf msgbuf = STRBUF_INIT; 207 208 assert(graph); 209 210 strbuf_reset(&msgbuf); 211 graph_padding_line(graph, &msgbuf); 212 return &msgbuf; 213} 214 215struct git_graph *graph_init(struct rev_info *opt) 216{ 217 struct git_graph *graph = xmalloc(sizeof(struct git_graph)); 218 graph->commit = NULL; 219 graph->revs = opt; 220 graph->num_parents = 0; 221 graph->expansion_row = 0; 222 graph->state = GRAPH_PADDING; 223 graph->prev_state = GRAPH_PADDING; 224 graph->commit_index = 0; 225 graph->prev_commit_index = 0; 226 graph->num_columns = 0; 227 graph->num_new_columns = 0; 228 graph->mapping_size = 0; 229 /* 230 * Start the column color at the maximum value, since we'll 231 * always increment it for the first commit we output. 232 * This way we start at 0 for the first commit. 233 */ 234 graph->default_column_color = COLUMN_COLORS_MAX - 1; 235 236 /* 237 * Allocate a reasonably large default number of columns 238 * We'll automatically grow columns later if we need more room. 239 */ 240 graph->column_capacity = 30; 241 graph->columns = xmalloc(sizeof(struct column) * 242 graph->column_capacity); 243 graph->new_columns = xmalloc(sizeof(struct column) * 244 graph->column_capacity); 245 graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity); 246 graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity); 247 248 /* 249 * The diff output prefix callback, with this we can make 250 * all the diff output to align with the graph lines. 251 */ 252 opt->diffopt.output_prefix = diff_output_prefix_callback; 253 opt->diffopt.output_prefix_data = graph; 254 255 return graph; 256} 257 258static void graph_update_state(struct git_graph *graph, enum graph_state s) 259{ 260 graph->prev_state = graph->state; 261 graph->state = s; 262} 263 264static void graph_ensure_capacity(struct git_graph *graph, int num_columns) 265{ 266 if (graph->column_capacity >= num_columns) 267 return; 268 269 do { 270 graph->column_capacity *= 2; 271 } while (graph->column_capacity < num_columns); 272 273 graph->columns = xrealloc(graph->columns, 274 sizeof(struct column) * 275 graph->column_capacity); 276 graph->new_columns = xrealloc(graph->new_columns, 277 sizeof(struct column) * 278 graph->column_capacity); 279 graph->mapping = xrealloc(graph->mapping, 280 sizeof(int) * 2 * graph->column_capacity); 281 graph->new_mapping = xrealloc(graph->new_mapping, 282 sizeof(int) * 2 * graph->column_capacity); 283} 284 285/* 286 * Returns 1 if the commit will be printed in the graph output, 287 * and 0 otherwise. 288 */ 289static int graph_is_interesting(struct git_graph *graph, struct commit *commit) 290{ 291 /* 292 * If revs->boundary is set, commits whose children have 293 * been shown are always interesting, even if they have the 294 * UNINTERESTING or TREESAME flags set. 295 */ 296 if (graph->revs && graph->revs->boundary) { 297 if (commit->object.flags & CHILD_SHOWN) 298 return 1; 299 } 300 301 /* 302 * Otherwise, use get_commit_action() to see if this commit is 303 * interesting 304 */ 305 return get_commit_action(graph->revs, commit) == commit_show; 306} 307 308static struct commit_list *next_interesting_parent(struct git_graph *graph, 309 struct commit_list *orig) 310{ 311 struct commit_list *list; 312 313 /* 314 * If revs->first_parent_only is set, only the first 315 * parent is interesting. None of the others are. 316 */ 317 if (graph->revs->first_parent_only) 318 return NULL; 319 320 /* 321 * Return the next interesting commit after orig 322 */ 323 for (list = orig->next; list; list = list->next) { 324 if (graph_is_interesting(graph, list->item)) 325 return list; 326 } 327 328 return NULL; 329} 330 331static struct commit_list *first_interesting_parent(struct git_graph *graph) 332{ 333 struct commit_list *parents = graph->commit->parents; 334 335 /* 336 * If this commit has no parents, ignore it 337 */ 338 if (!parents) 339 return NULL; 340 341 /* 342 * If the first parent is interesting, return it 343 */ 344 if (graph_is_interesting(graph, parents->item)) 345 return parents; 346 347 /* 348 * Otherwise, call next_interesting_parent() to get 349 * the next interesting parent 350 */ 351 return next_interesting_parent(graph, parents); 352} 353 354static unsigned short graph_get_current_column_color(const struct git_graph *graph) 355{ 356 if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF)) 357 return COLUMN_COLORS_MAX; 358 return graph->default_column_color; 359} 360 361/* 362 * Update the graph's default column color. 363 */ 364static void graph_increment_column_color(struct git_graph *graph) 365{ 366 graph->default_column_color = (graph->default_column_color + 1) % 367 COLUMN_COLORS_MAX; 368} 369 370static unsigned short graph_find_commit_color(const struct git_graph *graph, 371 const struct commit *commit) 372{ 373 int i; 374 for (i = 0; i < graph->num_columns; i++) { 375 if (graph->columns[i].commit == commit) 376 return graph->columns[i].color; 377 } 378 return graph_get_current_column_color(graph); 379} 380 381static void graph_insert_into_new_columns(struct git_graph *graph, 382 struct commit *commit, 383 int *mapping_index) 384{ 385 int i; 386 387 /* 388 * If the commit is already in the new_columns list, we don't need to 389 * add it. Just update the mapping correctly. 390 */ 391 for (i = 0; i < graph->num_new_columns; i++) { 392 if (graph->new_columns[i].commit == commit) { 393 graph->mapping[*mapping_index] = i; 394 *mapping_index += 2; 395 return; 396 } 397 } 398 399 /* 400 * This commit isn't already in new_columns. Add it. 401 */ 402 graph->new_columns[graph->num_new_columns].commit = commit; 403 graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit); 404 graph->mapping[*mapping_index] = graph->num_new_columns; 405 *mapping_index += 2; 406 graph->num_new_columns++; 407} 408 409static void graph_update_width(struct git_graph *graph, 410 int is_commit_in_existing_columns) 411{ 412 /* 413 * Compute the width needed to display the graph for this commit. 414 * This is the maximum width needed for any row. All other rows 415 * will be padded to this width. 416 * 417 * Compute the number of columns in the widest row: 418 * Count each existing column (graph->num_columns), and each new 419 * column added by this commit. 420 */ 421 int max_cols = graph->num_columns + graph->num_parents; 422 423 /* 424 * Even if the current commit has no parents to be printed, it 425 * still takes up a column for itself. 426 */ 427 if (graph->num_parents < 1) 428 max_cols++; 429 430 /* 431 * We added a column for the the current commit as part of 432 * graph->num_parents. If the current commit was already in 433 * graph->columns, then we have double counted it. 434 */ 435 if (is_commit_in_existing_columns) 436 max_cols--; 437 438 /* 439 * Each column takes up 2 spaces 440 */ 441 graph->width = max_cols * 2; 442} 443 444static void graph_update_columns(struct git_graph *graph) 445{ 446 struct commit_list *parent; 447 struct column *tmp_columns; 448 int max_new_columns; 449 int mapping_idx; 450 int i, seen_this, is_commit_in_columns; 451 452 /* 453 * Swap graph->columns with graph->new_columns 454 * graph->columns contains the state for the previous commit, 455 * and new_columns now contains the state for our commit. 456 * 457 * We'll re-use the old columns array as storage to compute the new 458 * columns list for the commit after this one. 459 */ 460 tmp_columns = graph->columns; 461 graph->columns = graph->new_columns; 462 graph->num_columns = graph->num_new_columns; 463 464 graph->new_columns = tmp_columns; 465 graph->num_new_columns = 0; 466 467 /* 468 * Now update new_columns and mapping with the information for the 469 * commit after this one. 470 * 471 * First, make sure we have enough room. At most, there will 472 * be graph->num_columns + graph->num_parents columns for the next 473 * commit. 474 */ 475 max_new_columns = graph->num_columns + graph->num_parents; 476 graph_ensure_capacity(graph, max_new_columns); 477 478 /* 479 * Clear out graph->mapping 480 */ 481 graph->mapping_size = 2 * max_new_columns; 482 for (i = 0; i < graph->mapping_size; i++) 483 graph->mapping[i] = -1; 484 485 /* 486 * Populate graph->new_columns and graph->mapping 487 * 488 * Some of the parents of this commit may already be in 489 * graph->columns. If so, graph->new_columns should only contain a 490 * single entry for each such commit. graph->mapping should 491 * contain information about where each current branch line is 492 * supposed to end up after the collapsing is performed. 493 */ 494 seen_this = 0; 495 mapping_idx = 0; 496 is_commit_in_columns = 1; 497 for (i = 0; i <= graph->num_columns; i++) { 498 struct commit *col_commit; 499 if (i == graph->num_columns) { 500 if (seen_this) 501 break; 502 is_commit_in_columns = 0; 503 col_commit = graph->commit; 504 } else { 505 col_commit = graph->columns[i].commit; 506 } 507 508 if (col_commit == graph->commit) { 509 int old_mapping_idx = mapping_idx; 510 seen_this = 1; 511 graph->commit_index = i; 512 for (parent = first_interesting_parent(graph); 513 parent; 514 parent = next_interesting_parent(graph, parent)) { 515 /* 516 * If this is a merge, or the start of a new 517 * childless column, increment the current 518 * color. 519 */ 520 if (graph->num_parents > 1 || 521 !is_commit_in_columns) { 522 graph_increment_column_color(graph); 523 } 524 graph_insert_into_new_columns(graph, 525 parent->item, 526 &mapping_idx); 527 } 528 /* 529 * We always need to increment mapping_idx by at 530 * least 2, even if it has no interesting parents. 531 * The current commit always takes up at least 2 532 * spaces. 533 */ 534 if (mapping_idx == old_mapping_idx) 535 mapping_idx += 2; 536 } else { 537 graph_insert_into_new_columns(graph, col_commit, 538 &mapping_idx); 539 } 540 } 541 542 /* 543 * Shrink mapping_size to be the minimum necessary 544 */ 545 while (graph->mapping_size > 1 && 546 graph->mapping[graph->mapping_size - 1] < 0) 547 graph->mapping_size--; 548 549 /* 550 * Compute graph->width for this commit 551 */ 552 graph_update_width(graph, is_commit_in_columns); 553} 554 555void graph_update(struct git_graph *graph, struct commit *commit) 556{ 557 struct commit_list *parent; 558 559 /* 560 * Set the new commit 561 */ 562 graph->commit = commit; 563 564 /* 565 * Count how many interesting parents this commit has 566 */ 567 graph->num_parents = 0; 568 for (parent = first_interesting_parent(graph); 569 parent; 570 parent = next_interesting_parent(graph, parent)) 571 { 572 graph->num_parents++; 573 } 574 575 /* 576 * Store the old commit_index in prev_commit_index. 577 * graph_update_columns() will update graph->commit_index for this 578 * commit. 579 */ 580 graph->prev_commit_index = graph->commit_index; 581 582 /* 583 * Call graph_update_columns() to update 584 * columns, new_columns, and mapping. 585 */ 586 graph_update_columns(graph); 587 588 graph->expansion_row = 0; 589 590 /* 591 * Update graph->state. 592 * Note that we don't call graph_update_state() here, since 593 * we don't want to update graph->prev_state. No line for 594 * graph->state was ever printed. 595 * 596 * If the previous commit didn't get to the GRAPH_PADDING state, 597 * it never finished its output. Goto GRAPH_SKIP, to print out 598 * a line to indicate that portion of the graph is missing. 599 * 600 * If there are 3 or more parents, we may need to print extra rows 601 * before the commit, to expand the branch lines around it and make 602 * room for it. We need to do this only if there is a branch row 603 * (or more) to the right of this commit. 604 * 605 * If there are less than 3 parents, we can immediately print the 606 * commit line. 607 */ 608 if (graph->state != GRAPH_PADDING) 609 graph->state = GRAPH_SKIP; 610 else if (graph->num_parents >= 3 && 611 graph->commit_index < (graph->num_columns - 1)) 612 graph->state = GRAPH_PRE_COMMIT; 613 else 614 graph->state = GRAPH_COMMIT; 615} 616 617static int graph_is_mapping_correct(struct git_graph *graph) 618{ 619 int i; 620 621 /* 622 * The mapping is up to date if each entry is at its target, 623 * or is 1 greater than its target. 624 * (If it is 1 greater than the target, '/' will be printed, so it 625 * will look correct on the next row.) 626 */ 627 for (i = 0; i < graph->mapping_size; i++) { 628 int target = graph->mapping[i]; 629 if (target < 0) 630 continue; 631 if (target == (i / 2)) 632 continue; 633 return 0; 634 } 635 636 return 1; 637} 638 639static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, 640 int chars_written) 641{ 642 /* 643 * Add additional spaces to the end of the strbuf, so that all 644 * lines for a particular commit have the same width. 645 * 646 * This way, fields printed to the right of the graph will remain 647 * aligned for the entire commit. 648 */ 649 int extra; 650 if (chars_written >= graph->width) 651 return; 652 653 extra = graph->width - chars_written; 654 strbuf_addf(sb, "%*s", (int) extra, ""); 655} 656 657static void graph_output_padding_line(struct git_graph *graph, 658 struct strbuf *sb) 659{ 660 int i; 661 662 /* 663 * We could conceivable be called with a NULL commit 664 * if our caller has a bug, and invokes graph_next_line() 665 * immediately after graph_init(), without first calling 666 * graph_update(). Return without outputting anything in this 667 * case. 668 */ 669 if (!graph->commit) 670 return; 671 672 /* 673 * Output a padding row, that leaves all branch lines unchanged 674 */ 675 for (i = 0; i < graph->num_new_columns; i++) { 676 strbuf_write_column(sb, &graph->new_columns[i], '|'); 677 strbuf_addch(sb, ' '); 678 } 679 680 graph_pad_horizontally(graph, sb, graph->num_new_columns * 2); 681} 682 683static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) 684{ 685 /* 686 * Output an ellipsis to indicate that a portion 687 * of the graph is missing. 688 */ 689 strbuf_addstr(sb, "..."); 690 graph_pad_horizontally(graph, sb, 3); 691 692 if (graph->num_parents >= 3 && 693 graph->commit_index < (graph->num_columns - 1)) 694 graph_update_state(graph, GRAPH_PRE_COMMIT); 695 else 696 graph_update_state(graph, GRAPH_COMMIT); 697} 698 699static void graph_output_pre_commit_line(struct git_graph *graph, 700 struct strbuf *sb) 701{ 702 int num_expansion_rows; 703 int i, seen_this; 704 int chars_written; 705 706 /* 707 * This function formats a row that increases the space around a commit 708 * with multiple parents, to make room for it. It should only be 709 * called when there are 3 or more parents. 710 * 711 * We need 2 extra rows for every parent over 2. 712 */ 713 assert(graph->num_parents >= 3); 714 num_expansion_rows = (graph->num_parents - 2) * 2; 715 716 /* 717 * graph->expansion_row tracks the current expansion row we are on. 718 * It should be in the range [0, num_expansion_rows - 1] 719 */ 720 assert(0 <= graph->expansion_row && 721 graph->expansion_row < num_expansion_rows); 722 723 /* 724 * Output the row 725 */ 726 seen_this = 0; 727 chars_written = 0; 728 for (i = 0; i < graph->num_columns; i++) { 729 struct column *col = &graph->columns[i]; 730 if (col->commit == graph->commit) { 731 seen_this = 1; 732 strbuf_write_column(sb, col, '|'); 733 strbuf_addf(sb, "%*s", graph->expansion_row, ""); 734 chars_written += 1 + graph->expansion_row; 735 } else if (seen_this && (graph->expansion_row == 0)) { 736 /* 737 * This is the first line of the pre-commit output. 738 * If the previous commit was a merge commit and 739 * ended in the GRAPH_POST_MERGE state, all branch 740 * lines after graph->prev_commit_index were 741 * printed as "\" on the previous line. Continue 742 * to print them as "\" on this line. Otherwise, 743 * print the branch lines as "|". 744 */ 745 if (graph->prev_state == GRAPH_POST_MERGE && 746 graph->prev_commit_index < i) 747 strbuf_write_column(sb, col, '\\'); 748 else 749 strbuf_write_column(sb, col, '|'); 750 chars_written++; 751 } else if (seen_this && (graph->expansion_row > 0)) { 752 strbuf_write_column(sb, col, '\\'); 753 chars_written++; 754 } else { 755 strbuf_write_column(sb, col, '|'); 756 chars_written++; 757 } 758 strbuf_addch(sb, ' '); 759 chars_written++; 760 } 761 762 graph_pad_horizontally(graph, sb, chars_written); 763 764 /* 765 * Increment graph->expansion_row, 766 * and move to state GRAPH_COMMIT if necessary 767 */ 768 graph->expansion_row++; 769 if (graph->expansion_row >= num_expansion_rows) 770 graph_update_state(graph, GRAPH_COMMIT); 771} 772 773static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) 774{ 775 /* 776 * For boundary commits, print 'o' 777 * (We should only see boundary commits when revs->boundary is set.) 778 */ 779 if (graph->commit->object.flags & BOUNDARY) { 780 assert(graph->revs->boundary); 781 strbuf_addch(sb, 'o'); 782 return; 783 } 784 785 /* 786 * If revs->left_right is set, print '<' for commits that 787 * come from the left side, and '>' for commits from the right 788 * side. 789 */ 790 if (graph->revs && graph->revs->left_right) { 791 if (graph->commit->object.flags & SYMMETRIC_LEFT) 792 strbuf_addch(sb, '<'); 793 else 794 strbuf_addch(sb, '>'); 795 return; 796 } 797 798 /* 799 * Print '*' in all other cases 800 */ 801 strbuf_addch(sb, '*'); 802} 803 804/* 805 * Draw an octopus merge and return the number of characters written. 806 */ 807static int graph_draw_octopus_merge(struct git_graph *graph, 808 struct strbuf *sb) 809{ 810 /* 811 * Here dashless_commits represents the number of parents 812 * which don't need to have dashes (because their edges fit 813 * neatly under the commit). 814 */ 815 const int dashless_commits = 2; 816 int col_num, i; 817 int num_dashes = 818 ((graph->num_parents - dashless_commits) * 2) - 1; 819 for (i = 0; i < num_dashes; i++) { 820 col_num = (i / 2) + dashless_commits; 821 strbuf_write_column(sb, &graph->new_columns[col_num], '-'); 822 } 823 col_num = (i / 2) + dashless_commits; 824 strbuf_write_column(sb, &graph->new_columns[col_num], '.'); 825 return num_dashes + 1; 826} 827 828static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) 829{ 830 int seen_this = 0; 831 int i, chars_written; 832 833 /* 834 * Output the row containing this commit 835 * Iterate up to and including graph->num_columns, 836 * since the current commit may not be in any of the existing 837 * columns. (This happens when the current commit doesn't have any 838 * children that we have already processed.) 839 */ 840 seen_this = 0; 841 chars_written = 0; 842 for (i = 0; i <= graph->num_columns; i++) { 843 struct column *col = &graph->columns[i]; 844 struct commit *col_commit; 845 if (i == graph->num_columns) { 846 if (seen_this) 847 break; 848 col_commit = graph->commit; 849 } else { 850 col_commit = graph->columns[i].commit; 851 } 852 853 if (col_commit == graph->commit) { 854 seen_this = 1; 855 graph_output_commit_char(graph, sb); 856 chars_written++; 857 858 if (graph->num_parents > 2) 859 chars_written += graph_draw_octopus_merge(graph, 860 sb); 861 } else if (seen_this && (graph->num_parents > 2)) { 862 strbuf_write_column(sb, col, '\\'); 863 chars_written++; 864 } else if (seen_this && (graph->num_parents == 2)) { 865 /* 866 * This is a 2-way merge commit. 867 * There is no GRAPH_PRE_COMMIT stage for 2-way 868 * merges, so this is the first line of output 869 * for this commit. Check to see what the previous 870 * line of output was. 871 * 872 * If it was GRAPH_POST_MERGE, the branch line 873 * coming into this commit may have been '\', 874 * and not '|' or '/'. If so, output the branch 875 * line as '\' on this line, instead of '|'. This 876 * makes the output look nicer. 877 */ 878 if (graph->prev_state == GRAPH_POST_MERGE && 879 graph->prev_commit_index < i) 880 strbuf_write_column(sb, col, '\\'); 881 else 882 strbuf_write_column(sb, col, '|'); 883 chars_written++; 884 } else { 885 strbuf_write_column(sb, col, '|'); 886 chars_written++; 887 } 888 strbuf_addch(sb, ' '); 889 chars_written++; 890 } 891 892 graph_pad_horizontally(graph, sb, chars_written); 893 894 /* 895 * Update graph->state 896 */ 897 if (graph->num_parents > 1) 898 graph_update_state(graph, GRAPH_POST_MERGE); 899 else if (graph_is_mapping_correct(graph)) 900 graph_update_state(graph, GRAPH_PADDING); 901 else 902 graph_update_state(graph, GRAPH_COLLAPSING); 903} 904 905static struct column *find_new_column_by_commit(struct git_graph *graph, 906 struct commit *commit) 907{ 908 int i; 909 for (i = 0; i < graph->num_new_columns; i++) { 910 if (graph->new_columns[i].commit == commit) 911 return &graph->new_columns[i]; 912 } 913 return NULL; 914} 915 916static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb) 917{ 918 int seen_this = 0; 919 int i, j, chars_written; 920 921 /* 922 * Output the post-merge row 923 */ 924 chars_written = 0; 925 for (i = 0; i <= graph->num_columns; i++) { 926 struct column *col = &graph->columns[i]; 927 struct commit *col_commit; 928 if (i == graph->num_columns) { 929 if (seen_this) 930 break; 931 col_commit = graph->commit; 932 } else { 933 col_commit = col->commit; 934 } 935 936 if (col_commit == graph->commit) { 937 /* 938 * Since the current commit is a merge find 939 * the columns for the parent commits in 940 * new_columns and use those to format the 941 * edges. 942 */ 943 struct commit_list *parents = NULL; 944 struct column *par_column; 945 seen_this = 1; 946 parents = first_interesting_parent(graph); 947 assert(parents); 948 par_column = find_new_column_by_commit(graph, parents->item); 949 assert(par_column); 950 951 strbuf_write_column(sb, par_column, '|'); 952 chars_written++; 953 for (j = 0; j < graph->num_parents - 1; j++) { 954 parents = next_interesting_parent(graph, parents); 955 assert(parents); 956 par_column = find_new_column_by_commit(graph, parents->item); 957 assert(par_column); 958 strbuf_write_column(sb, par_column, '\\'); 959 strbuf_addch(sb, ' '); 960 } 961 chars_written += j * 2; 962 } else if (seen_this) { 963 strbuf_write_column(sb, col, '\\'); 964 strbuf_addch(sb, ' '); 965 chars_written += 2; 966 } else { 967 strbuf_write_column(sb, col, '|'); 968 strbuf_addch(sb, ' '); 969 chars_written += 2; 970 } 971 } 972 973 graph_pad_horizontally(graph, sb, chars_written); 974 975 /* 976 * Update graph->state 977 */ 978 if (graph_is_mapping_correct(graph)) 979 graph_update_state(graph, GRAPH_PADDING); 980 else 981 graph_update_state(graph, GRAPH_COLLAPSING); 982} 983 984static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb) 985{ 986 int i; 987 int *tmp_mapping; 988 short used_horizontal = 0; 989 int horizontal_edge = -1; 990 int horizontal_edge_target = -1; 991 992 /* 993 * Clear out the new_mapping array 994 */ 995 for (i = 0; i < graph->mapping_size; i++) 996 graph->new_mapping[i] = -1; 997 998 for (i = 0; i < graph->mapping_size; i++) { 999 int target = graph->mapping[i];1000 if (target < 0)1001 continue;10021003 /*1004 * Since update_columns() always inserts the leftmost1005 * column first, each branch's target location should1006 * always be either its current location or to the left of1007 * its current location.1008 *1009 * We never have to move branches to the right. This makes1010 * the graph much more legible, since whenever branches1011 * cross, only one is moving directions.1012 */1013 assert(target * 2 <= i);10141015 if (target * 2 == i) {1016 /*1017 * This column is already in the1018 * correct place1019 */1020 assert(graph->new_mapping[i] == -1);1021 graph->new_mapping[i] = target;1022 } else if (graph->new_mapping[i - 1] < 0) {1023 /*1024 * Nothing is to the left.1025 * Move to the left by one1026 */1027 graph->new_mapping[i - 1] = target;1028 /*1029 * If there isn't already an edge moving horizontally1030 * select this one.1031 */1032 if (horizontal_edge == -1) {1033 int j;1034 horizontal_edge = i;1035 horizontal_edge_target = target;1036 /*1037 * The variable target is the index of the graph1038 * column, and therefore target*2+3 is the1039 * actual screen column of the first horizontal1040 * line.1041 */1042 for (j = (target * 2)+3; j < (i - 2); j += 2)1043 graph->new_mapping[j] = target;1044 }1045 } else if (graph->new_mapping[i - 1] == target) {1046 /*1047 * There is a branch line to our left1048 * already, and it is our target. We1049 * combine with this line, since we share1050 * the same parent commit.1051 *1052 * We don't have to add anything to the1053 * output or new_mapping, since the1054 * existing branch line has already taken1055 * care of it.1056 */1057 } else {1058 /*1059 * There is a branch line to our left,1060 * but it isn't our target. We need to1061 * cross over it.1062 *1063 * The space just to the left of this1064 * branch should always be empty.1065 *1066 * The branch to the left of that space1067 * should be our eventual target.1068 */1069 assert(graph->new_mapping[i - 1] > target);1070 assert(graph->new_mapping[i - 2] < 0);1071 assert(graph->new_mapping[i - 3] == target);1072 graph->new_mapping[i - 2] = target;1073 /*1074 * Mark this branch as the horizontal edge to1075 * prevent any other edges from moving1076 * horizontally.1077 */1078 if (horizontal_edge == -1)1079 horizontal_edge = i;1080 }1081 }10821083 /*1084 * The new mapping may be 1 smaller than the old mapping1085 */1086 if (graph->new_mapping[graph->mapping_size - 1] < 0)1087 graph->mapping_size--;10881089 /*1090 * Output out a line based on the new mapping info1091 */1092 for (i = 0; i < graph->mapping_size; i++) {1093 int target = graph->new_mapping[i];1094 if (target < 0)1095 strbuf_addch(sb, ' ');1096 else if (target * 2 == i)1097 strbuf_write_column(sb, &graph->new_columns[target], '|');1098 else if (target == horizontal_edge_target &&1099 i != horizontal_edge - 1) {1100 /*1101 * Set the mappings for all but the1102 * first segment to -1 so that they1103 * won't continue into the next line.1104 */1105 if (i != (target * 2)+3)1106 graph->new_mapping[i] = -1;1107 used_horizontal = 1;1108 strbuf_write_column(sb, &graph->new_columns[target], '_');1109 } else {1110 if (used_horizontal && i < horizontal_edge)1111 graph->new_mapping[i] = -1;1112 strbuf_write_column(sb, &graph->new_columns[target], '/');11131114 }1115 }11161117 graph_pad_horizontally(graph, sb, graph->mapping_size);11181119 /*1120 * Swap mapping and new_mapping1121 */1122 tmp_mapping = graph->mapping;1123 graph->mapping = graph->new_mapping;1124 graph->new_mapping = tmp_mapping;11251126 /*1127 * If graph->mapping indicates that all of the branch lines1128 * are already in the correct positions, we are done.1129 * Otherwise, we need to collapse some branch lines together.1130 */1131 if (graph_is_mapping_correct(graph))1132 graph_update_state(graph, GRAPH_PADDING);1133}11341135int graph_next_line(struct git_graph *graph, struct strbuf *sb)1136{1137 switch (graph->state) {1138 case GRAPH_PADDING:1139 graph_output_padding_line(graph, sb);1140 return 0;1141 case GRAPH_SKIP:1142 graph_output_skip_line(graph, sb);1143 return 0;1144 case GRAPH_PRE_COMMIT:1145 graph_output_pre_commit_line(graph, sb);1146 return 0;1147 case GRAPH_COMMIT:1148 graph_output_commit_line(graph, sb);1149 return 1;1150 case GRAPH_POST_MERGE:1151 graph_output_post_merge_line(graph, sb);1152 return 0;1153 case GRAPH_COLLAPSING:1154 graph_output_collapsing_line(graph, sb);1155 return 0;1156 }11571158 assert(0);1159 return 0;1160}11611162static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)1163{1164 int i, j;11651166 if (graph->state != GRAPH_COMMIT) {1167 graph_next_line(graph, sb);1168 return;1169 }11701171 /*1172 * Output the row containing this commit1173 * Iterate up to and including graph->num_columns,1174 * since the current commit may not be in any of the existing1175 * columns. (This happens when the current commit doesn't have any1176 * children that we have already processed.)1177 */1178 for (i = 0; i < graph->num_columns; i++) {1179 struct column *col = &graph->columns[i];1180 struct commit *col_commit = col->commit;1181 if (col_commit == graph->commit) {1182 strbuf_write_column(sb, col, '|');11831184 if (graph->num_parents < 3)1185 strbuf_addch(sb, ' ');1186 else {1187 int num_spaces = ((graph->num_parents - 2) * 2);1188 for (j = 0; j < num_spaces; j++)1189 strbuf_addch(sb, ' ');1190 }1191 } else {1192 strbuf_write_column(sb, col, '|');1193 strbuf_addch(sb, ' ');1194 }1195 }11961197 graph_pad_horizontally(graph, sb, graph->num_columns);11981199 /*1200 * Update graph->prev_state since we have output a padding line1201 */1202 graph->prev_state = GRAPH_PADDING;1203}12041205int graph_is_commit_finished(struct git_graph const *graph)1206{1207 return (graph->state == GRAPH_PADDING);1208}12091210void graph_show_commit(struct git_graph *graph)1211{1212 struct strbuf msgbuf = STRBUF_INIT;1213 int shown_commit_line = 0;12141215 if (!graph)1216 return;12171218 while (!shown_commit_line) {1219 shown_commit_line = graph_next_line(graph, &msgbuf);1220 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);1221 if (!shown_commit_line)1222 putchar('\n');1223 strbuf_setlen(&msgbuf, 0);1224 }12251226 strbuf_release(&msgbuf);1227}12281229void graph_show_oneline(struct git_graph *graph)1230{1231 struct strbuf msgbuf = STRBUF_INIT;12321233 if (!graph)1234 return;12351236 graph_next_line(graph, &msgbuf);1237 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);1238 strbuf_release(&msgbuf);1239}12401241void graph_show_padding(struct git_graph *graph)1242{1243 struct strbuf msgbuf = STRBUF_INIT;12441245 if (!graph)1246 return;12471248 graph_padding_line(graph, &msgbuf);1249 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);1250 strbuf_release(&msgbuf);1251}12521253int graph_show_remainder(struct git_graph *graph)1254{1255 struct strbuf msgbuf = STRBUF_INIT;1256 int shown = 0;12571258 if (!graph)1259 return 0;12601261 if (graph_is_commit_finished(graph))1262 return 0;12631264 for (;;) {1265 graph_next_line(graph, &msgbuf);1266 fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);1267 strbuf_setlen(&msgbuf, 0);1268 shown = 1;12691270 if (!graph_is_commit_finished(graph))1271 putchar('\n');1272 else1273 break;1274 }1275 strbuf_release(&msgbuf);12761277 return shown;1278}127912801281static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)1282{1283 char *p;12841285 if (!graph) {1286 fwrite(sb->buf, sizeof(char), sb->len, stdout);1287 return;1288 }12891290 /*1291 * Print the strbuf line by line,1292 * and display the graph info before each line but the first.1293 */1294 p = sb->buf;1295 while (p) {1296 size_t len;1297 char *next_p = strchr(p, '\n');1298 if (next_p) {1299 next_p++;1300 len = next_p - p;1301 } else {1302 len = (sb->buf + sb->len) - p;1303 }1304 fwrite(p, sizeof(char), len, stdout);1305 if (next_p && *next_p != '\0')1306 graph_show_oneline(graph);1307 p = next_p;1308 }1309}13101311void graph_show_commit_msg(struct git_graph *graph,1312 struct strbuf const *sb)1313{1314 int newline_terminated;13151316 if (!graph) {1317 /*1318 * If there's no graph, just print the message buffer.1319 *1320 * The message buffer for CMIT_FMT_ONELINE and1321 * CMIT_FMT_USERFORMAT are already missing a terminating1322 * newline. All of the other formats should have it.1323 */1324 fwrite(sb->buf, sizeof(char), sb->len, stdout);1325 return;1326 }13271328 newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');13291330 /*1331 * Show the commit message1332 */1333 graph_show_strbuf(graph, sb);13341335 /*1336 * If there is more output needed for this commit, show it now1337 */1338 if (!graph_is_commit_finished(graph)) {1339 /*1340 * If sb doesn't have a terminating newline, print one now,1341 * so we can start the remainder of the graph output on a1342 * new line.1343 */1344 if (!newline_terminated)1345 putchar('\n');13461347 graph_show_remainder(graph);13481349 /*1350 * If sb ends with a newline, our output should too.1351 */1352 if (newline_terminated)1353 putchar('\n');1354 }1355}