#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. */
+/* Size of rev graph with no "padding" columns */
+#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
+
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
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? */
};
LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
static void
move_view(struct view *view, enum request request)
{
- bool scroll = FALSE;
+ int scroll_steps = 0;
int steps;
switch (request) {
/* Check whether the view needs to be scrolled */
if (view->lineno < view->offset ||
view->lineno >= view->offset + view->height) {
+ scroll_steps = steps;
if (steps < 0 && -steps > view->offset) {
- steps = -view->offset;
+ scroll_steps = -view->offset;
} else if (steps > 0) {
if (view->lineno == view->lines - 1 &&
view->lines > view->height) {
- steps = view->lines - view->offset - 1;
- if (steps >= view->height)
- steps -= view->height - 1;
+ scroll_steps = view->lines - view->offset - 1;
+ if (scroll_steps >= view->height)
+ scroll_steps -= view->height - 1;
}
}
-
- scroll = TRUE;
}
if (!view_is_displayed(view)) {
if (ABS(steps) < view->height)
draw_view_line(view, view->lineno - steps - view->offset);
- if (scroll) {
- do_scroll_view(view, steps);
+ if (scroll_steps) {
+ do_scroll_view(view, scroll_steps);
return;
}
* Tree backend
*/
-/* Parse output from git ls-tree:
+/* Parse output from git-ls-tree(1):
*
* 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
* 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
static bool
tree_enter(struct view *view, struct line *line)
{
- enum open_flags flags = OPEN_DEFAULT;
- char *data = line->data;
+ enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
enum request request;
switch (line->type) {
} 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)) {
break;
case LINE_TREE_FILE:
- /* This causes the blob view to become split, and not having it
- * in the tree dir case will make the blob view automatically
- * disappear when moving to a different directory. */
- flags |= OPEN_SPLIT;
request = REQ_VIEW_BLOB;
break;
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;
+
+ 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);
- string_ncopy(view->ref, text + STRING_SIZE("100644 blob "), 40);
- string_copy(ref_blob, view->ref);
+ } else if (line->type != LINE_TREE_DIR) {
+ return;
}
+
+ string_ncopy(view->ref, text, 40);
}
static struct view_ops tree_ops = {
*/
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. */
return TRUE;
}
+
+struct rev_stack {
+ struct rev_stack *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_stack graph_parents[3];
+
+/* The current stack of revisions on the graph. */
+static struct rev_stack 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_stack *graph)
+{
+ return graph->parents->size > 1;
+}
+
+static inline void
+append_to_rev_graph(struct rev_stack *stack, chtype symbol)
+{
+ if (stack->commit->graph_size < ARRAY_SIZE(stack->commit->graph) - 1)
+ stack->commit->graph[stack->commit->graph_size++] = symbol;
+}
+
+static void
+done_rev_graph(struct rev_stack *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_stack(struct rev_stack *stack, char *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);
+ }
+}
+
+static void
+draw_rev_graph(struct rev_stack *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_stack *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_stack(graph->next, graph->rev[graph->pos]);
+ }
+
+ for (i = 0; i < graph->parents->size; i++)
+ push_rev_stack(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_stack(graph->next, graph->rev[i]);
+
+ draw_rev_graph(graph);
+ done_rev_graph(graph->prev);
+}
+
/* Reads git log --pretty=raw output and parses it into the commit struct. */
static bool
main_read(struct view *view, char *line)
{
+ static struct rev_stack *graph = graph_stacks;
enum line_type type = get_line_type(line);
struct commit *commit = view->lines
? view->line[view->lines - 1].data : NULL;
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_stack(graph->parents, line);
+ }
break;
case LINE_AUTHOR:
if (!commit)
break;
+ update_rev_graph(graph);
+ graph = graph->next;
+
if (end) {
char *email = end + 1;