Code

Improve parsing of the set command and variable arguments options
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 718e6c2d186254f5cca36a5fcee1742d7b9265ed..f56547455c50dcf170dbdb0f8424915a9db2c40e 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -12,6 +12,8 @@
  */
 
 #include "tig.h"
+#include "io.h"
+#include "graph.h"
 
 static void __NORETURN die(const char *err, ...);
 static void warn(const char *msg, ...);
@@ -59,6 +61,20 @@ struct menu_item {
 
 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
 
+enum graphic {
+       GRAPHIC_ASCII = 0,
+       GRAPHIC_DEFAULT,
+       GRAPHIC_UTF8
+};
+
+static const struct enum_map graphic_map[] = {
+#define GRAPHIC_(name) ENUM_MAP(#name, GRAPHIC_##name)
+       GRAPHIC_(ASCII),
+       GRAPHIC_(DEFAULT),
+       GRAPHIC_(UTF8)
+#undef GRAPHIC_
+};
+
 #define DATE_INFO \
        DATE_(NO), \
        DATE_(DEFAULT), \
@@ -192,455 +208,6 @@ get_author_initials(const char *author)
 }
 
 
-static bool
-argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
-{
-       int valuelen;
-
-       while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
-               bool advance = cmd[valuelen] != 0;
-
-               cmd[valuelen] = 0;
-               argv[(*argc)++] = chomp_string(cmd);
-               cmd = chomp_string(cmd + valuelen + advance);
-       }
-
-       if (*argc < SIZEOF_ARG)
-               argv[*argc] = NULL;
-       return *argc < SIZEOF_ARG;
-}
-
-static bool
-argv_from_env(const char **argv, const char *name)
-{
-       char *env = argv ? getenv(name) : NULL;
-       int argc = 0;
-
-       if (env && *env)
-               env = strdup(env);
-       return !env || argv_from_string(argv, &argc, env);
-}
-
-static void
-argv_free(const char *argv[])
-{
-       int argc;
-
-       if (!argv)
-               return;
-       for (argc = 0; argv[argc]; argc++)
-               free((void *) argv[argc]);
-       argv[0] = NULL;
-}
-
-static size_t
-argv_size(const char **argv)
-{
-       int argc = 0;
-
-       while (argv && argv[argc])
-               argc++;
-
-       return argc;
-}
-
-DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
-
-static bool
-argv_append(const char ***argv, const char *arg)
-{
-       size_t argc = argv_size(*argv);
-
-       if (!argv_realloc(argv, argc, 2))
-               return FALSE;
-
-       (*argv)[argc++] = strdup(arg);
-       (*argv)[argc] = NULL;
-       return TRUE;
-}
-
-static bool
-argv_append_array(const char ***dst_argv, const char *src_argv[])
-{
-       int i;
-
-       for (i = 0; src_argv && src_argv[i]; i++)
-               if (!argv_append(dst_argv, src_argv[i]))
-                       return FALSE;
-       return TRUE;
-}
-
-static bool
-argv_copy(const char ***dst, const char *src[])
-{
-       int argc;
-
-       for (argc = 0; src[argc]; argc++)
-               if (!argv_append(dst, src[argc]))
-                       return FALSE;
-       return TRUE;
-}
-
-
-/*
- * Executing external commands.
- */
-
-enum io_type {
-       IO_FD,                  /* File descriptor based IO. */
-       IO_BG,                  /* Execute command in the background. */
-       IO_FG,                  /* Execute command with same std{in,out,err}. */
-       IO_RD,                  /* Read only fork+exec IO. */
-       IO_WR,                  /* Write only fork+exec IO. */
-       IO_AP,                  /* Append fork+exec output to file. */
-};
-
-struct io {
-       int pipe;               /* Pipe end for reading or writing. */
-       pid_t pid;              /* PID of spawned process. */
-       int error;              /* Error status. */
-       char *buf;              /* Read buffer. */
-       size_t bufalloc;        /* Allocated buffer size. */
-       size_t bufsize;         /* Buffer content size. */
-       char *bufpos;           /* Current buffer position. */
-       unsigned int eof:1;     /* Has end of file been reached. */
-};
-
-static void
-io_init(struct io *io)
-{
-       memset(io, 0, sizeof(*io));
-       io->pipe = -1;
-}
-
-static bool
-io_open(struct io *io, const char *fmt, ...)
-{
-       char name[SIZEOF_STR] = "";
-       bool fits;
-       va_list args;
-
-       io_init(io);
-
-       va_start(args, fmt);
-       fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name);
-       va_end(args);
-
-       if (!fits) {
-               io->error = ENAMETOOLONG;
-               return FALSE;
-       }
-       io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
-       if (io->pipe == -1)
-               io->error = errno;
-       return io->pipe != -1;
-}
-
-static bool
-io_kill(struct io *io)
-{
-       return io->pid == 0 || kill(io->pid, SIGKILL) != -1;
-}
-
-static bool
-io_done(struct io *io)
-{
-       pid_t pid = io->pid;
-
-       if (io->pipe != -1)
-               close(io->pipe);
-       free(io->buf);
-       io_init(io);
-
-       while (pid > 0) {
-               int status;
-               pid_t waiting = waitpid(pid, &status, 0);
-
-               if (waiting < 0) {
-                       if (errno == EINTR)
-                               continue;
-                       io->error = errno;
-                       return FALSE;
-               }
-
-               return waiting == pid &&
-                      !WIFSIGNALED(status) &&
-                      WIFEXITED(status) &&
-                      !WEXITSTATUS(status);
-       }
-
-       return TRUE;
-}
-
-static bool
-io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...)
-{
-       int pipefds[2] = { -1, -1 };
-       va_list args;
-
-       io_init(io);
-
-       if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) {
-               io->error = errno;
-               return FALSE;
-       } else if (type == IO_AP) {
-               va_start(args, argv);
-               pipefds[1] = va_arg(args, int);
-               va_end(args);
-       }
-
-       if ((io->pid = fork())) {
-               if (io->pid == -1)
-                       io->error = errno;
-               if (pipefds[!(type == IO_WR)] != -1)
-                       close(pipefds[!(type == IO_WR)]);
-               if (io->pid != -1) {
-                       io->pipe = pipefds[!!(type == IO_WR)];
-                       return TRUE;
-               }
-
-       } else {
-               if (type != IO_FG) {
-                       int devnull = open("/dev/null", O_RDWR);
-                       int readfd  = type == IO_WR ? pipefds[0] : devnull;
-                       int writefd = (type == IO_RD || type == IO_AP)
-                                                       ? pipefds[1] : devnull;
-
-                       dup2(readfd,  STDIN_FILENO);
-                       dup2(writefd, STDOUT_FILENO);
-                       dup2(devnull, STDERR_FILENO);
-
-                       close(devnull);
-                       if (pipefds[0] != -1)
-                               close(pipefds[0]);
-                       if (pipefds[1] != -1)
-                               close(pipefds[1]);
-               }
-
-               if (dir && *dir && chdir(dir) == -1)
-                       exit(errno);
-
-               execvp(argv[0], (char *const*) argv);
-               exit(errno);
-       }
-
-       if (pipefds[!!(type == IO_WR)] != -1)
-               close(pipefds[!!(type == IO_WR)]);
-       return FALSE;
-}
-
-static bool
-io_complete(enum io_type type, const char **argv, const char *dir, int fd)
-{
-       struct io io;
-
-       return io_run(&io, type, dir, argv, fd) && io_done(&io);
-}
-
-static bool
-io_run_bg(const char **argv)
-{
-       return io_complete(IO_BG, argv, NULL, -1);
-}
-
-static bool
-io_run_fg(const char **argv, const char *dir)
-{
-       return io_complete(IO_FG, argv, dir, -1);
-}
-
-static bool
-io_run_append(const char **argv, int fd)
-{
-       return io_complete(IO_AP, argv, NULL, fd);
-}
-
-static bool
-io_eof(struct io *io)
-{
-       return io->eof;
-}
-
-static int
-io_error(struct io *io)
-{
-       return io->error;
-}
-
-static char *
-io_strerror(struct io *io)
-{
-       return strerror(io->error);
-}
-
-static bool
-io_can_read(struct io *io)
-{
-       struct timeval tv = { 0, 500 };
-       fd_set fds;
-
-       FD_ZERO(&fds);
-       FD_SET(io->pipe, &fds);
-
-       return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0;
-}
-
-static ssize_t
-io_read(struct io *io, void *buf, size_t bufsize)
-{
-       do {
-               ssize_t readsize = read(io->pipe, buf, bufsize);
-
-               if (readsize < 0 && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               else if (readsize == -1)
-                       io->error = errno;
-               else if (readsize == 0)
-                       io->eof = 1;
-               return readsize;
-       } while (1);
-}
-
-DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ)
-
-static char *
-io_get(struct io *io, int c, bool can_read)
-{
-       char *eol;
-       ssize_t readsize;
-
-       while (TRUE) {
-               if (io->bufsize > 0) {
-                       eol = memchr(io->bufpos, c, io->bufsize);
-                       if (eol) {
-                               char *line = io->bufpos;
-
-                               *eol = 0;
-                               io->bufpos = eol + 1;
-                               io->bufsize -= io->bufpos - line;
-                               return line;
-                       }
-               }
-
-               if (io_eof(io)) {
-                       if (io->bufsize) {
-                               io->bufpos[io->bufsize] = 0;
-                               io->bufsize = 0;
-                               return io->bufpos;
-                       }
-                       return NULL;
-               }
-
-               if (!can_read)
-                       return NULL;
-
-               if (io->bufsize > 0 && io->bufpos > io->buf)
-                       memmove(io->buf, io->bufpos, io->bufsize);
-
-               if (io->bufalloc == io->bufsize) {
-                       if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ))
-                               return NULL;
-                       io->bufalloc += BUFSIZ;
-               }
-
-               io->bufpos = io->buf;
-               readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
-               if (io_error(io))
-                       return NULL;
-               io->bufsize += readsize;
-       }
-}
-
-static bool
-io_write(struct io *io, const void *buf, size_t bufsize)
-{
-       size_t written = 0;
-
-       while (!io_error(io) && written < bufsize) {
-               ssize_t size;
-
-               size = write(io->pipe, buf + written, bufsize - written);
-               if (size < 0 && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               else if (size == -1)
-                       io->error = errno;
-               else
-                       written += size;
-       }
-
-       return written == bufsize;
-}
-
-static bool
-io_read_buf(struct io *io, char buf[], size_t bufsize)
-{
-       char *result = io_get(io, '\n', TRUE);
-
-       if (result) {
-               result = chomp_string(result);
-               string_ncopy_do(buf, bufsize, result, strlen(result));
-       }
-
-       return io_done(io) && result;
-}
-
-static bool
-io_run_buf(const char **argv, char buf[], size_t bufsize)
-{
-       struct io io;
-
-       return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize);
-}
-
-typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data);
-
-static int
-io_load(struct io *io, const char *separators,
-       io_read_fn read_property, void *data)
-{
-       char *name;
-       int state = OK;
-
-       while (state == OK && (name = io_get(io, '\n', TRUE))) {
-               char *value;
-               size_t namelen;
-               size_t valuelen;
-
-               name = chomp_string(name);
-               namelen = strcspn(name, separators);
-
-               if (name[namelen]) {
-                       name[namelen] = 0;
-                       value = chomp_string(name + namelen + 1);
-                       valuelen = strlen(value);
-
-               } else {
-                       value = "";
-                       valuelen = 0;
-               }
-
-               state = read_property(name, namelen, value, valuelen, data);
-       }
-
-       if (state != ERR && io_error(io))
-               state = ERR;
-       io_done(io);
-
-       return state;
-}
-
-static int
-io_run_load(const char **argv, const char *separators,
-           io_read_fn read_property, void *data)
-{
-       struct io io;
-
-       if (!io_run(&io, IO_RD, NULL, argv))
-               return ERR;
-       return io_load(&io, separators, read_property, data);
-}
-
-
 /*
  * User requests
  */
@@ -706,6 +273,7 @@ io_run_load(const char **argv, const char *separators,
        REQ_(TOGGLE_DATE,       "Toggle date display"), \
        REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
        REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
+       REQ_(TOGGLE_GRAPHIC,    "Toggle (line) graphics mode"), \
        REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
        REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
        REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
@@ -767,11 +335,11 @@ get_request(const char *name)
  */
 
 /* Option and state variables. */
+static enum graphic opt_line_graphics  = GRAPHIC_DEFAULT;
 static enum date opt_date              = DATE_DEFAULT;
 static enum author opt_author          = AUTHOR_DEFAULT;
+static bool opt_rev_graph              = TRUE;
 static bool opt_line_number            = FALSE;
-static bool opt_line_graphics          = TRUE;
-static bool opt_rev_graph              = FALSE;
 static bool opt_show_refs              = TRUE;
 static bool opt_untracked_dirs_content = TRUE;
 static int opt_num_interval            = 5;
@@ -797,6 +365,7 @@ static FILE *opt_tty                        = NULL;
 static const char **opt_diff_argv      = NULL;
 static const char **opt_rev_argv       = NULL;
 static const char **opt_file_argv      = NULL;
+static const char **opt_blame_argv     = NULL;
 
 #define is_initial_commit()    (!get_ref_head())
 #define is_head_commit(rev)    (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
@@ -865,7 +434,15 @@ LINE(STAT_UNSTAGED,"",                     COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(STAT_UNTRACKED,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(HELP_KEYMAP,  "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
 LINE(HELP_GROUP,   "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
-LINE(BLAME_ID,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
+LINE(BLAME_ID,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_0, "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_1, "",                 COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_2, "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_3, "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_4, "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_5, "",                 COLOR_WHITE,    COLOR_DEFAULT,  0), \
+LINE(GRAPH_LINE_6, "",                 COLOR_RED,      COLOR_DEFAULT,  0), \
+LINE(GRAPH_COMMIT, "",                 COLOR_BLUE,     COLOR_DEFAULT,  0)
 
 enum line_type {
 #define LINE(type, line, fg, bg, attr) \
@@ -1037,6 +614,7 @@ static struct keybinding default_keybindings[] = {
        { 'D',          REQ_TOGGLE_DATE },
        { 'A',          REQ_TOGGLE_AUTHOR },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
+       { '~',          REQ_TOGGLE_GRAPHIC },
        { 'F',          REQ_TOGGLE_REFS },
        { 'I',          REQ_TOGGLE_SORT_ORDER },
        { 'i',          REQ_TOGGLE_SORT_FIELD },
@@ -1356,6 +934,7 @@ add_builtin_run_requests(void)
        OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
        OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
        OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
+       OPT_ERR_(OUT_OF_MEMORY, "Out of memory"), \
        OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
        OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
        OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
@@ -1533,16 +1112,30 @@ parse_string(char *opt, const char *arg, size_t optsize)
        }
 }
 
+static enum option_code
+parse_args(const char ***args, const char *argv[])
+{
+       if (*args == NULL && !argv_copy(args, argv))
+               return OPT_ERR_OUT_OF_MEMORY;
+       return OPT_OK;
+}
+
 /* Wants: name = value */
 static enum option_code
 option_set_command(int argc, const char *argv[])
 {
-       if (argc != 3)
+       if (argc < 3)
                return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
 
        if (strcmp(argv[1], "="))
                return OPT_ERR_NO_VALUE_ASSIGNED;
 
+       if (!strcmp(argv[0], "blame-options"))
+               return parse_args(&opt_blame_argv, argv + 2);
+
+       if (argc != 3)
+               return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
+
        if (!strcmp(argv[0], "show-author"))
                return parse_enum(&opt_author, argv[2], author_map);
 
@@ -1559,7 +1152,7 @@ option_set_command(int argc, const char *argv[])
                return parse_bool(&opt_line_number, argv[2]);
 
        if (!strcmp(argv[0], "line-graphics"))
-               return parse_bool(&opt_line_graphics, argv[2]);
+               return parse_enum(&opt_line_graphics, argv[2], graphic_map);
 
        if (!strcmp(argv[0], "line-number-interval"))
                return parse_int(&opt_num_interval, argv[2], 1, 1024);
@@ -2000,7 +1593,7 @@ draw_text(struct view *view, enum line_type type, const char *string)
 }
 
 static bool
-draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
+draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
 {
        size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
        int max = view->width + view->yoffset - view->col;
@@ -2016,9 +1609,11 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
                waddch(view->win, graphic[i]);
 
        view->col += size;
-       if (size < max && skip <= size)
-               waddch(view->win, ' ');
-       view->col++;
+       if (separator) {
+               if (size < max && skip <= size)
+                       waddch(view->win, ' ');
+               view->col++;
+       }
 
        return view->width + view->yoffset <= view->col;
 }
@@ -2102,7 +1697,7 @@ draw_lineno(struct view *view, unsigned int lineno)
                view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
        else
                view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
-       return draw_graphic(view, LINE_DEFAULT, &separator, 1);
+       return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
 }
 
 static bool
@@ -2321,6 +1916,7 @@ redraw_display(bool clear)
        TOGGLE_(LINENO,    '.', "line numbers",      &opt_line_number, NULL) \
        TOGGLE_(DATE,      'D', "dates",             &opt_date,   date_map) \
        TOGGLE_(AUTHOR,    'A', "author names",      &opt_author, author_map) \
+       TOGGLE_(GRAPHIC,   '~', "graphics",          &opt_line_graphics, graphic_map) \
        TOGGLE_(REV_GRAPH, 'g', "revision graph",    &opt_rev_graph, NULL) \
        TOGGLE_(REFS,      'F', "reference display", &opt_show_refs, NULL)
 
@@ -2763,7 +2359,7 @@ format_arg(const char *name)
        } vars[] = {
 #define FORMAT_VAR(name, value, value_if_empty) \
        { name, STRING_SIZE(name), value, value_if_empty }
-               FORMAT_VAR("%(directory)",      opt_path,       ""),
+               FORMAT_VAR("%(directory)",      opt_path,       "."),
                FORMAT_VAR("%(file)",           opt_file,       ""),
                FORMAT_VAR("%(ref)",            opt_ref,        "HEAD"),
                FORMAT_VAR("%(head)",           ref_head,       ""),
@@ -2803,6 +2399,11 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                                break;
                        continue;
 
+               } else if (!strcmp(arg, "%(blameargs)")) {
+                       if (!argv_append_array(dst_argv, opt_blame_argv))
+                               break;
+                       continue;
+
                } else if (!strcmp(arg, "%(revargs)") ||
                           (first && !strcmp(arg, "%(commit)"))) {
                        if (!argv_append_array(dst_argv, opt_rev_argv))
@@ -2962,7 +2563,7 @@ update_view(struct view *view)
        if (!view->pipe)
                return TRUE;
 
-       if (!io_can_read(view->pipe)) {
+       if (!io_can_read(view->pipe, FALSE)) {
                if (view->lines == 0 && view_is_displayed(view)) {
                        time_t secs = time(NULL) - view->start_time;
 
@@ -3384,6 +2985,7 @@ view_driver(struct view *view, enum request request)
        case REQ_TOGGLE_LINENO:
        case REQ_TOGGLE_DATE:
        case REQ_TOGGLE_AUTHOR:
+       case REQ_TOGGLE_GRAPHIC:
        case REQ_TOGGLE_REV_GRAPH:
        case REQ_TOGGLE_REFS:
                toggle_option(request);
@@ -4600,7 +4202,7 @@ blame_read_file(struct view *view, const char *line, bool *read_file)
 {
        if (!line) {
                const char *blame_argv[] = {
-                       "git", "blame", "--incremental",
+                       "git", "blame", "%(blameargs)", "--incremental",
                                *opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
                };
 
@@ -6104,224 +5706,84 @@ static struct view_ops stage_ops = {
  * Revision graph
  */
 
-struct commit {
-       char id[SIZEOF_REV];            /* SHA1 ID. */
-       char title[128];                /* First line of the commit message. */
-       const char *author;             /* Author of the commit. */
-       struct time time;               /* Date from the author ident. */
-       struct ref_list *refs;          /* Repository references. */
-       chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
-       size_t graph_size;              /* The width of the graph array. */
-       bool has_parents;               /* Rewritten --parents seen. */
+static const enum line_type graph_colors[] = {
+       LINE_GRAPH_LINE_0,
+       LINE_GRAPH_LINE_1,
+       LINE_GRAPH_LINE_2,
+       LINE_GRAPH_LINE_3,
+       LINE_GRAPH_LINE_4,
+       LINE_GRAPH_LINE_5,
+       LINE_GRAPH_LINE_6,
 };
 
-/* 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;
-       unsigned int boundary:1;
-};
-
-/* 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)
+static enum line_type get_graph_color(struct graph_symbol *symbol)
 {
-       return graph->parents->size > 1;
+       if (symbol->commit)
+               return LINE_GRAPH_COMMIT;
+       assert(symbol->color < ARRAY_SIZE(graph_colors));
+       return graph_colors[symbol->color];
 }
 
-static inline void
-append_to_rev_graph(struct rev_graph *graph, chtype symbol)
+static bool
+draw_graph_utf8(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
 {
-       struct commit *commit = graph->commit;
+       const char *chars = graph_symbol_to_utf8(symbol);
 
-       if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
-               commit->graph[commit->graph_size++] = symbol;
+       return draw_text(view, color, chars + !!first); 
 }
 
-static void
-clear_rev_graph(struct rev_graph *graph)
-{
-       graph->boundary = 0;
-       graph->size = graph->pos = 0;
-       graph->commit = NULL;
-       memset(graph->parents, 0, sizeof(*graph->parents));
-}
-
-static void
-done_rev_graph(struct rev_graph *graph)
+static bool
+draw_graph_ascii(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
 {
-       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++;
-               }
-       }
+       const char *chars = graph_symbol_to_ascii(symbol);
 
-       clear_rev_graph(graph);
+       return draw_text(view, color, chars + !!first); 
 }
 
-static void
-push_rev_graph(struct rev_graph *graph, const char *parent)
+static bool
+draw_graph_chtype(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
 {
-       int i;
-
-       /* "Collapse" duplicate parents lines.
-        *
-        * FIXME: This needs to also update update the drawn graph but
-        * for now it just serves as a method for pruning graph lines. */
-       for (i = 0; i < graph->size; i++)
-               if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
-                       return;
+       const chtype *chars = graph_symbol_to_chtype(symbol);
 
-       if (graph->size < SIZEOF_REVITEMS) {
-               string_copy_rev(graph->rev[graph->size++], parent);
-       }
+       return draw_graphic(view, color, chars + !!first, 2 - !!first, FALSE); 
 }
 
-static chtype
-get_rev_graph_symbol(struct rev_graph *graph)
-{
-       chtype symbol;
-
-       if (graph->boundary)
-               symbol = REVGRAPH_BOUND;
-       else 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;
-}
+typedef bool (*draw_graph_fn)(struct view *, struct graph_symbol *, enum line_type, bool);
 
-static void
-draw_rev_graph(struct rev_graph *graph)
+static bool draw_graph(struct view *view, struct graph_canvas *canvas)
 {
-       struct rev_filler {
-               chtype separator, line;
-       };
-       enum { DEFAULT, RSHARP, RDIAG, LDIAG };
-       static struct rev_filler fillers[] = {
-               { ' ',  '|' },
-               { '`',  '.' },
-               { '\'', ' ' },
-               { '/',  ' ' },
+       static const draw_graph_fn fns[] = {
+               draw_graph_ascii,
+               draw_graph_chtype,
+               draw_graph_utf8
        };
-       chtype symbol = get_rev_graph_symbol(graph);
-       struct rev_filler *filler;
-       size_t i;
-
-       fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
-       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;
+       draw_graph_fn fn = fns[opt_line_graphics];
+       int 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;
+       for (i = 0; i < canvas->size; i++) {
+               struct graph_symbol *symbol = &canvas->symbols[i];
+               enum line_type color = get_graph_color(symbol);
 
-               push_rev_graph(graph->next, graph->rev[graph->pos]);
+               if (fn(view, symbol, color, i == 0))
+                       return TRUE;
        }
 
-       /* Interleave the new revision parent(s). */
-       for (i = 0; !graph->boundary && 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]);
+       return draw_text(view, LINE_MAIN_REVGRAPH, " ");
 }
 
-static void
-update_rev_graph(struct view *view, 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;
-
-       if (view->lines > 2)
-               view->line[view->lines - 3].dirty = 1;
-       if (view->lines > 1)
-               view->line[view->lines - 2].dirty = 1;
-       draw_rev_graph(graph->prev);
-       done_rev_graph(graph->prev->prev);
-}
-
-
 /*
  * Main view backend
  */
 
+struct commit {
+       char id[SIZEOF_REV];            /* SHA1 ID. */
+       char title[128];                /* First line of the commit message. */
+       const char *author;             /* Author of the commit. */
+       struct time time;               /* Date from the author ident. */
+       struct ref_list *refs;          /* Repository references. */
+       struct graph_canvas graph;      /* Ancestry chain graphics. */
+};
+
 static const char *main_argv[SIZEOF_ARG] = {
        "git", "log", "--no-color", "--pretty=raw", "--parents",
                "--topo-order", "%(diffargs)", "%(revargs)",
@@ -6342,8 +5804,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
        if (opt_author && draw_author(view, commit->author))
                return TRUE;
 
-       if (opt_rev_graph && commit->graph_size &&
-           draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
+       if (opt_rev_graph && draw_graph(view, &commit->graph))
                return TRUE;
 
        if (opt_show_refs && commit->refs) {
@@ -6384,13 +5845,11 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 static bool
 main_read(struct view *view, char *line)
 {
-       static struct rev_graph *graph = graph_stacks;
+       static struct graph graph;
        enum line_type type;
        struct commit *commit;
 
        if (!line) {
-               int i;
-
                if (!view->lines && !view->prev)
                        die("No revisions match the given arguments.");
                if (view->lines > 0) {
@@ -6399,38 +5858,30 @@ main_read(struct view *view, char *line)
                        if (!commit->author) {
                                view->lines--;
                                free(commit);
-                               graph->commit = NULL;
                        }
                }
-               update_rev_graph(view, graph);
 
-               for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
-                       clear_rev_graph(&graph_stacks[i]);
+               done_graph(&graph);
                return TRUE;
        }
 
        type = get_line_type(line);
        if (type == LINE_COMMIT) {
+               bool is_boundary;
+
                commit = calloc(1, sizeof(struct commit));
                if (!commit)
                        return FALSE;
 
                line += STRING_SIZE("commit ");
-               if (*line == '-') {
-                       graph->boundary = 1;
+               is_boundary = *line == '-';
+               if (is_boundary)
                        line++;
-               }
 
                string_copy_rev(commit->id, line);
                commit->refs = get_ref_list(commit->id);
-               graph->commit = commit;
                add_line_data(view, commit, LINE_MAIN_COMMIT);
-
-               while ((line = strchr(line, ' '))) {
-                       line++;
-                       push_rev_graph(graph->parents, line);
-                       commit->has_parents = TRUE;
-               }
+               graph_add_commit(&graph, &commit->graph, commit->id, line, is_boundary);
                return TRUE;
        }
 
@@ -6440,16 +5891,14 @@ main_read(struct view *view, char *line)
 
        switch (type) {
        case LINE_PARENT:
-               if (commit->has_parents)
-                       break;
-               push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
+               if (!graph.has_parents)
+                       graph_add_parent(&graph, line + STRING_SIZE("parent "));
                break;
 
        case LINE_AUTHOR:
                parse_author_line(line + STRING_SIZE("author "),
                                  &commit->author, &commit->time);
-               update_rev_graph(view, graph);
-               graph = graph->next;
+               graph_render_parents(&graph);
                break;
 
        default:
@@ -7267,7 +6716,7 @@ static const char usage[] =
 "\n"
 "Usage: tig        [options] [revs] [--] [paths]\n"
 "   or: tig show   [options] [revs] [--] [paths]\n"
-"   or: tig blame  [rev] path\n"
+"   or: tig blame  [options] [rev] [--] path\n"
 "   or: tig status\n"
 "   or: tig <      [git command output]\n"
 "\n"
@@ -7364,16 +6813,18 @@ parse_options(int argc, const char *argv[])
                return REQ_VIEW_STATUS;
 
        } else if (!strcmp(subcommand, "blame")) {
-               if (argc <= 2 || argc > 4)
+               filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv + 2);
+               filter_rev_parse(&opt_blame_argv, "--no-revs", "--flags", argv + 2);
+               filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv + 2);
+
+               if (!opt_file_argv || opt_file_argv[1] || (opt_rev_argv && opt_rev_argv[1]))
                        die("invalid number of options to blame\n\n%s", usage);
 
-               i = 2;
-               if (argc == 4) {
-                       string_ncopy(opt_ref, argv[i], strlen(argv[i]));
-                       i++;
+               if (opt_rev_argv) {
+                       string_ncopy(opt_ref, opt_rev_argv[0], strlen(opt_rev_argv[0]));
                }
 
-               string_ncopy(opt_file, argv[i], strlen(argv[i]));
+               string_ncopy(opt_file, opt_file_argv[0], strlen(opt_file_argv[0]));
                return REQ_VIEW_BLAME;
 
        } else if (!strcmp(subcommand, "show")) {