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