Code

get_author_initials: various fixes
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 01b55d74b81994856e3da0cf779e52c97a36615e..b0032ea67a9fd869b658fa3e984c38007a6250d6 100644 (file)
--- a/tig.c
+++ b/tig.c
  * GNU General Public License for more details.
  */
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#ifndef TIG_VERSION
-#define TIG_VERSION "unknown-version"
-#endif
-
-#ifndef DEBUG
-#define NDEBUG
-#endif
-
-#include <assert.h>
-#include <errno.h>
-#include <ctype.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <sys/select.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <time.h>
-#include <fcntl.h>
-
-#include <regex.h>
-
-#include <locale.h>
-#include <langinfo.h>
-#include <iconv.h>
-
-/* ncurses(3): Must be defined to have extended wide-character functions. */
-#define _XOPEN_SOURCE_EXTENDED
-
-#ifdef HAVE_NCURSESW_NCURSES_H
-#include <ncursesw/ncurses.h>
-#else
-#ifdef HAVE_NCURSES_NCURSES_H
-#include <ncurses/ncurses.h>
-#else
-#include <ncurses.h>
-#endif
-#endif
-
-#if __GNUC__ >= 3
-#define __NORETURN __attribute__((__noreturn__))
-#else
-#define __NORETURN
-#endif
+#include "tig.h"
+#include "io.h"
+#include "graph.h"
 
 static void __NORETURN die(const char *err, ...);
 static void warn(const char *msg, ...);
 static void report(const char *msg, ...);
 
-#define ABS(x)         ((x) >= 0  ? (x) : -(x))
-#define MIN(x, y)      ((x) < (y) ? (x) :  (y))
-#define MAX(x, y)      ((x) > (y) ? (x) :  (y))
-
-#define ARRAY_SIZE(x)  (sizeof(x) / sizeof(x[0]))
-#define STRING_SIZE(x) (sizeof(x) - 1)
-
-#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_ARG     32      /* Default argument array size. */
-
-/* Revision graph */
-
-#define REVGRAPH_INIT  'I'
-#define REVGRAPH_MERGE 'M'
-#define REVGRAPH_BRANCH        '+'
-#define REVGRAPH_COMMIT        '*'
-#define REVGRAPH_BOUND '^'
-
-#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)
-
-#define ICONV_NONE     ((iconv_t) -1)
-#ifndef ICONV_CONST
-#define ICONV_CONST    /* nothing */
-#endif
-
-/* The format and size of the date column in the main view. */
-#define DATE_FORMAT    "%Y-%m-%d %H:%M"
-#define DATE_COLS      STRING_SIZE("2006-04-29 14:21 ")
-#define DATE_SHORT_COLS        STRING_SIZE("2006-04-29 ")
-
-#define ID_COLS                8
-#define AUTHOR_COLS    19
-
-#define MIN_VIEW_HEIGHT 4
-
-#define NULL_ID                "0000000000000000000000000000000000000000"
-
-#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
-
-/* Some ASCII-shorthands fitted into the ncurses namespace. */
-#define KEY_CTL(x)     ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
-#define KEY_TAB                '\t'
-#define KEY_RETURN     '\r'
-#define KEY_ESC                27
-
 
 struct ref {
        char id[SIZEOF_REV];    /* Commit SHA1 ID */
@@ -161,374 +61,21 @@ struct menu_item {
 
 static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
 
-/*
- * Allocation helpers ... Entering macro hell to never be seen again.
- */
-
-#define DEFINE_ALLOCATOR(name, type, chunk_size)                               \
-static type *                                                                  \
-name(type **mem, size_t size, size_t increase)                                 \
-{                                                                              \
-       size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
-       size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
-       type *tmp = *mem;                                                       \
-                                                                               \
-       if (mem == NULL || num_chunks != num_chunks_new) {                      \
-               tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
-               if (tmp)                                                        \
-                       *mem = tmp;                                             \
-       }                                                                       \
-                                                                               \
-       return tmp;                                                             \
-}
-
-/*
- * String helpers
- */
-
-static inline void
-string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
-{
-       if (srclen > dstlen - 1)
-               srclen = dstlen - 1;
-
-       strncpy(dst, src, srclen);
-       dst[srclen] = 0;
-}
-
-/* Shorthands for safely copying into a fixed buffer. */
-
-#define string_copy(dst, src) \
-       string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
-
-#define string_ncopy(dst, src, srclen) \
-       string_ncopy_do(dst, sizeof(dst), src, srclen)
-
-#define string_copy_rev(dst, src) \
-       string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1)
-
-#define string_add(dst, from, src) \
-       string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
-
-static size_t
-string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
-{
-       size_t size, pos;
-
-       for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
-               if (src[pos] == '\t') {
-                       size_t expanded = tabsize - (size % tabsize);
-
-                       if (expanded + size >= dstlen - 1)
-                               expanded = dstlen - size - 1;
-                       memcpy(dst + size, "        ", expanded);
-                       size += expanded;
-               } else {
-                       dst[size++] = src[pos];
-               }
-       }
-
-       dst[size] = 0;
-       return pos;
-}
-
-static char *
-chomp_string(char *name)
-{
-       int namelen;
-
-       while (isspace(*name))
-               name++;
-
-       namelen = strlen(name) - 1;
-       while (namelen > 0 && isspace(name[namelen]))
-               name[namelen--] = 0;
-
-       return name;
-}
-
-static bool
-string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
-{
-       va_list args;
-       size_t pos = bufpos ? *bufpos : 0;
-
-       va_start(args, fmt);
-       pos += vsnprintf(buf + pos, bufsize - pos, fmt, args);
-       va_end(args);
-
-       if (bufpos)
-               *bufpos = pos;
-
-       return pos >= bufsize ? FALSE : TRUE;
-}
-
-#define string_format(buf, fmt, args...) \
-       string_nformat(buf, sizeof(buf), NULL, fmt, args)
-
-#define string_format_from(buf, from, fmt, args...) \
-       string_nformat(buf, sizeof(buf), from, fmt, args)
-
-static int
-string_enum_compare(const char *str1, const char *str2, int len)
-{
-       size_t i;
-
-#define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
-
-       /* Diff-Header == DIFF_HEADER */
-       for (i = 0; i < len; i++) {
-               if (toupper(str1[i]) == toupper(str2[i]))
-                       continue;
-
-               if (string_enum_sep(str1[i]) &&
-                   string_enum_sep(str2[i]))
-                       continue;
-
-               return str1[i] - str2[i];
-       }
-
-       return 0;
-}
-
-#define enum_equals(entry, str, len) \
-       ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
-
-struct enum_map {
-       const char *name;
-       int namelen;
-       int value;
-};
-
-#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
-
-static char *
-enum_map_name(const char *name, size_t namelen)
-{
-       static char buf[SIZEOF_STR];
-       int bufpos;
-
-       for (bufpos = 0; bufpos <= namelen; bufpos++) {
-               buf[bufpos] = tolower(name[bufpos]);
-               if (buf[bufpos] == '_')
-                       buf[bufpos] = '-';
-       }
-
-       buf[bufpos] = 0;
-       return buf;
-}
-
-#define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
-
-static bool
-map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
-{
-       size_t namelen = strlen(name);
-       int i;
-
-       for (i = 0; i < map_size; i++)
-               if (enum_equals(map[i], name, namelen)) {
-                       *value = map[i].value;
-                       return TRUE;
-               }
+#define GRAPHIC_ENUM(_) \
+       _(GRAPHIC, ASCII), \
+       _(GRAPHIC, DEFAULT), \
+       _(GRAPHIC, UTF_8)
 
-       return FALSE;
-}
+DEFINE_ENUM(graphic, GRAPHIC_ENUM);
 
-#define map_enum(attr, map, name) \
-       map_enum_do(map, ARRAY_SIZE(map), attr, name)
+#define DATE_ENUM(_) \
+       _(DATE, NO), \
+       _(DATE, DEFAULT), \
+       _(DATE, LOCAL), \
+       _(DATE, RELATIVE), \
+       _(DATE, SHORT)
 
-#define prefixcmp(str1, str2) \
-       strncmp(str1, str2, STRING_SIZE(str2))
-
-static inline int
-suffixcmp(const char *str, int slen, const char *suffix)
-{
-       size_t len = slen >= 0 ? slen : strlen(str);
-       size_t suffixlen = strlen(suffix);
-
-       return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
-}
-
-
-/*
- * Unicode / UTF-8 handling
- *
- * NOTE: Much of the following code for dealing with Unicode is derived from
- * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
- * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
- */
-
-static inline int
-unicode_width(unsigned long c, int tab_size)
-{
-       if (c >= 0x1100 &&
-          (c <= 0x115f                         /* Hangul Jamo */
-           || c == 0x2329
-           || c == 0x232a
-           || (c >= 0x2e80  && c <= 0xa4cf && c != 0x303f)
-                                               /* CJK ... Yi */
-           || (c >= 0xac00  && c <= 0xd7a3)    /* Hangul Syllables */
-           || (c >= 0xf900  && c <= 0xfaff)    /* CJK Compatibility Ideographs */
-           || (c >= 0xfe30  && c <= 0xfe6f)    /* CJK Compatibility Forms */
-           || (c >= 0xff00  && c <= 0xff60)    /* Fullwidth Forms */
-           || (c >= 0xffe0  && c <= 0xffe6)
-           || (c >= 0x20000 && c <= 0x2fffd)
-           || (c >= 0x30000 && c <= 0x3fffd)))
-               return 2;
-
-       if (c == '\t')
-               return tab_size;
-
-       return 1;
-}
-
-/* Number of bytes used for encoding a UTF-8 character indexed by first byte.
- * Illegal bytes are set one. */
-static const unsigned char utf8_bytes[256] = {
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
-       2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
-       3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
-};
-
-static inline unsigned char
-utf8_char_length(const char *string, const char *end)
-{
-       int c = *(unsigned char *) string;
-
-       return utf8_bytes[c];
-}
-
-/* Decode UTF-8 multi-byte representation into a Unicode character. */
-static inline unsigned long
-utf8_to_unicode(const char *string, size_t length)
-{
-       unsigned long unicode;
-
-       switch (length) {
-       case 1:
-               unicode  =   string[0];
-               break;
-       case 2:
-               unicode  =  (string[0] & 0x1f) << 6;
-               unicode +=  (string[1] & 0x3f);
-               break;
-       case 3:
-               unicode  =  (string[0] & 0x0f) << 12;
-               unicode += ((string[1] & 0x3f) << 6);
-               unicode +=  (string[2] & 0x3f);
-               break;
-       case 4:
-               unicode  =  (string[0] & 0x0f) << 18;
-               unicode += ((string[1] & 0x3f) << 12);
-               unicode += ((string[2] & 0x3f) << 6);
-               unicode +=  (string[3] & 0x3f);
-               break;
-       case 5:
-               unicode  =  (string[0] & 0x0f) << 24;
-               unicode += ((string[1] & 0x3f) << 18);
-               unicode += ((string[2] & 0x3f) << 12);
-               unicode += ((string[3] & 0x3f) << 6);
-               unicode +=  (string[4] & 0x3f);
-               break;
-       case 6:
-               unicode  =  (string[0] & 0x01) << 30;
-               unicode += ((string[1] & 0x3f) << 24);
-               unicode += ((string[2] & 0x3f) << 18);
-               unicode += ((string[3] & 0x3f) << 12);
-               unicode += ((string[4] & 0x3f) << 6);
-               unicode +=  (string[5] & 0x3f);
-               break;
-       default:
-               return 0;
-       }
-
-       /* Invalid characters could return the special 0xfffd value but NUL
-        * should be just as good. */
-       return unicode > 0xffff ? 0 : unicode;
-}
-
-/* Calculates how much of string can be shown within the given maximum width
- * and sets trimmed parameter to non-zero value if all of string could not be
- * shown. If the reserve flag is TRUE, it will reserve at least one
- * trailing character, which can be useful when drawing a delimiter.
- *
- * Returns the number of bytes to output from string to satisfy max_width. */
-static size_t
-utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
-{
-       const char *string = *start;
-       const char *end = strchr(string, '\0');
-       unsigned char last_bytes = 0;
-       size_t last_ucwidth = 0;
-
-       *width = 0;
-       *trimmed = 0;
-
-       while (string < end) {
-               unsigned char bytes = utf8_char_length(string, end);
-               size_t ucwidth;
-               unsigned long unicode;
-
-               if (string + bytes > end)
-                       break;
-
-               /* Change representation to figure out whether
-                * it is a single- or double-width character. */
-
-               unicode = utf8_to_unicode(string, bytes);
-               /* FIXME: Graceful handling of invalid Unicode character. */
-               if (!unicode)
-                       break;
-
-               ucwidth = unicode_width(unicode, tab_size);
-               if (skip > 0) {
-                       skip -= ucwidth <= skip ? ucwidth : skip;
-                       *start += bytes;
-               }
-               *width  += ucwidth;
-               if (*width > max_width) {
-                       *trimmed = 1;
-                       *width -= ucwidth;
-                       if (reserve && *width == max_width) {
-                               string -= last_bytes;
-                               *width -= last_ucwidth;
-                       }
-                       break;
-               }
-
-               string  += bytes;
-               last_bytes = ucwidth ? bytes : 0;
-               last_ucwidth = ucwidth;
-       }
-
-       return string - *start;
-}
-
-
-#define DATE_INFO \
-       DATE_(NO), \
-       DATE_(DEFAULT), \
-       DATE_(LOCAL), \
-       DATE_(RELATIVE), \
-       DATE_(SHORT)
-
-enum date {
-#define DATE_(name) DATE_##name
-       DATE_INFO
-#undef DATE_
-};
-
-static const struct enum_map date_map[] = {
-#define DATE_(name) ENUM_MAP(#name, DATE_##name)
-       DATE_INFO
-#undef DATE_
-};
+DEFINE_ENUM(date, DATE_ENUM);
 
 struct time {
        time_t sec;
@@ -590,504 +137,86 @@ mkdate(const struct time *time, enum date date)
 }
 
 
-#define AUTHOR_VALUES \
-       AUTHOR_(NO), \
-       AUTHOR_(FULL), \
-       AUTHOR_(ABBREVIATED)
+#define AUTHOR_ENUM(_) \
+       _(AUTHOR, NO), \
+       _(AUTHOR, FULL), \
+       _(AUTHOR, ABBREVIATED)
 
-enum author {
-#define AUTHOR_(name) AUTHOR_##name
-       AUTHOR_VALUES,
-#undef AUTHOR_
-       AUTHOR_DEFAULT = AUTHOR_FULL
-};
-
-static const struct enum_map author_map[] = {
-#define AUTHOR_(name) ENUM_MAP(#name, AUTHOR_##name)
-       AUTHOR_VALUES
-#undef AUTHOR_
-};
+DEFINE_ENUM(author, AUTHOR_ENUM);
 
 static const char *
-get_author_initials(const char *author)
-{
-       static char initials[AUTHOR_COLS * 6 + 1];
-       size_t pos = 0;
-       const char *end = strchr(author, '\0');
-
-#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
-
-       memset(initials, 0, sizeof(initials));
-       while (author < end) {
-               unsigned char bytes;
-               size_t i;
-
-               while (is_initial_sep(*author))
-                       author++;
-
-               bytes = utf8_char_length(author, end);
-               if (bytes < sizeof(initials) - 1 - pos) {
-                       while (bytes--) {
-                               initials[pos++] = *author++;
-                       }
-               }
-
-               for (i = pos; author < end && !is_initial_sep(*author); author++) {
-                       if (i < sizeof(initials) - 1)
-                               initials[i++] = *author;
-               }
-
-               initials[i++] = 0;
-       }
-
-       return initials;
-}
-
-
-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);
-}
-
-static int
-io_load(struct io *io, const char *separators,
-       int (*read_property)(char *, size_t, char *, size_t))
+get_author_initials(const char *author)
 {
-       char *name;
-       int state = OK;
+       static char initials[AUTHOR_COLS * 6 + 1];
+       size_t pos = 0;
+       const char *end = strchr(author, '\0');
 
-       while (state == OK && (name = io_get(io, '\n', TRUE))) {
-               char *value;
-               size_t namelen;
-               size_t valuelen;
+#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@' || (c) == '-')
 
-               name = chomp_string(name);
-               namelen = strcspn(name, separators);
+       memset(initials, 0, sizeof(initials));
+       while (author < end) {
+               unsigned char bytes;
+               size_t i;
 
-               if (name[namelen]) {
-                       name[namelen] = 0;
-                       value = chomp_string(name + namelen + 1);
-                       valuelen = strlen(value);
+               while (author < end && is_initial_sep(*author))
+                       author++;
 
-               } else {
-                       value = "";
-                       valuelen = 0;
+               bytes = utf8_char_length(author, end);
+               if (bytes >= sizeof(initials) - 1 - pos)
+                       break;
+               while (bytes--) {
+                       initials[pos++] = *author++;
                }
 
-               state = read_property(name, namelen, value, valuelen);
-       }
+               i = pos;
+               while (author < end && !is_initial_sep(*author)) {
+                       bytes = utf8_char_length(author, end);
+                       if (bytes >= sizeof(initials) - 1 - i) {
+                               while (author < end && !is_initial_sep(*author))
+                                       author++;
+                               break;
+                       }
+                       while (bytes--) {
+                               initials[i++] = *author++;
+                       }
+               }
 
-       if (state != ERR && io_error(io))
-               state = ERR;
-       io_done(io);
+               initials[i++] = 0;
+       }
 
-       return state;
+       return initials;
 }
 
-static int
-io_run_load(const char **argv, const char *separators,
-           int (*read_property)(char *, size_t, char *, size_t))
+#define author_trim(cols) (cols == 0 || cols > 5)
+
+static const char *
+mkauthor(const char *text, int cols, enum author author)
 {
-       struct io io;
+       bool trim = author_trim(cols);
+       bool abbreviate = author == AUTHOR_ABBREVIATED || !trim;
 
-       if (!io_run(&io, IO_RD, NULL, argv))
-               return ERR;
-       return io_load(&io, separators, read_property);
+       if (author == AUTHOR_NO)
+               return "";
+       if (abbreviate && text)
+               return get_author_initials(text);
+       return text;
+}
+
+static const char *
+mkmode(mode_t mode)
+{
+       if (S_ISDIR(mode))
+               return "drwxr-xr-x";
+       else if (S_ISLNK(mode))
+               return "lrwxrwxrwx";
+       else if (S_ISGITLINK(mode))
+               return "m---------";
+       else if (S_ISREG(mode) && mode & S_IXUSR)
+               return "-rwxr-xr-x";
+       else if (S_ISREG(mode))
+               return "-rw-r--r--";
+       else
+               return "----------";
 }
 
 
@@ -1156,6 +285,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"), \
@@ -1217,11 +347,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 enum author opt_author          = AUTHOR_FULL;
+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;
@@ -1244,9 +374,10 @@ static char opt_git_dir[SIZEOF_STR]       = "";
 static signed char opt_is_inside_work_tree     = -1; /* set to TRUE or FALSE */
 static char opt_editor[SIZEOF_STR]     = "";
 static FILE *opt_tty                   = NULL;
-static const char **opt_diff_args      = NULL;
-static const char **opt_rev_args       = NULL;
-static const char **opt_file_args      = 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)))
@@ -1315,7 +446,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) \
@@ -1487,6 +626,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 },
@@ -1499,40 +639,30 @@ static struct keybinding default_keybindings[] = {
        { 'e',          REQ_EDIT },
 };
 
-#define KEYMAP_INFO \
-       KEYMAP_(GENERIC), \
-       KEYMAP_(MAIN), \
-       KEYMAP_(DIFF), \
-       KEYMAP_(LOG), \
-       KEYMAP_(TREE), \
-       KEYMAP_(BLOB), \
-       KEYMAP_(BLAME), \
-       KEYMAP_(BRANCH), \
-       KEYMAP_(PAGER), \
-       KEYMAP_(HELP), \
-       KEYMAP_(STATUS), \
-       KEYMAP_(STAGE)
-
-enum keymap {
-#define KEYMAP_(name) KEYMAP_##name
-       KEYMAP_INFO
-#undef KEYMAP_
-};
+#define KEYMAP_ENUM(_) \
+       _(KEYMAP, GENERIC), \
+       _(KEYMAP, MAIN), \
+       _(KEYMAP, DIFF), \
+       _(KEYMAP, LOG), \
+       _(KEYMAP, TREE), \
+       _(KEYMAP, BLOB), \
+       _(KEYMAP, BLAME), \
+       _(KEYMAP, BRANCH), \
+       _(KEYMAP, PAGER), \
+       _(KEYMAP, HELP), \
+       _(KEYMAP, STATUS), \
+       _(KEYMAP, STAGE)
 
-static const struct enum_map keymap_table[] = {
-#define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
-       KEYMAP_INFO
-#undef KEYMAP_
-};
+DEFINE_ENUM(keymap, KEYMAP_ENUM);
 
-#define set_keymap(map, name) map_enum(map, keymap_table, name)
+#define set_keymap(map, name) map_enum(map, keymap_map, name)
 
 struct keybinding_table {
        struct keybinding *data;
        size_t size;
 };
 
-static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
+static struct keybinding_table keybindings[ARRAY_SIZE(keymap_map)];
 
 static void
 add_keybinding(enum keymap keymap, enum request request, int key)
@@ -1800,9 +930,37 @@ add_builtin_run_requests(void)
  * User config file handling.
  */
 
-static int   config_lineno;
-static bool  config_errors;
-static const char *config_msg;
+#define OPT_ERR_INFO \
+       OPT_ERR_(INTEGER_VALUE_OUT_OF_BOUND, "Integer value out of bound"), \
+       OPT_ERR_(INVALID_STEP_VALUE, "Invalid step value"), \
+       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"), \
+       OPT_ERR_(UNKNOWN_COLOR_NAME, "Unknown color name"), \
+       OPT_ERR_(UNKNOWN_KEY, "Unknown key"), \
+       OPT_ERR_(UNKNOWN_KEY_MAP, "Unknown key map"), \
+       OPT_ERR_(UNKNOWN_OPTION_COMMAND, "Unknown option command"), \
+       OPT_ERR_(UNKNOWN_REQUEST_NAME, "Unknown request name"), \
+       OPT_ERR_(UNKNOWN_VARIABLE_NAME, "Unknown variable name"), \
+       OPT_ERR_(UNMATCHED_QUOTATION, "Unmatched quotation"), \
+       OPT_ERR_(WRONG_NUMBER_OF_ARGUMENTS, "Wrong number of arguments"),
+
+enum option_code {
+#define OPT_ERR_(name, msg) OPT_ERR_ ## name
+       OPT_ERR_INFO
+#undef OPT_ERR_
+       OPT_OK
+};
+
+static const char *option_errors[] = {
+#define OPT_ERR_(name, msg) msg
+       OPT_ERR_INFO
+#undef OPT_ERR_
+};
 
 static const struct enum_map color_map[] = {
 #define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
@@ -1830,39 +988,37 @@ static const struct enum_map attr_map[] = {
 
 #define set_attribute(attr, name)      map_enum(attr, attr_map, name)
 
-static int parse_step(double *opt, const char *arg)
+static enum option_code
+parse_step(double *opt, const char *arg)
 {
        *opt = atoi(arg);
        if (!strchr(arg, '%'))
-               return OK;
+               return OPT_OK;
 
        /* "Shift down" so 100% and 1 does not conflict. */
        *opt = (*opt - 1) / 100;
        if (*opt >= 1.0) {
                *opt = 0.99;
-               config_msg = "Step value larger than 100%";
-               return ERR;
+               return OPT_ERR_INVALID_STEP_VALUE;
        }
        if (*opt < 0.0) {
                *opt = 1;
-               config_msg = "Invalid step value";
-               return ERR;
+               return OPT_ERR_INVALID_STEP_VALUE;
        }
-       return OK;
+       return OPT_OK;
 }
 
-static int
+static enum option_code
 parse_int(int *opt, const char *arg, int min, int max)
 {
        int value = atoi(arg);
 
        if (min <= value && value <= max) {
                *opt = value;
-               return OK;
+               return OPT_OK;
        }
 
-       config_msg = "Integer value out of bound";
-       return ERR;
+       return OPT_ERR_INTEGER_VALUE_OUT_OF_BOUND;
 }
 
 static bool
@@ -1876,15 +1032,13 @@ set_color(int *color, const char *name)
 }
 
 /* Wants: object fgcolor bgcolor [attribute] */
-static int
+static enum option_code
 option_color_command(int argc, const char *argv[])
 {
        struct line_info *info;
 
-       if (argc < 3) {
-               config_msg = "Wrong number of arguments given to color command";
-               return ERR;
-       }
+       if (argc < 3)
+               return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
 
        info = get_line_info(argv[0]);
        if (!info) {
@@ -1895,61 +1049,55 @@ option_color_command(int argc, const char *argv[])
                };
                int index;
 
-               if (!map_enum(&index, obsolete, argv[0])) {
-                       config_msg = "Unknown color name";
-                       return ERR;
-               }
+               if (!map_enum(&index, obsolete, argv[0]))
+                       return OPT_ERR_UNKNOWN_COLOR_NAME;
                info = &line_info[index];
        }
 
        if (!set_color(&info->fg, argv[1]) ||
-           !set_color(&info->bg, argv[2])) {
-               config_msg = "Unknown color";
-               return ERR;
-       }
+           !set_color(&info->bg, argv[2]))
+               return OPT_ERR_UNKNOWN_COLOR;
 
        info->attr = 0;
        while (argc-- > 3) {
                int attr;
 
-               if (!set_attribute(&attr, argv[argc])) {
-                       config_msg = "Unknown attribute";
-                       return ERR;
-               }
+               if (!set_attribute(&attr, argv[argc]))
+                       return OPT_ERR_UNKNOWN_ATTRIBUTE;
                info->attr |= attr;
        }
 
-       return OK;
+       return OPT_OK;
 }
 
-static int parse_bool(bool *opt, const char *arg)
+static enum option_code
+parse_bool(bool *opt, const char *arg)
 {
        *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
                ? TRUE : FALSE;
-       return OK;
+       return OPT_OK;
 }
 
-static int parse_enum_do(unsigned int *opt, const char *arg,
-                        const struct enum_map *map, size_t map_size)
+static enum option_code
+parse_enum_do(unsigned int *opt, const char *arg,
+             const struct enum_map *map, size_t map_size)
 {
        bool is_true;
 
        assert(map_size > 1);
 
        if (map_enum_do(map, map_size, (int *) opt, arg))
-               return OK;
-
-       if (parse_bool(&is_true, arg) != OK)
-               return ERR;
+               return OPT_OK;
 
+       parse_bool(&is_true, arg);
        *opt = is_true ? map[1].value : map[0].value;
-       return OK;
+       return OPT_OK;
 }
 
 #define parse_enum(opt, arg, map) \
        parse_enum_do(opt, arg, map, ARRAY_SIZE(map))
 
-static int
+static enum option_code
 parse_string(char *opt, const char *arg, size_t optsize)
 {
        int arglen = strlen(arg);
@@ -1957,30 +1105,38 @@ parse_string(char *opt, const char *arg, size_t optsize)
        switch (arg[0]) {
        case '\"':
        case '\'':
-               if (arglen == 1 || arg[arglen - 1] != arg[0]) {
-                       config_msg = "Unmatched quotation";
-                       return ERR;
-               }
+               if (arglen == 1 || arg[arglen - 1] != arg[0])
+                       return OPT_ERR_UNMATCHED_QUOTATION;
                arg += 1; arglen -= 2;
        default:
                string_ncopy_do(opt, optsize, arg, arglen);
-               return OK;
+               return OPT_OK;
        }
 }
 
+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 int
+static enum option_code
 option_set_command(int argc, const char *argv[])
 {
-       if (argc != 3) {
-               config_msg = "Wrong number of arguments given to set command";
-               return ERR;
-       }
+       if (argc < 3)
+               return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
 
-       if (strcmp(argv[1], "=")) {
-               config_msg = "No value assigned";
-               return ERR;
-       }
+       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);
@@ -1998,7 +1154,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);
@@ -2021,33 +1177,26 @@ option_set_command(int argc, const char *argv[])
        if (!strcmp(argv[0], "status-untracked-dirs"))
                return parse_bool(&opt_untracked_dirs_content, argv[2]);
 
-       config_msg = "Unknown variable name";
-       return ERR;
+       return OPT_ERR_UNKNOWN_VARIABLE_NAME;
 }
 
 /* Wants: mode request key */
-static int
+static enum option_code
 option_bind_command(int argc, const char *argv[])
 {
        enum request request;
        int keymap = -1;
        int key;
 
-       if (argc < 3) {
-               config_msg = "Wrong number of arguments given to bind command";
-               return ERR;
-       }
+       if (argc < 3)
+               return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
 
-       if (!set_keymap(&keymap, argv[0])) {
-               config_msg = "Unknown key map";
-               return ERR;
-       }
+       if (!set_keymap(&keymap, argv[0]))
+               return OPT_ERR_UNKNOWN_KEY_MAP;
 
        key = get_key_value(argv[1]);
-       if (key == ERR) {
-               config_msg = "Unknown key";
-               return ERR;
-       }
+       if (key == ERR)
+               return OPT_ERR_UNKNOWN_KEY;
 
        request = get_request(argv[2]);
        if (request == REQ_UNKNOWN) {
@@ -2061,32 +1210,27 @@ option_bind_command(int argc, const char *argv[])
                if (map_enum(&alias, obsolete, argv[2])) {
                        if (alias != REQ_NONE)
                                add_keybinding(keymap, alias, key);
-                       config_msg = "Obsolete request name";
-                       return ERR;
+                       return OPT_ERR_OBSOLETE_REQUEST_NAME;
                }
        }
        if (request == REQ_UNKNOWN && *argv[2]++ == '!')
                request = add_run_request(keymap, key, argv + 2);
-       if (request == REQ_UNKNOWN) {
-               config_msg = "Unknown request name";
-               return ERR;
-       }
+       if (request == REQ_UNKNOWN)
+               return OPT_ERR_UNKNOWN_REQUEST_NAME;
 
        add_keybinding(keymap, request, key);
 
-       return OK;
+       return OPT_OK;
 }
 
-static int
+static enum option_code
 set_option(const char *opt, char *value)
 {
        const char *argv[SIZEOF_ARG];
        int argc = 0;
 
-       if (!argv_from_string(argv, &argc, value)) {
-               config_msg = "Too many option arguments";
-               return ERR;
-       }
+       if (!argv_from_string(argv, &argc, value))
+               return OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
 
        if (!strcmp(opt, "color"))
                return option_color_command(argc, argv);
@@ -2097,17 +1241,21 @@ set_option(const char *opt, char *value)
        if (!strcmp(opt, "bind"))
                return option_bind_command(argc, argv);
 
-       config_msg = "Unknown option command";
-       return ERR;
+       return OPT_ERR_UNKNOWN_OPTION_COMMAND;
 }
 
+struct config_state {
+       int lineno;
+       bool errors;
+};
+
 static int
-read_option(char *opt, size_t optlen, char *value, size_t valuelen)
+read_option(char *opt, size_t optlen, char *value, size_t valuelen, void *data)
 {
-       int status = OK;
+       struct config_state *config = data;
+       enum option_code status = OPT_ERR_NO_OPTION_VALUE;
 
-       config_lineno++;
-       config_msg = "Internal error";
+       config->lineno++;
 
        /* Check for comment markers, since read_properties() will
         * only ensure opt and value are split at first " \t". */
@@ -2115,11 +1263,7 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen)
        if (optlen == 0)
                return OK;
 
-       if (opt[optlen] != 0) {
-               config_msg = "No option value";
-               status = ERR;
-
-       }  else {
+       if (opt[optlen] == 0) {
                /* Look for comment endings in the value. */
                size_t len = strcspn(value, "#");
 
@@ -2131,10 +1275,10 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen)
                status = set_option(opt, value);
        }
 
-       if (status == ERR) {
+       if (status != OPT_OK) {
                warn("Error on line %d, near '%.*s': %s",
-                    config_lineno, (int) optlen, opt, config_msg);
-               config_errors = TRUE;
+                    config->lineno, (int) optlen, opt, option_errors[status]);
+               config->errors = TRUE;
        }
 
        /* Always keep going if errors are encountered. */
@@ -2144,17 +1288,15 @@ read_option(char *opt, size_t optlen, char *value, size_t valuelen)
 static void
 load_option_file(const char *path)
 {
+       struct config_state config = { 0, FALSE };
        struct io io;
 
        /* It's OK that the file doesn't exist. */
        if (!io_open(&io, "%s", path))
                return;
 
-       config_lineno = 0;
-       config_errors = FALSE;
-
-       if (io_load(&io, " \t", read_option) == ERR ||
-           config_errors == TRUE)
+       if (io_load(&io, " \t", read_option, &config) == ERR ||
+           config.errors == TRUE)
                warn("Errors while loading %s.", path);
 }
 
@@ -2182,14 +1324,14 @@ load_options(void)
         * that conflict with keybindings. */
        add_builtin_run_requests();
 
-       if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) {
+       if (!opt_diff_argv && tig_diff_opts && *tig_diff_opts) {
                static const char *diff_opts[SIZEOF_ARG] = { NULL };
                int argc = 0;
 
                if (!string_format(buf, "%s", tig_diff_opts) ||
                    !argv_from_string(diff_opts, &argc, buf))
                        die("TIG_DIFF_OPTS contains too many arguments");
-               else if (!argv_copy(&opt_diff_args, diff_opts))
+               else if (!argv_copy(&opt_diff_argv, diff_opts))
                        die("Failed to format TIG_DIFF_OPTS arguments");
        }
 
@@ -2206,6 +1348,8 @@ struct view_ops;
 
 /* The display array of active views and the index of the current view. */
 static struct view *display[2];
+static WINDOW *display_win[2];
+static WINDOW *display_title[2];
 static unsigned int current_view;
 
 #define foreach_displayed_view(view, i) \
@@ -2236,7 +1380,6 @@ enum view_type {
 struct view {
        enum view_type type;    /* View type */
        const char *name;       /* View name */
-       const char *cmd_env;    /* Command line set via environment */
        const char *id;         /* Points to either of ref_{head,commit,blob} */
 
        struct view_ops *ops;   /* View operations */
@@ -2249,7 +1392,6 @@ struct view {
 
        int height, width;      /* The width and height of the main window */
        WINDOW *win;            /* The main window */
-       WINDOW *title;          /* The title window living below the main window */
 
        /* Navigation */
        unsigned long offset;   /* Offset of the window top */
@@ -2289,13 +1431,20 @@ struct view {
        time_t update_secs;
 };
 
+enum open_flags {
+       OPEN_DEFAULT = 0,       /* Use default view switching. */
+       OPEN_SPLIT = 1,         /* Split current view. */
+       OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
+       OPEN_REFRESH = 16,      /* Refresh view using previous command. */
+       OPEN_PREPARED = 32,     /* Open already prepared command. */
+       OPEN_EXTRA = 64,        /* Open extra data from command. */
+};
+
 struct view_ops {
        /* What type of content being displayed. Used in the title bar. */
        const char *type;
-       /* Default command arguments. */
-       const char **argv;
        /* Open and reads in all view content. */
-       bool (*open)(struct view *view);
+       bool (*open)(struct view *view, enum open_flags flags);
        /* Read one line; updates view->line. */
        bool (*read)(struct view *view, char *data);
        /* Draw one line; @lineno must be < view->height. */
@@ -2306,8 +1455,6 @@ struct view_ops {
        bool (*grep)(struct view *view, struct line *line);
        /* Select line */
        void (*select)(struct view *view, struct line *line);
-       /* Prepare view for loading */
-       bool (*prepare)(struct view *view);
 };
 
 static struct view_ops blame_ops;
@@ -2322,11 +1469,11 @@ static struct view_ops status_ops;
 static struct view_ops tree_ops;
 static struct view_ops branch_ops;
 
-#define VIEW_STR(type, name, env, ref, ops, map, git) \
-       { type, name, #env, ref, ops, map, git }
+#define VIEW_STR(type, name, ref, ops, map, git) \
+       { type, name, ref, ops, map, git }
 
 #define VIEW_(id, name, ops, git, ref) \
-       VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
+       VIEW_STR(VIEW_##id, name, ref, ops, KEYMAP_##id, git)
 
 static struct view views[] = {
        VIEW_(MAIN,   "main",   &main_ops,   TRUE,  ref_head),
@@ -2338,7 +1485,7 @@ static struct view views[] = {
        VIEW_(BRANCH, "branch", &branch_ops, TRUE,  ref_head),
        VIEW_(HELP,   "help",   &help_ops,   FALSE, ""),
        VIEW_(PAGER,  "pager",  &pager_ops,  FALSE, ""),
-       VIEW_(STATUS, "status", &status_ops, TRUE,  ""),
+       VIEW_(STATUS, "status", &status_ops, TRUE,  "status"),
        VIEW_(STAGE,  "stage",  &stage_ops,  TRUE,  ""),
 };
 
@@ -2373,7 +1520,9 @@ set_view_attr(struct view *view, enum line_type type)
        }
 }
 
-static int
+#define VIEW_MAX_LEN(view) ((view)->width + (view)->yoffset - (view)->col)
+
+static bool
 draw_chars(struct view *view, enum line_type type, const char *string,
           int max_len, bool use_tilde)
 {
@@ -2384,7 +1533,7 @@ draw_chars(struct view *view, enum line_type type, const char *string,
        size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
 
        if (max_len <= 0)
-               return 0;
+               return VIEW_MAX_LEN(view) <= 0;
 
        len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
 
@@ -2415,47 +1564,49 @@ draw_chars(struct view *view, enum line_type type, const char *string,
                }
        }
 
-       return col;
+       view->col += col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
-static int
+static bool
 draw_space(struct view *view, enum line_type type, int max, int spaces)
 {
        static char space[] = "                    ";
-       int col = 0;
 
        spaces = MIN(max, spaces);
 
        while (spaces > 0) {
                int len = MIN(spaces, sizeof(space) - 1);
 
-               col += draw_chars(view, type, space, len, FALSE);
+               if (draw_chars(view, type, space, len, FALSE))
+                       return TRUE;
                spaces -= len;
        }
 
-       return col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 static bool
-draw_text(struct view *view, enum line_type type, const char *string, bool trim)
+draw_text(struct view *view, enum line_type type, const char *string)
 {
        char text[SIZEOF_STR];
 
        do {
                size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
 
-               view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim);
+               if (draw_chars(view, type, text, VIEW_MAX_LEN(view), TRUE))
+                       return TRUE;
                string += pos;
-       } while (*string && view->width + view->yoffset > view->col);
+       } while (*string);
 
-       return view->width + view->yoffset <= view->col;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 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;
+       int max = VIEW_MAX_LEN(view);
        int i;
 
        if (max < size)
@@ -2468,27 +1619,26 @@ 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;
+       return VIEW_MAX_LEN(view) <= 0;
 }
 
 static bool
 draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
 {
-       int max = MIN(view->width + view->yoffset - view->col, len);
-       int col;
+       int max = MIN(VIEW_MAX_LEN(view), len);
+       int col = view->col;
 
-       if (text)
-               col = draw_chars(view, type, text, max - 1, trim);
-       else
-               col = draw_space(view, type, max - 1, max - 1);
+       if (!text) 
+               return draw_space(view, type, max, max);
 
-       view->col += col;
-       view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
-       return view->width + view->yoffset <= view->col;
+       return draw_chars(view, type, text, max - 1, trim)
+           || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
 }
 
 static bool
@@ -2497,38 +1647,28 @@ draw_date(struct view *view, struct time *time)
        const char *date = mkdate(time, opt_date);
        int cols = opt_date == DATE_SHORT ? DATE_SHORT_COLS : DATE_COLS;
 
+       if (opt_date == DATE_NO)
+               return FALSE;
+
        return draw_field(view, LINE_DATE, date, cols, FALSE);
 }
 
 static bool
 draw_author(struct view *view, const char *author)
 {
-       bool trim = opt_author_cols == 0 || opt_author_cols > 5;
-       bool abbreviate = opt_author == AUTHOR_ABBREVIATED || !trim;
+       bool trim = author_trim(opt_author_cols);
+       const char *text = mkauthor(author, opt_author_cols, opt_author);
 
-       if (abbreviate && author)
-               author = get_author_initials(author);
+       if (opt_author == AUTHOR_NO)
+               return FALSE;
 
-       return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
+       return draw_field(view, LINE_AUTHOR, text, opt_author_cols, trim);
 }
 
 static bool
 draw_mode(struct view *view, mode_t mode)
 {
-       const char *str;
-
-       if (S_ISDIR(mode))
-               str = "drwxr-xr-x";
-       else if (S_ISLNK(mode))
-               str = "lrwxrwxrwx";
-       else if (S_ISGITLINK(mode))
-               str = "m---------";
-       else if (S_ISREG(mode) && mode & S_IXUSR)
-               str = "-rwxr-xr-x";
-       else if (S_ISREG(mode))
-               str = "-rw-r--r--";
-       else
-               str = "----------";
+       const char *str = mkmode(mode);
 
        return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
 }
@@ -2538,7 +1678,7 @@ draw_lineno(struct view *view, unsigned int lineno)
 {
        char number[10];
        int digits3 = view->digits < 3 ? 3 : view->digits;
-       int max = MIN(view->width + view->yoffset - view->col, digits3);
+       int max = MIN(VIEW_MAX_LEN(view), digits3);
        char *text = NULL;
        chtype separator = opt_line_graphics ? ACS_VLINE : '|';
 
@@ -2551,10 +1691,47 @@ draw_lineno(struct view *view, unsigned int lineno)
                        text = number;
        }
        if (text)
-               view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
+               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);
+               draw_space(view, LINE_LINE_NUMBER, max, digits3);
+       return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
+}
+
+static bool
+draw_refs(struct view *view, struct ref_list *refs)
+{
+       size_t i;
+
+       if (!opt_show_refs || !refs)
+               return FALSE;
+
+       for (i = 0; i < refs->size; i++) {
+               struct ref *ref = refs->refs[i];
+               enum line_type type;
+
+               if (ref->head)
+                       type = LINE_MAIN_HEAD;
+               else if (ref->ltag)
+                       type = LINE_MAIN_LOCAL_TAG;
+               else if (ref->tag)
+                       type = LINE_MAIN_TAG;
+               else if (ref->tracked)
+                       type = LINE_MAIN_TRACKED;
+               else if (ref->remote)
+                       type = LINE_MAIN_REMOTE;
+               else
+                       type = LINE_MAIN_REF;
+
+               if (draw_text(view, type, "[") ||
+                   draw_text(view, type, ref->name) ||
+                   draw_text(view, type, "]"))
+                       return TRUE;
+
+               if (draw_text(view, LINE_DEFAULT, " "))
+                       return TRUE;
+       }
+
+       return FALSE;
 }
 
 static bool
@@ -2636,6 +1813,7 @@ update_view_title(struct view *view)
        char buf[SIZEOF_STR];
        char state[SIZEOF_STR];
        size_t bufpos = 0, statelen = 0;
+       WINDOW *window = display[0] == view ? display_title[0] : display_title[1];
 
        assert(view_is_displayed(view));
 
@@ -2676,13 +1854,13 @@ update_view_title(struct view *view)
        }
 
        if (view == display[current_view])
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+               wbkgdset(window, get_line_attr(LINE_TITLE_FOCUS));
        else
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+               wbkgdset(window, get_line_attr(LINE_TITLE_BLUR));
 
-       mvwaddnstr(view->title, 0, 0, buf, bufpos);
-       wclrtoeol(view->title);
-       wnoutrefresh(view->title);
+       mvwaddnstr(window, 0, 0, buf, bufpos);
+       wclrtoeol(window);
+       wnoutrefresh(window);
 }
 
 static int
@@ -2726,23 +1904,25 @@ resize_display(void)
        offset = 0;
 
        foreach_displayed_view (view, i) {
-               if (!view->win) {
-                       view->win = newwin(view->height, 0, offset, 0);
-                       if (!view->win)
+               if (!display_win[i]) {
+                       display_win[i] = newwin(view->height, view->width, offset, 0);
+                       if (!display_win[i])
                                die("Failed to create %s view", view->name);
 
-                       scrollok(view->win, FALSE);
+                       scrollok(display_win[i], FALSE);
 
-                       view->title = newwin(1, 0, offset + view->height, 0);
-                       if (!view->title)
+                       display_title[i] = newwin(1, view->width, offset + view->height, 0);
+                       if (!display_title[i])
                                die("Failed to create title window");
 
                } else {
-                       wresize(view->win, view->height, view->width);
-                       mvwin(view->win,   offset, 0);
-                       mvwin(view->title, offset + view->height, 0);
+                       wresize(display_win[i], view->height, view->width);
+                       mvwin(display_win[i],   offset, 0);
+                       mvwin(display_title[i], offset + view->height, 0);
                }
 
+               view->win = display_win[i];
+
                offset += view->height + 1;
        }
 }
@@ -2766,61 +1946,71 @@ redraw_display(bool clear)
  * Option management
  */
 
-static void
-toggle_enum_option_do(unsigned int *opt, const char *help,
-                     const struct enum_map *map, size_t size)
-{
-       *opt = (*opt + 1) % size;
-       redraw_display(FALSE);
-       report("Displaying %s %s", enum_name(map[*opt]), help);
-}
-
-#define toggle_enum_option(opt, help, map) \
-       toggle_enum_option_do(opt, help, map, ARRAY_SIZE(map))
-
-#define toggle_date() toggle_enum_option(&opt_date, "dates", date_map)
-#define toggle_author() toggle_enum_option(&opt_author, "author names", author_map)
-
-static void
-toggle_view_option(bool *option, const char *help)
-{
-       *option = !*option;
-       redraw_display(FALSE);
-       report("%sabling %s", *option ? "En" : "Dis", help);
-}
+#define TOGGLE_MENU \
+       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)
 
 static void
-open_option_menu(void)
-{
+toggle_option(enum request request)
+{
+       const struct {
+               enum request request;
+               const struct enum_map *map;
+               size_t map_size;
+       } data[] = {            
+#define TOGGLE_(id, key, help, value, map) { REQ_TOGGLE_ ## id, map, ARRAY_SIZE(map) },
+               TOGGLE_MENU
+#undef TOGGLE_
+       };
        const struct menu_item menu[] = {
-               { '.', "line numbers", &opt_line_number },
-               { 'D', "date display", &opt_date },
-               { 'A', "author display", &opt_author },
-               { 'g', "revision graph display", &opt_rev_graph },
-               { 'F', "reference display", &opt_show_refs },
+#define TOGGLE_(id, key, help, value, map) { key, help, value },
+               TOGGLE_MENU
+#undef TOGGLE_
                { 0 }
        };
-       int selected = 0;
+       int i = 0;
 
-       if (prompt_menu("Toggle option", menu, &selected)) {
-               if (menu[selected].data == &opt_date)
-                       toggle_date();
-               else if (menu[selected].data == &opt_author)
-                       toggle_author();
-               else
-                       toggle_view_option(menu[selected].data, menu[selected].text);
+       if (request == REQ_OPTIONS) {
+               if (!prompt_menu("Toggle option", menu, &i))
+                       return;
+       } else {
+               while (i < ARRAY_SIZE(data) && data[i].request != request)
+                       i++;
+               if (i >= ARRAY_SIZE(data))
+                       die("Invalid request (%d)", request);
+       }
+
+       if (data[i].map != NULL) {
+               unsigned int *opt = menu[i].data;
+
+               *opt = (*opt + 1) % data[i].map_size;
+               redraw_display(FALSE);
+               report("Displaying %s %s", enum_name(data[i].map[*opt]), menu[i].text);
+
+       } else {
+               bool *option = menu[i].data;
+
+               *option = !*option;
+               redraw_display(FALSE);
+               report("%sabling %s", *option ? "En" : "Dis", menu[i].text);
        }
 }
 
 static void
-maximize_view(struct view *view)
+maximize_view(struct view *view, bool redraw)
 {
        memset(display, 0, sizeof(display));
        current_view = 0;
        display[current_view] = view;
        resize_display();
-       redraw_display(FALSE);
-       report("");
+       if (redraw) {
+               redraw_display(FALSE);
+               report("");
+       }
 }
 
 
@@ -3205,7 +2395,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,       ""),
@@ -3224,7 +2414,7 @@ format_arg(const char *name)
 }
 
 static bool
-format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool first)
+format_argv(const char ***dst_argv, const char *src_argv[], bool first)
 {
        char buf[SIZEOF_STR];
        int argc;
@@ -3236,18 +2426,23 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                size_t bufpos = 0;
 
                if (!strcmp(arg, "%(fileargs)")) {
-                       if (!argv_append_array(dst_argv, opt_file_args))
+                       if (!argv_append_array(dst_argv, opt_file_argv))
                                break;
                        continue;
 
                } else if (!strcmp(arg, "%(diffargs)")) {
-                       if (!argv_append_array(dst_argv, opt_diff_args))
+                       if (!argv_append_array(dst_argv, opt_diff_argv))
+                               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_args))
+                       if (!argv_append_array(dst_argv, opt_rev_argv))
                                break;
                        continue;
                }
@@ -3257,7 +2452,7 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                        int len = next - arg;
                        const char *value;
 
-                       if (!next || !replace) {
+                       if (!next) {
                                len = strlen(arg);
                                value = "";
 
@@ -3272,7 +2467,7 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
                        if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
                                return FALSE;
 
-                       arg = next && replace ? strchr(next, ')') + 1 : NULL;
+                       arg = next ? strchr(next, ')') + 1 : NULL;
                }
 
                if (!argv_append(dst_argv, buf))
@@ -3329,51 +2524,26 @@ setup_update(struct view *view, const char *vid)
 }
 
 static bool
-prepare_io(struct view *view, const char *dir, const char *argv[], bool replace)
-{
-       view->dir = dir;
-       return format_argv(&view->argv, argv, replace, !view->parent);
-}
-
-static bool
-prepare_update(struct view *view, const char *argv[], const char *dir)
-{
-       if (view->pipe)
-               end_update(view, TRUE);
-       return prepare_io(view, dir, argv, FALSE);
-}
-
-static bool
-start_update(struct view *view, const char **argv, const char *dir)
+begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
 {
-       if (view->pipe)
-               io_done(view->pipe);
-       return prepare_io(view, dir, argv, FALSE) &&
-              io_run(&view->io, IO_RD, dir, view->argv);
-}
+       bool extra = !!(flags & (OPEN_EXTRA));
+       bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED | OPEN_EXTRA));
+       bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED);
 
-static bool
-prepare_update_file(struct view *view, const char *name)
-{
-       if (view->pipe)
-               end_update(view, TRUE);
-       argv_free(view->argv);
-       return io_open(&view->io, "%s/%s", opt_cdup[0] ? opt_cdup : ".", name);
-}
+       if (!reload && !strcmp(view->vid, view->id))
+               return TRUE;
 
-static bool
-begin_update(struct view *view, bool refresh)
-{
-       if (view->pipe)
-               end_update(view, TRUE);
+       if (view->pipe) {
+               if (extra)
+                       io_done(view->pipe);
+               else
+                       end_update(view, TRUE);
+       }
 
        if (!refresh) {
-               if (view->ops->prepare) {
-                       if (!view->ops->prepare(view))
-                               return FALSE;
-               } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
+               view->dir = dir;
+               if (!format_argv(&view->argv, argv, !view->prev))
                        return FALSE;
-               }
 
                /* Put the current ref_* value to the view title ref
                 * member. This is needed by the blob view. Most other
@@ -3386,11 +2556,18 @@ begin_update(struct view *view, bool refresh)
            !io_run(&view->io, IO_RD, view->dir, view->argv))
                return FALSE;
 
-       setup_update(view, view->id);
+       if (!extra)
+               setup_update(view, view->id);
 
        return TRUE;
 }
 
+static bool
+view_open(struct view *view, enum open_flags flags)
+{
+       return begin_update(view, NULL, NULL, flags);
+}
+
 static bool
 update_view(struct view *view)
 {
@@ -3404,7 +2581,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;
 
@@ -3501,49 +2678,90 @@ add_line_data(struct view *view, void *data, enum line_type type)
        return line;
 }
 
-static struct line *
-add_line_text(struct view *view, const char *text, enum line_type type)
-{
-       char *data = text ? strdup(text) : NULL;
-
-       return data ? add_line_data(view, data, type) : NULL;
-}
+static struct line *
+add_line_text(struct view *view, const char *text, enum line_type type)
+{
+       char *data = text ? strdup(text) : NULL;
+
+       return data ? add_line_data(view, data, type) : NULL;
+}
+
+static struct line *
+add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
+{
+       char buf[SIZEOF_STR];
+       va_list args;
+
+       va_start(args, fmt);
+       if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
+               buf[0] = 0;
+       va_end(args);
+
+       return buf[0] ? add_line_text(view, buf, type) : NULL;
+}
+
+/*
+ * View opening
+ */
+
+static void
+load_view(struct view *view, enum open_flags flags)
+{
+       if (view->pipe)
+               end_update(view, TRUE);
+       if (!view->ops->open(view, flags)) {
+               report("Failed to load %s view", view->name);
+               return;
+       }
+       restore_view_position(view);
+
+       if (view->pipe && view->lines == 0) {
+               /* Clear the old view and let the incremental updating refill
+                * the screen. */
+               werase(view->win);
+               view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
+               report("");
+       } else if (view_is_displayed(view)) {
+               redraw_view(view);
+               report("");
+       }
+}
+
+#define refresh_view(view) load_view(view, OPEN_REFRESH)
+#define reload_view(view) load_view(view, OPEN_RELOAD)
 
-static struct line *
-add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
+static void
+split_view(struct view *prev, struct view *view)
 {
-       char buf[SIZEOF_STR];
-       va_list args;
-
-       va_start(args, fmt);
-       if (vsnprintf(buf, sizeof(buf), fmt, args) >= sizeof(buf))
-               buf[0] = 0;
-       va_end(args);
+       display[1] = view;
+       current_view = 1;
+       view->parent = prev;
+       resize_display();
 
-       return buf[0] ? add_line_text(view, buf, type) : NULL;
-}
+       if (prev->lineno - prev->offset >= prev->height) {
+               /* Take the title line into account. */
+               int lines = prev->lineno - prev->offset - prev->height + 1;
 
-/*
- * View opening
- */
+               /* Scroll the view that was split if the current line is
+                * outside the new limited view. */
+               do_scroll_view(prev, lines);
+       }
 
-enum open_flags {
-       OPEN_DEFAULT = 0,       /* Use default view switching. */
-       OPEN_SPLIT = 1,         /* Split current view. */
-       OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
-       OPEN_REFRESH = 16,      /* Refresh view using previous command. */
-       OPEN_PREPARED = 32,     /* Open already prepared command. */
-};
+       if (view != prev && view_is_displayed(prev)) {
+               /* "Blur" the previous view. */
+               update_view_title(prev);
+       }
+}
 
 static void
 open_view(struct view *prev, enum request request, enum open_flags flags)
 {
        bool split = !!(flags & OPEN_SPLIT);
-       bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
-       bool nomaximize = !!(flags & OPEN_REFRESH);
+       bool reload = !!(flags & (OPEN_RELOAD | OPEN_PREPARED));
        struct view *view = VIEW(request);
        int nviews = displayed_views();
-       struct view *base_view = display[0];
+
+       assert(flags ^ OPEN_REFRESH);
 
        if (view == prev && nviews == 1 && !reload) {
                report("Already in %s view", view->name);
@@ -3556,14 +2774,9 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
        }
 
        if (split) {
-               display[1] = view;
-               current_view = 1;
-               view->parent = prev;
-       } else if (!nomaximize) {
-               /* Maximize the current view. */
-               memset(display, 0, sizeof(display));
-               current_view = 0;
-               display[current_view] = view;
+               split_view(prev, view);
+       } else {
+               maximize_view(view, FALSE);
        }
 
        /* No prev signals that this is the first loaded view. */
@@ -3571,51 +2784,31 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                view->prev = prev;
        }
 
-       /* Resize the view when switching between split- and full-screen,
-        * or when switching between two different full-screen views. */
-       if (nviews != displayed_views() ||
-           (nviews == 1 && base_view != display[0]))
-               resize_display();
-
-       if (view->ops->open) {
-               if (view->pipe)
-                       end_update(view, TRUE);
-               if (!view->ops->open(view)) {
-                       report("Failed to load %s view", view->name);
-                       return;
-               }
-               restore_view_position(view);
-
-       } else if ((reload || strcmp(view->vid, view->id)) &&
-                  !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
-               report("Failed to load %s view", view->name);
-               return;
-       }
+       load_view(view, flags);
+}
 
-       if (split && prev->lineno - prev->offset >= prev->height) {
-               /* Take the title line into account. */
-               int lines = prev->lineno - prev->offset - prev->height + 1;
+static void
+open_argv(struct view *prev, struct view *view, const char *argv[], const char *dir, enum open_flags flags)
+{
+       enum request request = view - views + REQ_OFFSET + 1;
 
-               /* Scroll the view that was split if the current line is
-                * outside the new limited view. */
-               do_scroll_view(prev, lines);
+       if (view->pipe)
+               end_update(view, TRUE);
+       view->dir = dir;
+       
+       if (!argv_copy(&view->argv, argv)) {
+               report("Failed to open %s view: %s", view->name, io_strerror(&view->io));
+       } else {
+               open_view(prev, request, flags | OPEN_PREPARED);
        }
+}
 
-       if (prev && view != prev && split && view_is_displayed(prev)) {
-               /* "Blur" the previous view. */
-               update_view_title(prev);
-       }
+static void
+open_file(struct view *prev, struct view *view, const char *file, enum open_flags flags)
+{
+       const char *file_argv[] = { opt_cdup, file , NULL };
 
-       if (view->pipe && view->lines == 0) {
-               /* Clear the old view and let the incremental updating refill
-                * the screen. */
-               werase(view->win);
-               view->p_restore = flags & (OPEN_RELOAD | OPEN_REFRESH);
-               report("");
-       } else if (view_is_displayed(view)) {
-               redraw_view(view);
-               report("");
-       }
+       open_argv(prev, view, file_argv, opt_cdup, flags); 
 }
 
 static void
@@ -3669,7 +2862,7 @@ open_run_request(enum request request)
                return;
        }
 
-       if (format_argv(&argv, req->argv, TRUE, FALSE))
+       if (format_argv(&argv, req->argv, FALSE))
                open_external_viewer(argv, NULL);
        if (argv)
                argv_free(argv);
@@ -3819,31 +3012,17 @@ view_driver(struct view *view, enum request request)
 
        case REQ_MAXIMIZE:
                if (displayed_views() == 2)
-                       maximize_view(view);
+                       maximize_view(view, TRUE);
                break;
 
        case REQ_OPTIONS:
-               open_option_menu();
-               break;
-
        case REQ_TOGGLE_LINENO:
-               toggle_view_option(&opt_line_number, "line numbers");
-               break;
-
        case REQ_TOGGLE_DATE:
-               toggle_date();
-               break;
-
        case REQ_TOGGLE_AUTHOR:
-               toggle_author();
-               break;
-
+       case REQ_TOGGLE_GRAPHIC:
        case REQ_TOGGLE_REV_GRAPH:
-               toggle_view_option(&opt_rev_graph, "revision graph display");
-               break;
-
        case REQ_TOGGLE_REFS:
-               toggle_view_option(&opt_show_refs, "reference display");
+               toggle_option(request);
                break;
 
        case REQ_TOGGLE_SORT_FIELD:
@@ -3890,7 +3069,7 @@ view_driver(struct view *view, enum request request)
                 * view itself. Parents to closed view should never be
                 * followed. */
                if (view->prev && view->prev != view) {
-                       maximize_view(view->prev);
+                       maximize_view(view->prev, TRUE);
                        view->prev = view;
                        break;
                }
@@ -4052,7 +3231,7 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno)
        if (opt_line_number && draw_lineno(view, lineno))
                return TRUE;
 
-       draw_text(view, line->type, line->data, TRUE);
+       draw_text(view, line->type, line->data);
        return TRUE;
 }
 
@@ -4187,8 +3366,7 @@ pager_select(struct view *view, struct line *line)
 
 static struct view_ops pager_ops = {
        "line",
-       NULL,
-       NULL,
+       view_open,
        pager_read,
        pager_draw,
        pager_request,
@@ -4196,9 +3374,15 @@ static struct view_ops pager_ops = {
        pager_select,
 };
 
-static const char *log_argv[SIZEOF_ARG] = {
-       "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
-};
+static bool
+log_open(struct view *view, enum open_flags flags)
+{
+       static const char *log_argv[] = {
+               "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
+       };
+
+       return begin_update(view, NULL, log_argv, flags);
+}
 
 static enum request
 log_request(struct view *view, enum request request, struct line *line)
@@ -4206,7 +3390,7 @@ log_request(struct view *view, enum request request, struct line *line)
        switch (request) {
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_LOG, OPEN_REFRESH);
+               refresh_view(view);
                return REQ_NONE;
        default:
                return pager_request(view, request, line);
@@ -4215,8 +3399,7 @@ log_request(struct view *view, enum request request, struct line *line)
 
 static struct view_ops log_ops = {
        "line",
-       log_argv,
-       NULL,
+       log_open,
        pager_read,
        pager_draw,
        log_request,
@@ -4224,20 +3407,26 @@ static struct view_ops log_ops = {
        pager_select,
 };
 
-static const char *diff_argv[SIZEOF_ARG] = {
-       "git", "show", "--pretty=fuller", "--no-color", "--root",
-               "--patch-with-stat", "--find-copies-harder", "-C",
-               "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
-};
+static bool
+diff_open(struct view *view, enum open_flags flags)
+{
+       static const char *diff_argv[] = {
+               "git", "show", "--pretty=fuller", "--no-color", "--root",
+                       "--patch-with-stat", "--find-copies-harder", "-C",
+                       "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
+       };
+
+       return begin_update(view, NULL, diff_argv, flags);
+}
 
 static bool
 diff_read(struct view *view, char *data)
 {
        if (!data) {
                /* Fall back to retry if no diff will be shown. */
-               if (view->lines == 0 && opt_file_args) {
+               if (view->lines == 0 && opt_file_argv) {
                        int pos = argv_size(view->argv)
-                               - argv_size(opt_file_args) - 1;
+                               - argv_size(opt_file_argv) - 1;
 
                        if (pos > 0 && !strcmp(view->argv[pos], "--")) {
                                for (; view->argv[pos]; pos++) {
@@ -4259,8 +3448,7 @@ diff_read(struct view *view, char *data)
 
 static struct view_ops diff_ops = {
        "line",
-       diff_argv,
-       NULL,
+       diff_open,
        diff_read,
        pager_draw,
        pager_request,
@@ -4272,7 +3460,7 @@ static struct view_ops diff_ops = {
  * Help backend
  */
 
-static bool help_keymap_hidden[ARRAY_SIZE(keymap_table)];
+static bool help_keymap_hidden[ARRAY_SIZE(keymap_map)];
 
 static bool
 help_open_keymap_title(struct view *view, enum keymap keymap)
@@ -4281,7 +3469,7 @@ help_open_keymap_title(struct view *view, enum keymap keymap)
 
        line = add_line_format(view, LINE_HELP_KEYMAP, "[%c] %s bindings",
                               help_keymap_hidden[keymap] ? '+' : '-',
-                              enum_name(keymap_table[keymap]));
+                              enum_name(keymap_map[keymap]));
        if (line)
                line->other = keymap;
 
@@ -4356,7 +3544,7 @@ help_open_keymap(struct view *view, enum keymap keymap)
 }
 
 static bool
-help_open(struct view *view)
+help_open(struct view *view, enum open_flags flags)
 {
        enum keymap keymap;
 
@@ -4364,7 +3552,7 @@ help_open(struct view *view)
        add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
        add_line_text(view, "", LINE_DEFAULT);
 
-       for (keymap = 0; keymap < ARRAY_SIZE(keymap_table); keymap++)
+       for (keymap = 0; keymap < ARRAY_SIZE(keymap_map); keymap++)
                help_open_keymap(view, keymap);
 
        return TRUE;
@@ -4378,8 +3566,7 @@ help_request(struct view *view, enum request request, struct line *line)
                if (line->type == LINE_HELP_KEYMAP) {
                        help_keymap_hidden[line->other] =
                                !help_keymap_hidden[line->other];
-                       view->p_restore = TRUE;
-                       open_view(view, REQ_VIEW_HELP, OPEN_REFRESH);
+                       refresh_view(view);
                }
 
                return REQ_NONE;
@@ -4390,7 +3577,6 @@ help_request(struct view *view, enum request request, struct line *line)
 
 static struct view_ops help_ops = {
        "line",
-       NULL,
        help_open,
        NULL,
        pager_draw,
@@ -4548,11 +3734,10 @@ tree_read_date(struct view *view, char *text, bool *read_date)
                return TRUE;
 
        } else if (!text) {
-               char *path = *opt_path ? opt_path : ".";
                /* Find next entry to process */
                const char *log_file[] = {
                        "git", "log", "--no-color", "--pretty=raw",
-                               "--cc", "--raw", view->id, "--", path, NULL
+                               "--cc", "--raw", view->id, "--", "%(directory)", NULL
                };
 
                if (!view->lines) {
@@ -4561,7 +3746,7 @@ tree_read_date(struct view *view, char *text, bool *read_date)
                        return TRUE;
                }
 
-               if (!start_update(view, log_file, opt_cdup)) {
+               if (!begin_update(view, opt_cdup, log_file, OPEN_EXTRA)) {
                        report("Failed to load tree data");
                        return TRUE;
                }
@@ -4676,20 +3861,20 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
        struct tree_entry *entry = line->data;
 
        if (line->type == LINE_TREE_HEAD) {
-               if (draw_text(view, line->type, "Directory path /", TRUE))
+               if (draw_text(view, line->type, "Directory path /"))
                        return TRUE;
        } else {
                if (draw_mode(view, entry->mode))
                        return TRUE;
 
-               if (opt_author && draw_author(view, entry->author))
+               if (draw_author(view, entry->author))
                        return TRUE;
 
-               if (opt_date && draw_date(view, &entry->time))
+               if (draw_date(view, &entry->time))
                        return TRUE;
        }
-       if (draw_text(view, line->type, entry->name, TRUE))
-               return TRUE;
+
+       draw_text(view, line->type, entry->name);
        return TRUE;
 }
 
@@ -4802,7 +3987,7 @@ tree_grep(struct view *view, struct line *line)
        struct tree_entry *entry = line->data;
        const char *text[] = {
                entry->name,
-               opt_author ? entry->author : "",
+               mkauthor(entry->author, opt_author_cols, opt_author),
                mkdate(&entry->time, opt_date),
                NULL
        };
@@ -4827,8 +4012,12 @@ tree_select(struct view *view, struct line *line)
 }
 
 static bool
-tree_prepare(struct view *view)
+tree_open(struct view *view, enum open_flags flags)
 {
+       static const char *tree_argv[] = {
+               "git", "ls-tree", "%(commit)", "%(directory)", NULL
+       };
+
        if (view->lines == 0 && opt_prefix[0]) {
                char *pos = opt_prefix;
 
@@ -4849,25 +4038,29 @@ tree_prepare(struct view *view)
                opt_path[0] = 0;
        }
 
-       return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
+       return begin_update(view, opt_cdup, tree_argv, flags);
 }
 
-static const char *tree_argv[SIZEOF_ARG] = {
-       "git", "ls-tree", "%(commit)", "%(directory)", NULL
-};
-
 static struct view_ops tree_ops = {
        "file",
-       tree_argv,
-       NULL,
+       tree_open,
        tree_read,
        tree_draw,
        tree_request,
        tree_grep,
        tree_select,
-       tree_prepare,
 };
 
+static bool
+blob_open(struct view *view, enum open_flags flags)
+{
+       static const char *blob_argv[] = {
+               "git", "cat-file", "blob", "%(blob)", NULL
+       };
+
+       return begin_update(view, NULL, blob_argv, flags);
+}
+
 static bool
 blob_read(struct view *view, char *line)
 {
@@ -4888,14 +4081,9 @@ blob_request(struct view *view, enum request request, struct line *line)
        }
 }
 
-static const char *blob_argv[SIZEOF_ARG] = {
-       "git", "cat-file", "blob", "%(blob)", NULL
-};
-
 static struct view_ops blob_ops = {
        "line",
-       blob_argv,
-       NULL,
+       blob_open,
        blob_read,
        pager_draw,
        blob_request,
@@ -4931,8 +4119,9 @@ struct blame {
 };
 
 static bool
-blame_open(struct view *view)
+blame_open(struct view *view, enum open_flags flags)
 {
+       const char *file_argv[] = { opt_cdup, opt_file , NULL };
        char path[SIZEOF_STR];
        size_t i;
 
@@ -4942,13 +4131,12 @@ blame_open(struct view *view)
                        return FALSE;
        }
 
-       if (*opt_ref || !io_open(&view->io, "%s%s", opt_cdup, opt_file)) {
+       if (*opt_ref || !begin_update(view, opt_cdup, file_argv, flags)) {
                const char *blame_cat_file_argv[] = {
-                       "git", "cat-file", "blob", path, NULL
+                       "git", "cat-file", "blob", "%(ref):%(file)", NULL
                };
 
-               if (!string_format(path, "%s:%s", opt_ref, opt_file) ||
-                   !start_update(view, blame_cat_file_argv, opt_cdup))
+               if (!begin_update(view, opt_cdup, blame_cat_file_argv, flags))
                        return FALSE;
        }
 
@@ -4970,7 +4158,7 @@ blame_open(struct view *view)
                        free(blame->commit);
        }
 
-       setup_update(view, opt_file);
+       string_format(view->vid, "%s", opt_file);
        string_format(view->ref, "%s ...", opt_file);
 
        return TRUE;
@@ -5057,14 +4245,14 @@ 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
                };
 
                if (view->lines == 0 && !view->prev)
                        die("No blame exist for %s", view->vid);
 
-               if (view->lines == 0 || !start_update(view, blame_argv, opt_cdup)) {
+               if (view->lines == 0 || !begin_update(view, opt_cdup, blame_argv, OPEN_EXTRA)) {
                        report("Failed to load blame data");
                        return TRUE;
                }
@@ -5166,10 +4354,10 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
                time = &blame->commit->time;
        }
 
-       if (opt_date && draw_date(view, time))
+       if (draw_date(view, time))
                return TRUE;
 
-       if (opt_author && draw_author(view, author))
+       if (draw_author(view, author))
                return TRUE;
 
        if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
@@ -5178,7 +4366,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
        if (draw_lineno(view, lineno))
                return TRUE;
 
-       draw_text(view, LINE_DEFAULT, blame->text, TRUE);
+       draw_text(view, LINE_DEFAULT, blame->text);
        return TRUE;
 }
 
@@ -5247,7 +4435,7 @@ blame_request(struct view *view, enum request request, struct line *line)
                        string_copy(opt_file, blame->commit->filename);
                        if (blame->lineno)
                                view->lineno = blame->lineno;
-                       open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+                       reload_view(view);
                }
                break;
 
@@ -5260,7 +4448,7 @@ blame_request(struct view *view, enum request request, struct line *line)
                        string_copy_rev(opt_ref, blame->commit->parent_id);
                        string_copy(opt_file, blame->commit->parent_filename);
                        setup_blame_parent_line(view, blame);
-                       open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+                       reload_view(view);
                }
                break;
 
@@ -5286,14 +4474,10 @@ blame_request(struct view *view, enum request request, struct line *line)
                                diff_index_argv[7] = "/dev/null";
                        }
 
-                       if (!prepare_update(diff, diff_index_argv, NULL)) {
-                               report("Failed to allocate diff command");
-                               break;
-                       }
-                       flags |= OPEN_PREPARED;
+                       open_argv(view, diff, diff_index_argv, NULL, flags);
+               } else {
+                       open_view(view, REQ_VIEW_DIFF, flags);
                }
-
-               open_view(view, REQ_VIEW_DIFF, flags);
                if (VIEW(REQ_VIEW_DIFF)->pipe && !strcmp(blame->commit->id, NULL_ID))
                        string_copy_rev(VIEW(REQ_VIEW_DIFF)->ref, NULL_ID);
                break;
@@ -5339,7 +4523,6 @@ blame_select(struct view *view, struct line *line)
 
 static struct view_ops blame_ops = {
        "line",
-       NULL,
        blame_open,
        blame_read,
        blame_draw,
@@ -5390,13 +4573,13 @@ branch_draw(struct view *view, struct line *line, unsigned int lineno)
        struct branch *branch = line->data;
        enum line_type type = branch->ref->head ? LINE_MAIN_HEAD : LINE_DEFAULT;
 
-       if (opt_date && draw_date(view, &branch->time))
+       if (draw_date(view, &branch->time))
                return TRUE;
 
-       if (opt_author && draw_author(view, branch->author))
+       if (draw_author(view, branch->author))
                return TRUE;
 
-       draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name, TRUE);
+       draw_text(view, type, branch->ref == &branch_all ? "All branches" : branch->ref->name);
        return TRUE;
 }
 
@@ -5408,7 +4591,7 @@ branch_request(struct view *view, enum request request, struct line *line)
        switch (request) {
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_BRANCH, OPEN_REFRESH);
+               refresh_view(view);
                return REQ_NONE;
 
        case REQ_TOGGLE_SORT_FIELD:
@@ -5426,10 +4609,7 @@ branch_request(struct view *view, enum request request, struct line *line)
                };
                struct view *main_view = VIEW(REQ_VIEW_MAIN);
 
-               if (!prepare_update(main_view, all_branches_argv, NULL))
-                       report("Failed to load view of all branches");
-               else
-                       open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT);
+               open_argv(view, main_view, all_branches_argv, NULL, OPEN_SPLIT);
                return REQ_NONE;
        }
        default:
@@ -5496,19 +4676,18 @@ branch_open_visitor(void *data, const struct ref *ref)
 }
 
 static bool
-branch_open(struct view *view)
+branch_open(struct view *view, enum open_flags flags)
 {
        const char *branch_log[] = {
                "git", "log", "--no-color", "--pretty=raw",
                        "--simplify-by-decoration", "--all", NULL
        };
 
-       if (!start_update(view, branch_log, NULL)) {
+       if (!begin_update(view, NULL, branch_log, flags)) {
                report("Failed to load branch data");
                return TRUE;
        }
 
-       setup_update(view, view->id);
        branch_open_visitor(view, &branch_all);
        foreach_ref(branch_open_visitor, view);
        view->p_restore = TRUE;
@@ -5522,7 +4701,7 @@ branch_grep(struct view *view, struct line *line)
        struct branch *branch = line->data;
        const char *text[] = {
                branch->ref->name,
-               branch->author,
+               mkauthor(branch->author, opt_author_cols, opt_author),
                NULL
        };
 
@@ -5542,7 +4721,6 @@ branch_select(struct view *view, struct line *line)
 
 static struct view_ops branch_ops = {
        "branch",
-       NULL,
        branch_open,
        branch_read,
        branch_draw,
@@ -5794,7 +4972,7 @@ status_update_onbranch(void)
  * info using git-diff-files(1), and finally untracked files using
  * git-ls-files(1). */
 static bool
-status_open(struct view *view)
+status_open(struct view *view, enum open_flags flags)
 {
        reset_view(view);
 
@@ -5865,25 +5043,16 @@ status_draw(struct view *view, struct line *line, unsigned int lineno)
                static char buf[] = { '?', ' ', ' ', ' ', 0 };
 
                buf[0] = status->status;
-               if (draw_text(view, line->type, buf, TRUE))
+               if (draw_text(view, line->type, buf))
                        return TRUE;
                type = LINE_DEFAULT;
                text = status->new.name;
        }
 
-       draw_text(view, type, text, TRUE);
+       draw_text(view, type, text);
        return TRUE;
 }
 
-static enum request
-status_load_error(struct view *view, struct view *stage, const char *path)
-{
-       if (displayed_views() == 2 || display[current_view] != view)
-               maximize_view(view);
-       report("Failed to load '%s': %s", path, io_strerror(&stage->io));
-       return REQ_NONE;
-}
-
 static enum request
 status_enter(struct view *view, struct line *line)
 {
@@ -5893,7 +5062,7 @@ status_enter(struct view *view, struct line *line)
         * path, so leave it empty. */
        const char *newpath = status && status->status != 'U' ? status->new.name : NULL;
        const char *info;
-       enum open_flags split;
+       enum open_flags flags = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
        struct view *stage = VIEW(REQ_VIEW_STAGE);
 
        if (line->type == LINE_STAT_NONE ||
@@ -5910,8 +5079,7 @@ status_enter(struct view *view, struct line *line)
                                        "--", "/dev/null", newpath, NULL
                        };
 
-                       if (!prepare_update(stage, no_head_diff_argv, opt_cdup))
-                               return status_load_error(view, stage, newpath);
+                       open_argv(view, stage, no_head_diff_argv, opt_cdup, flags); 
                } else {
                        const char *index_show_argv[] = {
                                "git", "diff-index", "--root", "--patch-with-stat",
@@ -5919,8 +5087,7 @@ status_enter(struct view *view, struct line *line)
                                        oldpath, newpath, NULL
                        };
 
-                       if (!prepare_update(stage, index_show_argv, opt_cdup))
-                               return status_load_error(view, stage, newpath);
+                       open_argv(view, stage, index_show_argv, opt_cdup, flags);
                }
 
                if (status)
@@ -5936,8 +5103,7 @@ status_enter(struct view *view, struct line *line)
                                "-C", "-M", "--", oldpath, newpath, NULL
                };
 
-               if (!prepare_update(stage, files_show_argv, opt_cdup))
-                       return status_load_error(view, stage, newpath);
+               open_argv(view, stage, files_show_argv, opt_cdup, flags);
                if (status)
                        info = "Unstaged changes to %s";
                else
@@ -5955,8 +5121,7 @@ status_enter(struct view *view, struct line *line)
                        return REQ_NONE;
                }
 
-               if (!prepare_update_file(stage, newpath))
-                       return status_load_error(view, stage, newpath);
+               open_file(view, stage, newpath, flags);
                info = "Untracked file %s";
                break;
 
@@ -5967,8 +5132,6 @@ status_enter(struct view *view, struct line *line)
                die("line type %d not handled in switch", line->type);
        }
 
-       split = view_is_displayed(view) ? OPEN_SPLIT : OPEN_DEFAULT;
-       open_view(view, REQ_VIEW_STAGE, OPEN_PREPARED | split);
        if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
                if (status) {
                        stage_status = *status;
@@ -6238,7 +5401,7 @@ status_request(struct view *view, enum request request, struct line *line)
                return request;
        }
 
-       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+       refresh_view(view);
 
        return REQ_NONE;
 }
@@ -6309,7 +5472,6 @@ status_grep(struct view *view, struct line *line)
 
 static struct view_ops status_ops = {
        "file",
-       NULL,
        status_open,
        NULL,
        status_draw,
@@ -6519,8 +5681,7 @@ stage_request(struct view *view, enum request request, struct line *line)
                return request;
        }
 
-       VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
-       open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
+       refresh_view(view->parent);
 
        /* Check whether the staged entry still exists, and close the
         * stage view if it doesn't. */
@@ -6529,26 +5690,14 @@ stage_request(struct view *view, enum request request, struct line *line)
                return REQ_VIEW_CLOSE;
        }
 
-       if (stage_line_type == LINE_STAT_UNTRACKED) {
-               if (!suffixcmp(stage_status.new.name, -1, "/")) {
-                       report("Cannot display a directory");
-                       return REQ_NONE;
-               }
-
-               if (!prepare_update_file(view, stage_status.new.name)) {
-                       report("Failed to open file: %s", strerror(errno));
-                       return REQ_NONE;
-               }
-       }
-       open_view(view, REQ_VIEW_STAGE, OPEN_REFRESH);
+       refresh_view(view);
 
        return REQ_NONE;
 }
 
 static struct view_ops stage_ops = {
        "line",
-       NULL,
-       NULL,
+       view_open,
        pager_read,
        pager_draw,
        stage_request,
@@ -6561,230 +5710,96 @@ 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. */
-};
-
-/* 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 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,
 };
 
-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;
-
-       if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
-               commit->graph[commit->graph_size++] = symbol;
-}
+       const char *chars = graph_symbol_to_utf8(symbol);
 
-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));
+       return draw_text(view, color, chars + !!first); 
 }
 
-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]);
-}
-
-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);
+       return draw_text(view, LINE_MAIN_REVGRAPH, " ");
 }
 
-
 /*
  * Main view backend
  */
 
-static const char *main_argv[SIZEOF_ARG] = {
-       "git", "log", "--no-color", "--pretty=raw", "--parents",
-               "--topo-order", "%(diffargs)", "%(revargs)",
-               "--", "%(fileargs)", NULL
+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 bool
+main_open(struct view *view, enum open_flags flags)
+{
+       static const char *main_argv[] = {
+               "git", "log", "--no-color", "--pretty=raw", "--parents",
+                       "--topo-order", "%(diffargs)", "%(revargs)",
+                       "--", "%(fileargs)", NULL
+       };
+
+       return begin_update(view, NULL, main_argv, flags);
+}
+
 static bool
 main_draw(struct view *view, struct line *line, unsigned int lineno)
 {
@@ -6793,47 +5808,19 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
        if (!commit->author)
                return FALSE;
 
-       if (opt_date && draw_date(view, &commit->time))
+       if (draw_date(view, &commit->time))
                return TRUE;
 
-       if (opt_author && draw_author(view, commit->author))
+       if (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) {
-               size_t i;
-
-               for (i = 0; i < commit->refs->size; i++) {
-                       struct ref *ref = commit->refs->refs[i];
-                       enum line_type type;
-
-                       if (ref->head)
-                               type = LINE_MAIN_HEAD;
-                       else if (ref->ltag)
-                               type = LINE_MAIN_LOCAL_TAG;
-                       else if (ref->tag)
-                               type = LINE_MAIN_TAG;
-                       else if (ref->tracked)
-                               type = LINE_MAIN_TRACKED;
-                       else if (ref->remote)
-                               type = LINE_MAIN_REMOTE;
-                       else
-                               type = LINE_MAIN_REF;
-
-                       if (draw_text(view, type, "[", TRUE) ||
-                           draw_text(view, type, ref->name, TRUE) ||
-                           draw_text(view, type, "]", TRUE))
-                               return TRUE;
-
-                       if (draw_text(view, LINE_DEFAULT, " ", TRUE))
-                               return TRUE;
-               }
-       }
+       if (draw_refs(view, commit->refs))
+               return TRUE;
 
-       draw_text(view, LINE_DEFAULT, commit->title, TRUE);
+       draw_text(view, LINE_DEFAULT, commit->title);
        return TRUE;
 }
 
@@ -6841,13 +5828,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) {
@@ -6856,38 +5841,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;
        }
 
@@ -6897,16 +5874,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:
@@ -6942,11 +5917,13 @@ main_request(struct view *view, enum request request, struct line *line)
 
        switch (request) {
        case REQ_ENTER:
+               if (view_is_displayed(view) && display[0] != view)
+                       maximize_view(view, TRUE);
                open_view(view, REQ_VIEW_DIFF, flags);
                break;
        case REQ_REFRESH:
                load_refs();
-               open_view(view, REQ_VIEW_MAIN, OPEN_REFRESH);
+               refresh_view(view);
                break;
        default:
                return request;
@@ -6978,7 +5955,7 @@ main_grep(struct view *view, struct line *line)
        struct commit *commit = line->data;
        const char *text[] = {
                commit->title,
-               opt_author ? commit->author : "",
+               mkauthor(commit->author, opt_author_cols, opt_author),
                mkdate(&commit->time, opt_date),
                NULL
        };
@@ -6997,8 +5974,7 @@ main_select(struct view *view, struct line *line)
 
 static struct view_ops main_ops = {
        "commit",
-       main_argv,
-       NULL,
+       main_open,
        main_read,
        main_draw,
        main_request,
@@ -7103,7 +6079,7 @@ init_display(void)
                init_colors();
 
        getmaxyx(stdscr, y, x);
-       status_win = newwin(1, 0, y - 1, 0);
+       status_win = newwin(1, x, y - 1, 0);
        if (!status_win)
                die("Failed to create status window");
 
@@ -7435,7 +6411,7 @@ get_ref_list(const char *id)
 }
 
 static int
-read_ref(char *id, size_t idlen, char *name, size_t namelen)
+read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
 {
        struct ref *ref = NULL;
        bool tag = FALSE;
@@ -7553,7 +6529,7 @@ load_refs(void)
        for (i = 0; i < refs_size; i++)
                refs[i]->id[0] = 0;
 
-       if (io_run_load(ls_remote_argv, "\t", read_ref) == ERR)
+       if (io_run_load(ls_remote_argv, "\t", read_ref, NULL) == ERR)
                return ERR;
 
        /* Update the ref lists to reflect changes. */
@@ -7588,19 +6564,19 @@ set_remote_branch(const char *name, const char *value, size_t valuelen)
 }
 
 static void
-set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
+set_repo_config_option(char *name, char *value, enum option_code (*cmd)(int, const char **))
 {
        const char *argv[SIZEOF_ARG] = { name, "=" };
        int argc = 1 + (cmd == option_set_command);
-       int error = ERR;
+       enum option_code error;
 
        if (!argv_from_string(argv, &argc, value))
-               config_msg = "Too many option arguments";
+               error = OPT_ERR_TOO_MANY_OPTION_ARGUMENTS;
        else
                error = cmd(argc, argv);
 
-       if (error == ERR)
-               warn("Option 'tig.%s': %s", name, config_msg);
+       if (error != OPT_OK)
+               warn("Option 'tig.%s': %s", name, option_errors[error]);
 }
 
 static bool
@@ -7642,7 +6618,7 @@ set_work_tree(const char *value)
 }
 
 static int
-read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
+read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data)
 {
        if (!strcmp(name, "i18n.commitencoding"))
                string_ncopy(opt_encoding, value, valuelen);
@@ -7674,11 +6650,11 @@ load_git_config(void)
 {
        const char *config_list_argv[] = { "git", "config", "--list", NULL };
 
-       return io_run_load(config_list_argv, "=", read_repo_config_option);
+       return io_run_load(config_list_argv, "=", read_repo_config_option, NULL);
 }
 
 static int
-read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
+read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data)
 {
        if (!opt_git_dir[0]) {
                string_ncopy(opt_git_dir, name, namelen);
@@ -7709,7 +6685,7 @@ load_repo_info(void)
                        "--show-cdup", "--show-prefix", NULL
        };
 
-       return io_run_load(rev_parse_argv, "=", read_repo_info);
+       return io_run_load(rev_parse_argv, "=", read_repo_info, NULL);
 }
 
 
@@ -7722,7 +6698,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"
@@ -7767,11 +6743,11 @@ warn(const char *msg, ...)
        va_end(args);
 }
 
-static const char ***filter_args;
-
 static int
-read_filter_args(char *name, size_t namelen, char *value, size_t valuelen)
+read_filter_args(char *name, size_t namelen, char *value, size_t valuelen, void *data)
 {
+       const char ***filter_args = data;
+
        return argv_append(filter_args, name) ? OK : ERR;
 }
 
@@ -7781,10 +6757,9 @@ filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const c
        const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 };
        const char **all_argv = NULL;
 
-       filter_args = args;
        if (!argv_append_array(&all_argv, rev_parse_argv) ||
            !argv_append_array(&all_argv, argv) ||
-           !io_run_load(all_argv, "\n", read_filter_args) == ERR)
+           !io_run_load(all_argv, "\n", read_filter_args, args) == ERR)
                die("Failed to split arguments");
        argv_free(all_argv);
        free(all_argv);
@@ -7793,9 +6768,9 @@ filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const c
 static void
 filter_options(const char *argv[])
 {
-       filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv);
-       filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv);
-       filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv);
+       filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv);
+       filter_rev_parse(&opt_diff_argv, "--no-revs", "--flags", argv);
+       filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv);
 }
 
 static enum request
@@ -7820,16 +6795,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")) {
@@ -7843,7 +6820,7 @@ parse_options(int argc, const char *argv[])
                const char *opt = argv[i];
 
                if (seen_dashdash) {
-                       argv_append(&opt_file_args, opt);
+                       argv_append(&opt_file_argv, opt);
                        continue;
 
                } else if (!strcmp(opt, "--")) {
@@ -7859,7 +6836,7 @@ parse_options(int argc, const char *argv[])
                        quit(0);
 
                } else if (!strcmp(opt, "--all")) {
-                       argv_append(&opt_rev_args, opt);
+                       argv_append(&opt_rev_argv, opt);
                        continue;
                }
 
@@ -7879,7 +6856,6 @@ main(int argc, const char *argv[])
        const char *codeset = "UTF-8";
        enum request request = parse_options(argc, argv);
        struct view *view;
-       size_t i;
 
        signal(SIGINT, quit);
        signal(SIGPIPE, SIG_IGN);
@@ -7916,16 +6892,6 @@ main(int argc, const char *argv[])
        if (load_refs() == ERR)
                die("Failed to load refs.");
 
-       foreach_view (view, i) {
-               if (getenv(view->cmd_env))
-                       warn("Use of the %s environment variable is deprecated,"
-                            " use options or TIG_DIFF_ARGS instead",
-                            view->cmd_env);
-               if (!argv_from_env(view->ops->argv, view->cmd_env))
-                       die("Too many arguments in the `%s` environment variable",
-                           view->cmd_env);
-       }
-
        init_display();
 
        while (view_driver(display[current_view], request)) {
@@ -7967,10 +6933,8 @@ main(int argc, const char *argv[])
 
                                if (!argv_from_string(argv, &argc, cmd)) {
                                        report("Too many arguments");
-                               } else if (!prepare_update(next, argv, NULL)) {
-                                       report("Failed to format command");
                                } else {
-                                       open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED);
+                                       open_argv(view, next, argv, NULL, OPEN_DEFAULT);
                                }
                        }