X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=3fce6f086685395490beacd03f7afb968c5c7670;hb=c00eb27d07cc218c1b889e1e32a5f5ac4e40c308;hp=f52c8a4505d1c894f83b736b2384961d73f663df;hpb=4036f35cb03abc1e4672c9c3796a751da1e2a920;p=tig.git diff --git a/tig.c b/tig.c index f52c8a4..3fce6f0 100644 --- a/tig.c +++ b/tig.c @@ -166,6 +166,15 @@ struct ref { static struct ref **get_refs(const char *id); +enum format_flags { + FORMAT_ALL, /* Perform replacement in all arguments. */ + FORMAT_DASH, /* Perform replacement up until "--". */ + FORMAT_NONE /* No replacement should be performed. */ +}; + +static bool format_command(char dst[], const char *src[], enum format_flags flags); +static bool format_argv(const char *dst[], const char *src[], enum format_flags flags); + struct int_map { const char *name; int namelen; @@ -332,6 +341,147 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) return bufsize; } +static bool +argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) +{ + int valuelen; + + while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) { + bool advance = cmd[valuelen] != 0; + + cmd[valuelen] = 0; + argv[(*argc)++] = chomp_string(cmd); + cmd += valuelen + advance; + } + + if (*argc < SIZEOF_ARG) + argv[*argc] = NULL; + return *argc < SIZEOF_ARG; +} + + +/* + * Executing external commands. + */ + +enum io_type { + IO_FD, /* File descriptor based IO. */ + IO_RD, /* Read only fork+exec IO. */ + IO_WR, /* Write only fork+exec IO. */ +}; + +struct io { + enum io_type type; /* The requested type of pipe. */ + FILE *pipe; /* Pipe for reading or writing. */ + int error; /* Error status. */ + char sh[SIZEOF_STR]; /* Shell command buffer. */ + char *buf; /* Read/write buffer. */ + size_t bufalloc; /* Allocated buffer size. */ +}; + +static void +reset_io(struct io *io) +{ + io->pipe = NULL; + io->buf = NULL; + io->bufalloc = 0; + io->error = 0; +} + +static void +init_io(struct io *io, enum io_type type) +{ + reset_io(io); + io->type = type; +} + +static bool +init_io_fd(struct io *io, FILE *pipe) +{ + init_io(io, IO_FD); + io->pipe = pipe; + return io->pipe != NULL; +} + +static bool +done_io(struct io *io) +{ + free(io->buf); + if (io->type == IO_FD) + fclose(io->pipe); + else + pclose(io->pipe); + reset_io(io); + return TRUE; +} + +static bool +start_io(struct io *io) +{ + io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w"); + return io->pipe != NULL; +} + +static bool +run_io(struct io *io, enum io_type type, const char *cmd) +{ + init_io(io, type); + string_ncopy(io->sh, cmd, strlen(cmd)); + return start_io(io); +} + +static bool +run_io_format(struct io *io, const char *cmd, ...) +{ + va_list args; + + va_start(args, cmd); + init_io(io, IO_RD); + + if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh)) + io->sh[0] = 0; + va_end(args); + + return io->sh[0] ? start_io(io) : FALSE; +} + +static bool +io_eof(struct io *io) +{ + return feof(io->pipe); +} + +static int +io_error(struct io *io) +{ + return io->error; +} + +static bool +io_strerror(struct io *io) +{ + return strerror(io->error); +} + +static char * +io_gets(struct io *io) +{ + if (!io->buf) { + io->buf = malloc(BUFSIZ); + if (!io->buf) + return NULL; + io->bufalloc = BUFSIZ; + } + + if (!fgets(io->buf, io->bufalloc, io->pipe)) { + if (ferror(io->pipe)) + io->error = errno; + return NULL; + } + + return io->buf; +} + /* * User requests @@ -982,7 +1132,7 @@ get_key(enum request request) struct run_request { enum keymap keymap; int key; - char cmd[SIZEOF_STR]; + const char *argv[SIZEOF_ARG]; }; static struct run_request *run_request; @@ -992,24 +1142,24 @@ static enum request add_run_request(enum keymap keymap, int key, int argc, const char **argv) { struct run_request *req; - char cmd[SIZEOF_STR]; - size_t bufpos; - for (bufpos = 0; argc > 0; argc--, argv++) - if (!string_format_from(cmd, &bufpos, "%s ", *argv)) - return REQ_NONE; + if (argc >= ARRAY_SIZE(req->argv) - 1) + return REQ_NONE; req = realloc(run_request, (run_requests + 1) * sizeof(*run_request)); if (!req) return REQ_NONE; run_request = req; - req = &run_request[run_requests++]; - string_copy(req->cmd, cmd); + req = &run_request[run_requests]; req->keymap = keymap; req->key = key; + req->argv[0] = NULL; - return REQ_NONE + run_requests; + if (!format_argv(req->argv, argv, FORMAT_NONE)) + return REQ_NONE; + + return REQ_NONE + ++run_requests; } static struct run_request * @@ -1023,20 +1173,23 @@ get_run_request(enum request request) static void add_builtin_run_requests(void) { + const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL }; + const char *gc[] = { "git", "gc", NULL }; struct { enum keymap keymap; int key; - const char *argv[1]; + int argc; + const char **argv; } reqs[] = { - { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } }, - { KEYMAP_GENERIC, 'G', { "git gc" } }, + { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick }, + { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc }, }; int i; for (i = 0; i < ARRAY_SIZE(reqs); i++) { enum request req; - req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv); + req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv); if (req != REQ_NONE) add_keybinding(reqs[i].keymap, req, reqs[i].key); } @@ -1268,21 +1421,11 @@ static int set_option(const char *opt, char *value) { const char *argv[SIZEOF_ARG]; - int valuelen; int argc = 0; - /* Tokenize */ - while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) { - argv[argc++] = value; - value += valuelen; - - /* Nothing more to tokenize or last available token. */ - if (!*value || argc >= ARRAY_SIZE(argv)) - break; - - *value++ = 0; - while (isspace(*value)) - value++; + if (!argv_from_string(argv, &argc, value)) { + config_msg = "Too many option arguments"; + return ERR; } if (!strcmp(opt, "color")) @@ -1419,7 +1562,6 @@ struct view { enum keymap keymap; /* What keymap does this view have */ bool git_dir; /* Whether the view requires a git directory. */ - char cmd[SIZEOF_STR]; /* Command buffer */ char ref[SIZEOF_REF]; /* Hovered commit reference */ char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */ @@ -1452,7 +1594,8 @@ struct view { unsigned long col; /* Column when drawing. */ /* Loading */ - FILE *pipe; + struct io io; + struct io *pipe; time_t start_time; }; @@ -2218,6 +2361,103 @@ reset_view(struct view *view) view->vid[0] = 0; } +static void +free_argv(const char *argv[]) +{ + int argc; + + for (argc = 0; argv[argc]; argc++) + free((void *) argv[argc]); +} + +static bool +format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags) +{ + char buf[SIZEOF_STR]; + int argc; + bool noreplace = flags == FORMAT_NONE; + + free_argv(dst_argv); + + for (argc = 0; src_argv[argc]; argc++) { + const char *arg = src_argv[argc]; + size_t bufpos = 0; + + while (arg) { + char *next = strstr(arg, "%("); + int len = next - arg; + const char *value; + + if (!next || noreplace) { + if (flags == FORMAT_DASH && !strcmp(arg, "--")) + noreplace = TRUE; + len = strlen(arg); + value = ""; + + } else if (!prefixcmp(next, "%(directory)")) { + value = opt_path; + + } else if (!prefixcmp(next, "%(file)")) { + value = opt_file; + + } else if (!prefixcmp(next, "%(ref)")) { + value = *opt_ref ? opt_ref : "HEAD"; + + } else if (!prefixcmp(next, "%(head)")) { + value = ref_head; + + } else if (!prefixcmp(next, "%(commit)")) { + value = ref_commit; + + } else if (!prefixcmp(next, "%(blob)")) { + value = ref_blob; + + } else { + report("Unknown replacement: `%s`", next); + return FALSE; + } + + if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value)) + return FALSE; + + arg = next && !noreplace ? strchr(next, ')') + 1 : NULL; + } + + dst_argv[argc] = strdup(buf); + if (!dst_argv[argc]) + break; + } + + dst_argv[argc] = NULL; + + return src_argv[argc] == NULL; +} + +static bool +format_command(char dst[], const char *src_argv[], enum format_flags flags) +{ + const char *dst_argv[SIZEOF_ARG * 2] = { NULL }; + int bufsize = 0; + int argc; + + if (!format_argv(dst_argv, src_argv, flags)) { + free_argv(dst_argv); + return FALSE; + } + + for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) { + if (bufsize > 0) + dst[bufsize++] = ' '; + bufsize = sq_quote(dst, bufsize, dst_argv[argc]); + } + + if (bufsize < SIZEOF_STR) + dst[bufsize] = 0; + free_argv(dst_argv); + + return src_argv[argc] == NULL && bufsize < SIZEOF_STR; +} + static void end_update(struct view *view, bool force) { @@ -2227,10 +2467,7 @@ end_update(struct view *view, bool force) if (!force) return; set_nonblocking_input(FALSE); - if (view->pipe == stdin) - fclose(view->pipe); - else - pclose(view->pipe); + done_io(view->pipe); view->pipe = NULL; } @@ -2240,22 +2477,31 @@ setup_update(struct view *view, const char *vid) set_nonblocking_input(TRUE); reset_view(view); string_copy_rev(view->vid, vid); + view->pipe = &view->io; view->start_time = time(NULL); } static bool begin_update(struct view *view, bool refresh) { - if (opt_cmd[0]) { - string_copy(view->cmd, opt_cmd); - opt_cmd[0] = 0; + if (init_io_fd(&view->io, opt_pipe)) { + opt_pipe = NULL; + + } else if (opt_cmd[0]) { + if (!run_io(&view->io, IO_RD, opt_cmd)) + return FALSE; /* 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); + string_copy(view->ref, opt_cmd); else view->ref[0] = 0; + opt_cmd[0] = 0; + + } else if (refresh) { + if (!start_io(&view->io)) + return FALSE; } else if (view == VIEW(REQ_VIEW_TREE)) { const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; @@ -2266,14 +2512,14 @@ begin_update(struct view *view, bool refresh) else if (sq_quote(path, 0, opt_path) >= sizeof(path)) return FALSE; - if (!string_format(view->cmd, format, view->id, path)) + if (!run_io_format(&view->io, format, view->id, path)) return FALSE; - } else if (!refresh) { + } 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)) + if (!run_io_format(&view->io, format, id, id, id, id, id)) return FALSE; /* Put the current ref_* value to the view title ref @@ -2283,17 +2529,6 @@ begin_update(struct view *view, bool refresh) string_copy_rev(view->ref, view->id); } - /* Special case for the pager view. */ - if (opt_pipe) { - view->pipe = opt_pipe; - opt_pipe = NULL; - } else { - view->pipe = popen(view->cmd, "r"); - } - - if (!view->pipe) - return FALSE; - setup_update(view, view->id); return TRUE; @@ -2333,7 +2568,6 @@ realloc_lines(struct view *view, size_t line_size) static bool update_view(struct view *view) { - char in_buffer[BUFSIZ]; char out_buffer[BUFSIZ * 2]; char *line; /* The number of lines to read. If too low it will cause too much @@ -2353,7 +2587,7 @@ update_view(struct view *view) if (!realloc_lines(view, view->lines + lines)) goto alloc_error; - while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) { + while ((line = io_gets(view->pipe))) { size_t linelen = strlen(line); if (linelen) @@ -2396,11 +2630,11 @@ update_view(struct view *view) } } - if (ferror(view->pipe) && errno != 0) { - report("Failed to read: %s", strerror(errno)); + if (io_error(view->pipe)) { + report("Failed to read: %s", io_strerror(view->pipe)); end_update(view, TRUE); - } else if (feof(view->pipe)) { + } else if (io_eof(view->pipe)) { report(""); end_update(view, FALSE); } @@ -2629,49 +2863,14 @@ open_run_request(enum request request) { struct run_request *req = get_run_request(request); char buf[SIZEOF_STR * 2]; - size_t bufpos; - char *cmd; if (!req) { report("Unknown run request"); return; } - bufpos = 0; - cmd = req->cmd; - - while (cmd) { - char *next = strstr(cmd, "%("); - int len = next - cmd; - char *value; - - if (!next) { - len = strlen(cmd); - value = ""; - - } else if (!strncmp(next, "%(head)", 7)) { - value = ref_head; - - } else if (!strncmp(next, "%(commit)", 9)) { - value = ref_commit; - - } else if (!strncmp(next, "%(blob)", 7)) { - value = ref_blob; - - } else { - report("Unknown replacement in run request: `%s`", req->cmd); - return; - } - - if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value)) - return; - - if (next) - next = strchr(next, ')') + 1; - cmd = next; - } - - open_external_viewer(buf); + if (format_command(buf, req->argv, FORMAT_ALL)) + open_external_viewer(buf); } /* @@ -3176,6 +3375,9 @@ help_open(struct view *view) for (i = 0; i < run_requests; i++) { struct run_request *req = get_run_request(REQ_NONE + i + 1); const char *key; + char cmd[SIZEOF_STR]; + size_t bufpos; + int argc; if (!req) continue; @@ -3184,9 +3386,13 @@ help_open(struct view *view) if (!*key) key = "(no key defined)"; + for (bufpos = 0, argc = 0; req->argv[argc]; argc++) + if (!string_format_from(cmd, &bufpos, "%s%s", + argc ? " " : "", req->argv[argc])) + return REQ_NONE; + if (!string_format(buf, " %-10s %-14s `%s`", - keymap_table[req->keymap].name, - key, req->cmd)) + keymap_table[req->keymap].name, key, cmd)) continue; add_line_text(view, buf, LINE_DEFAULT); @@ -3532,11 +3738,10 @@ blame_open(struct view *view) if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) return FALSE; - if (*opt_ref || !(view->pipe = fopen(opt_file, "r"))) { + if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) { const char *id = *opt_ref ? ref : "HEAD"; - if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, id, path) || - !(view->pipe = popen(view->cmd, "r"))) + if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path)) return FALSE; } @@ -3625,7 +3830,7 @@ blame_read_file(struct view *view, const char *line, bool *read_file) if (!line) { char ref[SIZEOF_STR] = ""; char path[SIZEOF_STR]; - FILE *pipe = NULL; + struct io io = {}; if (view->lines == 0 && !view->parent) die("No blame exist for %s", view->vid); @@ -3633,14 +3838,13 @@ blame_read_file(struct view *view, const char *line, bool *read_file) if (view->lines == 0 || sq_quote(path, 0, opt_file) >= sizeof(path) || (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) || - !string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path) || - !(pipe = popen(view->cmd, "r"))) { + !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) { report("Failed to load blame data"); return TRUE; } - fclose(view->pipe); - view->pipe = pipe; + done_io(view->pipe); + view->io = io; *read_file = FALSE; return FALSE; @@ -3764,12 +3968,25 @@ blame_request(struct view *view, enum request request, struct line *line) struct blame *blame = line->data; switch (request) { + case REQ_VIEW_BLAME: + if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) { + report("Commit ID unknown"); + break; + } + string_copy(opt_ref, blame->commit->id); + open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH); + return request; + case REQ_ENTER: if (!blame->commit) { report("No commit loaded yet"); break; } + if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) && + !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref)) + break; + if (!strcmp(blame->commit->id, NULL_ID)) { char path[SIZEOF_STR];