Cleanup status_request to make it reload the status view by default
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 1ad9264..ce21fba 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
+/* Copyright (c) 2006-2007 Jonas Fonseca <fonseca@diku.dk>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
  * GNU General Public License for more details.
  */
 
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
 #ifndef TIG_VERSION
 #define TIG_VERSION "unknown-version"
 #endif
@@ -40,8 +44,6 @@
 
 #include <curses.h>
 
-#include "config.h"
-
 #if __GNUC__ >= 3
 #define __NORETURN __attribute__((__noreturn__))
 #else
@@ -78,6 +80,9 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 #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"
@@ -92,6 +97,10 @@ 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 "git config"
+#endif
+
 #define TIG_LS_REMOTE \
        "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
@@ -114,6 +123,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 #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'
@@ -301,12 +311,14 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        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"), \
        REQ_(NEXT,              "Move to next"), \
        REQ_(PREVIOUS,          "Move to previous"), \
        REQ_(VIEW_NEXT,         "Move focus to next view"), \
+       REQ_(REFRESH,           "Reload and refresh"), \
        REQ_(VIEW_CLOSE,        "Close the current view"), \
        REQ_(QUIT,              "Close all views and quit"), \
        \
@@ -340,7 +352,9 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
        REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
        REQ_(STATUS_UPDATE,     "Update file status"), \
-       REQ_(EDIT,              "Open in editor")
+       REQ_(STATUS_MERGE,      "Merge file using external tool"), \
+       REQ_(EDIT,              "Open in editor"), \
+       REQ_(CHERRY_PICK,       "Cherry-pick commit to current branch")
 
 
 /* User action requests. */
@@ -427,6 +441,7 @@ 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_is_inside_work_tree    = -1; /* set to TRUE or FALSE */
 static char opt_editor[SIZEOF_STR]     = "";
 
 enum option_type {
@@ -615,10 +630,10 @@ 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(STAT_SECTION, "",                 COLOR_DEFAULT,  COLOR_BLUE,     A_BOLD), \
+LINE(STAT_SECTION, "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
 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_STAGED,  "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
+LINE(STAT_UNSTAGED,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(STAT_UNTRACKED,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  0)
 
 enum line_type {
@@ -731,6 +746,7 @@ static struct keybinding default_keybindings[] = {
        { 'p',          REQ_VIEW_PAGER },
        { 'h',          REQ_VIEW_HELP },
        { 'S',          REQ_VIEW_STATUS },
+       { 'c',          REQ_VIEW_STAGE },
 
        /* View manipulation */
        { 'q',          REQ_VIEW_CLOSE },
@@ -738,6 +754,7 @@ static struct keybinding default_keybindings[] = {
        { KEY_RETURN,   REQ_ENTER },
        { KEY_UP,       REQ_PREVIOUS },
        { KEY_DOWN,     REQ_NEXT },
+       { 'R',          REQ_REFRESH },
 
        /* Cursor navigation */
        { 'k',          REQ_MOVE_UP },
@@ -771,7 +788,9 @@ static struct keybinding default_keybindings[] = {
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
        { 'u',          REQ_STATUS_UPDATE },
+       { 'M',          REQ_STATUS_MERGE },
        { 'e',          REQ_EDIT },
+       { 'C',          REQ_CHERRY_PICK },
 
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
@@ -786,7 +805,8 @@ static struct keybinding default_keybindings[] = {
        KEYMAP_(BLOB), \
        KEYMAP_(PAGER), \
        KEYMAP_(HELP), \
-       KEYMAP_(STATUS)
+       KEYMAP_(STATUS), \
+       KEYMAP_(STAGE)
 
 enum keymap {
 #define KEYMAP_(name) KEYMAP_##name
@@ -1282,6 +1302,7 @@ 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}
@@ -1299,6 +1320,7 @@ static struct view views[] = {
        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])
@@ -1928,7 +1950,7 @@ update_view(struct view *view)
                        line[linelen - 1] = 0;
 
                if (opt_iconv != ICONV_NONE) {
-                       ICONV_INBUF_TYPE inbuf = line;
+                       ICONV_CONST char *inbuf = line;
                        size_t inlen = linelen;
 
                        char *outbuf = out_buffer;
@@ -2126,11 +2148,36 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
 }
 
 static void
-open_editor(struct view *view, char *file)
+open_external_viewer(const char *cmd)
+{
+       def_prog_mode();           /* save current tty modes */
+       endwin();                  /* restore original tty modes */
+       system(cmd);
+       fprintf(stderr, "Press Enter to continue");
+       getc(stdin);
+       reset_prog_mode();
+       redraw_display();
+}
+
+static void
+open_mergetool(const char *file)
+{
+       char cmd[SIZEOF_STR];
+       char file_sq[SIZEOF_STR];
+
+       if (sq_quote(file_sq, 0, file) < sizeof(file_sq) &&
+           string_format(cmd, "git mergetool %s", file_sq)) {
+               open_external_viewer(cmd);
+       }
+}
+
+static void
+open_editor(bool from_root, const char *file)
 {
        char cmd[SIZEOF_STR];
        char file_sq[SIZEOF_STR];
        char *editor;
+       char *prefix = from_root ? opt_cdup : "";
 
        editor = getenv("GIT_EDITOR");
        if (!editor && *opt_editor)
@@ -2143,12 +2190,8 @@ open_editor(struct view *view, char *file)
                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();
+           string_format(cmd, "%s %s%s", editor, prefix, file_sq)) {
+               open_external_viewer(cmd);
        }
 }
 
@@ -2161,6 +2204,11 @@ view_driver(struct view *view, enum request request)
 {
        int i;
 
+       if (request == REQ_NONE) {
+               doupdate();
+               return TRUE;
+       }
+
        if (view && view->lines) {
                request = view->ops->request(view, request, &view->line[view->lineno]);
                if (request == REQ_NONE)
@@ -2202,12 +2250,28 @@ view_driver(struct view *view, enum request request)
                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_STATUS:
+               if (opt_is_inside_work_tree == FALSE) {
+                       report("The status view requires a working tree");
+                       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_STATUS:
                open_view(view, request, OPEN_DEFAULT);
                break;
 
@@ -2217,7 +2281,7 @@ view_driver(struct view *view, enum request request)
 
                if ((view == VIEW(REQ_VIEW_DIFF) &&
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
-                  (view == VIEW(REQ_VIEW_DIFF) &&
+                  (view == VIEW(REQ_VIEW_STAGE) &&
                     view->parent == VIEW(REQ_VIEW_STATUS)) ||
                   (view == VIEW(REQ_VIEW_BLOB) &&
                     view->parent == VIEW(REQ_VIEW_TREE))) {
@@ -2253,6 +2317,10 @@ view_driver(struct view *view, enum request request)
                report("");
                break;
        }
+       case REQ_REFRESH:
+               report("Refreshing is not yet supported for the %s view", view->name);
+               break;
+
        case REQ_TOGGLE_LINENO:
                opt_line_number = !opt_line_number;
                redraw_display();
@@ -2302,13 +2370,14 @@ view_driver(struct view *view, enum request request)
                report("Nothing to edit");
                break;
 
+       case REQ_CHERRY_PICK:
+               report("Nothing to cherry-pick");
+               break;
+
        case REQ_ENTER:
                report("Nothing to enter");
                break;
 
-       case REQ_NONE:
-               doupdate();
-               return TRUE;
 
        case REQ_VIEW_CLOSE:
                /* XXX: Mark closed views by letting view->parent point to the
@@ -2601,6 +2670,9 @@ help_open(struct view *view)
        for (i = 0; i < ARRAY_SIZE(req_info); i++) {
                char *key;
 
+               if (req_info[i].request == REQ_NONE)
+                       continue;
+
                if (!req_info[i].request) {
                        add_line_text(view, "", LINE_DEFAULT);
                        add_line_text(view, req_info[i].help, LINE_DEFAULT);
@@ -2608,6 +2680,9 @@ help_open(struct view *view)
                }
 
                key = get_key(req_info[i].request);
+               if (!*key)
+                       key = "(no key defined)";
+
                if (!string_format(buf, "    %-25s %s", key, req_info[i].help))
                        continue;
 
@@ -2857,7 +2932,7 @@ static struct view_ops tree_ops = {
 static bool
 blob_read(struct view *view, char *line)
 {
-       return add_line_text(view, line, LINE_DEFAULT);
+       return add_line_text(view, line, LINE_DEFAULT) != NULL;
 }
 
 static struct view_ops blob_ops = {
@@ -2888,6 +2963,9 @@ struct status {
        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
  */
@@ -2925,6 +3003,7 @@ static bool
 status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
 {
        struct status *file = NULL;
+       struct status *unmerged = NULL;
        char buf[SIZEOF_STR * 4];
        size_t bufsize = 0;
        FILE *pipe;
@@ -2974,6 +3053,23 @@ status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
                                if (!sep)
                                        break;
                                sepsize = sep - buf + 1;
+
+                               /* Collapse all 'M'odified entries that
+                                * follow a associated 'U'nmerged entry.
+                                */
+                               if (file->status == 'U') {
+                                       unmerged = file;
+
+                               } else if (unmerged) {
+                                       int collapse = !strcmp(buf, unmerged->name);
+
+                                       unmerged = NULL;
+                                       if (collapse) {
+                                               free(file);
+                                               view->lines--;
+                                               continue;
+                                       }
+                               }
                        }
 
                        /* git-ls-files just delivers a NUL separated
@@ -2999,7 +3095,8 @@ error_out:
        return TRUE;
 }
 
-#define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
+/* Don't show unmerged entries in the staged section. */
+#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --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"
@@ -3016,12 +3113,13 @@ status_open(struct view *view)
        struct stat statbuf;
        char exclude[SIZEOF_STR];
        char cmd[SIZEOF_STR];
+       unsigned long prev_lineno = view->lineno;
        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->lines = view->line_size = view->lineno = 0;
        view->line = NULL;
 
        if (!realloc_lines(view, view->line_size + 6))
@@ -3045,6 +3143,13 @@ status_open(struct view *view)
            !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
                return FALSE;
 
+       /* If all went well restore the previous line number to stay in
+        * the context. */
+       if (prev_lineno < view->lines)
+               view->lineno = prev_lineno;
+       else
+               view->lineno = view->lines - 1;
+
        return TRUE;
 }
 
@@ -3165,9 +3270,16 @@ status_enter(struct view *view, struct line *line)
                die("w00t");
        }
 
-       open_view(view, REQ_VIEW_DIFF, OPEN_RELOAD | OPEN_SPLIT);
-       if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
-               string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
+       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;
@@ -3249,8 +3361,6 @@ status_update(struct view *view)
        } 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
@@ -3263,21 +3373,34 @@ status_request(struct view *view, enum request request, struct line *line)
                status_update(view);
                break;
 
+       case REQ_STATUS_MERGE:
+               open_mergetool(status->name);
+               break;
+
        case REQ_EDIT:
                if (!status)
                        return request;
 
-               open_editor(view, status->name);
+               open_editor(status->status != '?', status->name);
                break;
 
        case REQ_ENTER:
+               /* After returning the status view has been split to
+                * show the stage view. No further reloading is
+                * necessary. */
                status_enter(view, line);
+               return REQ_NONE;
+
+       case REQ_REFRESH:
+               /* Simply reload the view. */
                break;
 
        default:
                return request;
        }
 
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+
        return REQ_NONE;
 }
 
@@ -3287,6 +3410,7 @@ status_select(struct view *view, struct line *line)
        struct status *status = line->data;
        char file[SIZEOF_STR] = "all files";
        char *text;
+       char *key;
 
        if (status && !string_format(file, "'%s'", status->name))
                return;
@@ -3315,7 +3439,15 @@ status_select(struct view *view, struct line *line)
                die("w00t");
        }
 
-       string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
+       if (status && status->status == 'U') {
+               text = "Press %s to resolve conflict in %s";
+               key = get_key(REQ_STATUS_MERGE);
+
+       } else {
+               key = get_key(REQ_STATUS_UPDATE);
+       }
+
+       string_format(view->ref, text, key, file);
 }
 
 static bool
@@ -3361,6 +3493,171 @@ static struct view_ops status_ops = {
 };
 
 
+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(stage_status.status != '?', 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
  */
@@ -3787,6 +4084,20 @@ main_read(struct view *view, char *line)
        return TRUE;
 }
 
+static void
+cherry_pick_commit(struct commit *commit)
+{
+       char cmd[SIZEOF_STR];
+       char *cherry_pick = getenv("TIG_CHERRY_PICK");
+
+       if (!cherry_pick)
+               cherry_pick = "git cherry-pick";
+
+       if (string_format(cmd, "%s %s", cherry_pick, commit->id)) {
+               open_external_viewer(cmd);
+       }
+}
+
 static enum request
 main_request(struct view *view, enum request request, struct line *line)
 {
@@ -3794,6 +4105,8 @@ main_request(struct view *view, enum request request, struct line *line)
 
        if (request == REQ_ENTER)
                open_view(view, REQ_VIEW_DIFF, flags);
+       else if (request == REQ_CHERRY_PICK)
+               cherry_pick_commit(line->data);
        else
                return request;
 
@@ -4032,6 +4345,21 @@ report(const char *msg, ...)
        if (input_mode)
                return;
 
+       if (!view) {
+               char buf[SIZEOF_STR];
+               va_list args;
+
+               va_start(args, msg);
+               if (vsnprintf(buf, sizeof(buf), msg, args) >= sizeof(buf)) {
+                       buf[sizeof(buf) - 1] = 0;
+                       buf[sizeof(buf) - 2] = '.';
+                       buf[sizeof(buf) - 3] = '.';
+                       buf[sizeof(buf) - 4] = '.';
+               }
+               va_end(args);
+               die("%s", buf);
+       }
+
        if (!status_empty || *msg) {
                va_list args;
 
@@ -4304,17 +4632,28 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t 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])
+       if (!opt_git_dir[0]) {
                string_ncopy(opt_git_dir, name, namelen);
-       else
+
+       } else if (opt_is_inside_work_tree == -1) {
+               /* This can be 3 different values depending on the
+                * version of git being used. If git-rev-parse does not
+                * understand --is-inside-work-tree it will simply echo
+                * the option else either "true" or "false" is printed.
+                * Default to true for the unknown case. */
+               opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
+
+       } else {
                string_ncopy(opt_cdup, name, namelen);
+       }
+
        return OK;
 }
 
@@ -4323,7 +4662,7 @@ read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
 static int
 load_repo_info(void)
 {
-       return read_properties(popen("git rev-parse --git-dir --show-cdup 2>/dev/null", "r"),
+       return read_properties(popen("git rev-parse --git-dir --is-inside-work-tree --show-cdup 2>/dev/null", "r"),
                               "=", read_repo_info);
 }
 
@@ -4415,10 +4754,6 @@ main(int argc, char *argv[])
        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.");
 
@@ -4430,6 +4765,10 @@ main(int argc, char *argv[])
        if (!parse_options(argc, argv))
                return 0;
 
+       /* Require a git repository unless when running in pager mode. */
+       if (!opt_git_dir[0])
+               die("Not a git repository");
+
        if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
                opt_iconv = iconv_open(opt_codeset, opt_encoding);
                if (opt_iconv == ICONV_NONE)