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