status: "wire" REQ_STATUS_UPDATE handling through status_request
[tig] / tig.c
diff --git a/tig.c b/tig.c
index 888133e..f7e9714 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.6.git"
+#ifndef VERSION
+#define 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>
@@ -47,7 +48,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);
 
@@ -169,7 +170,7 @@ 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)
@@ -177,6 +178,9 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t 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)
 {
@@ -332,7 +336,8 @@ 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") \
 
 
 /* User action requests. */
@@ -384,7 +389,7 @@ get_request(const char *name)
  */
 
 static const char usage[] =
-VERSION " (" __DATE__ ")\n"
+"tig " VERSION " (" __DATE__ ")\n"
 "\n"
 "Usage: tig [options]\n"
 "   or: tig [options] [--] [git log options]\n"
@@ -417,6 +422,8 @@ 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]    = "";
 
 enum option_type {
        OPT_NONE,
@@ -493,7 +500,7 @@ parse_options(int argc, char *argv[])
 
                if (!strcmp(opt, "-S")) {
                        opt_request = REQ_VIEW_STATUS;
-                       break;
+                       continue;
                }
 
                if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
@@ -759,6 +766,7 @@ static struct keybinding default_keybindings[] = {
        { '.',          REQ_TOGGLE_LINENO },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
        { ':',          REQ_PROMPT },
+       { 'u',          REQ_STATUS_UPDATE },
 
        /* Using the ncurses SIGWINCH handler. */
        { KEY_RESIZE,   REQ_SCREEN_RESIZE },
@@ -1038,7 +1046,7 @@ option_set_command(int argc, char *argv[])
                                        break;
                                }
                default:
-                       string_copy(opt_encoding, arg);
+                       string_ncopy(opt_encoding, arg, strlen(arg));
                        return OK;
                }
        }
@@ -1116,7 +1124,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;
 
@@ -1135,7 +1143,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;
@@ -1147,7 +1155,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;
        }
 
@@ -1255,8 +1263,8 @@ struct view_ops {
        bool (*read)(struct view *view, char *data);
        /* Draw one line; @lineno must be < view->height. */
        bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected);
-       /* Depending on view, change display based on current line. */
-       bool (*enter)(struct view *view, struct line *line);
+       /* 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 */
@@ -1283,8 +1291,8 @@ static struct view views[] = {
        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,  ""),
+       VIEW_(HELP,   "help",   &help_ops,   ""),
+       VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
        VIEW_(STATUS, "status", &status_ops, ""),
 };
 
@@ -1360,7 +1368,7 @@ update_view_title(struct view *view)
 
        assert(view_is_displayed(view));
 
-       if (view->lines || view->pipe) {
+       if (!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
@@ -1388,7 +1396,7 @@ update_view_title(struct view *view)
 
                if (minsize < view->width)
                        refsize = view->width - minsize + 7;
-               string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
+               string_format_from(buf, &bufpos, " %.*s", (int) refsize, view->ref);
        }
 
        if (statelen && bufpos < view->width) {
@@ -1798,8 +1806,6 @@ end_update(struct view *view)
 static bool
 begin_update(struct view *view)
 {
-       const char *id = view->id;
-
        if (view->pipe)
                end_update(view);
 
@@ -1820,11 +1826,12 @@ begin_update(struct view *view)
                else if (sq_quote(path, 0, opt_path) >= sizeof(path))
                        return FALSE;
 
-               if (!string_format(view->cmd, format, id, 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;
@@ -1833,7 +1840,7 @@ begin_update(struct view *view)
                 * 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(view->ref, id);
+               string_copy_rev(view->ref, view->id);
        }
 
        /* Special case for the pager view. */
@@ -1852,7 +1859,7 @@ begin_update(struct view *view)
        view->offset = 0;
        view->lines  = 0;
        view->lineno = 0;
-       string_copy_rev(view->vid, id);
+       string_copy_rev(view->vid, view->id);
 
        if (view->line) {
                int i;
@@ -2120,6 +2127,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:
@@ -2147,7 +2160,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_VIEW_PAGER:
-               if (!VIEW(REQ_VIEW_PAGER)->lines) {
+               if (!opt_pipe && !VIEW(REQ_VIEW_PAGER)->lines) {
                        report("No pager content, press %s to run command from prompt",
                               get_key(REQ_PROMPT));
                        break;
@@ -2172,22 +2185,21 @@ view_driver(struct view *view, enum request request)
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
                   (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)
+                               break;
                } 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]);
 
        case REQ_VIEW_NEXT:
        {
@@ -2240,7 +2252,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)", VERSION, __DATE__);
                return TRUE;
 
        case REQ_SCREEN_RESIZE:
@@ -2250,6 +2262,7 @@ view_driver(struct view *view, enum request request)
                redraw_display();
                break;
 
+
        case REQ_NONE:
                doupdate();
                return TRUE;
@@ -2451,11 +2464,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))) {
@@ -2474,7 +2490,7 @@ pager_enter(struct view *view, struct line *line)
        if (split)
                update_view_title(view);
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static bool
@@ -2509,7 +2525,7 @@ static struct view_ops pager_ops = {
        NULL,
        pager_read,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2563,7 +2579,7 @@ static struct view_ops help_ops = {
        help_open,
        NULL,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2676,11 +2692,13 @@ tree_read(struct view *view, char *text)
        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;
-       enum request request;
+
+       if (request != REQ_ENTER)
+               return request;
 
        switch (line->type) {
        case LINE_TREE_DIR:
@@ -2724,7 +2742,7 @@ tree_enter(struct view *view, struct line *line)
 
        open_view(view, request, flags);
 
-       return TRUE;
+       return REQ_NONE;
 }
 
 static void
@@ -2747,7 +2765,7 @@ static struct view_ops tree_ops = {
        NULL,
        tree_read,
        pager_draw,
-       tree_enter,
+       tree_request,
        pager_grep,
        tree_select,
 };
@@ -2763,7 +2781,7 @@ static struct view_ops blob_ops = {
        NULL,
        blob_read,
        pager_draw,
-       pager_enter,
+       pager_request,
        pager_grep,
        pager_select,
 };
@@ -2900,9 +2918,10 @@ error_out:
 #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_exclude=$(git rev-parse --git-dir)/info/exclude;" \
-       "test -f \"$_git_exclude\" && exclude=\"--exclude-from=$_git_exclude\";" \
-       "git ls-files -z --others --exclude-per-directory=.gitignore \"$exclude\"" \
+       "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
@@ -2910,6 +2929,9 @@ error_out:
 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++)
@@ -2921,9 +2943,22 @@ status_open(struct view *view)
        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, STATUS_LIST_OTHER_CMD, FALSE, LINE_STAT_UNTRACKED))
+           !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
                return FALSE;
 
        return TRUE;
@@ -2985,34 +3020,117 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
        return TRUE;
 }
 
-static bool
-status_enter(struct view *view, struct line *line)
+static void status_update(struct view *view);
+
+static enum request
+status_request(struct view *view, enum request request, struct line *line)
 {
        struct status *status = line->data;
-       const char *cmd;
+       char path[SIZEOF_STR] = "";
+       char *info;
+       size_t cmdsize = 0;
+
+       if (request == REQ_STATUS_UPDATE) {
+               status_update(view);
+               return REQ_NONE;
+       }
+
+       if (request != REQ_ENTER)
+               return request;
+
+       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_DIFF, OPEN_RELOAD | OPEN_SPLIT);
+       if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
+               string_format(VIEW(REQ_VIEW_DIFF)->ref, info, 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 (!status)
-               return TRUE;
+       if (opt_cdup[0] &&
+           type != LINE_STAT_UNTRACKED &&
+           !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+               return FALSE;
 
-       switch (line->type) {
+       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;
-               cmd = "git update-index -z --index-info";
+
+               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;
-               cmd = "git update-index -z --add --remove --stdin";
+
+               string_add(cmd, cmdsize, "git update-index -z --add --remove --stdin");
                break;
 
        default:
@@ -3032,36 +3150,69 @@ status_enter(struct view *view, struct line *line)
        if (written != bufsize)
                return FALSE;
 
-       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
        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 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 Enter to unstage file for commit";
+               text = "Press %s to unstage %s for commit";
                break;
 
        case LINE_STAT_UNSTAGED:
-               text = "Press Enter to stage file for commit  ";
+               text = "Press %s to stage %s for commit";
                break;
 
        case LINE_STAT_UNTRACKED:
-               text = "Press Enter to stage file for addition";
+               text = "Press %s to stage %s for addition";
                break;
 
        case LINE_STAT_NONE:
-               return;
+               text = "Nothing to update";
+               break;
 
        default:
                die("w00t");
        }
 
-       string_ncopy(view->ref, text, strlen(text));
+       string_format(view->ref, text, get_key(REQ_STATUS_UPDATE), file);
 }
 
 static bool
@@ -3101,7 +3252,7 @@ static struct view_ops status_ops = {
        status_open,
        NULL,
        status_draw,
-       status_enter,
+       status_request,
        status_grep,
        status_select,
 };
@@ -3193,7 +3344,7 @@ push_rev_graph(struct rev_graph *graph, char *parent)
                        return;
 
        if (graph->size < SIZEOF_REVITEMS) {
-               string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
+               string_copy_rev(graph->rev[graph->size++], parent);
        }
 }
 
@@ -3481,7 +3632,7 @@ main_read(struct view *view, char *line)
                                ident = "Unknown";
                }
 
-               string_copy(commit->author, ident);
+               string_ncopy(commit->author, ident, strlen(ident));
 
                /* Parse epoch and timezone */
                if (emailend[1] == ' ') {
@@ -3527,19 +3678,23 @@ main_read(struct view *view, char *line)
                /* FIXME: More graceful handling of titles; append "..." to
                 * shortened titles, etc. */
 
-               string_copy(commit->title, line);
+               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
@@ -3587,7 +3742,7 @@ static struct view_ops main_ops = {
        NULL,
        main_read,
        main_draw,
-       main_enter,
+       main_request,
        main_grep,
        main_select,
 };
@@ -3973,7 +4128,7 @@ 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;
@@ -4032,10 +4187,10 @@ 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);
 
        return OK;
 }
@@ -4048,8 +4203,27 @@ load_repo_config(void)
 }
 
 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;
@@ -4127,14 +4301,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.");
 
@@ -4150,10 +4333,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);
 
@@ -4207,7 +4386,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;