Add test cases for git-am
[gitweb.git] / graph.c
diff --git a/graph.c b/graph.c
index 616e18b13f496cd97bce5333b6c7655ae3cfa2f0..26b8c5209e280697cc35ffd5313fe3e79681fc43 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -54,12 +54,22 @@ struct git_graph {
         * The commit currently being processed
         */
        struct commit *commit;
+       /* The rev-info used for the current traversal */
+       struct rev_info *revs;
        /*
-        * The number of parents this commit has.
-        * (Stored so we don't have to walk over them each time we need
-        * this number)
+        * 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().
         */
        int num_parents;
+       /*
+        * 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;
        /*
         * The next expansion row to print
         * when state is GRAPH_PRE_COMMIT
@@ -119,10 +129,11 @@ struct git_graph {
        int *new_mapping;
 };
 
-struct git_graph *graph_init(void)
+struct git_graph *graph_init(struct rev_info *opt)
 {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
        graph->commit = NULL;
+       graph->revs = opt;
        graph->num_parents = 0;
        graph->expansion_row = 0;
        graph->state = GRAPH_PADDING;
@@ -174,6 +185,28 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
                                      sizeof(int) * 2 * graph->column_capacity);
 }
 
+/*
+ * Returns 1 if the commit will be printed in the graph output,
+ * and 0 otherwise.
+ */
+static int graph_is_interesting(struct git_graph *graph, struct commit *commit)
+{
+       /*
+        * 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) {
+               if (commit->object.flags & CHILD_SHOWN)
+                       return 1;
+       }
+
+       /*
+        * Uninteresting and pruned commits won't be printed
+        */
+       return (commit->object.flags & (UNINTERESTING | TREESAME)) ? 0 : 1;
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
                                          struct commit *commit,
                                          int *mapping_index)
@@ -181,9 +214,9 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
        int i;
 
        /*
-        * Ignore uinteresting and pruned commits
+        * Ignore uinteresting commits
         */
-       if (commit->object.flags & (UNINTERESTING | TREESAME))
+       if (!graph_is_interesting(graph, commit))
                return;
 
        /*
@@ -207,13 +240,48 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
        graph->num_new_columns++;
 }
 
+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;
+
+       /*
+        * Even if the current commit has no parents to be printed, it
+        * still takes up a column for itself.
+        */
+       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;
+}
+
 static void graph_update_columns(struct git_graph *graph)
 {
        struct commit_list *parent;
        struct column *tmp_columns;
        int max_new_columns;
        int mapping_idx;
-       int i, seen_this;
+       int i, seen_this, is_commit_in_columns;
 
        /*
         * Swap graph->columns with graph->new_columns
@@ -259,17 +327,20 @@ static void graph_update_columns(struct git_graph *graph)
         */
        seen_this = 0;
        mapping_idx = 0;
+       is_commit_in_columns = 1;
        for (i = 0; i <= graph->num_columns; i++) {
                struct commit *col_commit;
                if (i == graph->num_columns) {
                        if (seen_this)
                                break;
+                       is_commit_in_columns = 0;
                        col_commit = graph->commit;
                } else {
                        col_commit = graph->columns[i].commit;
                }
 
                if (col_commit == graph->commit) {
+                       int old_mapping_idx = mapping_idx;
                        seen_this = 1;
                        for (parent = graph->commit->parents;
                             parent;
@@ -278,6 +349,14 @@ static void graph_update_columns(struct git_graph *graph)
                                                              parent->item,
                                                              &mapping_idx);
                        }
+                       /*
+                        * 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;
                } else {
                        graph_insert_into_new_columns(graph, col_commit,
                                                      &mapping_idx);
@@ -290,6 +369,11 @@ static void graph_update_columns(struct git_graph *graph)
        while (graph->mapping_size > 1 &&
               graph->mapping[graph->mapping_size - 1] < 0)
                graph->mapping_size--;
+
+       /*
+        * Compute graph->width for this commit
+        */
+       graph_update_width(graph, is_commit_in_columns);
 }
 
 void graph_update(struct git_graph *graph, struct commit *commit)
@@ -302,11 +386,13 @@ void graph_update(struct git_graph *graph, struct commit *commit)
        graph->commit = commit;
 
        /*
-        * Count how many parents this commit has
+        * Count how many interesting parents this commit has
         */
        graph->num_parents = 0;
-       for (parent = commit->parents; parent; parent = parent->next)
-               graph->num_parents++;
+       for (parent = commit->parents; parent; parent = parent->next) {
+               if (graph_is_interesting(graph, parent->item))
+                       graph->num_parents++;
+       }
 
        /*
         * Call graph_update_columns() to update
@@ -368,22 +454,12 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
         *
         * This way, fields printed to the right of the graph will remain
         * aligned for the entire commit.
-        *
-        * This computation results in 3 extra space to the right in most
-        * cases, but only 1 extra space if the commit doesn't have any
-        * children that have already been displayed in the graph (i.e.,
-        * if the current commit isn't in graph->columns).
         */
-       size_t extra;
-       size_t final_width = graph->num_columns + graph->num_parents;
-       if (graph->num_parents < 1)
-               final_width++;
-       final_width *= 2;
-
-       if (sb->len >= final_width)
+       int extra;
+       if (sb->len >= graph->width)
                return;
 
-       extra = final_width - sb->len;
+       extra = graph->width - sb->len;
        strbuf_addf(sb, "%*s", (int) extra, "");
 }
 
@@ -477,6 +553,51 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
                graph->state = GRAPH_COMMIT;
 }
 
+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 'M' for merge commits
+        *
+        * Note that we don't check graph->num_parents to determine if the
+        * commit is a merge, since that only tracks the number of
+        * "interesting" parents.  We want to print 'M' for merge commits
+        * even if they have less than 2 interesting parents.
+        */
+       if (graph->commit->parents != NULL &&
+           graph->commit->parents->next != NULL) {
+               strbuf_addch(sb, 'M');
+               return;
+       }
+
+       /*
+        * Print '*' in all other cases
+        */
+       strbuf_addch(sb, '*');
+}
+
 void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
@@ -502,10 +623,7 @@ void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 
                if (col_commit == graph->commit) {
                        seen_this = 1;
-                       if (graph->num_parents > 1)
-                               strbuf_addch(sb, 'M');
-                       else
-                               strbuf_addch(sb, '*');
+                       graph_output_commit_char(graph, sb);
 
                        if (graph->num_parents < 2)
                                strbuf_addch(sb, ' ');