X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/0ff3b97cdb548763d320ef004a846e20b86209b7..ca1d71eaa9b69092b0d210f6d45b8afea9070b0d:/tig.c diff --git a/tig.c b/tig.c index b712173..3d57311 100644 --- 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 @@ -27,10 +27,11 @@ #include #include #include +#include +#include #include #include -#include #include #include @@ -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); @@ -110,6 +111,7 @@ 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 "" /* Some ascii-shorthands fitted into the ncurses namespace. */ #define KEY_TAB '\t' @@ -168,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) @@ -176,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) { @@ -293,6 +298,7 @@ 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_GROUP("View manipulation") \ REQ_(ENTER, "Enter current line and scroll"), \ @@ -330,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. */ @@ -382,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" @@ -394,6 +401,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" @@ -414,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, @@ -488,6 +498,11 @@ parse_options(int argc, char *argv[]) continue; } + if (!strcmp(opt, "-S")) { + opt_request = REQ_VIEW_STATUS; + break; + } + if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) { opt_line_number = TRUE; continue; @@ -595,7 +610,12 @@ 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) \ @@ -706,6 +726,7 @@ static struct keybinding default_keybindings[] = { { 'f', REQ_VIEW_BLOB }, { 'p', REQ_VIEW_PAGER }, { 'h', REQ_VIEW_HELP }, + { 'S', REQ_VIEW_STATUS }, /* View manipulation */ { 'q', REQ_VIEW_CLOSE }, @@ -745,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 }, @@ -758,7 +780,8 @@ static struct keybinding default_keybindings[] = { KEYMAP_(TREE), \ KEYMAP_(BLOB), \ KEYMAP_(PAGER), \ - KEYMAP_(HELP) \ + KEYMAP_(HELP), \ + KEYMAP_(STATUS) enum keymap { #define KEYMAP_(name) KEYMAP_##name @@ -1023,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; } } @@ -1101,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; @@ -1120,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; @@ -1132,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; } @@ -1253,6 +1276,7 @@ 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; #define VIEW_STR(name, cmd, env, ref, ops, map) \ { name, cmd, #env, ref, ops, map} @@ -1262,13 +1286,14 @@ static struct view_ops help_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", &help_ops, ""), - VIEW_(PAGER, "pager", &pager_ops, ""), + 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, ""), }; #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) @@ -1371,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) { @@ -1781,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); @@ -1803,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; @@ -1816,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. */ @@ -1835,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; @@ -2098,6 +2122,8 @@ open_view(struct view *prev, enum request request, enum open_flags flags) * User request switch noodle */ +static void status_update(struct view *view); + static int view_driver(struct view *view, enum request request) { @@ -2130,7 +2156,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; @@ -2143,6 +2169,7 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_LOG: case REQ_VIEW_TREE: case REQ_VIEW_HELP: + case REQ_VIEW_STATUS: open_view(view, request, OPEN_DEFAULT); break; @@ -2154,10 +2181,15 @@ 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; @@ -2222,7 +2254,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: @@ -2232,6 +2264,10 @@ view_driver(struct view *view, enum request request) redraw_display(); break; + case REQ_STATUS_UPDATE: + status_update(view); + break; + case REQ_NONE: doupdate(); return TRUE; @@ -2752,6 +2788,387 @@ static struct view_ops blob_ops = { /* + * Status backend + */ + +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]; +}; + +/* 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" + +/* 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 bool +status_enter(struct view *view, struct line *line) +{ + return TRUE; +} + +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; + + open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD); + return TRUE; +} + +static void +status_update(struct view *view) +{ + if (view == VIEW(REQ_VIEW_STATUS)) { + struct line *line = view->lines + ? &view->line[view->lineno] : NULL; + + if (!line || !line->data) { + report("No file chosen for update"); + return; + } + + if (!status_update_file(view, line->data, line->type)) + report("Failed to update file status"); + } else { + report("This action is only valid for the status view"); + } +} + +static void +status_select(struct view *view, struct line *line) +{ + char *text; + + switch (line->type) { + case LINE_STAT_STAGED: + text = "Press Enter to unstage file for commit"; + break; + + case LINE_STAT_UNSTAGED: + text = "Press Enter to stage file for commit "; + break; + + case LINE_STAT_UNTRACKED: + text = "Press Enter to stage file for addition"; + break; + + case LINE_STAT_NONE: + return; + + default: + die("w00t"); + } + + string_ncopy(view->ref, text, strlen(text)); +} + +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_enter, + status_grep, + status_select, +}; + + +/* * Revision graph */ @@ -2837,7 +3254,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); } } @@ -3125,7 +3542,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] == ' ') { @@ -3171,7 +3588,7 @@ 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; @@ -3617,7 +4034,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; @@ -3676,10 +4093,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; } @@ -3692,8 +4109,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; @@ -3771,14 +4207,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."); @@ -3794,10 +4239,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); @@ -3851,7 +4292,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;