Code

stage: add request handler supporting file edits and chunk staging
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index cc3dc50fbd2609a2e676d0b9bd5aacc2b2f67727..420a13b886afbcae5bd29676a2ef34c9bca7d96b 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -11,8 +11,8 @@
  * GNU General Public License for more details.
  */
 
-#ifndef        VERSION
-#define VERSION        "tig-0.4.git"
+#ifndef TIG_VERSION
+#define TIG_VERSION "unknown-version"
 #endif
 
 #ifndef DEBUG
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <time.h>
 
-#include <sys/types.h>
 #include <regex.h>
 
 #include <locale.h>
@@ -39,6 +40,8 @@
 
 #include <curses.h>
 
+#include "config.h"
+
 #if __GNUC__ >= 3
 #define __NORETURN __attribute__((__noreturn__))
 #else
@@ -47,7 +50,7 @@
 
 static void __NORETURN die(const char *err, ...);
 static void report(const char *msg, ...);
-static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, int, char *, int));
+static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
 static void set_nonblocking_input(bool loading);
 static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
 
@@ -71,9 +74,6 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 #define SIZEOF_REVGRAPH        19      /* Size of revision ancestry graphics. */
 
-/* Size of rev graph with no  "padding" columns */
-#define SIZEOF_REVITEMS        (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
-
 /* This color name can be used to refer to the default term colors. */
 #define COLOR_DEFAULT  (-1)
 
@@ -92,8 +92,12 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 #define        SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
 
+#ifndef GIT_CONFIG
+#define "git config"
+#endif
+
 #define TIG_LS_REMOTE \
-       "git ls-remote . 2>/dev/null"
+       "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
 #define TIG_DIFF_CMD \
        "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null"
@@ -113,6 +117,8 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 /* XXX: Needs to be defined to the empty string. */
 #define TIG_HELP_CMD   ""
 #define TIG_PAGER_CMD  ""
+#define TIG_STATUS_CMD ""
+#define TIG_STAGE_CMD  ""
 
 /* Some ascii-shorthands fitted into the ncurses namespace. */
 #define KEY_TAB                '\t'
@@ -124,6 +130,7 @@ struct ref {
        char *name;             /* Ref name; tag or head names are shortened. */
        char id[SIZEOF_REV];    /* Commit SHA1 ID */
        unsigned int tag:1;     /* Is it a tag? */
+       unsigned int remote:1;  /* Is it a remote ref? */
        unsigned int next:1;    /* For ref lists: are there more refs? */
 };
 
@@ -170,11 +177,17 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
 /* Shorthands for safely copying into a fixed buffer. */
 
 #define string_copy(dst, src) \
-       string_ncopy_do(dst, sizeof(dst), src, sizeof(dst))
+       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 char *
 chomp_string(char *name)
 {
@@ -271,6 +284,9 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        }
        BUFPUT('\'');
 
+       if (bufsize < SIZEOF_STR)
+               buf[bufsize] = 0;
+
        return bufsize;
 }
 
@@ -289,6 +305,8 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(VIEW_BLOB,         "Show blob view"), \
        REQ_(VIEW_HELP,         "Show help page"), \
        REQ_(VIEW_PAGER,        "Show pager view"), \
+       REQ_(VIEW_STATUS,       "Show status view"), \
+       REQ_(VIEW_STAGE,        "Show stage view"), \
        \
        REQ_GROUP("View manipulation") \
        REQ_(ENTER,             "Enter current line and scroll"), \
@@ -326,7 +344,9 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(SHOW_VERSION,      "Show version information"), \
        REQ_(STOP_LOADING,      "Stop all loading views"), \
        REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
-       REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization")
+       REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
+       REQ_(STATUS_UPDATE,     "Update file status"), \
+       REQ_(EDIT,              "Open in editor")
 
 
 /* User action requests. */
@@ -378,7 +398,7 @@ get_request(const char *name)
  */
 
 static const char usage[] =
-VERSION " (" __DATE__ ")\n"
+"tig " TIG_VERSION " (" __DATE__ ")\n"
 "\n"
 "Usage: tig [options]\n"
 "   or: tig [options] [--] [git log options]\n"
@@ -390,6 +410,7 @@ VERSION " (" __DATE__ ")\n"
 "Options:\n"
 "  -l                          Start up in log view\n"
 "  -d                          Start up in diff view\n"
+"  -S                          Start up in status view\n"
 "  -n[I], --line-number[=I]    Show line numbers with given interval\n"
 "  -b[N], --tab-size[=N]       Set number of spaces for tab expansion\n"
 "  --                          Mark end of tig options\n"
@@ -398,7 +419,7 @@ VERSION " (" __DATE__ ")\n"
 
 /* Option and state variables. */
 static bool opt_line_number            = FALSE;
-static bool opt_rev_graph              = TRUE;
+static bool opt_rev_graph              = FALSE;
 static int opt_num_interval            = NUMBER_INTERVAL;
 static int opt_tab_size                        = TABSIZE;
 static enum request opt_request                = REQ_VIEW_MAIN;
@@ -410,6 +431,9 @@ static bool opt_utf8                        = TRUE;
 static char opt_codeset[20]            = "UTF-8";
 static iconv_t opt_iconv               = ICONV_NONE;
 static char opt_search[SIZEOF_STR]     = "";
+static char opt_cdup[SIZEOF_STR]       = "";
+static char opt_git_dir[SIZEOF_STR]    = "";
+static char opt_editor[SIZEOF_STR]     = "";
 
 enum option_type {
        OPT_NONE,
@@ -463,6 +487,17 @@ parse_options(int argc, char *argv[])
        for (i = 1; i < argc; i++) {
                char *opt = argv[i];
 
+               if (!strcmp(opt, "log") ||
+                   !strcmp(opt, "diff") ||
+                   !strcmp(opt, "show")) {
+                       opt_request = opt[0] == 'l'
+                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+                       break;
+               }
+
+               if (opt[0] && opt[0] != '-')
+                       break;
+
                if (!strcmp(opt, "-l")) {
                        opt_request = REQ_VIEW_LOG;
                        continue;
@@ -473,6 +508,11 @@ parse_options(int argc, char *argv[])
                        continue;
                }
 
+               if (!strcmp(opt, "-S")) {
+                       opt_request = REQ_VIEW_STATUS;
+                       continue;
+               }
+
                if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
                        opt_line_number = TRUE;
                        continue;
@@ -484,7 +524,7 @@ parse_options(int argc, char *argv[])
                }
 
                if (check_option(opt, 'v', "version", OPT_NONE)) {
-                       printf("tig version %s\n", VERSION);
+                       printf("tig version %s\n", TIG_VERSION);
                        return FALSE;
                }
 
@@ -498,17 +538,6 @@ parse_options(int argc, char *argv[])
                        break;
                }
 
-               if (!strcmp(opt, "log") ||
-                   !strcmp(opt, "diff") ||
-                   !strcmp(opt, "show")) {
-                       opt_request = opt[0] == 'l'
-                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
-                       break;
-               }
-
-               if (opt[0] && opt[0] != '-')
-                       break;
-
                die("unknown option '%s'\n\n%s", opt, usage);
        }
 
@@ -522,7 +551,7 @@ parse_options(int argc, char *argv[])
                if (opt_request == REQ_VIEW_MAIN)
                        /* XXX: This is vulnerable to the user overriding
                         * options required for the main view parser. */
-                       string_copy(opt_cmd, "git log --stat --pretty=raw");
+                       string_copy(opt_cmd, "git log --pretty=raw");
                else
                        string_copy(opt_cmd, "git");
                buf_size = strlen(opt_cmd);
@@ -536,7 +565,6 @@ parse_options(int argc, char *argv[])
                        die("command too long");
 
                opt_cmd[buf_size] = 0;
-
        }
 
        if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
@@ -589,9 +617,15 @@ LINE(MAIN_AUTHOR,  "",                     COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(MAIN_COMMIT,  "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
 LINE(MAIN_DELIM,   "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(MAIN_TAG,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
+LINE(MAIN_REMOTE,  "",                 COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
 LINE(MAIN_REF,     "",                 COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
 LINE(TREE_DIR,     "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
-LINE(TREE_FILE,    "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL)
+LINE(TREE_FILE,    "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
+LINE(STAT_SECTION, "",                 COLOR_DEFAULT,  COLOR_BLUE,     A_BOLD), \
+LINE(STAT_NONE,    "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
+LINE(STAT_STAGED,  "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
+LINE(STAT_UNSTAGED,"",                 COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(STAT_UNTRACKED,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  0)
 
 enum line_type {
 #define LINE(type, line, fg, bg, attr) \
@@ -702,6 +736,8 @@ static struct keybinding default_keybindings[] = {
        { 'f',          REQ_VIEW_BLOB },
        { 'p',          REQ_VIEW_PAGER },
        { 'h',          REQ_VIEW_HELP },
+       { 'S',          REQ_VIEW_STATUS },
+       { 'c',          REQ_VIEW_STAGE },
 
        /* View manipulation */
        { 'q',          REQ_VIEW_CLOSE },
@@ -741,9 +777,8 @@ static struct keybinding default_keybindings[] = {
        { '.',          REQ_TOGGLE_LINENO },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
-
-       /* wgetch() with nodelay() enabled returns ERR when there's no input. */
-       { ERR,          REQ_NONE },
+       { 'u',          REQ_STATUS_UPDATE },
+       { 'e',          REQ_EDIT },
 
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
@@ -757,7 +792,9 @@ static struct keybinding default_keybindings[] = {
        KEYMAP_(TREE), \
        KEYMAP_(BLOB), \
        KEYMAP_(PAGER), \
-       KEYMAP_(HELP) \
+       KEYMAP_(HELP), \
+       KEYMAP_(STATUS), \
+       KEYMAP_(STAGE)
 
 enum keymap {
 #define KEYMAP_(name) KEYMAP_##name
@@ -872,7 +909,7 @@ get_key(enum request request)
        static char buf[BUFSIZ];
        static char key_char[] = "'X'";
        size_t pos = 0;
-       char *sep = "    ";
+       char *sep = "";
        int i;
 
        buf[pos] = 0;
@@ -1022,7 +1059,7 @@ option_set_command(int argc, char *argv[])
                                        break;
                                }
                default:
-                       string_copy(opt_encoding, arg);
+                       string_ncopy(opt_encoding, arg, strlen(arg));
                        return OK;
                }
        }
@@ -1100,7 +1137,7 @@ set_option(char *opt, char *value)
 }
 
 static int
-read_option(char *opt, int optlen, char *value, int valuelen)
+read_option(char *opt, size_t optlen, char *value, size_t valuelen)
 {
        int status = OK;
 
@@ -1119,7 +1156,7 @@ read_option(char *opt, int optlen, char *value, int valuelen)
 
        }  else {
                /* Look for comment endings in the value. */
-               int len = strcspn(value, "#");
+               size_t len = strcspn(value, "#");
 
                if (len < valuelen) {
                        valuelen = len;
@@ -1131,7 +1168,7 @@ read_option(char *opt, int optlen, char *value, int valuelen)
 
        if (status == ERR) {
                fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
-                       config_lineno, optlen, opt, config_msg);
+                       config_lineno, (int) optlen, opt, config_msg);
                config_errors = TRUE;
        }
 
@@ -1176,6 +1213,9 @@ struct view_ops;
 static struct view *display[2];
 static unsigned int current_view;
 
+/* Reading from the prompt? */
+static bool input_mode = FALSE;
+
 #define foreach_displayed_view(view, i) \
        for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
 
@@ -1230,12 +1270,14 @@ struct view {
 struct view_ops {
        /* What type of content being displayed. Used in the title bar. */
        const char *type;
-       /* Draw one line; @lineno must be < view->height. */
-       bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
+       /* Open and reads in all view content. */
+       bool (*open)(struct view *view);
        /* Read one line; updates view->line. */
        bool (*read)(struct view *view, char *data);
-       /* Depending on view, change display based on current line. */
-       bool (*enter)(struct view *view, struct line *line);
+       /* Draw one line; @lineno must be < view->height. */
+       bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
+       /* Depending on view handle a special requests. */
+       enum request (*request)(struct view *view, enum request request, struct line *line);
        /* Search for regex in a line. */
        bool (*grep)(struct view *view, struct line *line);
        /* Select line */
@@ -1246,6 +1288,9 @@ static struct view_ops pager_ops;
 static struct view_ops main_ops;
 static struct view_ops tree_ops;
 static struct view_ops blob_ops;
+static struct view_ops help_ops;
+static struct view_ops status_ops;
+static struct view_ops stage_ops;
 
 #define VIEW_STR(name, cmd, env, ref, ops, map) \
        { name, cmd, #env, ref, ops, map}
@@ -1255,13 +1300,15 @@ static struct view_ops blob_ops;
 
 
 static struct view views[] = {
-       VIEW_(MAIN,  "main",  &main_ops,  ref_head),
-       VIEW_(DIFF,  "diff",  &pager_ops, ref_commit),
-       VIEW_(LOG,   "log",   &pager_ops, ref_head),
-       VIEW_(TREE,  "tree",  &tree_ops,  ref_commit),
-       VIEW_(BLOB,  "blob",  &blob_ops,  ref_blob),
-       VIEW_(HELP,  "help",  &pager_ops, "static"),
-       VIEW_(PAGER, "pager", &pager_ops, "static"),
+       VIEW_(MAIN,   "main",   &main_ops,   ref_head),
+       VIEW_(DIFF,   "diff",   &pager_ops,  ref_commit),
+       VIEW_(LOG,    "log",    &pager_ops,  ref_head),
+       VIEW_(TREE,   "tree",   &tree_ops,   ref_commit),
+       VIEW_(BLOB,   "blob",   &blob_ops,   ref_blob),
+       VIEW_(HELP,   "help",   &help_ops,   ""),
+       VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
+       VIEW_(STATUS, "status", &status_ops, ""),
+       VIEW_(STAGE,  "stage",  &stage_ops,  ""),
 };
 
 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
@@ -1277,6 +1324,7 @@ draw_view_line(struct view *view, unsigned int lineno)
 {
        struct line *line;
        bool selected = (view->offset + lineno == view->lineno);
+       bool draw_ok;
 
        assert(view_is_displayed(view));
 
@@ -1294,7 +1342,11 @@ draw_view_line(struct view *view, unsigned int lineno)
                wclrtoeol(view->win);
        }
 
-       return view->ops->draw(view, line, lineno, selected);
+       scrollok(view->win, FALSE);
+       draw_ok = view->ops->draw(view, line, lineno, selected);
+       scrollok(view->win, TRUE);
+
+       return draw_ok;
 }
 
 static void
@@ -1308,7 +1360,10 @@ redraw_view_from(struct view *view, int lineno)
        }
 
        redrawwin(view->win);
-       wrefresh(view->win);
+       if (input_mode)
+               wnoutrefresh(view->win);
+       else
+               wrefresh(view->win);
 }
 
 static void
@@ -1322,44 +1377,60 @@ redraw_view(struct view *view)
 static void
 update_view_title(struct view *view)
 {
-       assert(view_is_displayed(view));
-
-       if (view == display[current_view])
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
-       else
-               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
-
-       werase(view->title);
-       wmove(view->title, 0, 0);
+       char buf[SIZEOF_STR];
+       char state[SIZEOF_STR];
+       size_t bufpos = 0, statelen = 0;
 
-       if (*view->ref)
-               wprintw(view->title, "[%s] %s", view->name, view->ref);
-       else
-               wprintw(view->title, "[%s]", view->name);
+       assert(view_is_displayed(view));
 
-       if (view->lines || view->pipe) {
+       if (view != VIEW(REQ_VIEW_STATUS) && (view->lines || view->pipe)) {
                unsigned int view_lines = view->offset + view->height;
                unsigned int lines = view->lines
                                   ? MIN(view_lines, view->lines) * 100 / view->lines
                                   : 0;
 
-               wprintw(view->title, " - %s %d of %d (%d%%)",
-                       view->ops->type,
-                       view->lineno + 1,
-                       view->lines,
-                       lines);
+               string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+                                  view->ops->type,
+                                  view->lineno + 1,
+                                  view->lines,
+                                  lines);
+
+               if (view->pipe) {
+                       time_t secs = time(NULL) - view->start_time;
+
+                       /* Three git seconds are a long time ... */
+                       if (secs > 2)
+                               string_format_from(state, &statelen, " %lds", secs);
+               }
        }
 
-       if (view->pipe) {
-               time_t secs = time(NULL) - view->start_time;
+       string_format_from(buf, &bufpos, "[%s]", view->name);
+       if (*view->ref && bufpos < view->width) {
+               size_t refsize = strlen(view->ref);
+               size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
 
-               /* Three git seconds are a long time ... */
-               if (secs > 2)
-                       wprintw(view->title, " %lds", secs);
+               if (minsize < view->width)
+                       refsize = view->width - minsize + 7;
+               string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
        }
 
+       if (statelen && bufpos < view->width) {
+               string_format_from(buf, &bufpos, " %s", state);
+       }
+
+       if (view == display[current_view])
+               wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+       else
+               wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+
+       mvwaddnstr(view->title, 0, 0, buf, bufpos);
+       wclrtoeol(view->title);
        wmove(view->title, 0, view->width - 1);
-       wrefresh(view->title);
+
+       if (input_mode)
+               wnoutrefresh(view->title);
+       else
+               wrefresh(view->title);
 }
 
 static void
@@ -1426,10 +1497,8 @@ redraw_display(void)
 }
 
 static void
-update_display_cursor(void)
+update_display_cursor(struct view *view)
 {
-       struct view *view = display[current_view];
-
        /* Move the cursor to the right-most column of the cursor line.
         *
         * XXX: This could turn out to be a bit expensive, but it ensures that
@@ -1602,7 +1671,8 @@ move_view(struct view *view, enum request request)
        }
 
        if (!view_is_displayed(view)) {
-               view->offset += steps;
+               view->offset += scroll_steps;
+               assert(0 <= view->offset && view->offset < view->lines);
                view->ops->select(view, &view->line[view->lineno]);
                return;
        }
@@ -1751,32 +1821,44 @@ end_update(struct view *view)
 static bool
 begin_update(struct view *view)
 {
-       const char *id = view->id;
-
        if (view->pipe)
                end_update(view);
 
        if (opt_cmd[0]) {
                string_copy(view->cmd, opt_cmd);
                opt_cmd[0] = 0;
-               /* When running random commands, the view ref could have become
-                * invalid so clear it. */
-               view->ref[0] = 0;
+               /* When running random commands, initially show the
+                * command in the title. However, it maybe later be
+                * overwritten if a commit line is selected. */
+               if (view == VIEW(REQ_VIEW_PAGER))
+                       string_copy(view->ref, view->cmd);
+               else
+                       view->ref[0] = 0;
 
        } else if (view == VIEW(REQ_VIEW_TREE)) {
                const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+               char path[SIZEOF_STR];
 
                if (strcmp(view->vid, view->id))
-                       opt_path[0] = 0;
+                       opt_path[0] = path[0] = 0;
+               else if (sq_quote(path, 0, opt_path) >= sizeof(path))
+                       return FALSE;
 
-               if (!string_format(view->cmd, format, id, opt_path))
+               if (!string_format(view->cmd, format, view->id, path))
                        return FALSE;
 
        } else {
                const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
+               const char *id = view->id;
 
                if (!string_format(view->cmd, format, id, id, id, id, id))
                        return FALSE;
+
+               /* Put the current ref_* value to the view title ref
+                * member. This is needed by the blob view. Most other
+                * views sets it automatically after loading because the
+                * first line is a commit line. */
+               string_copy_rev(view->ref, view->id);
        }
 
        /* Special case for the pager view. */
@@ -1795,7 +1877,7 @@ begin_update(struct view *view)
        view->offset = 0;
        view->lines  = 0;
        view->lineno = 0;
-       string_copy(view->vid, id);
+       string_copy_rev(view->vid, view->id);
 
        if (view->line) {
                int i;
@@ -1856,7 +1938,7 @@ update_view(struct view *view)
                        line[linelen - 1] = 0;
 
                if (opt_iconv != ICONV_NONE) {
-                       char *inbuf = line;
+                       ICONV_INBUF_TYPE inbuf = line;
                        size_t inlen = linelen;
 
                        char *outbuf = out_buffer;
@@ -1907,6 +1989,12 @@ update_view(struct view *view)
                if (redraw_from > 0)
                        redraw_from--;
 
+               /* Since revision graph visualization requires knowledge
+                * about the parent commit, it causes a further one-off
+                * needed to be redrawn for incremental updates. */
+               if (redraw_from > 0 && opt_rev_graph)
+                       redraw_from--;
+
                /* Incrementally draw avoids flickering. */
                redraw_view_from(view, redraw_from);
        }
@@ -1931,52 +2019,36 @@ alloc_error:
        report("Allocation failure");
 
 end:
+       view->ops->read(view, NULL);
        end_update(view);
        return FALSE;
 }
 
-
-/*
- * View opening
- */
-
-static void open_help_view(struct view *view)
+static struct line *
+add_line_data(struct view *view, void *data, enum line_type type)
 {
-       char buf[BUFSIZ];
-       int lines = ARRAY_SIZE(req_info) + 2;
-       int i;
-
-       if (view->lines > 0)
-               return;
-
-       for (i = 0; i < ARRAY_SIZE(req_info); i++)
-               if (!req_info[i].request)
-                       lines++;
+       struct line *line = &view->line[view->lines++];
 
-       view->line = calloc(lines, sizeof(*view->line));
-       if (!view->line) {
-               report("Allocation failure");
-               return;
-       }
+       memset(line, 0, sizeof(*line));
+       line->type = type;
+       line->data = data;
 
-       view->ops->read(view, "Quick reference for tig keybindings:");
+       return line;
+}
 
-       for (i = 0; i < ARRAY_SIZE(req_info); i++) {
-               char *key;
+static struct line *
+add_line_text(struct view *view, char *data, enum line_type type)
+{
+       if (data)
+               data = strdup(data);
 
-               if (!req_info[i].request) {
-                       view->ops->read(view, "");
-                       view->ops->read(view, req_info[i].help);
-                       continue;
-               }
+       return data ? add_line_data(view, data, type) : NULL;
+}
 
-               key = get_key(req_info[i].request);
-               if (!string_format(buf, "%-25s %s", key, req_info[i].help))
-                       continue;
 
-               view->ops->read(view, buf);
-       }
-}
+/*
+ * View opening
+ */
 
 enum open_flags {
        OPEN_DEFAULT = 0,       /* Use default view switching. */
@@ -2000,8 +2072,11 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                return;
        }
 
-       if (view == VIEW(REQ_VIEW_HELP)) {
-               open_help_view(view);
+       if (view->ops->open) {
+               if (!view->ops->open(view)) {
+                       report("Failed to load %s view", view->name);
+                       return;
+               }
 
        } else if ((reload || strcmp(view->vid, view->id)) &&
                   !begin_update(view)) {
@@ -2060,6 +2135,32 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                update_view_title(view);
 }
 
+static void
+open_editor(struct view *view, char *file)
+{
+       char cmd[SIZEOF_STR];
+       char file_sq[SIZEOF_STR];
+       char *editor;
+
+       editor = getenv("GIT_EDITOR");
+       if (!editor && *opt_editor)
+               editor = opt_editor;
+       if (!editor)
+               editor = getenv("VISUAL");
+       if (!editor)
+               editor = getenv("EDITOR");
+       if (!editor)
+               editor = "vi";
+
+       if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
+           string_format(cmd, "%s %s", editor, file_sq)) {
+               def_prog_mode();           /* save current tty modes */
+               endwin();                  /* restore original tty modes */
+               system(cmd);
+               reset_prog_mode();
+               redraw_display();
+       }
+}
 
 /*
  * User request switch noodle
@@ -2070,6 +2171,12 @@ view_driver(struct view *view, enum request request)
 {
        int i;
 
+       if (view && view->lines) {
+               request = view->ops->request(view, request, &view->line[view->lineno]);
+               if (request == REQ_NONE)
+                       return TRUE;
+       }
+
        switch (request) {
        case REQ_MOVE_UP:
        case REQ_MOVE_DOWN:
@@ -2089,16 +2196,37 @@ view_driver(struct view *view, enum request request)
 
        case REQ_VIEW_BLOB:
                if (!ref_blob[0]) {
-                       report("No file chosen, press 't' to open tree view");
+                       report("No file chosen, press %s to open tree view",
+                              get_key(REQ_VIEW_TREE));
                        break;
                }
-               /* Fall-through */
+               open_view(view, request, OPEN_DEFAULT);
+               break;
+
+       case REQ_VIEW_PAGER:
+               if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
+                       report("No pager content, press %s to run command from prompt",
+                              get_key(REQ_PROMPT));
+                       break;
+               }
+               open_view(view, request, OPEN_DEFAULT);
+               break;
+
+       case REQ_VIEW_STAGE:
+               if (!VIEW(REQ_VIEW_STAGE)->lines) {
+                       report("No stage content, press %s to open the status view and choose file",
+                              get_key(REQ_VIEW_STATUS));
+                       break;
+               }
+               open_view(view, request, OPEN_DEFAULT);
+               break;
+
        case REQ_VIEW_MAIN:
        case REQ_VIEW_DIFF:
        case REQ_VIEW_LOG:
        case REQ_VIEW_TREE:
        case REQ_VIEW_HELP:
-       case REQ_VIEW_PAGER:
+       case REQ_VIEW_STATUS:
                open_view(view, request, OPEN_DEFAULT);
                break;
 
@@ -2108,24 +2236,25 @@ view_driver(struct view *view, enum request request)
 
                if ((view == VIEW(REQ_VIEW_DIFF) &&
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
+                  (view == VIEW(REQ_VIEW_STAGE) &&
+                    view->parent == VIEW(REQ_VIEW_STATUS)) ||
                   (view == VIEW(REQ_VIEW_BLOB) &&
                     view->parent == VIEW(REQ_VIEW_TREE))) {
+                       int line;
+
                        view = view->parent;
+                       line = view->lineno;
                        move_view(view, request);
                        if (view_is_displayed(view))
                                update_view_title(view);
+                       if (line != view->lineno)
+                               view->ops->request(view, REQ_ENTER,
+                                                  &view->line[view->lineno]);
+
                } else {
                        move_view(view, request);
-                       break;
-               }
-               /* Fall-through */
-
-       case REQ_ENTER:
-               if (!view->lines) {
-                       report("Nothing to enter");
-                       break;
                }
-               return view->ops->enter(view, &view->line[view->lineno]);
+               break;
 
        case REQ_VIEW_NEXT:
        {
@@ -2178,7 +2307,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_SHOW_VERSION:
-               report("%s (built %s)", VERSION, __DATE__);
+               report("tig-%s (built %s)", TIG_VERSION, __DATE__);
                return TRUE;
 
        case REQ_SCREEN_RESIZE:
@@ -2188,6 +2317,14 @@ view_driver(struct view *view, enum request request)
                redraw_display();
                break;
 
+       case REQ_EDIT:
+               report("Nothing to edit");
+               break;
+
+       case REQ_ENTER:
+               report("Nothing to enter");
+               break;
+
        case REQ_NONE:
                doupdate();
                return TRUE;
@@ -2301,7 +2438,7 @@ add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep)
        char *ref = NULL;
        FILE *pipe;
 
-       if (!string_format(refbuf, "git describe %s", commit_id))
+       if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id))
                return TRUE;
 
        pipe = popen(refbuf, "r");
@@ -2343,7 +2480,8 @@ add_pager_refs(struct view *view, struct line *line)
 
        do {
                struct ref *ref = refs[refpos];
-               char *fmt = ref->tag ? "%s[%s]" : "%s%s";
+               char *fmt = ref->tag    ? "%s[%s]" :
+                           ref->remote ? "%s<%s>" : "%s%s";
 
                if (!string_format_from(buf, &bufpos, fmt, sep, ref->name))
                        return;
@@ -2365,26 +2503,20 @@ try_add_describe_ref:
        if (!realloc_lines(view, view->line_size + 1))
                return;
 
-       line = &view->line[view->lines];
-       line->data = strdup(buf);
-       if (!line->data)
-               return;
-
-       line->type = LINE_PP_REFS;
-       view->lines++;
+       add_line_text(view, buf, LINE_PP_REFS);
 }
 
 static bool
 pager_read(struct view *view, char *data)
 {
-       struct line *line = &view->line[view->lines];
+       struct line *line;
 
-       line->data = strdup(data);
-       if (!line->data)
-               return FALSE;
+       if (!data)
+               return TRUE;
 
-       line->type = get_line_type(line->data);
-       view->lines++;
+       line = add_line_text(view, data, get_line_type(data));
+       if (!line)
+               return FALSE;
 
        if (line->type == LINE_COMMIT &&
            (view == VIEW(REQ_VIEW_DIFF) ||
@@ -2394,11 +2526,14 @@ pager_read(struct view *view, char *data)
        return TRUE;
 }
 
-static bool
-pager_enter(struct view *view, struct line *line)
+static enum request
+pager_request(struct view *view, enum request request, struct line *line)
 {
        int split = 0;
 
+       if (request != REQ_ENTER)
+               return request;
+
        if (line->type == LINE_COMMIT &&
           (view == VIEW(REQ_VIEW_LOG) ||
            view == VIEW(REQ_VIEW_PAGER))) {
@@ -2417,7 +2552,7 @@ pager_enter(struct view *view, struct line *line)
        if (split)
                update_view_title(view);
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static bool
@@ -2439,18 +2574,74 @@ static void
 pager_select(struct view *view, struct line *line)
 {
        if (line->type == LINE_COMMIT) {
-               char *text = line->data;
+               char *text = line->data + STRING_SIZE("commit ");
 
-               string_copy(view->ref, text + STRING_SIZE("commit "));
-               string_copy(ref_commit, view->ref);
+               if (view != VIEW(REQ_VIEW_PAGER))
+                       string_copy_rev(view->ref, text);
+               string_copy_rev(ref_commit, text);
        }
 }
 
 static struct view_ops pager_ops = {
        "line",
-       pager_draw,
+       NULL,
        pager_read,
-       pager_enter,
+       pager_draw,
+       pager_request,
+       pager_grep,
+       pager_select,
+};
+
+
+/*
+ * Help backend
+ */
+
+static bool
+help_open(struct view *view)
+{
+       char buf[BUFSIZ];
+       int lines = ARRAY_SIZE(req_info) + 2;
+       int i;
+
+       if (view->lines > 0)
+               return TRUE;
+
+       for (i = 0; i < ARRAY_SIZE(req_info); i++)
+               if (!req_info[i].request)
+                       lines++;
+
+       view->line = calloc(lines, sizeof(*view->line));
+       if (!view->line)
+               return FALSE;
+
+       add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
+
+       for (i = 0; i < ARRAY_SIZE(req_info); i++) {
+               char *key;
+
+               if (!req_info[i].request) {
+                       add_line_text(view, "", LINE_DEFAULT);
+                       add_line_text(view, req_info[i].help, LINE_DEFAULT);
+                       continue;
+               }
+
+               key = get_key(req_info[i].request);
+               if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
+                       continue;
+
+               add_line_text(view, buf, LINE_DEFAULT);
+       }
+
+       return TRUE;
+}
+
+static struct view_ops help_ops = {
+       "line",
+       help_open,
+       NULL,
+       pager_draw,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2460,6 +2651,50 @@ static struct view_ops pager_ops = {
  * Tree backend
  */
 
+struct tree_stack_entry {
+       struct tree_stack_entry *prev;  /* Entry below this in the stack */
+       unsigned long lineno;           /* Line number to restore */
+       char *name;                     /* Position of name in opt_path */
+};
+
+/* The top of the path stack. */
+static struct tree_stack_entry *tree_stack = NULL;
+unsigned long tree_lineno = 0;
+
+static void
+pop_tree_stack_entry(void)
+{
+       struct tree_stack_entry *entry = tree_stack;
+
+       tree_lineno = entry->lineno;
+       entry->name[0] = 0;
+       tree_stack = entry->prev;
+       free(entry);
+}
+
+static void
+push_tree_stack_entry(char *name, unsigned long lineno)
+{
+       struct tree_stack_entry *entry = calloc(1, sizeof(*entry));
+       size_t pathlen = strlen(opt_path);
+
+       if (!entry)
+               return;
+
+       entry->prev = tree_stack;
+       entry->name = opt_path + pathlen;
+       tree_stack = entry;
+
+       if (!string_format_from(opt_path, &pathlen, "%s/", name)) {
+               pop_tree_stack_entry();
+               return;
+       }
+
+       /* Move the current line to the first tree entry. */
+       tree_lineno = 1;
+       entry->lineno = lineno;
+}
+
 /* Parse output from git-ls-tree(1):
  *
  * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26        Makefile
@@ -2489,7 +2724,7 @@ tree_compare_entry(enum line_type type1, char *name1,
 static bool
 tree_read(struct view *view, char *text)
 {
-       size_t textlen = strlen(text);
+       size_t textlen = text ? strlen(text) : 0;
        char buf[SIZEOF_STR];
        unsigned long pos;
        enum line_type type;
@@ -2503,21 +2738,18 @@ tree_read(struct view *view, char *text)
 
        if (first_read) {
                /* Add path info line */
-               if (string_format(buf, "Directory path /%s", opt_path) &&
-                   realloc_lines(view, view->line_size + 1) &&
-                   pager_read(view, buf))
-                       view->line[view->lines - 1].type = LINE_DEFAULT;
-               else
+               if (!string_format(buf, "Directory path /%s", opt_path) ||
+                   !realloc_lines(view, view->line_size + 1) ||
+                   !add_line_text(view, buf, LINE_DEFAULT))
                        return FALSE;
 
                /* Insert "link" to parent directory. */
-               if (*opt_path &&
-                   string_format(buf, TREE_UP_FORMAT, view->ref) &&
-                   realloc_lines(view, view->line_size + 1) &&
-                   pager_read(view, buf))
-                       view->line[view->lines - 1].type = LINE_TREE_DIR;
-               else if (*opt_path)
-                       return FALSE;
+               if (*opt_path) {
+                       if (!string_format(buf, TREE_UP_FORMAT, view->ref) ||
+                           !realloc_lines(view, view->line_size + 1) ||
+                           !add_line_text(view, buf, LINE_TREE_DIR))
+                               return FALSE;
+               }
        }
 
        /* Strip the path part ... */
@@ -2556,55 +2788,51 @@ tree_read(struct view *view, char *text)
                return TRUE;
        }
 
-       if (!pager_read(view, text))
+       if (!add_line_text(view, text, type))
                return FALSE;
 
-       /* Move the current line to the first tree entry. */
-       if (first_read)
-               view->lineno++;
+       if (tree_lineno > view->lineno) {
+               view->lineno = tree_lineno;
+               tree_lineno = 0;
+       }
 
-       view->line[view->lines - 1].type = type;
        return TRUE;
 }
 
-static bool
-tree_enter(struct view *view, struct line *line)
+static enum request
+tree_request(struct view *view, enum request request, struct line *line)
 {
-       enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
-       enum request request;
+       enum open_flags flags;
+
+       if (request != REQ_ENTER)
+               return request;
+
+       /* Cleanup the stack if the tree view is at a different tree. */
+       while (!*opt_path && tree_stack)
+               pop_tree_stack_entry();
 
        switch (line->type) {
        case LINE_TREE_DIR:
                /* Depending on whether it is a subdir or parent (updir?) link
                 * mangle the path buffer. */
                if (line == &view->line[1] && *opt_path) {
-                       size_t path_len = strlen(opt_path);
-                       char *dirsep = opt_path + path_len - 1;
-
-                       while (dirsep > opt_path && dirsep[-1] != '/')
-                               dirsep--;
-
-                       dirsep[0] = 0;
+                       pop_tree_stack_entry();
 
                } else {
-                       size_t pathlen = strlen(opt_path);
-                       size_t origlen = pathlen;
                        char *data = line->data;
                        char *basename = data + SIZEOF_TREE_ATTR;
 
-                       if (!string_format_from(opt_path, &pathlen, "%s/", basename)) {
-                               opt_path[origlen] = 0;
-                               return TRUE;
-                       }
+                       push_tree_stack_entry(basename, view->lineno);
                }
 
                /* Trees and subtrees share the same ID, so they are not not
                 * unique like blobs. */
-               flags |= OPEN_RELOAD;
+               flags = OPEN_RELOAD;
                request = REQ_VIEW_TREE;
                break;
 
        case LINE_TREE_FILE:
+               flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
                request = REQ_VIEW_BLOB;
                break;
 
@@ -2613,35 +2841,34 @@ tree_enter(struct view *view, struct line *line)
        }
 
        open_view(view, request, flags);
+       if (request == REQ_VIEW_TREE) {
+               view->lineno = tree_lineno;
+       }
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static void
 tree_select(struct view *view, struct line *line)
 {
-       char *text = line->data;
-
-       text += STRING_SIZE("100644 blob ");
+       char *text = line->data + STRING_SIZE("100644 blob ");
 
        if (line->type == LINE_TREE_FILE) {
-               string_ncopy(ref_blob, text, 40);
-               /* Also update the blob view's ref, since all there must always
-                * be in sync. */
-               string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob);
+               string_copy_rev(ref_blob, text);
 
        } else if (line->type != LINE_TREE_DIR) {
                return;
        }
 
-       string_ncopy(view->ref, text, 40);
+       string_copy_rev(view->ref, text);
 }
 
 static struct view_ops tree_ops = {
        "file",
-       pager_draw,
+       NULL,
        tree_read,
-       tree_enter,
+       pager_draw,
+       tree_request,
        pager_grep,
        tree_select,
 };
@@ -2649,38 +2876,891 @@ static struct view_ops tree_ops = {
 static bool
 blob_read(struct view *view, char *line)
 {
-       bool state = pager_read(view, line);
-
-       if (state == TRUE)
-               view->line[view->lines - 1].type = LINE_DEFAULT;
-
-       return state;
+       return add_line_text(view, line, LINE_DEFAULT);
 }
 
 static struct view_ops blob_ops = {
        "line",
-       pager_draw,
+       NULL,
        blob_read,
-       pager_enter,
+       pager_draw,
+       pager_request,
        pager_grep,
        pager_select,
 };
 
 
 /*
- * Main view backend
+ * Status backend
  */
 
-struct commit {
-       char id[SIZEOF_REV];            /* SHA1 ID. */
-       char title[75];                 /* First line of the commit message. */
-       char author[75];                /* Author of the commit. */
-       struct tm time;                 /* Date from the author ident. */
+struct status {
+       char status;
+       struct {
+               mode_t mode;
+               char rev[SIZEOF_REV];
+       } old;
+       struct {
+               mode_t mode;
+               char rev[SIZEOF_REV];
+       } new;
+       char name[SIZEOF_STR];
+};
+
+static struct status stage_status;
+static enum line_type stage_line_type;
+
+/* Get fields from the diff line:
+ * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
+ */
+static inline bool
+status_get_diff(struct status *file, char *buf, size_t bufsize)
+{
+       char *old_mode = buf +  1;
+       char *new_mode = buf +  8;
+       char *old_rev  = buf + 15;
+       char *new_rev  = buf + 56;
+       char *status   = buf + 97;
+
+       if (bufsize != 99 ||
+           old_mode[-1] != ':' ||
+           new_mode[-1] != ' ' ||
+           old_rev[-1]  != ' ' ||
+           new_rev[-1]  != ' ' ||
+           status[-1]   != ' ')
+               return FALSE;
+
+       file->status = *status;
+
+       string_copy_rev(file->old.rev, old_rev);
+       string_copy_rev(file->new.rev, new_rev);
+
+       file->old.mode = strtoul(old_mode, NULL, 8);
+       file->new.mode = strtoul(new_mode, NULL, 8);
+
+       file->name[0] = 0;
+
+       return TRUE;
+}
+
+static bool
+status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
+{
+       struct status *file = NULL;
+       char buf[SIZEOF_STR * 4];
+       size_t bufsize = 0;
+       FILE *pipe;
+
+       pipe = popen(cmd, "r");
+       if (!pipe)
+               return FALSE;
+
+       add_line_data(view, NULL, type);
+
+       while (!feof(pipe) && !ferror(pipe)) {
+               char *sep;
+               size_t readsize;
+
+               readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
+               if (!readsize)
+                       break;
+               bufsize += readsize;
+
+               /* Process while we have NUL chars. */
+               while ((sep = memchr(buf, 0, bufsize))) {
+                       size_t sepsize = sep - buf + 1;
+
+                       if (!file) {
+                               if (!realloc_lines(view, view->line_size + 1))
+                                       goto error_out;
+
+                               file = calloc(1, sizeof(*file));
+                               if (!file)
+                                       goto error_out;
+
+                               add_line_data(view, file, type);
+                       }
+
+                       /* Parse diff info part. */
+                       if (!diff) {
+                               file->status = '?';
+
+                       } else if (!file->status) {
+                               if (!status_get_diff(file, buf, sepsize))
+                                       goto error_out;
+
+                               bufsize -= sepsize;
+                               memmove(buf, sep + 1, bufsize);
+
+                               sep = memchr(buf, 0, bufsize);
+                               if (!sep)
+                                       break;
+                               sepsize = sep - buf + 1;
+                       }
+
+                       /* git-ls-files just delivers a NUL separated
+                        * list of file names similar to the second half
+                        * of the git-diff-* output. */
+                       string_ncopy(file->name, buf, sepsize);
+                       bufsize -= sepsize;
+                       memmove(buf, sep + 1, bufsize);
+                       file = NULL;
+               }
+       }
+
+       if (ferror(pipe)) {
+error_out:
+               pclose(pipe);
+               return FALSE;
+       }
+
+       if (!view->line[view->lines - 1].data)
+               add_line_data(view, NULL, LINE_STAT_NONE);
+
+       pclose(pipe);
+       return TRUE;
+}
+
+#define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
+#define STATUS_DIFF_FILES_CMD "git diff-files -z"
+#define STATUS_LIST_OTHER_CMD \
+       "git ls-files -z --others --exclude-per-directory=.gitignore"
+
+#define STATUS_DIFF_SHOW_CMD \
+       "git diff --root --patch-with-stat --find-copies-harder -B -C %s -- %s 2>/dev/null"
+
+/* First parse staged info using git-diff-index(1), then parse unstaged
+ * info using git-diff-files(1), and finally untracked files using
+ * git-ls-files(1). */
+static bool
+status_open(struct view *view)
+{
+       struct stat statbuf;
+       char exclude[SIZEOF_STR];
+       char cmd[SIZEOF_STR];
+       size_t i;
+
+       for (i = 0; i < view->lines; i++)
+               free(view->line[i].data);
+       free(view->line);
+       view->lines = view->line_size = 0;
+       view->line = NULL;
+
+       if (!realloc_lines(view, view->line_size + 6))
+               return FALSE;
+
+       if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
+               return FALSE;
+
+       string_copy(cmd, STATUS_LIST_OTHER_CMD);
+
+       if (stat(exclude, &statbuf) >= 0) {
+               size_t cmdsize = strlen(cmd);
+
+               if (!string_format_from(cmd, &cmdsize, " %s", "--exclude-from=") ||
+                   sq_quote(cmd, cmdsize, exclude) >= sizeof(cmd))
+                       return FALSE;
+       }
+
+       if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
+           !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
+           !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
+               return FALSE;
+
+       return TRUE;
+}
+
+static bool
+status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+{
+       struct status *status = line->data;
+
+       wmove(view->win, lineno, 0);
+
+       if (selected) {
+               wattrset(view->win, get_line_attr(LINE_CURSOR));
+               wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
+
+       } else if (!status && line->type != LINE_STAT_NONE) {
+               wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
+               wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
+
+       } else {
+               wattrset(view->win, get_line_attr(line->type));
+       }
+
+       if (!status) {
+               char *text;
+
+               switch (line->type) {
+               case LINE_STAT_STAGED:
+                       text = "Changes to be committed:";
+                       break;
+
+               case LINE_STAT_UNSTAGED:
+                       text = "Changed but not updated:";
+                       break;
+
+               case LINE_STAT_UNTRACKED:
+                       text = "Untracked files:";
+                       break;
+
+               case LINE_STAT_NONE:
+                       text = "    (no files)";
+                       break;
+
+               default:
+                       return FALSE;
+               }
+
+               waddstr(view->win, text);
+               return TRUE;
+       }
+
+       waddch(view->win, status->status);
+       if (!selected)
+               wattrset(view->win, A_NORMAL);
+       wmove(view->win, lineno, 4);
+       waddstr(view->win, status->name);
+
+       return TRUE;
+}
+
+static enum request
+status_enter(struct view *view, struct line *line)
+{
+       struct status *status = line->data;
+       char path[SIZEOF_STR] = "";
+       char *info;
+       size_t cmdsize = 0;
+
+       if (line->type == LINE_STAT_NONE ||
+           (!status && line[1].type == LINE_STAT_NONE)) {
+               report("No file to diff");
+               return REQ_NONE;
+       }
+
+       if (status && sq_quote(path, 0, status->name) >= sizeof(path))
+               return REQ_QUIT;
+
+       if (opt_cdup[0] &&
+           line->type != LINE_STAT_UNTRACKED &&
+           !string_format_from(opt_cmd, &cmdsize, "cd %s;", opt_cdup))
+               return REQ_QUIT;
+
+       switch (line->type) {
+       case LINE_STAT_STAGED:
+               if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
+                                       "--cached", path))
+                       return REQ_QUIT;
+               if (status)
+                       info = "Staged changes to %s";
+               else
+                       info = "Staged changes";
+               break;
+
+       case LINE_STAT_UNSTAGED:
+               if (!string_format_from(opt_cmd, &cmdsize, STATUS_DIFF_SHOW_CMD,
+                                       "", path))
+                       return REQ_QUIT;
+               if (status)
+                       info = "Unstaged changes to %s";
+               else
+                       info = "Unstaged changes";
+               break;
+
+       case LINE_STAT_UNTRACKED:
+               if (opt_pipe)
+                       return REQ_QUIT;
+
+
+               if (!status) {
+                       report("No file to show");
+                       return REQ_NONE;
+               }
+
+               opt_pipe = fopen(status->name, "r");
+               info = "Untracked file %s";
+               break;
+
+       default:
+               die("w00t");
+       }
+
+       open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
+       if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
+               if (status) {
+                       stage_status = *status;
+               } else {
+                       memset(&stage_status, 0, sizeof(stage_status));
+               }
+
+               stage_line_type = line->type;
+               string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
+       }
+
+       return REQ_NONE;
+}
+
+
+static bool
+status_update_file(struct view *view, struct status *status, enum line_type type)
+{
+       char cmd[SIZEOF_STR];
+       char buf[SIZEOF_STR];
+       size_t cmdsize = 0;
+       size_t bufsize = 0;
+       size_t written = 0;
+       FILE *pipe;
+
+       if (opt_cdup[0] &&
+           type != LINE_STAT_UNTRACKED &&
+           !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+               return FALSE;
+
+       switch (type) {
+       case LINE_STAT_STAGED:
+               if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
+                                       status->old.mode,
+                                       status->old.rev,
+                                       status->name, 0))
+                       return FALSE;
+
+               string_add(cmd, cmdsize, "git update-index -z --index-info");
+               break;
+
+       case LINE_STAT_UNSTAGED:
+       case LINE_STAT_UNTRACKED:
+               if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
+                       return FALSE;
+
+               string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
+               break;
+
+       default:
+               die("w00t");
+       }
+
+       pipe = popen(cmd, "w");
+       if (!pipe)
+               return FALSE;
+
+       while (!ferror(pipe) && written < bufsize) {
+               written += fwrite(buf + written, 1, bufsize - written, pipe);
+       }
+
+       pclose(pipe);
+
+       if (written != bufsize)
+               return FALSE;
+
+       return TRUE;
+}
+
+static void
+status_update(struct view *view)
+{
+       struct line *line = &view->line[view->lineno];
+
+       assert(view->lines);
+
+       if (!line->data) {
+               while (++line < view->line + view->lines && line->data) {
+                       if (!status_update_file(view, line->data, line->type))
+                               report("Failed to update file status");
+               }
+
+               if (!line[-1].data) {
+                       report("Nothing to update");
+                       return;
+               }
+
+       } else if (!status_update_file(view, line->data, line->type)) {
+               report("Failed to update file status");
+       }
+
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+}
+
+static enum request
+status_request(struct view *view, enum request request, struct line *line)
+{
+       struct status *status = line->data;
+
+       switch (request) {
+       case REQ_STATUS_UPDATE:
+               status_update(view);
+               break;
+
+       case REQ_EDIT:
+               if (!status)
+                       return request;
+
+               open_editor(view, status->name);
+               break;
+
+       case REQ_ENTER:
+               status_enter(view, line);
+               break;
+
+       default:
+               return request;
+       }
+
+       return REQ_NONE;
+}
+
+static void
+status_select(struct view *view, struct line *line)
+{
+       struct status *status = line->data;
+       char file[SIZEOF_STR] = "all files";
+       char *text;
+
+       if (status && !string_format(file, "'%s'", status->name))
+               return;
+
+       if (!status && line[1].type == LINE_STAT_NONE)
+               line++;
+
+       switch (line->type) {
+       case LINE_STAT_STAGED:
+               text = "Press %s to unstage %s for commit";
+               break;
+
+       case LINE_STAT_UNSTAGED:
+               text = "Press %s to stage %s for commit";
+               break;
+
+       case LINE_STAT_UNTRACKED:
+               text = "Press %s to stage %s for addition";
+               break;
+
+       case LINE_STAT_NONE:
+               text = "Nothing to update";
+               break;
+
+       default:
+               die("w00t");
+       }
+
+       string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
+}
+
+static bool
+status_grep(struct view *view, struct line *line)
+{
+       struct status *status = line->data;
+       enum { S_STATUS, S_NAME, S_END } state;
+       char buf[2] = "?";
+       regmatch_t pmatch;
+
+       if (!status)
+               return FALSE;
+
+       for (state = S_STATUS; state < S_END; state++) {
+               char *text;
+
+               switch (state) {
+               case S_NAME:    text = status->name;    break;
+               case S_STATUS:
+                       buf[0] = status->status;
+                       text = buf;
+                       break;
+
+               default:
+                       return FALSE;
+               }
+
+               if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+static struct view_ops status_ops = {
+       "file",
+       status_open,
+       NULL,
+       status_draw,
+       status_request,
+       status_grep,
+       status_select,
+};
+
+
+static bool
+stage_diff_line(FILE *pipe, struct line *line)
+{
+       char *buf = line->data;
+       size_t bufsize = strlen(buf);
+       size_t written = 0;
+
+       while (!ferror(pipe) && written < bufsize) {
+               written += fwrite(buf + written, 1, bufsize - written, pipe);
+       }
+
+       fputc('\n', pipe);
+
+       return written == bufsize;
+}
+
+static struct line *
+stage_diff_hdr(struct view *view, struct line *line)
+{
+       int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
+       struct line *diff_hdr;
+
+       if (line->type == LINE_DIFF_CHUNK)
+               diff_hdr = line - 1;
+       else
+               diff_hdr = view->line + 1;
+
+       while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
+               if (diff_hdr->type == LINE_DIFF_HEADER)
+                       return diff_hdr;
+
+               diff_hdr += diff_hdr_dir;
+       }
+
+       return NULL;
+}
+
+static bool
+stage_update_chunk(struct view *view, struct line *line)
+{
+       char cmd[SIZEOF_STR];
+       size_t cmdsize = 0;
+       struct line *diff_hdr, *diff_chunk, *diff_end;
+       FILE *pipe;
+
+       diff_hdr = stage_diff_hdr(view, line);
+       if (!diff_hdr)
+               return FALSE;
+
+       if (opt_cdup[0] &&
+           !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+               return FALSE;
+
+       if (!string_format_from(cmd, &cmdsize,
+                               "git apply --cached %s - && "
+                               "git update-index -q --unmerged --refresh 2>/dev/null",
+                               stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
+               return FALSE;
+
+       pipe = popen(cmd, "w");
+       if (!pipe)
+               return FALSE;
+
+       diff_end = view->line + view->lines;
+       if (line->type != LINE_DIFF_CHUNK) {
+               diff_chunk = diff_hdr;
+
+       } else {
+               for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
+                       if (diff_chunk->type == LINE_DIFF_CHUNK ||
+                           diff_chunk->type == LINE_DIFF_HEADER)
+                               diff_end = diff_chunk;
+
+               diff_chunk = line;
+
+               while (diff_hdr->type != LINE_DIFF_CHUNK) {
+                       switch (diff_hdr->type) {
+                       case LINE_DIFF_HEADER:
+                       case LINE_DIFF_INDEX:
+                       case LINE_DIFF_ADD:
+                       case LINE_DIFF_DEL:
+                               break;
+
+                       default:
+                               diff_hdr++;
+                               continue;
+                       }
+
+                       if (!stage_diff_line(pipe, diff_hdr++)) {
+                               pclose(pipe);
+                               return FALSE;
+                       }
+               }
+       }
+
+       while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
+               diff_chunk++;
+
+       pclose(pipe);
+
+       if (diff_chunk != diff_end)
+               return FALSE;
+
+       return TRUE;
+}
+
+static void
+stage_update(struct view *view, struct line *line)
+{
+       if (stage_line_type != LINE_STAT_UNTRACKED &&
+           (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
+               if (!stage_update_chunk(view, line)) {
+                       report("Failed to apply chunk");
+                       return;
+               }
+
+       } else if (!status_update_file(view, &stage_status, stage_line_type)) {
+               report("Failed to update file");
+               return;
+       }
+
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+
+       view = VIEW(REQ_VIEW_STATUS);
+       if (view_is_displayed(view))
+               status_enter(view, &view->line[view->lineno]);
+}
+
+static enum request
+stage_request(struct view *view, enum request request, struct line *line)
+{
+       switch (request) {
+       case REQ_STATUS_UPDATE:
+               stage_update(view, line);
+               break;
+
+       case REQ_EDIT:
+               if (!stage_status.name[0])
+                       return request;
+
+               open_editor(view, stage_status.name);
+               break;
+
+       case REQ_ENTER:
+               pager_request(view, request, line);
+               break;
+
+       default:
+               return request;
+       }
+
+       return REQ_NONE;
+}
+
+static struct view_ops stage_ops = {
+       "line",
+       NULL,
+       pager_read,
+       pager_draw,
+       stage_request,
+       pager_grep,
+       pager_select,
+};
+
+
+/*
+ * Revision graph
+ */
+
+struct commit {
+       char id[SIZEOF_REV];            /* SHA1 ID. */
+       char title[128];                /* First line of the commit message. */
+       char author[75];                /* Author of the commit. */
+       struct tm time;                 /* Date from the author ident. */
        struct ref **refs;              /* Repository references. */
        chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
        size_t graph_size;              /* The width of the graph array. */
 };
 
+/* Size of rev graph with no  "padding" columns */
+#define SIZEOF_REVITEMS        (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
+
+struct rev_graph {
+       struct rev_graph *prev, *next, *parents;
+       char rev[SIZEOF_REVITEMS][SIZEOF_REV];
+       size_t size;
+       struct commit *commit;
+       size_t pos;
+};
+
+/* Parents of the commit being visualized. */
+static struct rev_graph graph_parents[4];
+
+/* The current stack of revisions on the graph. */
+static struct rev_graph graph_stacks[4] = {
+       { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
+       { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
+       { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
+       { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
+};
+
+static inline bool
+graph_parent_is_merge(struct rev_graph *graph)
+{
+       return graph->parents->size > 1;
+}
+
+static inline void
+append_to_rev_graph(struct rev_graph *graph, chtype symbol)
+{
+       struct commit *commit = graph->commit;
+
+       if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
+               commit->graph[commit->graph_size++] = symbol;
+}
+
+static void
+done_rev_graph(struct rev_graph *graph)
+{
+       if (graph_parent_is_merge(graph) &&
+           graph->pos < graph->size - 1 &&
+           graph->next->size == graph->size + graph->parents->size - 1) {
+               size_t i = graph->pos + graph->parents->size - 1;
+
+               graph->commit->graph_size = i * 2;
+               while (i < graph->next->size - 1) {
+                       append_to_rev_graph(graph, ' ');
+                       append_to_rev_graph(graph, '\\');
+                       i++;
+               }
+       }
+
+       graph->size = graph->pos = 0;
+       graph->commit = NULL;
+       memset(graph->parents, 0, sizeof(*graph->parents));
+}
+
+static void
+push_rev_graph(struct rev_graph *graph, char *parent)
+{
+       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;
+
+       if (graph->size < SIZEOF_REVITEMS) {
+               string_copy_rev(graph->rev[graph->size++], parent);
+       }
+}
+
+static chtype
+get_rev_graph_symbol(struct rev_graph *graph)
+{
+       chtype symbol;
+
+       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;
+}
+
+static void
+draw_rev_graph(struct rev_graph *graph)
+{
+       struct rev_filler {
+               chtype separator, line;
+       };
+       enum { DEFAULT, RSHARP, RDIAG, LDIAG };
+       static struct rev_filler fillers[] = {
+               { ' ',  REVGRAPH_LINE },
+               { '`',  '.' },
+               { '\'', ' ' },
+               { '/',  ' ' },
+       };
+       chtype symbol = get_rev_graph_symbol(graph);
+       struct rev_filler *filler;
+       size_t i;
+
+       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;
+
+       /* First, traverse all lines of revisions up to the active one. */
+       for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
+               if (!strcmp(graph->rev[graph->pos], graph->commit->id))
+                       break;
+
+               push_rev_graph(graph->next, graph->rev[graph->pos]);
+       }
+
+       /* Interleave the new revision parent(s). */
+       for (i = 0; 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 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;
+
+       draw_rev_graph(graph->prev);
+       done_rev_graph(graph->prev->prev);
+}
+
+
+/*
+ * Main view backend
+ */
+
 static bool
 main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
@@ -2748,6 +3828,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                for (i = 0; i < commit->graph_size; i++)
                        waddch(view->win, commit->graph[i]);
 
+               waddch(view->win, ' ');
                col += commit->graph_size + 1;
        }
 
@@ -2761,6 +3842,8 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                                ;
                        else if (commit->refs[i]->tag)
                                wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
+                       else if (commit->refs[i]->remote)
+                               wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
                        else
                                wattrset(view->win, get_line_attr(LINE_MAIN_REF));
                        waddstr(view->win, "[");
@@ -2788,232 +3871,72 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
        return TRUE;
 }
 
-
-struct rev_stack {
-       struct rev_stack *prev, *next, *parents;
-       char rev[SIZEOF_REVITEMS][SIZEOF_REV];
-       size_t size;
-       struct commit *commit;
-       size_t pos;
-};
-
-/* Parents of the commit being visualized. */
-static struct rev_stack graph_parents[3];
-
-/* The current stack of revisions on the graph. */
-static struct rev_stack graph_stacks[3] = {
-       { &graph_stacks[2], &graph_stacks[1], &graph_parents[0] },
-       { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
-       { &graph_stacks[1], &graph_stacks[0], &graph_parents[2] },
-};
-
-static inline bool
-graph_parent_is_merge(struct rev_stack *graph)
-{
-       return graph->parents->size > 1;
-}
-
-static void
-reset_rev_graph(struct rev_stack *graph)
-{
-       graph->size = graph->pos = 0;
-       graph->commit = NULL;
-       memset(graph->parents, 0, sizeof(*graph->parents));
-}
-
-static inline void
-append_to_rev_graph(struct rev_stack *stack, chtype symbol)
-{
-       if (stack->commit->graph_size < ARRAY_SIZE(stack->commit->graph) - 1)
-               stack->commit->graph[stack->commit->graph_size++] = symbol;
-}
-
-static void
-push_rev_stack(struct rev_stack *stack, char *parent)
-{
-       /* Combine duplicate parents lines. */
-       if (stack->size > 0 &&
-           !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV))
-               return;
-
-       if (stack->size < SIZEOF_REVITEMS) {
-               string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV);
-       }
-}
-
-static void
-draw_rev_graph(struct rev_stack *graph)
-{
-       chtype symbol, separator, line;
-       size_t i;
-
-       /* Place the symbol for this commit. */
-       if (graph->parents->size == 0)
-               symbol = REVGRAPH_INIT;
-       else if (graph->parents->size > 1)
-               symbol = REVGRAPH_MERGE;
-       else if (graph->pos >= graph->size)
-               symbol = REVGRAPH_BRANCH;
-       else
-               symbol = REVGRAPH_COMMIT;
-
-       separator = ' ';
-       line = REVGRAPH_LINE;
-
-       for (i = 0; i < graph->pos; i++) {
-               append_to_rev_graph(graph, line);
-               if (graph_parent_is_merge(graph->prev) &&
-                   graph->prev->pos == i) {
-                       separator = '`';
-                       line = '.';
-               }
-               append_to_rev_graph(graph, separator);
-       }
-
-       append_to_rev_graph(graph, symbol);
-
-       if (graph->prev->size > graph->size) {
-               separator = '\'';
-               line = ' ';
-       } else {
-               separator = ' ';
-               line = REVGRAPH_LINE;
-       }
-       i++;
-
-       for (; i < graph->size; i++) {
-               append_to_rev_graph(graph, separator);
-               append_to_rev_graph(graph, line);
-               if (graph_parent_is_merge(graph->prev)) {
-                       if (i < graph->prev->pos + graph->parents->size) {
-                               separator = '`';
-                               line = '.';
-                       }
-               }
-               if (graph->prev->size > graph->size) {
-                       separator = '/';
-                       line = ' ';
-               }
-       }
-
-       if (graph->prev->size > graph->size) {
-               append_to_rev_graph(graph, separator);
-               if (line != ' ')
-                       append_to_rev_graph(graph, line);
-       }
-}
-
-void
-update_rev_graph(struct rev_stack *graph)
-{
-       size_t i;
-
-       /* First traverse all lines of revisions up to the active one. */
-       for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
-               if (!strcmp(graph->rev[graph->pos], graph->commit->id))
-                       break;
-
-               push_rev_stack(graph->next, graph->rev[graph->pos]);
-       }
-
-       for (i = 0; i < graph->parents->size; i++)
-               push_rev_stack(graph->next, graph->parents->rev[i]);
-
-       /* FIXME: Moving branches left and right when collapsing a branch. */
-       for (i = graph->pos + 1; i < graph->size; i++)
-               push_rev_stack(graph->next, graph->rev[i]);
-
-       draw_rev_graph(graph);
-
-       graph = graph->prev;
-
-       if (graph_parent_is_merge(graph) &&
-           graph->pos < graph->size - 1 &&
-           graph->next->size == graph->size + graph->parents->size - 1) {
-               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++;
-               }
-       }
-
-       reset_rev_graph(graph);
-}
-
 /* Reads git log --pretty=raw output and parses it into the commit struct. */
 static bool
 main_read(struct view *view, char *line)
 {
-       static struct rev_stack *graph = graph_stacks;
-       enum line_type type = get_line_type(line);
-       struct commit *commit = view->lines
-                             ? view->line[view->lines - 1].data : NULL;
+       static struct rev_graph *graph = graph_stacks;
+       enum line_type type;
+       struct commit *commit;
 
-       switch (type) {
-       case LINE_COMMIT:
+       if (!line) {
+               update_rev_graph(graph);
+               return TRUE;
+       }
+
+       type = get_line_type(line);
+       if (type == LINE_COMMIT) {
                commit = calloc(1, sizeof(struct commit));
                if (!commit)
                        return FALSE;
 
-               line += STRING_SIZE("commit ");
-
-               view->line[view->lines++].data = commit;
-               string_copy(commit->id, line);
+               string_copy_rev(commit->id, line + STRING_SIZE("commit "));
                commit->refs = get_refs(commit->id);
                graph->commit = commit;
-               break;
+               add_line_data(view, commit, LINE_MAIN_COMMIT);
+               return TRUE;
+       }
+
+       if (!view->lines)
+               return TRUE;
+       commit = view->line[view->lines - 1].data;
 
+       switch (type) {
        case LINE_PARENT:
-               if (commit) {
-                       line += STRING_SIZE("parent ");
-                       push_rev_stack(graph->parents, line);
-               }
+               push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
                break;
 
        case LINE_AUTHOR:
        {
+               /* Parse author lines where the name may be empty:
+                *      author  <email@address.tld> 1138474660 +0100
+                */
                char *ident = line + STRING_SIZE("author ");
-               char *end = strchr(ident, '<');
+               char *nameend = strchr(ident, '<');
+               char *emailend = strchr(ident, '>');
 
-               if (!commit)
+               if (!nameend || !emailend)
                        break;
 
                update_rev_graph(graph);
                graph = graph->next;
 
-               if (end) {
-                       char *email = end + 1;
-
-                       for (; end > ident && isspace(end[-1]); end--) ;
-
-                       if (end == ident && *email) {
-                               ident = email;
-                               end = strchr(ident, '>');
-                               for (; end > ident && isspace(end[-1]); end--) ;
-                       }
-                       *end = 0;
+               *nameend = *emailend = 0;
+               ident = chomp_string(ident);
+               if (!*ident) {
+                       ident = chomp_string(nameend + 1);
+                       if (!*ident)
+                               ident = "Unknown";
                }
 
-               /* End is NULL or ident meaning there's no author. */
-               if (end <= ident)
-                       ident = "Unknown";
-
-               string_copy(commit->author, ident);
+               string_ncopy(commit->author, ident, strlen(ident));
 
                /* Parse epoch and timezone */
-               if (end) {
-                       char *secs = strchr(end + 1, '>');
-                       char *zone;
-                       time_t time;
-
-                       if (!secs || secs[1] != ' ')
-                               break;
+               if (emailend[1] == ' ') {
+                       char *secs = emailend + 2;
+                       char *zone = strchr(secs, ' ');
+                       time_t time = (time_t) atol(secs);
 
-                       secs += 2;
-                       time = (time_t) atol(secs);
-                       zone = strchr(secs, ' ');
                        if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
                                long tz;
 
@@ -3028,39 +3951,47 @@ main_read(struct view *view, char *line)
 
                                time -= tz;
                        }
+
                        gmtime_r(&time, &commit->time);
                }
                break;
        }
        default:
-               if (!commit)
-                       break;
-
                /* Fill in the commit title if it has not already been set. */
                if (commit->title[0])
                        break;
 
                /* Require titles to start with a non-space character at the
                 * offset used by git log. */
-               /* FIXME: More gracefull handling of titles; append "..." to
-                * shortened titles, etc. */
-               if (strncmp(line, "    ", 4) ||
-                   isspace(line[4]))
+               if (strncmp(line, "    ", 4))
+                       break;
+               line += 4;
+               /* Well, if the title starts with a whitespace character,
+                * try to be forgiving.  Otherwise we end up with no title. */
+               while (isspace(*line))
+                       line++;
+               if (*line == '\0')
                        break;
+               /* FIXME: More graceful handling of titles; append "..." to
+                * shortened titles, etc. */
 
-               string_copy(commit->title, line + 4);
+               string_ncopy(commit->title, line, strlen(line));
        }
 
        return TRUE;
 }
 
-static bool
-main_enter(struct view *view, struct line *line)
+static enum request
+main_request(struct view *view, enum request request, struct line *line)
 {
        enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
 
-       open_view(view, REQ_VIEW_DIFF, flags);
-       return TRUE;
+       if (request == REQ_ENTER)
+               open_view(view, REQ_VIEW_DIFF, flags);
+       else
+               return request;
+
+       return REQ_NONE;
 }
 
 static bool
@@ -3099,15 +4030,16 @@ main_select(struct view *view, struct line *line)
 {
        struct commit *commit = line->data;
 
-       string_copy(view->ref, commit->id);
-       string_copy(ref_commit, view->ref);
+       string_copy_rev(view->ref, commit->id);
+       string_copy_rev(ref_commit, view->ref);
 }
 
 static struct view_ops main_ops = {
        "commit",
-       main_draw,
+       NULL,
        main_read,
-       main_enter,
+       main_draw,
+       main_request,
        main_grep,
        main_select,
 };
@@ -3283,33 +4215,37 @@ static bool cursed = FALSE;
 /* The status window is used for polling keystrokes. */
 static WINDOW *status_win;
 
+static bool status_empty = TRUE;
+
 /* Update status and title window. */
 static void
 report(const char *msg, ...)
 {
-       static bool empty = TRUE;
        struct view *view = display[current_view];
 
-       if (!empty || *msg) {
+       if (input_mode)
+               return;
+
+       if (!status_empty || *msg) {
                va_list args;
 
                va_start(args, msg);
 
-               werase(status_win);
                wmove(status_win, 0, 0);
                if (*msg) {
                        vwprintw(status_win, msg, args);
-                       empty = FALSE;
+                       status_empty = FALSE;
                } else {
-                       empty = TRUE;
+                       status_empty = TRUE;
                }
+               wclrtoeol(status_win);
                wrefresh(status_win);
 
                va_end(args);
        }
 
        update_view_title(view);
-       update_display_cursor();
+       update_display_cursor(view);
 }
 
 /* Controls when nodelay should be in effect when polling user input. */
@@ -3372,10 +4308,16 @@ read_prompt(const char *prompt)
                struct view *view;
                int i, key;
 
+               input_mode = TRUE;
+
                foreach_view (view, i)
                        update_view(view);
 
-               report("%s%.*s", prompt, pos, buf);
+               input_mode = FALSE;
+
+               mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
+               wclrtoeol(status_win);
+
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
                switch (key) {
@@ -3410,11 +4352,12 @@ read_prompt(const char *prompt)
                }
        }
 
-       if (status == CANCEL) {
-               /* Clear the status window */
-               report("");
+       /* Clear the status window */
+       status_empty = FALSE;
+       report("");
+
+       if (status == CANCEL)
                return NULL;
-       }
 
        buf[pos++] = 0;
 
@@ -3482,10 +4425,11 @@ get_refs(char *id)
 }
 
 static int
-read_ref(char *id, int idlen, char *name, int namelen)
+read_ref(char *id, size_t idlen, char *name, size_t namelen)
 {
        struct ref *ref;
        bool tag = FALSE;
+       bool remote = FALSE;
 
        if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
                /* Commits referenced by tags has "^{}" appended. */
@@ -3499,6 +4443,11 @@ read_ref(char *id, int idlen, char *name, int namelen)
                namelen -= STRING_SIZE("refs/tags/");
                name    += STRING_SIZE("refs/tags/");
 
+       } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) {
+               remote = TRUE;
+               namelen -= STRING_SIZE("refs/remotes/");
+               name    += STRING_SIZE("refs/remotes/");
+
        } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
                namelen -= STRING_SIZE("refs/heads/");
                name    += STRING_SIZE("refs/heads/");
@@ -3519,7 +4468,8 @@ read_ref(char *id, int idlen, char *name, int namelen)
        strncpy(ref->name, name, namelen);
        ref->name[namelen] = 0;
        ref->tag = tag;
-       string_copy(ref->id, id);
+       ref->remote = remote;
+       string_copy_rev(ref->id, id);
 
        return OK;
 }
@@ -3534,10 +4484,13 @@ load_refs(void)
 }
 
 static int
-read_repo_config_option(char *name, int namelen, char *value, int valuelen)
+read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
 {
        if (!strcmp(name, "i18n.commitencoding"))
-               string_copy(opt_encoding, value);
+               string_ncopy(opt_encoding, value, valuelen);
+
+       if (!strcmp(name, "core.editor"))
+               string_ncopy(opt_editor, value, valuelen);
 
        return OK;
 }
@@ -3545,13 +4498,32 @@ read_repo_config_option(char *name, int namelen, char *value, int valuelen)
 static int
 load_repo_config(void)
 {
-       return read_properties(popen("git repo-config --list", "r"),
+       return read_properties(popen(GIT_CONFIG " --list", "r"),
                               "=", read_repo_config_option);
 }
 
+static int
+read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
+{
+       if (!opt_git_dir[0])
+               string_ncopy(opt_git_dir, name, namelen);
+       else
+               string_ncopy(opt_cdup, name, namelen);
+       return OK;
+}
+
+/* XXX: The line outputted by "--show-cdup" can be empty so the option
+ * must be the last one! */
+static int
+load_repo_info(void)
+{
+       return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
+                              "=", read_repo_info);
+}
+
 static int
 read_properties(FILE *pipe, const char *separators,
-               int (*read_property)(char *, int, char *, int))
+               int (*read_property)(char *, size_t, char *, size_t))
 {
        char buffer[BUFSIZ];
        char *name;
@@ -3629,14 +4601,23 @@ main(int argc, char *argv[])
        signal(SIGINT, quit);
 
        if (setlocale(LC_ALL, "")) {
-               string_copy(opt_codeset, nl_langinfo(CODESET));
+               char *codeset = nl_langinfo(CODESET);
+
+               string_ncopy(opt_codeset, codeset, strlen(codeset));
        }
 
+       if (load_repo_info() == ERR)
+               die("Failed to load repo info.");
+
+       /* Require a git repository unless when running in pager mode. */
+       if (!opt_git_dir[0])
+               die("Not a git repository");
+
        if (load_options() == ERR)
                die("Failed to load user config.");
 
        /* Load the repo config file so options can be overwritten from
-        * the command line.  */
+        * the command line. */
        if (load_repo_config() == ERR)
                die("Failed to load repo config.");
 
@@ -3652,10 +4633,6 @@ main(int argc, char *argv[])
        if (load_refs() == ERR)
                die("Failed to load refs.");
 
-       /* Require a git repository unless when running in pager mode. */
-       if (refs_size == 0 && opt_request != REQ_VIEW_PAGER)
-               die("Not a git repository");
-
        for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
                view->cmd_env = getenv(view->cmd_env);
 
@@ -3673,6 +4650,13 @@ main(int argc, char *argv[])
                /* Refresh, accept single keystroke of input */
                key = wgetch(status_win);
 
+               /* wgetch() with nodelay() enabled returns ERR when there's no
+                * input. */
+               if (key == ERR) {
+                       request = REQ_NONE;
+                       continue;
+               }
+
                request = get_keybinding(display[current_view]->keymap, key);
 
                /* Some low-level request handling. This keeps access to
@@ -3702,7 +4686,7 @@ main(int argc, char *argv[])
                        char *search = read_prompt(prompt);
 
                        if (search)
-                               string_copy(opt_search, search);
+                               string_ncopy(opt_search, search, strlen(search));
                        else
                                request = REQ_NONE;
                        break;