Use 'graph' instead of 'stack' in the rev graph code
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 66c5f76..96ab67f 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -59,6 +59,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 */
+
+/* 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. */
@@ -109,7 +119,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 struct ref {
        char *name;             /* Ref name; tag or head names are shortened. */
-       char id[41];            /* Commit SHA1 ID */
+       char id[SIZEOF_REV];    /* Commit SHA1 ID */
        unsigned int tag:1;     /* Is it a tag? */
        unsigned int next:1;    /* For ref lists: are there more refs? */
 };
@@ -2447,7 +2457,7 @@ static struct view_ops pager_ops = {
  * Tree backend
  */
 
-/* Parse output from git ls-tree:
+/* Parse output from git-ls-tree(1):
  *
  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26        Makefile
  * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6        README
@@ -2558,7 +2568,6 @@ static bool
 tree_enter(struct view *view, struct line *line)
 {
        enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
-       char *data = line->data;
        enum request request;
 
        switch (line->type) {
@@ -2577,6 +2586,7 @@ tree_enter(struct view *view, struct line *line)
                } else {
                        size_t pathlen = strlen(opt_path);
                        size_t origlen = pathlen;
+                       char *data = line->data;
                        char *basename = data + SIZEOF_TREE_ATTR;
 
                        if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
@@ -2601,29 +2611,27 @@ tree_enter(struct view *view, struct line *line)
 
        open_view(view, request, flags);
 
-       if (!VIEW(request)->pipe)
-               return TRUE;
-
-       /* For tree views insert the path to the parent as the first line. */
-       if (request == REQ_VIEW_BLOB) {
-               /* Mirror what is showed in the title bar. */
-               string_ncopy(ref_blob, data + STRING_SIZE("100644 blob "), 40);
-               string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
-               return TRUE;
-       }
-
        return TRUE;
 }
 
 static void
 tree_select(struct view *view, struct line *line)
 {
-       if (line->type == LINE_TREE_DIR || line->type == LINE_TREE_FILE) {
-               char *text = line->data;
+       char *text = line->data;
 
-               string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
-               string_copy(ref_blob, view->ref);
+       text += STRING_SIZE("100644 blob ");
+
+       if (line->type == LINE_TREE_FILE) {
+               string_ncopy(ref_blob, text, 40);
+               /* Also update the blob view's ref, since all there must always
+                * be in sync. */
+               string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
+
+       } else if (line->type != LINE_TREE_DIR) {
+               return;
        }
+
+       string_ncopy(view->ref, text, 40);
 }
 
 static struct view_ops tree_ops = {
@@ -2657,11 +2665,11 @@ static struct view_ops blob_ops = {
 
 
 /*
- * Main view backend
+ * Revision graph
  */
 
 struct commit {
-       char id[41];                    /* SHA1 ID. */
+       char id[SIZEOF_REV];            /* SHA1 ID. */
        char title[75];                 /* First line of the commit message. */
        char author[75];                /* Author of the commit. */
        struct tm time;                 /* Date from the author ident. */
@@ -2670,6 +2678,165 @@ 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[3];
+
+/* The current stack of revisions on the graph. */
+static struct rev_graph graph_stacks[3] = {
+       { &graph_stacks[2], &graph_stacks[1], &graph_parents[0] },
+       { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
+       { &graph_stacks[1], &graph_stacks[0], &graph_parents[2] },
+};
+
+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)
+{
+       if (graph->commit->graph_size < ARRAY_SIZE(graph->commit->graph) - 1)
+               graph->commit->graph[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 void
+draw_rev_graph(struct rev_graph *graph)
+{
+       chtype symbol, separator, line;
+       size_t i;
+
+       /* Place the symbol for this commit. */
+       if (graph->parents->size == 0)
+               symbol = REVGRAPH_INIT;
+       else if (graph->parents->size > 1)
+               symbol = REVGRAPH_MERGE;
+       else if (graph->pos >= graph->size)
+               symbol = REVGRAPH_BRANCH;
+       else
+               symbol = REVGRAPH_COMMIT;
+
+       separator = ' ';
+       line = REVGRAPH_LINE;
+
+       for (i = 0; i < graph->pos; i++) {
+               append_to_rev_graph(graph, line);
+               if (graph_parent_is_merge(graph->prev) &&
+                   graph->prev->pos == i) {
+                       separator = '`';
+                       line = '.';
+               }
+               append_to_rev_graph(graph, separator);
+       }
+
+       append_to_rev_graph(graph, symbol);
+
+       if (graph->prev->size > graph->size) {
+               separator = '\'';
+               line = ' ';
+       } else {
+               separator = ' ';
+               line = REVGRAPH_LINE;
+       }
+       i++;
+
+       for (; i < graph->size; i++) {
+               append_to_rev_graph(graph, separator);
+               append_to_rev_graph(graph, line);
+               if (graph_parent_is_merge(graph->prev)) {
+                       if (i < graph->prev->pos + graph->parents->size) {
+                               separator = '`';
+                               line = '.';
+                       }
+               }
+               if (graph->prev->size > graph->size) {
+                       separator = '/';
+                       line = ' ';
+               }
+       }
+
+       if (graph->prev->size > graph->size) {
+               append_to_rev_graph(graph, separator);
+               if (line != ' ')
+                       append_to_rev_graph(graph, line);
+       }
+}
+
+void
+update_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]);
+       }
+
+       for (i = 0; i < graph->parents->size; i++)
+               push_rev_graph(graph->next, graph->parents->rev[i]);
+
+       /* FIXME: Moving branches left and right when collapsing a branch. */
+       for (i = graph->pos + 1; i < graph->size; i++)
+               push_rev_graph(graph->next, graph->rev[i]);
+
+       draw_rev_graph(graph);
+       done_rev_graph(graph->prev);
+}
+
+
+/*
+ * Main view backend
+ */
+
 static bool
 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
@@ -2781,6 +2948,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
 static bool
 main_read(struct view *view, char *line)
 {
+       static struct rev_graph *graph = graph_stacks;
        enum line_type type = get_line_type(line);
        struct commit *commit = view->lines
                              ? view->line[view->lines - 1].data : NULL;
@@ -2796,7 +2964,14 @@ main_read(struct view *view, char *line)
                view->line[view->lines++].data = commit;
                string_copy(commit->id, line);
                commit->refs = get_refs(commit->id);
-               commit->graph[commit->graph_size++] = ACS_LTEE;
+               graph->commit = commit;
+               break;
+
+       case LINE_PARENT:
+               if (commit) {
+                       line += STRING_SIZE("parent ");
+                       push_rev_graph(graph->parents, line);
+               }
                break;
 
        case LINE_AUTHOR:
@@ -2807,6 +2982,9 @@ main_read(struct view *view, char *line)
                if (!commit)
                        break;
 
+               update_rev_graph(graph);
+               graph = graph->next;
+
                if (end) {
                        char *email = end + 1;