graph.c 34.3 KB
Newer Older
A
Adam Simpkins 已提交
1 2
#include "cache.h"
#include "commit.h"
3
#include "color.h"
A
Adam Simpkins 已提交
4 5 6 7
#include "graph.h"
#include "diff.h"
#include "revision.h"

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/* Internal API */

/*
 * Output the next line for a graph.
 * This formats the next graph line into the specified strbuf.  It is not
 * terminated with a newline.
 *
 * Returns 1 if the line includes the current commit, and 0 otherwise.
 * graph_next_line() will return 1 exactly once for each time
 * graph_update() is called.
 */
static int graph_next_line(struct git_graph *graph, struct strbuf *sb);

/*
 * Output a padding line in the graph.
 * This is similar to graph_next_line().  However, it is guaranteed to
 * never print the current commit line.  Instead, if the commit line is
 * next, it will simply output a line of vertical padding, extending the
 * branch lines downwards, but leaving them otherwise unchanged.
 */
static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);

/*
 * Print a strbuf to stdout.  If the graph is non-NULL, all lines but the
 * first will be prefixed with the graph output.
 *
 * If the strbuf ends with a newline, the output will end after this
 * newline.  A new graph line will not be printed after the final newline.
 * If the strbuf is empty, no output will be printed.
 *
M
Mike Ralphson 已提交
38
 * Since the first line will not include the graph output, the caller is
39 40 41 42 43 44
 * responsible for printing this line's graph (perhaps via
 * graph_show_commit() or graph_show_oneline()) before calling
 * graph_show_strbuf().
 */
static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);

A
Adam Simpkins 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57
/*
 * TODO:
 * - Limit the number of columns, similar to the way gitk does.
 *   If we reach more than a specified number of columns, omit
 *   sections of some columns.
 */

struct column {
	/*
	 * The parent commit of this column.
	 */
	struct commit *commit;
	/*
58 59
	 * The color to (optionally) print this column in.  This is an
	 * index into column_colors.
A
Adam Simpkins 已提交
60
	 */
61
	unsigned short color;
A
Adam Simpkins 已提交
62 63 64 65 66 67 68 69 70 71 72
};

enum graph_state {
	GRAPH_PADDING,
	GRAPH_SKIP,
	GRAPH_PRE_COMMIT,
	GRAPH_COMMIT,
	GRAPH_POST_MERGE,
	GRAPH_COLLAPSING
};

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
/*
 * The list of available column colors.
 */
static char column_colors[][COLOR_MAXLEN] = {
	GIT_COLOR_RED,
	GIT_COLOR_GREEN,
	GIT_COLOR_YELLOW,
	GIT_COLOR_BLUE,
	GIT_COLOR_MAGENTA,
	GIT_COLOR_CYAN,
	GIT_COLOR_BOLD GIT_COLOR_RED,
	GIT_COLOR_BOLD GIT_COLOR_GREEN,
	GIT_COLOR_BOLD GIT_COLOR_YELLOW,
	GIT_COLOR_BOLD GIT_COLOR_BLUE,
	GIT_COLOR_BOLD GIT_COLOR_MAGENTA,
	GIT_COLOR_BOLD GIT_COLOR_CYAN,
};

#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))

static const char *column_get_color_code(const struct column *c)
{
	return column_colors[c->color];
}

static void strbuf_write_column(struct strbuf *sb, const struct column *c,
				char col_char)
{
	if (c->color < COLUMN_COLORS_MAX)
		strbuf_addstr(sb, column_get_color_code(c));
	strbuf_addch(sb, col_char);
	if (c->color < COLUMN_COLORS_MAX)
		strbuf_addstr(sb, GIT_COLOR_RESET);
}

A
Adam Simpkins 已提交
108 109 110 111 112
struct git_graph {
	/*
	 * The commit currently being processed
	 */
	struct commit *commit;
113 114
	/* The rev-info used for the current traversal */
	struct rev_info *revs;
A
Adam Simpkins 已提交
115
	/*
116 117 118 119 120
	 * The number of interesting parents that this commit has.
	 *
	 * Note that this is not the same as the actual number of parents.
	 * This count excludes parents that won't be printed in the graph
	 * output, as determined by graph_is_interesting().
A
Adam Simpkins 已提交
121 122
	 */
	int num_parents;
123 124 125 126 127 128
	/*
	 * The width of the graph output for this commit.
	 * All rows for this commit are padded to this width, so that
	 * messages printed after the graph output are aligned.
	 */
	int width;
A
Adam Simpkins 已提交
129 130 131 132 133 134 135 136 137 138
	/*
	 * The next expansion row to print
	 * when state is GRAPH_PRE_COMMIT
	 */
	int expansion_row;
	/*
	 * The current output state.
	 * This tells us what kind of line graph_next_line() should output.
	 */
	enum graph_state state;
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
	/*
	 * The output state for the previous line of output.
	 * This is primarily used to determine how the first merge line
	 * should appear, based on the last line of the previous commit.
	 */
	enum graph_state prev_state;
	/*
	 * The index of the column that refers to this commit.
	 *
	 * If none of the incoming columns refer to this commit,
	 * this will be equal to num_columns.
	 */
	int commit_index;
	/*
	 * The commit_index for the previously displayed commit.
	 *
	 * This is used to determine how the first line of a merge
	 * graph output should appear, based on the last line of the
	 * previous commit.
	 */
	int prev_commit_index;
A
Adam Simpkins 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	/*
	 * The maximum number of columns that can be stored in the columns
	 * and new_columns arrays.  This is also half the number of entries
	 * that can be stored in the mapping and new_mapping arrays.
	 */
	int column_capacity;
	/*
	 * The number of columns (also called "branch lines" in some places)
	 */
	int num_columns;
	/*
	 * The number of columns in the new_columns array
	 */
	int num_new_columns;
	/*
	 * The number of entries in the mapping array
	 */
	int mapping_size;
	/*
	 * The column state before we output the current commit.
	 */
	struct column *columns;
	/*
	 * The new column state after we output the current commit.
	 * Only valid when state is GRAPH_COLLAPSING.
	 */
	struct column *new_columns;
	/*
	 * An array that tracks the current state of each
	 * character in the output line during state GRAPH_COLLAPSING.
	 * Each entry is -1 if this character is empty, or a non-negative
	 * integer if the character contains a branch line.  The value of
	 * the integer indicates the target position for this branch line.
	 * (I.e., this array maps the current column positions to their
	 * desired positions.)
	 *
	 * The maximum capacity of this array is always
	 * sizeof(int) * 2 * column_capacity.
	 */
	int *mapping;
	/*
	 * A temporary array for computing the next mapping state
	 * while we are outputting a mapping line.  This is stored as part
	 * of the git_graph simply so we don't have to allocate a new
	 * temporary array each time we have to output a collapsing line.
	 */
	int *new_mapping;
207 208 209 210 211
	/*
	 * The current default column color being used.  This is
	 * stored as an index into the array column_colors.
	 */
	unsigned short default_column_color;
A
Adam Simpkins 已提交
212 213
};

214
struct git_graph *graph_init(struct rev_info *opt)
A
Adam Simpkins 已提交
215 216 217
{
	struct git_graph *graph = xmalloc(sizeof(struct git_graph));
	graph->commit = NULL;
218
	graph->revs = opt;
A
Adam Simpkins 已提交
219 220 221
	graph->num_parents = 0;
	graph->expansion_row = 0;
	graph->state = GRAPH_PADDING;
222 223 224
	graph->prev_state = GRAPH_PADDING;
	graph->commit_index = 0;
	graph->prev_commit_index = 0;
A
Adam Simpkins 已提交
225 226 227
	graph->num_columns = 0;
	graph->num_new_columns = 0;
	graph->mapping_size = 0;
228
	graph->default_column_color = 0;
A
Adam Simpkins 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

	/*
	 * Allocate a reasonably large default number of columns
	 * We'll automatically grow columns later if we need more room.
	 */
	graph->column_capacity = 30;
	graph->columns = xmalloc(sizeof(struct column) *
				 graph->column_capacity);
	graph->new_columns = xmalloc(sizeof(struct column) *
				     graph->column_capacity);
	graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
	graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);

	return graph;
}

245 246 247 248 249 250
static void graph_update_state(struct git_graph *graph, enum graph_state s)
{
	graph->prev_state = graph->state;
	graph->state = s;
}

A
Adam Simpkins 已提交
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
{
	if (graph->column_capacity >= num_columns)
		return;

	do {
		graph->column_capacity *= 2;
	} while (graph->column_capacity < num_columns);

	graph->columns = xrealloc(graph->columns,
				  sizeof(struct column) *
				  graph->column_capacity);
	graph->new_columns = xrealloc(graph->new_columns,
				      sizeof(struct column) *
				      graph->column_capacity);
	graph->mapping = xrealloc(graph->mapping,
				  sizeof(int) * 2 * graph->column_capacity);
	graph->new_mapping = xrealloc(graph->new_mapping,
				      sizeof(int) * 2 * graph->column_capacity);
}

272 273 274 275
/*
 * Returns 1 if the commit will be printed in the graph output,
 * and 0 otherwise.
 */
276
static int graph_is_interesting(struct git_graph *graph, struct commit *commit)
277
{
278 279 280 281 282 283
	/*
	 * If revs->boundary is set, commits whose children have
	 * been shown are always interesting, even if they have the
	 * UNINTERESTING or TREESAME flags set.
	 */
	if (graph->revs && graph->revs->boundary) {
284
		if (commit->object.flags & CHILD_SHOWN)
285 286 287
			return 1;
	}

288 289 290 291 292 293
	/*
	 * Uninteresting and pruned commits won't be printed
	 */
	return (commit->object.flags & (UNINTERESTING | TREESAME)) ? 0 : 1;
}

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
static struct commit_list *next_interesting_parent(struct git_graph *graph,
						   struct commit_list *orig)
{
	struct commit_list *list;

	/*
	 * If revs->first_parent_only is set, only the first
	 * parent is interesting.  None of the others are.
	 */
	if (graph->revs->first_parent_only)
		return NULL;

	/*
	 * Return the next interesting commit after orig
	 */
	for (list = orig->next; list; list = list->next) {
		if (graph_is_interesting(graph, list->item))
			return list;
	}

	return NULL;
}

static struct commit_list *first_interesting_parent(struct git_graph *graph)
{
	struct commit_list *parents = graph->commit->parents;

	/*
	 * If this commit has no parents, ignore it
	 */
	if (!parents)
		return NULL;

	/*
	 * If the first parent is interesting, return it
	 */
	if (graph_is_interesting(graph, parents->item))
		return parents;

	/*
	 * Otherwise, call next_interesting_parent() to get
	 * the next interesting parent
	 */
	return next_interesting_parent(graph, parents);
}

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
static unsigned short graph_get_current_column_color(const struct git_graph *graph)
{
	if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
		return COLUMN_COLORS_MAX;
	return graph->default_column_color;
}

/*
 * Update the graph's default column color.
 */
static void graph_increment_column_color(struct git_graph *graph)
{
	graph->default_column_color = (graph->default_column_color + 1) %
		COLUMN_COLORS_MAX;
}

static unsigned short graph_find_commit_color(const struct git_graph *graph,
					      const struct commit *commit)
{
	int i;
	for (i = 0; i < graph->num_columns; i++) {
		if (graph->columns[i].commit == commit)
			return graph->columns[i].color;
	}
	return graph_get_current_column_color(graph);
}

A
Adam Simpkins 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
static void graph_insert_into_new_columns(struct git_graph *graph,
					  struct commit *commit,
					  int *mapping_index)
{
	int i;

	/*
	 * If the commit is already in the new_columns list, we don't need to
	 * add it.  Just update the mapping correctly.
	 */
	for (i = 0; i < graph->num_new_columns; i++) {
		if (graph->new_columns[i].commit == commit) {
			graph->mapping[*mapping_index] = i;
			*mapping_index += 2;
			return;
		}
	}

	/*
	 * This commit isn't already in new_columns.  Add it.
	 */
	graph->new_columns[graph->num_new_columns].commit = commit;
389
	graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);
A
Adam Simpkins 已提交
390 391 392 393 394
	graph->mapping[*mapping_index] = graph->num_new_columns;
	*mapping_index += 2;
	graph->num_new_columns++;
}

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
static void graph_update_width(struct git_graph *graph,
			       int is_commit_in_existing_columns)
{
	/*
	 * Compute the width needed to display the graph for this commit.
	 * This is the maximum width needed for any row.  All other rows
	 * will be padded to this width.
	 *
	 * Compute the number of columns in the widest row:
	 * Count each existing column (graph->num_columns), and each new
	 * column added by this commit.
	 */
	int max_cols = graph->num_columns + graph->num_parents;

	/*
410 411
	 * Even if the current commit has no parents to be printed, it
	 * still takes up a column for itself.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
	 */
	if (graph->num_parents < 1)
		max_cols++;

	/*
	 * We added a column for the the current commit as part of
	 * graph->num_parents.  If the current commit was already in
	 * graph->columns, then we have double counted it.
	 */
	if (is_commit_in_existing_columns)
		max_cols--;

	/*
	 * Each column takes up 2 spaces
	 */
	graph->width = max_cols * 2;
}

A
Adam Simpkins 已提交
430 431 432 433 434 435
static void graph_update_columns(struct git_graph *graph)
{
	struct commit_list *parent;
	struct column *tmp_columns;
	int max_new_columns;
	int mapping_idx;
436
	int i, seen_this, is_commit_in_columns;
A
Adam Simpkins 已提交
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481

	/*
	 * Swap graph->columns with graph->new_columns
	 * graph->columns contains the state for the previous commit,
	 * and new_columns now contains the state for our commit.
	 *
	 * We'll re-use the old columns array as storage to compute the new
	 * columns list for the commit after this one.
	 */
	tmp_columns = graph->columns;
	graph->columns = graph->new_columns;
	graph->num_columns = graph->num_new_columns;

	graph->new_columns = tmp_columns;
	graph->num_new_columns = 0;

	/*
	 * Now update new_columns and mapping with the information for the
	 * commit after this one.
	 *
	 * First, make sure we have enough room.  At most, there will
	 * be graph->num_columns + graph->num_parents columns for the next
	 * commit.
	 */
	max_new_columns = graph->num_columns + graph->num_parents;
	graph_ensure_capacity(graph, max_new_columns);

	/*
	 * Clear out graph->mapping
	 */
	graph->mapping_size = 2 * max_new_columns;
	for (i = 0; i < graph->mapping_size; i++)
		graph->mapping[i] = -1;

	/*
	 * Populate graph->new_columns and graph->mapping
	 *
	 * Some of the parents of this commit may already be in
	 * graph->columns.  If so, graph->new_columns should only contain a
	 * single entry for each such commit.  graph->mapping should
	 * contain information about where each current branch line is
	 * supposed to end up after the collapsing is performed.
	 */
	seen_this = 0;
	mapping_idx = 0;
482
	is_commit_in_columns = 1;
A
Adam Simpkins 已提交
483 484 485 486 487
	for (i = 0; i <= graph->num_columns; i++) {
		struct commit *col_commit;
		if (i == graph->num_columns) {
			if (seen_this)
				break;
488
			is_commit_in_columns = 0;
A
Adam Simpkins 已提交
489 490 491 492 493 494
			col_commit = graph->commit;
		} else {
			col_commit = graph->columns[i].commit;
		}

		if (col_commit == graph->commit) {
495
			int old_mapping_idx = mapping_idx;
A
Adam Simpkins 已提交
496
			seen_this = 1;
497
			graph->commit_index = i;
498
			for (parent = first_interesting_parent(graph);
A
Adam Simpkins 已提交
499
			     parent;
500
			     parent = next_interesting_parent(graph, parent)) {
501 502 503 504 505 506
				/*
				 * If this is a merge increment the current
				 * color.
				 */
				if (graph->num_parents > 1)
					graph_increment_column_color(graph);
A
Adam Simpkins 已提交
507 508 509 510
				graph_insert_into_new_columns(graph,
							      parent->item,
							      &mapping_idx);
			}
511 512 513 514 515 516 517 518
			/*
			 * We always need to increment mapping_idx by at
			 * least 2, even if it has no interesting parents.
			 * The current commit always takes up at least 2
			 * spaces.
			 */
			if (mapping_idx == old_mapping_idx)
				mapping_idx += 2;
A
Adam Simpkins 已提交
519 520 521 522 523 524 525 526 527 528 529 530
		} else {
			graph_insert_into_new_columns(graph, col_commit,
						      &mapping_idx);
		}
	}

	/*
	 * Shrink mapping_size to be the minimum necessary
	 */
	while (graph->mapping_size > 1 &&
	       graph->mapping[graph->mapping_size - 1] < 0)
		graph->mapping_size--;
531 532 533 534 535

	/*
	 * Compute graph->width for this commit
	 */
	graph_update_width(graph, is_commit_in_columns);
A
Adam Simpkins 已提交
536 537 538 539 540 541 542 543 544 545 546 547
}

void graph_update(struct git_graph *graph, struct commit *commit)
{
	struct commit_list *parent;

	/*
	 * Set the new commit
	 */
	graph->commit = commit;

	/*
548
	 * Count how many interesting parents this commit has
A
Adam Simpkins 已提交
549 550
	 */
	graph->num_parents = 0;
551 552 553 554 555
	for (parent = first_interesting_parent(graph);
	     parent;
	     parent = next_interesting_parent(graph, parent))
	{
		graph->num_parents++;
556
	}
A
Adam Simpkins 已提交
557

558 559 560 561 562 563 564
	/*
	 * Store the old commit_index in prev_commit_index.
	 * graph_update_columns() will update graph->commit_index for this
	 * commit.
	 */
	graph->prev_commit_index = graph->commit_index;

A
Adam Simpkins 已提交
565 566 567 568 569 570 571 572 573 574
	/*
	 * Call graph_update_columns() to update
	 * columns, new_columns, and mapping.
	 */
	graph_update_columns(graph);

	graph->expansion_row = 0;

	/*
	 * Update graph->state.
575 576 577
	 * Note that we don't call graph_update_state() here, since
	 * we don't want to update graph->prev_state.  No line for
	 * graph->state was ever printed.
A
Adam Simpkins 已提交
578 579 580 581 582
	 *
	 * If the previous commit didn't get to the GRAPH_PADDING state,
	 * it never finished its output.  Goto GRAPH_SKIP, to print out
	 * a line to indicate that portion of the graph is missing.
	 *
583 584 585 586
	 * If there are 3 or more parents, we may need to print extra rows
	 * before the commit, to expand the branch lines around it and make
	 * room for it.  We need to do this only if there is a branch row
	 * (or more) to the right of this commit.
A
Adam Simpkins 已提交
587 588 589 590 591 592
	 *
	 * If there are less than 3 parents, we can immediately print the
	 * commit line.
	 */
	if (graph->state != GRAPH_PADDING)
		graph->state = GRAPH_SKIP;
593 594
	else if (graph->num_parents >= 3 &&
		 graph->commit_index < (graph->num_columns - 1))
A
Adam Simpkins 已提交
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
		graph->state = GRAPH_PRE_COMMIT;
	else
		graph->state = GRAPH_COMMIT;
}

static int graph_is_mapping_correct(struct git_graph *graph)
{
	int i;

	/*
	 * The mapping is up to date if each entry is at its target,
	 * or is 1 greater than its target.
	 * (If it is 1 greater than the target, '/' will be printed, so it
	 * will look correct on the next row.)
	 */
	for (i = 0; i < graph->mapping_size; i++) {
		int target = graph->mapping[i];
		if (target < 0)
			continue;
		if (target == (i / 2))
			continue;
		return 0;
	}

	return 1;
}

622 623
static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
				   int chars_written)
A
Adam Simpkins 已提交
624 625 626 627 628 629 630 631
{
	/*
	 * Add additional spaces to the end of the strbuf, so that all
	 * lines for a particular commit have the same width.
	 *
	 * This way, fields printed to the right of the graph will remain
	 * aligned for the entire commit.
	 */
632
	int extra;
633
	if (chars_written >= graph->width)
A
Adam Simpkins 已提交
634 635
		return;

636
	extra = graph->width - chars_written;
A
Adam Simpkins 已提交
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
	strbuf_addf(sb, "%*s", (int) extra, "");
}

static void graph_output_padding_line(struct git_graph *graph,
				      struct strbuf *sb)
{
	int i;

	/*
	 * We could conceivable be called with a NULL commit
	 * if our caller has a bug, and invokes graph_next_line()
	 * immediately after graph_init(), without first calling
	 * graph_update().  Return without outputting anything in this
	 * case.
	 */
	if (!graph->commit)
		return;

	/*
	 * Output a padding row, that leaves all branch lines unchanged
	 */
	for (i = 0; i < graph->num_new_columns; i++) {
659 660
		strbuf_write_column(sb, &graph->new_columns[i], '|');
		strbuf_addch(sb, ' ');
A
Adam Simpkins 已提交
661 662
	}

663
	graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
A
Adam Simpkins 已提交
664 665 666 667 668 669 670 671 672
}

static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
{
	/*
	 * Output an ellipsis to indicate that a portion
	 * of the graph is missing.
	 */
	strbuf_addstr(sb, "...");
673
	graph_pad_horizontally(graph, sb, 3);
A
Adam Simpkins 已提交
674

675 676
	if (graph->num_parents >= 3 &&
	    graph->commit_index < (graph->num_columns - 1))
677
		graph_update_state(graph, GRAPH_PRE_COMMIT);
A
Adam Simpkins 已提交
678
	else
679
		graph_update_state(graph, GRAPH_COMMIT);
A
Adam Simpkins 已提交
680 681 682 683 684 685 686
}

static void graph_output_pre_commit_line(struct git_graph *graph,
					 struct strbuf *sb)
{
	int num_expansion_rows;
	int i, seen_this;
687
	int chars_written;
A
Adam Simpkins 已提交
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709

	/*
	 * This function formats a row that increases the space around a commit
	 * with multiple parents, to make room for it.  It should only be
	 * called when there are 3 or more parents.
	 *
	 * We need 2 extra rows for every parent over 2.
	 */
	assert(graph->num_parents >= 3);
	num_expansion_rows = (graph->num_parents - 2) * 2;

	/*
	 * graph->expansion_row tracks the current expansion row we are on.
	 * It should be in the range [0, num_expansion_rows - 1]
	 */
	assert(0 <= graph->expansion_row &&
	       graph->expansion_row < num_expansion_rows);

	/*
	 * Output the row
	 */
	seen_this = 0;
710
	chars_written = 0;
A
Adam Simpkins 已提交
711 712 713 714
	for (i = 0; i < graph->num_columns; i++) {
		struct column *col = &graph->columns[i];
		if (col->commit == graph->commit) {
			seen_this = 1;
715
			strbuf_write_column(sb, col, '|');
716 717
			strbuf_addf(sb, "%*s", graph->expansion_row, "");
			chars_written += 1 + graph->expansion_row;
718 719 720 721 722 723 724 725 726 727 728 729
		} else if (seen_this && (graph->expansion_row == 0)) {
			/*
			 * This is the first line of the pre-commit output.
			 * If the previous commit was a merge commit and
			 * ended in the GRAPH_POST_MERGE state, all branch
			 * lines after graph->prev_commit_index were
			 * printed as "\" on the previous line.  Continue
			 * to print them as "\" on this line.  Otherwise,
			 * print the branch lines as "|".
			 */
			if (graph->prev_state == GRAPH_POST_MERGE &&
			    graph->prev_commit_index < i)
730
				strbuf_write_column(sb, col, '\\');
731
			else
732 733
				strbuf_write_column(sb, col, '|');
			chars_written++;
734
		} else if (seen_this && (graph->expansion_row > 0)) {
735 736
			strbuf_write_column(sb, col, '\\');
			chars_written++;
A
Adam Simpkins 已提交
737
		} else {
738 739
			strbuf_write_column(sb, col, '|');
			chars_written++;
A
Adam Simpkins 已提交
740
		}
741 742
		strbuf_addch(sb, ' ');
		chars_written++;
A
Adam Simpkins 已提交
743 744
	}

745
	graph_pad_horizontally(graph, sb, chars_written);
A
Adam Simpkins 已提交
746 747 748 749 750 751 752

	/*
	 * Increment graph->expansion_row,
	 * and move to state GRAPH_COMMIT if necessary
	 */
	graph->expansion_row++;
	if (graph->expansion_row >= num_expansion_rows)
753
		graph_update_state(graph, GRAPH_COMMIT);
A
Adam Simpkins 已提交
754 755
}

756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
{
	/*
	 * For boundary commits, print 'o'
	 * (We should only see boundary commits when revs->boundary is set.)
	 */
	if (graph->commit->object.flags & BOUNDARY) {
		assert(graph->revs->boundary);
		strbuf_addch(sb, 'o');
		return;
	}

	/*
	 * If revs->left_right is set, print '<' for commits that
	 * come from the left side, and '>' for commits from the right
	 * side.
	 */
	if (graph->revs && graph->revs->left_right) {
		if (graph->commit->object.flags & SYMMETRIC_LEFT)
			strbuf_addch(sb, '<');
		else
			strbuf_addch(sb, '>');
		return;
	}

	/*
	 * Print '*' in all other cases
	 */
	strbuf_addch(sb, '*');
}

787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
/*
 * Draw an octopus merge and return the number of characters written.
 */
static int graph_draw_octopus_merge(struct git_graph *graph,
				    struct strbuf *sb)
{
	/*
	 * Here dashless_commits represents the number of parents
	 * which don't need to have dashes (because their edges fit
	 * neatly under the commit).
	 */
	const int dashless_commits = 2;
	int col_num, i;
	int num_dashes =
		((graph->num_parents - dashless_commits) * 2) - 1;
	for (i = 0; i < num_dashes; i++) {
		col_num = (i / 2) + dashless_commits;
		strbuf_write_column(sb, &graph->new_columns[col_num], '-');
	}
	col_num = (i / 2) + dashless_commits;
	strbuf_write_column(sb, &graph->new_columns[col_num], '.');
	return num_dashes + 1;
}

811
static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
A
Adam Simpkins 已提交
812 813
{
	int seen_this = 0;
814
	int i, chars_written;
A
Adam Simpkins 已提交
815 816 817 818 819 820 821 822 823

	/*
	 * Output the row containing this commit
	 * Iterate up to and including graph->num_columns,
	 * since the current commit may not be in any of the existing
	 * columns.  (This happens when the current commit doesn't have any
	 * children that we have already processed.)
	 */
	seen_this = 0;
824
	chars_written = 0;
A
Adam Simpkins 已提交
825
	for (i = 0; i <= graph->num_columns; i++) {
826
		struct column *col = &graph->columns[i];
A
Adam Simpkins 已提交
827 828 829 830 831 832 833 834 835 836 837
		struct commit *col_commit;
		if (i == graph->num_columns) {
			if (seen_this)
				break;
			col_commit = graph->commit;
		} else {
			col_commit = graph->columns[i].commit;
		}

		if (col_commit == graph->commit) {
			seen_this = 1;
838
			graph_output_commit_char(graph, sb);
839
			chars_written++;
A
Adam Simpkins 已提交
840

841
			if (graph->num_parents > 2)
842 843
				chars_written += graph_draw_octopus_merge(graph,
									  sb);
844
		} else if (seen_this && (graph->num_parents > 2)) {
845 846
			strbuf_write_column(sb, col, '\\');
			chars_written++;
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
		} else if (seen_this && (graph->num_parents == 2)) {
			/*
			 * This is a 2-way merge commit.
			 * There is no GRAPH_PRE_COMMIT stage for 2-way
			 * merges, so this is the first line of output
			 * for this commit.  Check to see what the previous
			 * line of output was.
			 *
			 * If it was GRAPH_POST_MERGE, the branch line
			 * coming into this commit may have been '\',
			 * and not '|' or '/'.  If so, output the branch
			 * line as '\' on this line, instead of '|'.  This
			 * makes the output look nicer.
			 */
			if (graph->prev_state == GRAPH_POST_MERGE &&
			    graph->prev_commit_index < i)
863
				strbuf_write_column(sb, col, '\\');
864
			else
865 866
				strbuf_write_column(sb, col, '|');
			chars_written++;
A
Adam Simpkins 已提交
867
		} else {
868 869
			strbuf_write_column(sb, col, '|');
			chars_written++;
A
Adam Simpkins 已提交
870
		}
871 872
		strbuf_addch(sb, ' ');
		chars_written++;
A
Adam Simpkins 已提交
873 874
	}

875
	graph_pad_horizontally(graph, sb, chars_written);
A
Adam Simpkins 已提交
876 877 878 879 880

	/*
	 * Update graph->state
	 */
	if (graph->num_parents > 1)
881
		graph_update_state(graph, GRAPH_POST_MERGE);
A
Adam Simpkins 已提交
882
	else if (graph_is_mapping_correct(graph))
883
		graph_update_state(graph, GRAPH_PADDING);
A
Adam Simpkins 已提交
884
	else
885
		graph_update_state(graph, GRAPH_COLLAPSING);
A
Adam Simpkins 已提交
886 887
}

888 889 890 891 892 893 894 895
static struct column *find_new_column_by_commit(struct git_graph *graph,
						struct commit *commit)
{
	int i;
	for (i = 0; i < graph->num_new_columns; i++) {
		if (graph->new_columns[i].commit == commit)
			return &graph->new_columns[i];
	}
896
	return NULL;
897 898
}

899
static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
A
Adam Simpkins 已提交
900 901
{
	int seen_this = 0;
902
	int i, j, chars_written;
A
Adam Simpkins 已提交
903 904 905 906

	/*
	 * Output the post-merge row
	 */
907
	chars_written = 0;
A
Adam Simpkins 已提交
908
	for (i = 0; i <= graph->num_columns; i++) {
909
		struct column *col = &graph->columns[i];
A
Adam Simpkins 已提交
910 911 912 913 914 915
		struct commit *col_commit;
		if (i == graph->num_columns) {
			if (seen_this)
				break;
			col_commit = graph->commit;
		} else {
916
			col_commit = col->commit;
A
Adam Simpkins 已提交
917 918 919
		}

		if (col_commit == graph->commit) {
920 921 922 923 924 925 926 927
			/*
			 * Since the current commit is a merge find
			 * the columns for the parent commits in
			 * new_columns and use those to format the
			 * edges.
			 */
			struct commit_list *parents = NULL;
			struct column *par_column;
A
Adam Simpkins 已提交
928
			seen_this = 1;
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944
			parents = first_interesting_parent(graph);
			assert(parents);
			par_column = find_new_column_by_commit(graph, parents->item);
			assert(par_column);

			strbuf_write_column(sb, par_column, '|');
			chars_written++;
			for (j = 0; j < graph->num_parents - 1; j++) {
				parents = next_interesting_parent(graph, parents);
				assert(parents);
				par_column = find_new_column_by_commit(graph, parents->item);
				assert(par_column);
				strbuf_write_column(sb, par_column, '\\');
				strbuf_addch(sb, ' ');
			}
			chars_written += j * 2;
945
		} else if (seen_this) {
946 947 948
			strbuf_write_column(sb, col, '\\');
			strbuf_addch(sb, ' ');
			chars_written += 2;
A
Adam Simpkins 已提交
949
		} else {
950 951 952
			strbuf_write_column(sb, col, '|');
			strbuf_addch(sb, ' ');
			chars_written += 2;
A
Adam Simpkins 已提交
953 954 955
		}
	}

956
	graph_pad_horizontally(graph, sb, chars_written);
A
Adam Simpkins 已提交
957 958 959 960 961

	/*
	 * Update graph->state
	 */
	if (graph_is_mapping_correct(graph))
962
		graph_update_state(graph, GRAPH_PADDING);
A
Adam Simpkins 已提交
963
	else
964
		graph_update_state(graph, GRAPH_COLLAPSING);
A
Adam Simpkins 已提交
965 966
}

967
static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
A
Adam Simpkins 已提交
968 969 970
{
	int i;
	int *tmp_mapping;
971 972 973
	short used_horizontal = 0;
	int horizontal_edge = -1;
	int horizontal_edge_target = -1;
A
Adam Simpkins 已提交
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010

	/*
	 * Clear out the new_mapping array
	 */
	for (i = 0; i < graph->mapping_size; i++)
		graph->new_mapping[i] = -1;

	for (i = 0; i < graph->mapping_size; i++) {
		int target = graph->mapping[i];
		if (target < 0)
			continue;

		/*
		 * Since update_columns() always inserts the leftmost
		 * column first, each branch's target location should
		 * always be either its current location or to the left of
		 * its current location.
		 *
		 * We never have to move branches to the right.  This makes
		 * the graph much more legible, since whenever branches
		 * cross, only one is moving directions.
		 */
		assert(target * 2 <= i);

		if (target * 2 == i) {
			/*
			 * This column is already in the
			 * correct place
			 */
			assert(graph->new_mapping[i] == -1);
			graph->new_mapping[i] = target;
		} else if (graph->new_mapping[i - 1] < 0) {
			/*
			 * Nothing is to the left.
			 * Move to the left by one
			 */
			graph->new_mapping[i - 1] = target;
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
			/*
			 * If there isn't already an edge moving horizontally
			 * select this one.
			 */
			if (horizontal_edge == -1) {
				int j;
				horizontal_edge = i;
				horizontal_edge_target = target;
				/*
				 * The variable target is the index of the graph
				 * column, and therefore target*2+3 is the
				 * actual screen column of the first horizontal
				 * line.
				 */
				for (j = (target * 2)+3; j < (i - 2); j += 2)
					graph->new_mapping[j] = target;
			}
A
Adam Simpkins 已提交
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
		} else if (graph->new_mapping[i - 1] == target) {
			/*
			 * There is a branch line to our left
			 * already, and it is our target.  We
			 * combine with this line, since we share
			 * the same parent commit.
			 *
			 * We don't have to add anything to the
			 * output or new_mapping, since the
			 * existing branch line has already taken
			 * care of it.
			 */
		} else {
			/*
			 * There is a branch line to our left,
			 * but it isn't our target.  We need to
			 * cross over it.
			 *
			 * The space just to the left of this
			 * branch should always be empty.
1048 1049 1050
			 *
			 * The branch to the left of that space
			 * should be our eventual target.
A
Adam Simpkins 已提交
1051 1052 1053
			 */
			assert(graph->new_mapping[i - 1] > target);
			assert(graph->new_mapping[i - 2] < 0);
1054
			assert(graph->new_mapping[i - 3] == target);
A
Adam Simpkins 已提交
1055
			graph->new_mapping[i - 2] = target;
1056 1057 1058 1059 1060 1061 1062
			/*
			 * Mark this branch as the horizontal edge to
			 * prevent any other edges from moving
			 * horizontally.
			 */
			if (horizontal_edge == -1)
				horizontal_edge = i;
A
Adam Simpkins 已提交
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
		}
	}

	/*
	 * The new mapping may be 1 smaller than the old mapping
	 */
	if (graph->new_mapping[graph->mapping_size - 1] < 0)
		graph->mapping_size--;

	/*
	 * Output out a line based on the new mapping info
	 */
	for (i = 0; i < graph->mapping_size; i++) {
		int target = graph->new_mapping[i];
		if (target < 0)
			strbuf_addch(sb, ' ');
		else if (target * 2 == i)
1080
			strbuf_write_column(sb, &graph->new_columns[target], '|');
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
		else if (target == horizontal_edge_target &&
			 i != horizontal_edge - 1) {
				/*
				 * Set the mappings for all but the
				 * first segment to -1 so that they
				 * won't continue into the next line.
				 */
				if (i != (target * 2)+3)
					graph->new_mapping[i] = -1;
				used_horizontal = 1;
			strbuf_write_column(sb, &graph->new_columns[target], '_');
		} else {
			if (used_horizontal && i < horizontal_edge)
				graph->new_mapping[i] = -1;
1095
			strbuf_write_column(sb, &graph->new_columns[target], '/');
1096 1097

		}
A
Adam Simpkins 已提交
1098 1099
	}

1100
	graph_pad_horizontally(graph, sb, graph->mapping_size);
A
Adam Simpkins 已提交
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114

	/*
	 * Swap mapping and new_mapping
	 */
	tmp_mapping = graph->mapping;
	graph->mapping = graph->new_mapping;
	graph->new_mapping = tmp_mapping;

	/*
	 * If graph->mapping indicates that all of the branch lines
	 * are already in the correct positions, we are done.
	 * Otherwise, we need to collapse some branch lines together.
	 */
	if (graph_is_mapping_correct(graph))
1115
		graph_update_state(graph, GRAPH_PADDING);
A
Adam Simpkins 已提交
1116 1117
}

1118
static int graph_next_line(struct git_graph *graph, struct strbuf *sb)
A
Adam Simpkins 已提交
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
{
	switch (graph->state) {
	case GRAPH_PADDING:
		graph_output_padding_line(graph, sb);
		return 0;
	case GRAPH_SKIP:
		graph_output_skip_line(graph, sb);
		return 0;
	case GRAPH_PRE_COMMIT:
		graph_output_pre_commit_line(graph, sb);
		return 0;
	case GRAPH_COMMIT:
		graph_output_commit_line(graph, sb);
		return 1;
	case GRAPH_POST_MERGE:
		graph_output_post_merge_line(graph, sb);
		return 0;
	case GRAPH_COLLAPSING:
		graph_output_collapsing_line(graph, sb);
		return 0;
	}

	assert(0);
	return 0;
}

1145
static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
A
Adam Simpkins 已提交
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
{
	int i, j;

	if (graph->state != GRAPH_COMMIT) {
		graph_next_line(graph, sb);
		return;
	}

	/*
	 * Output the row containing this commit
	 * Iterate up to and including graph->num_columns,
	 * since the current commit may not be in any of the existing
	 * columns.  (This happens when the current commit doesn't have any
	 * children that we have already processed.)
	 */
	for (i = 0; i < graph->num_columns; i++) {
1162 1163
		struct column *col = &graph->columns[i];
		struct commit *col_commit = col->commit;
A
Adam Simpkins 已提交
1164
		if (col_commit == graph->commit) {
1165
			strbuf_write_column(sb, col, '|');
A
Adam Simpkins 已提交
1166 1167 1168 1169 1170 1171 1172 1173 1174

			if (graph->num_parents < 3)
				strbuf_addch(sb, ' ');
			else {
				int num_spaces = ((graph->num_parents - 2) * 2);
				for (j = 0; j < num_spaces; j++)
					strbuf_addch(sb, ' ');
			}
		} else {
1175 1176
			strbuf_write_column(sb, col, '|');
			strbuf_addch(sb, ' ');
A
Adam Simpkins 已提交
1177 1178 1179
		}
	}

1180
	graph_pad_horizontally(graph, sb, graph->num_columns);
1181 1182 1183 1184 1185

	/*
	 * Update graph->prev_state since we have output a padding line
	 */
	graph->prev_state = GRAPH_PADDING;
A
Adam Simpkins 已提交
1186 1187 1188 1189 1190 1191 1192 1193 1194
}

int graph_is_commit_finished(struct git_graph const *graph)
{
	return (graph->state == GRAPH_PADDING);
}

void graph_show_commit(struct git_graph *graph)
{
1195
	struct strbuf msgbuf = STRBUF_INIT;
A
Adam Simpkins 已提交
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
	int shown_commit_line = 0;

	if (!graph)
		return;

	while (!shown_commit_line) {
		shown_commit_line = graph_next_line(graph, &msgbuf);
		fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
		if (!shown_commit_line)
			putchar('\n');
		strbuf_setlen(&msgbuf, 0);
	}

	strbuf_release(&msgbuf);
}

void graph_show_oneline(struct git_graph *graph)
{
1214
	struct strbuf msgbuf = STRBUF_INIT;
A
Adam Simpkins 已提交
1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225

	if (!graph)
		return;

	graph_next_line(graph, &msgbuf);
	fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
	strbuf_release(&msgbuf);
}

void graph_show_padding(struct git_graph *graph)
{
1226
	struct strbuf msgbuf = STRBUF_INIT;
A
Adam Simpkins 已提交
1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237

	if (!graph)
		return;

	graph_padding_line(graph, &msgbuf);
	fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
	strbuf_release(&msgbuf);
}

int graph_show_remainder(struct git_graph *graph)
{
1238
	struct strbuf msgbuf = STRBUF_INIT;
A
Adam Simpkins 已提交
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
	int shown = 0;

	if (!graph)
		return 0;

	if (graph_is_commit_finished(graph))
		return 0;

	for (;;) {
		graph_next_line(graph, &msgbuf);
		fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
		strbuf_setlen(&msgbuf, 0);
		shown = 1;

		if (!graph_is_commit_finished(graph))
			putchar('\n');
		else
			break;
	}
	strbuf_release(&msgbuf);

	return shown;
}


1264
static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
A
Adam Simpkins 已提交
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338
{
	char *p;

	if (!graph) {
		fwrite(sb->buf, sizeof(char), sb->len, stdout);
		return;
	}

	/*
	 * Print the strbuf line by line,
	 * and display the graph info before each line but the first.
	 */
	p = sb->buf;
	while (p) {
		size_t len;
		char *next_p = strchr(p, '\n');
		if (next_p) {
			next_p++;
			len = next_p - p;
		} else {
			len = (sb->buf + sb->len) - p;
		}
		fwrite(p, sizeof(char), len, stdout);
		if (next_p && *next_p != '\0')
			graph_show_oneline(graph);
		p = next_p;
	}
}

void graph_show_commit_msg(struct git_graph *graph,
			   struct strbuf const *sb)
{
	int newline_terminated;

	if (!graph) {
		/*
		 * If there's no graph, just print the message buffer.
		 *
		 * The message buffer for CMIT_FMT_ONELINE and
		 * CMIT_FMT_USERFORMAT are already missing a terminating
		 * newline.  All of the other formats should have it.
		 */
		fwrite(sb->buf, sizeof(char), sb->len, stdout);
		return;
	}

	newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');

	/*
	 * Show the commit message
	 */
	graph_show_strbuf(graph, sb);

	/*
	 * If there is more output needed for this commit, show it now
	 */
	if (!graph_is_commit_finished(graph)) {
		/*
		 * If sb doesn't have a terminating newline, print one now,
		 * so we can start the remainder of the graph output on a
		 * new line.
		 */
		if (!newline_terminated)
			putchar('\n');

		graph_show_remainder(graph);

		/*
		 * If sb ends with a newline, our output should too.
		 */
		if (newline_terminated)
			putchar('\n');
	}
}