git
/
graph.c
1550 строк · 40.2 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "gettext.h"5#include "config.h"6#include "commit.h"7#include "color.h"8#include "graph.h"9#include "revision.h"10#include "strvec.h"11
12/* Internal API */
13
14/*
15* Output a padding line in the graph.
16* This is similar to graph_next_line(). However, it is guaranteed to
17* never print the current commit line. Instead, if the commit line is
18* next, it will simply output a line of vertical padding, extending the
19* branch lines downwards, but leaving them otherwise unchanged.
20*/
21static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);22
23/*
24* Print a strbuf. If the graph is non-NULL, all lines but the first will be
25* prefixed with the graph output.
26*
27* If the strbuf ends with a newline, the output will end after this
28* newline. A new graph line will not be printed after the final newline.
29* If the strbuf is empty, no output will be printed.
30*
31* Since the first line will not include the graph output, the caller is
32* responsible for printing this line's graph (perhaps via
33* graph_show_commit() or graph_show_oneline()) before calling
34* graph_show_strbuf().
35*
36* Note that unlike some other graph display functions, you must pass the file
37* handle directly. It is assumed that this is the same file handle as the
38* file specified by the graph diff options. This is necessary so that
39* graph_show_strbuf can be called even with a NULL graph.
40* If a NULL graph is supplied, the strbuf is printed as-is.
41*/
42static void graph_show_strbuf(struct git_graph *graph,43FILE *file,44struct strbuf const *sb);45
46/*
47* TODO:
48* - Limit the number of columns, similar to the way gitk does.
49* If we reach more than a specified number of columns, omit
50* sections of some columns.
51*/
52
53struct column {54/*55* The parent commit of this column.
56*/
57struct commit *commit;58/*59* The color to (optionally) print this column in. This is an
60* index into column_colors.
61*/
62unsigned short color;63};64
65enum graph_state {66GRAPH_PADDING,67GRAPH_SKIP,68GRAPH_PRE_COMMIT,69GRAPH_COMMIT,70GRAPH_POST_MERGE,71GRAPH_COLLAPSING
72};73
74static void graph_show_line_prefix(const struct diff_options *diffopt)75{
76if (!diffopt || !diffopt->line_prefix)77return;78
79fwrite(diffopt->line_prefix,80sizeof(char),81diffopt->line_prefix_length,82diffopt->file);83}
84
85static const char **column_colors;86static unsigned short column_colors_max;87
88static void parse_graph_colors_config(struct strvec *colors, const char *string)89{
90const char *end, *start;91
92start = string;93end = string + strlen(string);94while (start < end) {95const char *comma = strchrnul(start, ',');96char color[COLOR_MAXLEN];97
98if (!color_parse_mem(start, comma - start, color))99strvec_push(colors, color);100else101warning(_("ignored invalid color '%.*s' in log.graphColors"),102(int)(comma - start), start);103start = comma + 1;104}105strvec_push(colors, GIT_COLOR_RESET);106}
107
108void graph_set_column_colors(const char **colors, unsigned short colors_max)109{
110column_colors = colors;111column_colors_max = colors_max;112}
113
114static const char *column_get_color_code(unsigned short color)115{
116return column_colors[color];117}
118
119struct graph_line {120struct strbuf *buf;121size_t width;122};123
124static inline void graph_line_addch(struct graph_line *line, int c)125{
126strbuf_addch(line->buf, c);127line->width++;128}
129
130static inline void graph_line_addchars(struct graph_line *line, int c, size_t n)131{
132strbuf_addchars(line->buf, c, n);133line->width += n;134}
135
136static inline void graph_line_addstr(struct graph_line *line, const char *s)137{
138strbuf_addstr(line->buf, s);139line->width += strlen(s);140}
141
142static inline void graph_line_addcolor(struct graph_line *line, unsigned short color)143{
144strbuf_addstr(line->buf, column_get_color_code(color));145}
146
147static void graph_line_write_column(struct graph_line *line, const struct column *c,148char col_char)149{
150if (c->color < column_colors_max)151graph_line_addcolor(line, c->color);152graph_line_addch(line, col_char);153if (c->color < column_colors_max)154graph_line_addcolor(line, column_colors_max);155}
156
157struct git_graph {158/*159* The commit currently being processed
160*/
161struct commit *commit;162/* The rev-info used for the current traversal */163struct rev_info *revs;164/*165* The number of interesting parents that this commit has.
166*
167* Note that this is not the same as the actual number of parents.
168* This count excludes parents that won't be printed in the graph
169* output, as determined by graph_is_interesting().
170*/
171int num_parents;172/*173* The width of the graph output for this commit.
174* All rows for this commit are padded to this width, so that
175* messages printed after the graph output are aligned.
176*/
177int width;178/*179* The next expansion row to print
180* when state is GRAPH_PRE_COMMIT
181*/
182int expansion_row;183/*184* The current output state.
185* This tells us what kind of line graph_next_line() should output.
186*/
187enum graph_state state;188/*189* The output state for the previous line of output.
190* This is primarily used to determine how the first merge line
191* should appear, based on the last line of the previous commit.
192*/
193enum graph_state prev_state;194/*195* The index of the column that refers to this commit.
196*
197* If none of the incoming columns refer to this commit,
198* this will be equal to num_columns.
199*/
200int commit_index;201/*202* The commit_index for the previously displayed commit.
203*
204* This is used to determine how the first line of a merge
205* graph output should appear, based on the last line of the
206* previous commit.
207*/
208int prev_commit_index;209/*210* Which layout variant to use to display merge commits. If the
211* commit's first parent is known to be in a column to the left of the
212* merge, then this value is 0 and we use the layout on the left.
213* Otherwise, the value is 1 and the layout on the right is used. This
214* field tells us how many columns the first parent occupies.
215*
216* 0) 1)
217*
218* | | | *-. | | *---.
219* | |_|/|\ \ | | |\ \ \
220* |/| | | | | | | | | | *
221*/
222int merge_layout;223/*224* The number of columns added to the graph by the current commit. For
225* 2-way and octopus merges, this is usually one less than the
226* number of parents:
227*
228* | | | | | \
229* | * | | *---. \
230* | |\ \ | |\ \ \ \
231* | | | | | | | | | |
232*
233* num_parents: 2 num_parents: 4
234* edges_added: 1 edges_added: 3
235*
236* For left-skewed merges, the first parent fuses with its neighbor and
237* so one less column is added:
238*
239* | | | | | \
240* | * | | *-. \
241* |/| | |/|\ \ \
242* | | | | | | | |
243*
244* num_parents: 2 num_parents: 4
245* edges_added: 0 edges_added: 2
246*
247* This number determines how edges to the right of the merge are
248* displayed in commit and post-merge lines; if no columns have been
249* added then a vertical line should be used where a right-tracking
250* line would otherwise be used.
251*
252* | * \ | * |
253* | |\ \ |/| |
254* | | * \ | * |
255*/
256int edges_added;257/*258* The number of columns added by the previous commit, which is used to
259* smooth edges appearing to the right of a commit in a commit line
260* following a post-merge line.
261*/
262int prev_edges_added;263/*264* The maximum number of columns that can be stored in the columns
265* and new_columns arrays. This is also half the number of entries
266* that can be stored in the mapping and old_mapping arrays.
267*/
268int column_capacity;269/*270* The number of columns (also called "branch lines" in some places)
271*/
272int num_columns;273/*274* The number of columns in the new_columns array
275*/
276int num_new_columns;277/*278* The number of entries in the mapping array
279*/
280int mapping_size;281/*282* The column state before we output the current commit.
283*/
284struct column *columns;285/*286* The new column state after we output the current commit.
287* Only valid when state is GRAPH_COLLAPSING.
288*/
289struct column *new_columns;290/*291* An array that tracks the current state of each
292* character in the output line during state GRAPH_COLLAPSING.
293* Each entry is -1 if this character is empty, or a non-negative
294* integer if the character contains a branch line. The value of
295* the integer indicates the target position for this branch line.
296* (I.e., this array maps the current column positions to their
297* desired positions.)
298*
299* The maximum capacity of this array is always
300* sizeof(int) * 2 * column_capacity.
301*/
302int *mapping;303/*304* A copy of the contents of the mapping array from the last commit,
305* which we use to improve the display of columns that are tracking
306* from right to left through a commit line. We also use this to
307* avoid allocating a fresh array when we compute the next mapping.
308*/
309int *old_mapping;310/*311* The current default column color being used. This is
312* stored as an index into the array column_colors.
313*/
314unsigned short default_column_color;315};316
317static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void *data)318{
319struct git_graph *graph = data;320static struct strbuf msgbuf = STRBUF_INIT;321
322assert(opt);323
324strbuf_reset(&msgbuf);325if (opt->line_prefix)326strbuf_add(&msgbuf, opt->line_prefix,327opt->line_prefix_length);328if (graph)329graph_padding_line(graph, &msgbuf);330return &msgbuf;331}
332
333static const struct diff_options *default_diffopt;334
335void graph_setup_line_prefix(struct diff_options *diffopt)336{
337default_diffopt = diffopt;338
339/* setup an output prefix callback if necessary */340if (diffopt && !diffopt->output_prefix)341diffopt->output_prefix = diff_output_prefix_callback;342}
343
344struct git_graph *graph_init(struct rev_info *opt)345{
346struct git_graph *graph = xmalloc(sizeof(struct git_graph));347
348if (!column_colors) {349char *string;350if (git_config_get_string("log.graphcolors", &string)) {351/* not configured -- use default */352graph_set_column_colors(column_colors_ansi,353column_colors_ansi_max);354} else {355static struct strvec custom_colors = STRVEC_INIT;356strvec_clear(&custom_colors);357parse_graph_colors_config(&custom_colors, string);358free(string);359/* graph_set_column_colors takes a max-index, not a count */360graph_set_column_colors(custom_colors.v,361custom_colors.nr - 1);362}363}364
365graph->commit = NULL;366graph->revs = opt;367graph->num_parents = 0;368graph->expansion_row = 0;369graph->state = GRAPH_PADDING;370graph->prev_state = GRAPH_PADDING;371graph->commit_index = 0;372graph->prev_commit_index = 0;373graph->merge_layout = 0;374graph->edges_added = 0;375graph->prev_edges_added = 0;376graph->num_columns = 0;377graph->num_new_columns = 0;378graph->mapping_size = 0;379/*380* Start the column color at the maximum value, since we'll
381* always increment it for the first commit we output.
382* This way we start at 0 for the first commit.
383*/
384graph->default_column_color = column_colors_max - 1;385
386/*387* Allocate a reasonably large default number of columns
388* We'll automatically grow columns later if we need more room.
389*/
390graph->column_capacity = 30;391ALLOC_ARRAY(graph->columns, graph->column_capacity);392ALLOC_ARRAY(graph->new_columns, graph->column_capacity);393ALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity);394ALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity);395
396/*397* The diff output prefix callback, with this we can make
398* all the diff output to align with the graph lines.
399*/
400opt->diffopt.output_prefix = diff_output_prefix_callback;401opt->diffopt.output_prefix_data = graph;402
403return graph;404}
405
406void graph_clear(struct git_graph *graph)407{
408if (!graph)409return;410
411free(graph->columns);412free(graph->new_columns);413free(graph->mapping);414free(graph->old_mapping);415free(graph);416}
417
418static void graph_update_state(struct git_graph *graph, enum graph_state s)419{
420graph->prev_state = graph->state;421graph->state = s;422}
423
424static void graph_ensure_capacity(struct git_graph *graph, int num_columns)425{
426if (graph->column_capacity >= num_columns)427return;428
429do {430graph->column_capacity *= 2;431} while (graph->column_capacity < num_columns);432
433REALLOC_ARRAY(graph->columns, graph->column_capacity);434REALLOC_ARRAY(graph->new_columns, graph->column_capacity);435REALLOC_ARRAY(graph->mapping, graph->column_capacity * 2);436REALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2);437}
438
439/*
440* Returns 1 if the commit will be printed in the graph output,
441* and 0 otherwise.
442*/
443static int graph_is_interesting(struct git_graph *graph, struct commit *commit)444{
445/*446* If revs->boundary is set, commits whose children have
447* been shown are always interesting, even if they have the
448* UNINTERESTING or TREESAME flags set.
449*/
450if (graph->revs && graph->revs->boundary) {451if (commit->object.flags & CHILD_SHOWN)452return 1;453}454
455/*456* Otherwise, use get_commit_action() to see if this commit is
457* interesting
458*/
459return get_commit_action(graph->revs, commit) == commit_show;460}
461
462static struct commit_list *next_interesting_parent(struct git_graph *graph,463struct commit_list *orig)464{
465struct commit_list *list;466
467/*468* If revs->first_parent_only is set, only the first
469* parent is interesting. None of the others are.
470*/
471if (graph->revs->first_parent_only)472return NULL;473
474/*475* Return the next interesting commit after orig
476*/
477for (list = orig->next; list; list = list->next) {478if (graph_is_interesting(graph, list->item))479return list;480}481
482return NULL;483}
484
485static struct commit_list *first_interesting_parent(struct git_graph *graph)486{
487struct commit_list *parents = graph->commit->parents;488
489/*490* If this commit has no parents, ignore it
491*/
492if (!parents)493return NULL;494
495/*496* If the first parent is interesting, return it
497*/
498if (graph_is_interesting(graph, parents->item))499return parents;500
501/*502* Otherwise, call next_interesting_parent() to get
503* the next interesting parent
504*/
505return next_interesting_parent(graph, parents);506}
507
508static unsigned short graph_get_current_column_color(const struct git_graph *graph)509{
510if (!want_color(graph->revs->diffopt.use_color))511return column_colors_max;512return graph->default_column_color;513}
514
515/*
516* Update the graph's default column color.
517*/
518static void graph_increment_column_color(struct git_graph *graph)519{
520graph->default_column_color = (graph->default_column_color + 1) %521column_colors_max;522}
523
524static unsigned short graph_find_commit_color(const struct git_graph *graph,525const struct commit *commit)526{
527int i;528for (i = 0; i < graph->num_columns; i++) {529if (graph->columns[i].commit == commit)530return graph->columns[i].color;531}532return graph_get_current_column_color(graph);533}
534
535static int graph_find_new_column_by_commit(struct git_graph *graph,536struct commit *commit)537{
538int i;539for (i = 0; i < graph->num_new_columns; i++) {540if (graph->new_columns[i].commit == commit)541return i;542}543return -1;544}
545
546static void graph_insert_into_new_columns(struct git_graph *graph,547struct commit *commit,548int idx)549{
550int i = graph_find_new_column_by_commit(graph, commit);551int mapping_idx;552
553/*554* If the commit is not already in the new_columns array, then add it
555* and record it as being in the final column.
556*/
557if (i < 0) {558i = graph->num_new_columns++;559graph->new_columns[i].commit = commit;560graph->new_columns[i].color = graph_find_commit_color(graph, commit);561}562
563if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {564/*565* If this is the first parent of a merge, choose a layout for
566* the merge line based on whether the parent appears in a
567* column to the left of the merge
568*/
569int dist, shift;570
571dist = idx - i;572shift = (dist > 1) ? 2 * dist - 3 : 1;573
574graph->merge_layout = (dist > 0) ? 0 : 1;575graph->edges_added = graph->num_parents + graph->merge_layout - 2;576
577mapping_idx = graph->width + (graph->merge_layout - 1) * shift;578graph->width += 2 * graph->merge_layout;579
580} else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 2]) {581/*582* If some columns have been added by a merge, but this commit
583* was found in the last existing column, then adjust the
584* numbers so that the two edges immediately join, i.e.:
585*
586* * | * |
587* |\ \ => |\|
588* | |/ | *
589* | *
590*/
591mapping_idx = graph->width - 2;592graph->edges_added = -1;593} else {594mapping_idx = graph->width;595graph->width += 2;596}597
598graph->mapping[mapping_idx] = i;599}
600
601static void graph_update_columns(struct git_graph *graph)602{
603struct commit_list *parent;604int max_new_columns;605int i, seen_this, is_commit_in_columns;606
607/*608* Swap graph->columns with graph->new_columns
609* graph->columns contains the state for the previous commit,
610* and new_columns now contains the state for our commit.
611*
612* We'll re-use the old columns array as storage to compute the new
613* columns list for the commit after this one.
614*/
615SWAP(graph->columns, graph->new_columns);616graph->num_columns = graph->num_new_columns;617graph->num_new_columns = 0;618
619/*620* Now update new_columns and mapping with the information for the
621* commit after this one.
622*
623* First, make sure we have enough room. At most, there will
624* be graph->num_columns + graph->num_parents columns for the next
625* commit.
626*/
627max_new_columns = graph->num_columns + graph->num_parents;628graph_ensure_capacity(graph, max_new_columns);629
630/*631* Clear out graph->mapping
632*/
633graph->mapping_size = 2 * max_new_columns;634for (i = 0; i < graph->mapping_size; i++)635graph->mapping[i] = -1;636
637graph->width = 0;638graph->prev_edges_added = graph->edges_added;639graph->edges_added = 0;640
641/*642* Populate graph->new_columns and graph->mapping
643*
644* Some of the parents of this commit may already be in
645* graph->columns. If so, graph->new_columns should only contain a
646* single entry for each such commit. graph->mapping should
647* contain information about where each current branch line is
648* supposed to end up after the collapsing is performed.
649*/
650seen_this = 0;651is_commit_in_columns = 1;652for (i = 0; i <= graph->num_columns; i++) {653struct commit *col_commit;654if (i == graph->num_columns) {655if (seen_this)656break;657is_commit_in_columns = 0;658col_commit = graph->commit;659} else {660col_commit = graph->columns[i].commit;661}662
663if (col_commit == graph->commit) {664seen_this = 1;665graph->commit_index = i;666graph->merge_layout = -1;667for (parent = first_interesting_parent(graph);668parent;669parent = next_interesting_parent(graph, parent)) {670/*671* If this is a merge, or the start of a new
672* childless column, increment the current
673* color.
674*/
675if (graph->num_parents > 1 ||676!is_commit_in_columns) {677graph_increment_column_color(graph);678}679graph_insert_into_new_columns(graph, parent->item, i);680}681/*682* We always need to increment graph->width by at
683* least 2, even if it has no interesting parents.
684* The current commit always takes up at least 2
685* spaces.
686*/
687if (graph->num_parents == 0)688graph->width += 2;689} else {690graph_insert_into_new_columns(graph, col_commit, -1);691}692}693
694/*695* Shrink mapping_size to be the minimum necessary
696*/
697while (graph->mapping_size > 1 &&698graph->mapping[graph->mapping_size - 1] < 0)699graph->mapping_size--;700}
701
702static int graph_num_dashed_parents(struct git_graph *graph)703{
704return graph->num_parents + graph->merge_layout - 3;705}
706
707static int graph_num_expansion_rows(struct git_graph *graph)708{
709/*710* Normally, we need two expansion rows for each dashed parent line from
711* an octopus merge:
712*
713* | *
714* | |\
715* | | \
716* | | \
717* | *-. \
718* | |\ \ \
719*
720* If the merge is skewed to the left, then its parents occupy one less
721* column, and we don't need as many expansion rows to route around it;
722* in some cases that means we don't need any expansion rows at all:
723*
724* | *
725* | |\
726* | * \
727* |/|\ \
728*/
729return graph_num_dashed_parents(graph) * 2;730}
731
732static int graph_needs_pre_commit_line(struct git_graph *graph)733{
734return graph->num_parents >= 3 &&735graph->commit_index < (graph->num_columns - 1) &&736graph->expansion_row < graph_num_expansion_rows(graph);737}
738
739void graph_update(struct git_graph *graph, struct commit *commit)740{
741struct commit_list *parent;742
743/*744* Set the new commit
745*/
746graph->commit = commit;747
748/*749* Count how many interesting parents this commit has
750*/
751graph->num_parents = 0;752for (parent = first_interesting_parent(graph);753parent;754parent = next_interesting_parent(graph, parent))755{756graph->num_parents++;757}758
759/*760* Store the old commit_index in prev_commit_index.
761* graph_update_columns() will update graph->commit_index for this
762* commit.
763*/
764graph->prev_commit_index = graph->commit_index;765
766/*767* Call graph_update_columns() to update
768* columns, new_columns, and mapping.
769*/
770graph_update_columns(graph);771
772graph->expansion_row = 0;773
774/*775* Update graph->state.
776* Note that we don't call graph_update_state() here, since
777* we don't want to update graph->prev_state. No line for
778* graph->state was ever printed.
779*
780* If the previous commit didn't get to the GRAPH_PADDING state,
781* it never finished its output. Goto GRAPH_SKIP, to print out
782* a line to indicate that portion of the graph is missing.
783*
784* If there are 3 or more parents, we may need to print extra rows
785* before the commit, to expand the branch lines around it and make
786* room for it. We need to do this only if there is a branch row
787* (or more) to the right of this commit.
788*
789* If there are less than 3 parents, we can immediately print the
790* commit line.
791*/
792if (graph->state != GRAPH_PADDING)793graph->state = GRAPH_SKIP;794else if (graph_needs_pre_commit_line(graph))795graph->state = GRAPH_PRE_COMMIT;796else797graph->state = GRAPH_COMMIT;798}
799
800static int graph_is_mapping_correct(struct git_graph *graph)801{
802int i;803
804/*805* The mapping is up to date if each entry is at its target,
806* or is 1 greater than its target.
807* (If it is 1 greater than the target, '/' will be printed, so it
808* will look correct on the next row.)
809*/
810for (i = 0; i < graph->mapping_size; i++) {811int target = graph->mapping[i];812if (target < 0)813continue;814if (target == (i / 2))815continue;816return 0;817}818
819return 1;820}
821
822static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line)823{
824/*825* Add additional spaces to the end of the strbuf, so that all
826* lines for a particular commit have the same width.
827*
828* This way, fields printed to the right of the graph will remain
829* aligned for the entire commit.
830*/
831if (line->width < graph->width)832graph_line_addchars(line, ' ', graph->width - line->width);833}
834
835static void graph_output_padding_line(struct git_graph *graph,836struct graph_line *line)837{
838int i;839
840/*841* Output a padding row, that leaves all branch lines unchanged
842*/
843for (i = 0; i < graph->num_new_columns; i++) {844graph_line_write_column(line, &graph->new_columns[i], '|');845graph_line_addch(line, ' ');846}847}
848
849
850int graph_width(struct git_graph *graph)851{
852return graph->width;853}
854
855
856static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line)857{
858/*859* Output an ellipsis to indicate that a portion
860* of the graph is missing.
861*/
862graph_line_addstr(line, "...");863
864if (graph_needs_pre_commit_line(graph))865graph_update_state(graph, GRAPH_PRE_COMMIT);866else867graph_update_state(graph, GRAPH_COMMIT);868}
869
870static void graph_output_pre_commit_line(struct git_graph *graph,871struct graph_line *line)872{
873int i, seen_this;874
875/*876* This function formats a row that increases the space around a commit
877* with multiple parents, to make room for it. It should only be
878* called when there are 3 or more parents.
879*
880* We need 2 extra rows for every parent over 2.
881*/
882assert(graph->num_parents >= 3);883
884/*885* graph->expansion_row tracks the current expansion row we are on.
886* It should be in the range [0, num_expansion_rows - 1]
887*/
888assert(0 <= graph->expansion_row &&889graph->expansion_row < graph_num_expansion_rows(graph));890
891/*892* Output the row
893*/
894seen_this = 0;895for (i = 0; i < graph->num_columns; i++) {896struct column *col = &graph->columns[i];897if (col->commit == graph->commit) {898seen_this = 1;899graph_line_write_column(line, col, '|');900graph_line_addchars(line, ' ', graph->expansion_row);901} else if (seen_this && (graph->expansion_row == 0)) {902/*903* This is the first line of the pre-commit output.
904* If the previous commit was a merge commit and
905* ended in the GRAPH_POST_MERGE state, all branch
906* lines after graph->prev_commit_index were
907* printed as "\" on the previous line. Continue
908* to print them as "\" on this line. Otherwise,
909* print the branch lines as "|".
910*/
911if (graph->prev_state == GRAPH_POST_MERGE &&912graph->prev_commit_index < i)913graph_line_write_column(line, col, '\\');914else915graph_line_write_column(line, col, '|');916} else if (seen_this && (graph->expansion_row > 0)) {917graph_line_write_column(line, col, '\\');918} else {919graph_line_write_column(line, col, '|');920}921graph_line_addch(line, ' ');922}923
924/*925* Increment graph->expansion_row,
926* and move to state GRAPH_COMMIT if necessary
927*/
928graph->expansion_row++;929if (!graph_needs_pre_commit_line(graph))930graph_update_state(graph, GRAPH_COMMIT);931}
932
933static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line)934{
935/*936* For boundary commits, print 'o'
937* (We should only see boundary commits when revs->boundary is set.)
938*/
939if (graph->commit->object.flags & BOUNDARY) {940assert(graph->revs->boundary);941graph_line_addch(line, 'o');942return;943}944
945/*946* get_revision_mark() handles all other cases without assert()
947*/
948graph_line_addstr(line, get_revision_mark(graph->revs, graph->commit));949}
950
951/*
952* Draw the horizontal dashes of an octopus merge.
953*/
954static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line)955{
956/*957* The parents of a merge commit can be arbitrarily reordered as they
958* are mapped onto display columns, for example this is a valid merge:
959*
960* | | *---.
961* | | |\ \ \
962* | | |/ / /
963* | |/| | /
964* | |_|_|/
965* |/| | |
966* 3 1 0 2
967*
968* The numbers denote which parent of the merge each visual column
969* corresponds to; we can't assume that the parents will initially
970* display in the order given by new_columns.
971*
972* To find the right color for each dash, we need to consult the
973* mapping array, starting from the column 2 places to the right of the
974* merge commit, and use that to find out which logical column each
975* edge will collapse to.
976*
977* Commits are rendered once all edges have collapsed to their correct
978* logcial column, so commit_index gives us the right visual offset for
979* the merge commit.
980*/
981
982int i, j;983struct column *col;984
985int dashed_parents = graph_num_dashed_parents(graph);986
987for (i = 0; i < dashed_parents; i++) {988j = graph->mapping[(graph->commit_index + i + 2) * 2];989col = &graph->new_columns[j];990
991graph_line_write_column(line, col, '-');992graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-');993}994
995return;996}
997
998static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line)999{
1000int seen_this = 0;1001int i;1002
1003/*1004* Output the row containing this commit
1005* Iterate up to and including graph->num_columns,
1006* since the current commit may not be in any of the existing
1007* columns. (This happens when the current commit doesn't have any
1008* children that we have already processed.)
1009*/
1010seen_this = 0;1011for (i = 0; i <= graph->num_columns; i++) {1012struct column *col = &graph->columns[i];1013struct commit *col_commit;1014if (i == graph->num_columns) {1015if (seen_this)1016break;1017col_commit = graph->commit;1018} else {1019col_commit = graph->columns[i].commit;1020}1021
1022if (col_commit == graph->commit) {1023seen_this = 1;1024graph_output_commit_char(graph, line);1025
1026if (graph->num_parents > 2)1027graph_draw_octopus_merge(graph, line);1028} else if (seen_this && (graph->edges_added > 1)) {1029graph_line_write_column(line, col, '\\');1030} else if (seen_this && (graph->edges_added == 1)) {1031/*1032* This is either a right-skewed 2-way merge
1033* commit, or a left-skewed 3-way merge.
1034* There is no GRAPH_PRE_COMMIT stage for such
1035* merges, so this is the first line of output
1036* for this commit. Check to see what the previous
1037* line of output was.
1038*
1039* If it was GRAPH_POST_MERGE, the branch line
1040* coming into this commit may have been '\',
1041* and not '|' or '/'. If so, output the branch
1042* line as '\' on this line, instead of '|'. This
1043* makes the output look nicer.
1044*/
1045if (graph->prev_state == GRAPH_POST_MERGE &&1046graph->prev_edges_added > 0 &&1047graph->prev_commit_index < i)1048graph_line_write_column(line, col, '\\');1049else1050graph_line_write_column(line, col, '|');1051} else if (graph->prev_state == GRAPH_COLLAPSING &&1052graph->old_mapping[2 * i + 1] == i &&1053graph->mapping[2 * i] < i) {1054graph_line_write_column(line, col, '/');1055} else {1056graph_line_write_column(line, col, '|');1057}1058graph_line_addch(line, ' ');1059}1060
1061/*1062* Update graph->state
1063*/
1064if (graph->num_parents > 1)1065graph_update_state(graph, GRAPH_POST_MERGE);1066else if (graph_is_mapping_correct(graph))1067graph_update_state(graph, GRAPH_PADDING);1068else1069graph_update_state(graph, GRAPH_COLLAPSING);1070}
1071
1072static const char merge_chars[] = {'/', '|', '\\'};1073
1074static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)1075{
1076int seen_this = 0;1077int i, j;1078
1079struct commit_list *first_parent = first_interesting_parent(graph);1080struct column *parent_col = NULL;1081
1082/*1083* Output the post-merge row
1084*/
1085for (i = 0; i <= graph->num_columns; i++) {1086struct column *col = &graph->columns[i];1087struct commit *col_commit;1088if (i == graph->num_columns) {1089if (seen_this)1090break;1091col_commit = graph->commit;1092} else {1093col_commit = col->commit;1094}1095
1096if (col_commit == graph->commit) {1097/*1098* Since the current commit is a merge find
1099* the columns for the parent commits in
1100* new_columns and use those to format the
1101* edges.
1102*/
1103struct commit_list *parents = first_parent;1104int par_column;1105int idx = graph->merge_layout;1106char c;1107seen_this = 1;1108
1109for (j = 0; j < graph->num_parents; j++) {1110par_column = graph_find_new_column_by_commit(graph, parents->item);1111assert(par_column >= 0);1112
1113c = merge_chars[idx];1114graph_line_write_column(line, &graph->new_columns[par_column], c);1115if (idx == 2) {1116if (graph->edges_added > 0 || j < graph->num_parents - 1)1117graph_line_addch(line, ' ');1118} else {1119idx++;1120}1121parents = next_interesting_parent(graph, parents);1122}1123if (graph->edges_added == 0)1124graph_line_addch(line, ' ');1125
1126} else if (seen_this) {1127if (graph->edges_added > 0)1128graph_line_write_column(line, col, '\\');1129else1130graph_line_write_column(line, col, '|');1131graph_line_addch(line, ' ');1132} else {1133graph_line_write_column(line, col, '|');1134if (graph->merge_layout != 0 || i != graph->commit_index - 1) {1135if (parent_col)1136graph_line_write_column(1137line, parent_col, '_');1138else1139graph_line_addch(line, ' ');1140}1141}1142
1143if (col_commit == first_parent->item)1144parent_col = col;1145}1146
1147/*1148* Update graph->state
1149*/
1150if (graph_is_mapping_correct(graph))1151graph_update_state(graph, GRAPH_PADDING);1152else1153graph_update_state(graph, GRAPH_COLLAPSING);1154}
1155
1156static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line)1157{
1158int i;1159short used_horizontal = 0;1160int horizontal_edge = -1;1161int horizontal_edge_target = -1;1162
1163/*1164* Swap the mapping and old_mapping arrays
1165*/
1166SWAP(graph->mapping, graph->old_mapping);1167
1168/*1169* Clear out the mapping array
1170*/
1171for (i = 0; i < graph->mapping_size; i++)1172graph->mapping[i] = -1;1173
1174for (i = 0; i < graph->mapping_size; i++) {1175int target = graph->old_mapping[i];1176if (target < 0)1177continue;1178
1179/*1180* Since update_columns() always inserts the leftmost
1181* column first, each branch's target location should
1182* always be either its current location or to the left of
1183* its current location.
1184*
1185* We never have to move branches to the right. This makes
1186* the graph much more legible, since whenever branches
1187* cross, only one is moving directions.
1188*/
1189assert(target * 2 <= i);1190
1191if (target * 2 == i) {1192/*1193* This column is already in the
1194* correct place
1195*/
1196assert(graph->mapping[i] == -1);1197graph->mapping[i] = target;1198} else if (graph->mapping[i - 1] < 0) {1199/*1200* Nothing is to the left.
1201* Move to the left by one
1202*/
1203graph->mapping[i - 1] = target;1204/*1205* If there isn't already an edge moving horizontally
1206* select this one.
1207*/
1208if (horizontal_edge == -1) {1209int j;1210horizontal_edge = i;1211horizontal_edge_target = target;1212/*1213* The variable target is the index of the graph
1214* column, and therefore target*2+3 is the
1215* actual screen column of the first horizontal
1216* line.
1217*/
1218for (j = (target * 2)+3; j < (i - 2); j += 2)1219graph->mapping[j] = target;1220}1221} else if (graph->mapping[i - 1] == target) {1222/*1223* There is a branch line to our left
1224* already, and it is our target. We
1225* combine with this line, since we share
1226* the same parent commit.
1227*
1228* We don't have to add anything to the
1229* output or mapping, since the
1230* existing branch line has already taken
1231* care of it.
1232*/
1233} else {1234/*1235* There is a branch line to our left,
1236* but it isn't our target. We need to
1237* cross over it.
1238*
1239* The space just to the left of this
1240* branch should always be empty.
1241*/
1242assert(graph->mapping[i - 1] > target);1243assert(graph->mapping[i - 2] < 0);1244graph->mapping[i - 2] = target;1245/*1246* Mark this branch as the horizontal edge to
1247* prevent any other edges from moving
1248* horizontally.
1249*/
1250if (horizontal_edge == -1) {1251int j;1252horizontal_edge_target = target;1253horizontal_edge = i - 1;1254
1255for (j = (target * 2) + 3; j < (i - 2); j += 2)1256graph->mapping[j] = target;1257}1258}1259}1260
1261/*1262* Copy the current mapping array into old_mapping
1263*/
1264COPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size);1265
1266/*1267* The new mapping may be 1 smaller than the old mapping
1268*/
1269if (graph->mapping[graph->mapping_size - 1] < 0)1270graph->mapping_size--;1271
1272/*1273* Output out a line based on the new mapping info
1274*/
1275for (i = 0; i < graph->mapping_size; i++) {1276int target = graph->mapping[i];1277if (target < 0)1278graph_line_addch(line, ' ');1279else if (target * 2 == i)1280graph_line_write_column(line, &graph->new_columns[target], '|');1281else if (target == horizontal_edge_target &&1282i != horizontal_edge - 1) {1283/*1284* Set the mappings for all but the
1285* first segment to -1 so that they
1286* won't continue into the next line.
1287*/
1288if (i != (target * 2)+3)1289graph->mapping[i] = -1;1290used_horizontal = 1;1291graph_line_write_column(line, &graph->new_columns[target], '_');1292} else {1293if (used_horizontal && i < horizontal_edge)1294graph->mapping[i] = -1;1295graph_line_write_column(line, &graph->new_columns[target], '/');1296
1297}1298}1299
1300/*1301* If graph->mapping indicates that all of the branch lines
1302* are already in the correct positions, we are done.
1303* Otherwise, we need to collapse some branch lines together.
1304*/
1305if (graph_is_mapping_correct(graph))1306graph_update_state(graph, GRAPH_PADDING);1307}
1308
1309int graph_next_line(struct git_graph *graph, struct strbuf *sb)1310{
1311int shown_commit_line = 0;1312struct graph_line line = { .buf = sb, .width = 0 };1313
1314/*1315* We could conceivable be called with a NULL commit
1316* if our caller has a bug, and invokes graph_next_line()
1317* immediately after graph_init(), without first calling
1318* graph_update(). Return without outputting anything in this
1319* case.
1320*/
1321if (!graph->commit)1322return -1;1323
1324switch (graph->state) {1325case GRAPH_PADDING:1326graph_output_padding_line(graph, &line);1327break;1328case GRAPH_SKIP:1329graph_output_skip_line(graph, &line);1330break;1331case GRAPH_PRE_COMMIT:1332graph_output_pre_commit_line(graph, &line);1333break;1334case GRAPH_COMMIT:1335graph_output_commit_line(graph, &line);1336shown_commit_line = 1;1337break;1338case GRAPH_POST_MERGE:1339graph_output_post_merge_line(graph, &line);1340break;1341case GRAPH_COLLAPSING:1342graph_output_collapsing_line(graph, &line);1343break;1344}1345
1346graph_pad_horizontally(graph, &line);1347return shown_commit_line;1348}
1349
1350static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)1351{
1352int i;1353struct graph_line line = { .buf = sb, .width = 0 };1354
1355if (graph->state != GRAPH_COMMIT) {1356graph_next_line(graph, sb);1357return;1358}1359
1360/*1361* Output the row containing this commit
1362* Iterate up to and including graph->num_columns,
1363* since the current commit may not be in any of the existing
1364* columns. (This happens when the current commit doesn't have any
1365* children that we have already processed.)
1366*/
1367for (i = 0; i < graph->num_columns; i++) {1368struct column *col = &graph->columns[i];1369
1370graph_line_write_column(&line, col, '|');1371
1372if (col->commit == graph->commit && graph->num_parents > 2) {1373int len = (graph->num_parents - 2) * 2;1374graph_line_addchars(&line, ' ', len);1375} else {1376graph_line_addch(&line, ' ');1377}1378}1379
1380graph_pad_horizontally(graph, &line);1381
1382/*1383* Update graph->prev_state since we have output a padding line
1384*/
1385graph->prev_state = GRAPH_PADDING;1386}
1387
1388int graph_is_commit_finished(struct git_graph const *graph)1389{
1390return (graph->state == GRAPH_PADDING);1391}
1392
1393void graph_show_commit(struct git_graph *graph)1394{
1395struct strbuf msgbuf = STRBUF_INIT;1396int shown_commit_line = 0;1397
1398graph_show_line_prefix(default_diffopt);1399
1400if (!graph)1401return;1402
1403/*1404* When showing a diff of a merge against each of its parents, we
1405* are called once for each parent without graph_update having been
1406* called. In this case, simply output a single padding line.
1407*/
1408if (graph_is_commit_finished(graph)) {1409graph_show_padding(graph);1410shown_commit_line = 1;1411}1412
1413while (!shown_commit_line && !graph_is_commit_finished(graph)) {1414shown_commit_line = graph_next_line(graph, &msgbuf);1415fwrite(msgbuf.buf, sizeof(char), msgbuf.len,1416graph->revs->diffopt.file);1417if (!shown_commit_line) {1418putc('\n', graph->revs->diffopt.file);1419graph_show_line_prefix(&graph->revs->diffopt);1420}1421strbuf_setlen(&msgbuf, 0);1422}1423
1424strbuf_release(&msgbuf);1425}
1426
1427void graph_show_oneline(struct git_graph *graph)1428{
1429struct strbuf msgbuf = STRBUF_INIT;1430
1431graph_show_line_prefix(default_diffopt);1432
1433if (!graph)1434return;1435
1436graph_next_line(graph, &msgbuf);1437fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file);1438strbuf_release(&msgbuf);1439}
1440
1441void graph_show_padding(struct git_graph *graph)1442{
1443struct strbuf msgbuf = STRBUF_INIT;1444
1445graph_show_line_prefix(default_diffopt);1446
1447if (!graph)1448return;1449
1450graph_padding_line(graph, &msgbuf);1451fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file);1452strbuf_release(&msgbuf);1453}
1454
1455int graph_show_remainder(struct git_graph *graph)1456{
1457struct strbuf msgbuf = STRBUF_INIT;1458int shown = 0;1459
1460graph_show_line_prefix(default_diffopt);1461
1462if (!graph)1463return 0;1464
1465if (graph_is_commit_finished(graph))1466return 0;1467
1468for (;;) {1469graph_next_line(graph, &msgbuf);1470fwrite(msgbuf.buf, sizeof(char), msgbuf.len,1471graph->revs->diffopt.file);1472strbuf_setlen(&msgbuf, 0);1473shown = 1;1474
1475if (!graph_is_commit_finished(graph)) {1476putc('\n', graph->revs->diffopt.file);1477graph_show_line_prefix(&graph->revs->diffopt);1478} else {1479break;1480}1481}1482strbuf_release(&msgbuf);1483
1484return shown;1485}
1486
1487static void graph_show_strbuf(struct git_graph *graph,1488FILE *file,1489struct strbuf const *sb)1490{
1491char *p;1492
1493/*1494* Print the strbuf line by line,
1495* and display the graph info before each line but the first.
1496*/
1497p = sb->buf;1498while (p) {1499size_t len;1500char *next_p = strchr(p, '\n');1501if (next_p) {1502next_p++;1503len = next_p - p;1504} else {1505len = (sb->buf + sb->len) - p;1506}1507fwrite(p, sizeof(char), len, file);1508if (next_p && *next_p != '\0')1509graph_show_oneline(graph);1510p = next_p;1511}1512}
1513
1514void graph_show_commit_msg(struct git_graph *graph,1515FILE *file,1516struct strbuf const *sb)1517{
1518int newline_terminated;1519
1520/*1521* Show the commit message
1522*/
1523graph_show_strbuf(graph, file, sb);1524
1525if (!graph)1526return;1527
1528newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');1529
1530/*1531* If there is more output needed for this commit, show it now
1532*/
1533if (!graph_is_commit_finished(graph)) {1534/*1535* If sb doesn't have a terminating newline, print one now,
1536* so we can start the remainder of the graph output on a
1537* new line.
1538*/
1539if (!newline_terminated)1540putc('\n', file);1541
1542graph_show_remainder(graph);1543
1544/*1545* If sb ends with a newline, our output should too.
1546*/
1547if (newline_terminated)1548putc('\n', file);1549}1550}
1551