X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/91c5d983a57f52daca6794cad36cb2a4f1a232a7..ca1d71eaa9b69092b0d210f6d45b8afea9070b0d:/tig.c diff --git a/tig.c b/tig.c index d6e54b1..3d57311 100644 --- a/tig.c +++ b/tig.c @@ -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); @@ -335,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. */ @@ -421,6 +423,7 @@ 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, @@ -763,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 }, @@ -1042,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; } } @@ -1120,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; @@ -1139,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; @@ -1151,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; } @@ -1287,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, ""), }; @@ -1392,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) { @@ -1802,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); @@ -1824,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; @@ -1837,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. */ @@ -1856,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; @@ -2119,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) { @@ -2151,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; @@ -2176,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; @@ -2254,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; @@ -2904,9 +2918,7 @@ 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" /* First parse staged info using git-diff-index(1), then parse unstaged * info using git-diff-files(1), and finally untracked files using @@ -2914,6 +2926,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++) @@ -2925,9 +2940,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; @@ -2992,7 +3020,12 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele static bool status_enter(struct view *view, struct line *line) { - struct status *status = line->data; + 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; @@ -3000,15 +3033,12 @@ status_enter(struct view *view, struct line *line) size_t written = 0; FILE *pipe; - if (!status) - return TRUE; - if (opt_cdup[0] && - line->type != LINE_STAT_UNTRACKED && + 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, @@ -3049,6 +3079,25 @@ status_enter(struct view *view, struct line *line) } 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; @@ -3205,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); } } @@ -3493,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] == ' ') { @@ -3539,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; @@ -3985,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; @@ -4044,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; } @@ -4060,23 +4109,27 @@ load_repo_config(void) } static int -read_repo_info(char *name, int namelen, char *value, int valuelen) +read_repo_info(char *name, size_t namelen, char *value, size_t valuelen) { - if (!opt_cdup[0]) - string_copy(opt_cdup, name); + 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 --show-cdup", "r"), + 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; @@ -4154,20 +4207,26 @@ 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."); - if (load_repo_info() == ERR) - die("Failed to load repo info."); - if (!parse_options(argc, argv)) return 0; @@ -4180,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); @@ -4237,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;