X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/1dcb3bec828736c4a9d290d81b03e10aad1eaac8..446a5c3610dc9960923e7d9a14365949cb29a0ab:/tig.c diff --git a/tig.c b/tig.c index 2431f66..34a6358 100644 --- a/tig.c +++ b/tig.c @@ -60,10 +60,16 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define SIZEOF_STR 1024 /* Default string size. */ #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */ #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL */ -#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */ -/* Size of rev graph with no "padding" columns */ -#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2)) +/* Revision graph */ + +#define REVGRAPH_INIT 'I' +#define REVGRAPH_MERGE 'M' +#define REVGRAPH_BRANCH '+' +#define REVGRAPH_COMMIT '*' +#define REVGRAPH_LINE '|' + +#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */ /* This color name can be used to refer to the default term colors. */ #define COLOR_DEFAULT (-1) @@ -1922,6 +1928,7 @@ alloc_error: report("Allocation failure"); end: + view->ops->read(view, NULL); end_update(view); return FALSE; } @@ -2370,6 +2377,9 @@ pager_read(struct view *view, char *data) { struct line *line = &view->line[view->lines]; + if (!data) + return TRUE; + line->data = strdup(data); if (!line->data) return FALSE; @@ -2480,7 +2490,7 @@ tree_compare_entry(enum line_type type1, char *name1, static bool tree_read(struct view *view, char *text) { - size_t textlen = strlen(text); + size_t textlen = text ? strlen(text) : 0; char buf[SIZEOF_STR]; unsigned long pos; enum line_type type; @@ -2659,7 +2669,7 @@ static struct view_ops blob_ops = { /* - * Main view backend + * Revision graph */ struct commit { @@ -2672,6 +2682,193 @@ struct commit { size_t graph_size; /* The width of the graph array. */ }; +/* Size of rev graph with no "padding" columns */ +#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2)) + +struct rev_graph { + struct rev_graph *prev, *next, *parents; + char rev[SIZEOF_REVITEMS][SIZEOF_REV]; + size_t size; + struct commit *commit; + size_t pos; +}; + +/* Parents of the commit being visualized. */ +static struct rev_graph graph_parents[4]; + +/* The current stack of revisions on the graph. */ +static struct rev_graph graph_stacks[4] = { + { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] }, + { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] }, + { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] }, + { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] }, +}; + +static inline bool +graph_parent_is_merge(struct rev_graph *graph) +{ + return graph->parents->size > 1; +} + +static inline void +append_to_rev_graph(struct rev_graph *graph, chtype symbol) +{ + struct commit *commit = graph->commit; + + if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1) + commit->graph[commit->graph_size++] = symbol; +} + +static void +done_rev_graph(struct rev_graph *graph) +{ + if (graph_parent_is_merge(graph) && + graph->pos < graph->size - 1 && + graph->next->size == graph->size + graph->parents->size - 1) { + size_t i = graph->pos + graph->parents->size - 1; + + graph->commit->graph_size = i * 2; + while (i < graph->next->size - 1) { + append_to_rev_graph(graph, ' '); + append_to_rev_graph(graph, '\\'); + i++; + } + } + + graph->size = graph->pos = 0; + graph->commit = NULL; + memset(graph->parents, 0, sizeof(*graph->parents)); +} + +static void +push_rev_graph(struct rev_graph *graph, char *parent) +{ + /* Combine duplicate parents lines. */ + if (graph->size > 0 && + !strncmp(graph->rev[graph->size - 1], parent, SIZEOF_REV)) + return; + + if (graph->size < SIZEOF_REVITEMS) { + string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV); + } +} + +static chtype +get_rev_graph_symbol(struct rev_graph *graph) +{ + chtype symbol; + + if (graph->parents->size == 0) + symbol = REVGRAPH_INIT; + else if (graph_parent_is_merge(graph)) + symbol = REVGRAPH_MERGE; + else if (graph->pos >= graph->size) + symbol = REVGRAPH_BRANCH; + else + symbol = REVGRAPH_COMMIT; + + return symbol; +} + +static void +draw_rev_graph(struct rev_graph *graph) +{ + struct rev_filler { + chtype separator, line; + }; + enum { DEFAULT, RSHARP, RDIAG, LDIAG }; + static struct rev_filler fillers[] = { + { ' ', REVGRAPH_LINE }, + { '`', '.' }, + { '\'', ' ' }, + { '/', ' ' }, + }; + chtype symbol = get_rev_graph_symbol(graph); + struct rev_filler *filler; + size_t i; + + filler = &fillers[DEFAULT]; + + for (i = 0; i < graph->pos; i++) { + append_to_rev_graph(graph, filler->line); + if (graph_parent_is_merge(graph->prev) && + graph->prev->pos == i) + filler = &fillers[RSHARP]; + + append_to_rev_graph(graph, filler->separator); + } + + /* Place the symbol for this revision. */ + append_to_rev_graph(graph, symbol); + + if (graph->prev->size > graph->size) + filler = &fillers[RDIAG]; + else + filler = &fillers[DEFAULT]; + + i++; + + for (; i < graph->size; i++) { + append_to_rev_graph(graph, filler->separator); + append_to_rev_graph(graph, filler->line); + if (graph_parent_is_merge(graph->prev) && + i < graph->prev->pos + graph->parents->size) + filler = &fillers[RSHARP]; + if (graph->prev->size > graph->size) + filler = &fillers[LDIAG]; + } + + if (graph->prev->size > graph->size) { + append_to_rev_graph(graph, filler->separator); + if (filler->line != ' ') + append_to_rev_graph(graph, filler->line); + } +} + +/* Prepare the next rev graph */ +static void +prepare_rev_graph(struct rev_graph *graph) +{ + size_t i; + + /* First, traverse all lines of revisions up to the active one. */ + for (graph->pos = 0; graph->pos < graph->size; graph->pos++) { + if (!strcmp(graph->rev[graph->pos], graph->commit->id)) + break; + + push_rev_graph(graph->next, graph->rev[graph->pos]); + } + + /* Interleave the new revision parent(s). */ + for (i = 0; i < graph->parents->size; i++) + push_rev_graph(graph->next, graph->parents->rev[i]); + + /* Lastly, put any remaining revisions. */ + for (i = graph->pos + 1; i < graph->size; i++) + push_rev_graph(graph->next, graph->rev[i]); +} + +static void +update_rev_graph(struct rev_graph *graph) +{ + /* If this is the finalizing update ... */ + if (graph->commit) + prepare_rev_graph(graph); + + /* Graph visualization needs a one rev look-ahead, + * so the first update doesn't visualize anything. */ + if (!graph->prev->commit) + return; + + draw_rev_graph(graph->prev); + done_rev_graph(graph->prev->prev); +} + + +/* + * Main view backend + */ + static bool main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected) { @@ -2739,6 +2936,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select for (i = 0; i < commit->graph_size; i++) waddch(view->win, commit->graph[i]); + waddch(view->win, ' '); col += commit->graph_size + 1; } @@ -2779,97 +2977,22 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select return TRUE; } - -struct rev_stack { - char rev[SIZEOF_REVITEMS][SIZEOF_REV]; - size_t size; -}; - -/* The current stack of revisions on the graph. */ -static struct rev_stack graph_stacks[2]; -static unsigned int graph_stack_no; - -/* Parents of the commit being visualized. */ -static struct rev_stack graph_parents; - -static void -push_rev_stack(struct rev_stack *stack, char *parent) -{ - fprintf(stderr, " (%s)", parent); - - /* Combine duplicate parents lines. */ - if (stack->size > 0 && - !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV)) - return; - - if (stack->size < SIZEOF_REVITEMS) { - string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV); - } -} - -void -update_rev_graph(struct commit *commit) -{ - struct rev_stack *stack = &graph_stacks[graph_stack_no++ & 1]; - struct rev_stack *graph = &graph_stacks[graph_stack_no & 1]; - chtype symbol; - size_t stackpos = 0; - size_t i; - - // FIXME: Initial commit ... assert(rev_graph_commit == commit); - fprintf(stderr, "\n%p <%s> ", graph, commit->id); - - /* First traverse all lines of revisions up to the active one. */ - for (stackpos = 0; stackpos < stack->size; stackpos++) { - if (!strcmp(stack->rev[stackpos], commit->id)) { - while (stackpos + 1 < stack->size && - !strcmp(stack->rev[stackpos + 1], commit->id)) - stackpos++; - break; - } - - push_rev_stack(graph, stack->rev[stackpos]); - commit->graph[commit->graph_size++] = ACS_VLINE; - commit->graph[commit->graph_size++] = ' '; - } - - assert(commit->graph_size < ARRAY_SIZE(commit->graph)); - - for (i = 0; i < graph_parents.size; i++) - push_rev_stack(graph, graph_parents.rev[i]); - - /* Place the symbol for this commit. */ - if (graph_parents.size == 0) - symbol = 'I'; - else if (graph_parents.size > 1) - symbol = 'M'; - else if (stackpos >= stack->size) - symbol = '+'; - else - symbol = '*'; - - commit->graph[commit->graph_size++] = symbol; - - stackpos++; - - /* FIXME: Moving branches left and right when collapsing a branch. */ - while (stackpos < stack->size) { - push_rev_stack(graph, stack->rev[stackpos++]); - commit->graph[commit->graph_size++] = ' '; - commit->graph[commit->graph_size++] = ACS_VLINE; - } - - stack->size = graph_parents.size = 0; -} - /* Reads git log --pretty=raw output and parses it into the commit struct. */ static bool main_read(struct view *view, char *line) { - enum line_type type = get_line_type(line); + static struct rev_graph *graph = graph_stacks; + enum line_type type; struct commit *commit = view->lines ? view->line[view->lines - 1].data : NULL; + if (!line) { + update_rev_graph(graph); + return TRUE; + } + + type = get_line_type(line); + switch (type) { case LINE_COMMIT: commit = calloc(1, sizeof(struct commit)); @@ -2881,13 +3004,13 @@ main_read(struct view *view, char *line) view->line[view->lines++].data = commit; string_copy(commit->id, line); commit->refs = get_refs(commit->id); - fprintf(stderr, "\n%p [%s]", &graph_stacks[graph_stack_no], commit->id); + graph->commit = commit; break; case LINE_PARENT: if (commit) { line += STRING_SIZE("parent "); - push_rev_stack(&graph_parents, line); + push_rev_graph(graph->parents, line); } break; @@ -2899,7 +3022,8 @@ main_read(struct view *view, char *line) if (!commit) break; - update_rev_graph(commit); + update_rev_graph(graph); + graph = graph->next; if (end) { char *email = end + 1;