X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=tig.c;h=a82a4686e4696316a00eeb9473d8deb934df6e4b;hb=ac02669e69c583b09ce6fe4c19f0f29b6392e151;hp=77e1d8183372669bc1fed7fda0cbb97ada649417;hpb=bb46d935db3f3548bbe2d411f58588943058148c;p=tig.git diff --git a/tig.c b/tig.c index 77e1d81..a82a468 100644 --- a/tig.c +++ b/tig.c @@ -209,7 +209,7 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen) #define string_add(dst, from, src) \ string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src)) -static void +static size_t string_expand(char *dst, size_t dstlen, const char *src, int tabsize) { size_t size, pos; @@ -228,6 +228,7 @@ string_expand(char *dst, size_t dstlen, const char *src, int tabsize) } dst[size] = 0; + return pos; } static char * @@ -676,18 +677,49 @@ argv_free(const char *argv[]) { int argc; + if (!argv) + return; for (argc = 0; argv[argc]; argc++) free((void *) argv[argc]); argv[0] = NULL; } +DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG) + +static bool +argv_append(const char ***argv, const char *arg) +{ + int argc = 0; + + while (*argv && (*argv)[argc]) + argc++; + + if (!argv_realloc(argv, argc, 2)) + return FALSE; + + (*argv)[argc++] = strdup(arg); + (*argv)[argc] = NULL; + return TRUE; +} + +static bool +argv_append_array(const char ***dst_argv, const char *src_argv[]) +{ + int i; + + for (i = 0; src_argv && src_argv[i]; i++) + if (!argv_append(dst_argv, src_argv[i])) + return FALSE; + return TRUE; +} + static bool -argv_copy(const char *dst[], const char *src[]) +argv_copy(const char ***dst, const char *src[]) { int argc; for (argc = 0; src[argc]; argc++) - if (!(dst[argc] = strdup(src[argc]))) + if (!argv_append(dst, src[argc])) return FALSE; return TRUE; } @@ -1202,6 +1234,9 @@ static char opt_git_dir[SIZEOF_STR] = ""; static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */ static char opt_editor[SIZEOF_STR] = ""; static FILE *opt_tty = NULL; +static const char **opt_diff_args = NULL; +static const char **opt_rev_args = NULL; +static const char **opt_file_args = NULL; #define is_initial_commit() (!get_ref_head()) #define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id))) @@ -1674,7 +1709,7 @@ get_keys(enum keymap keymap, enum request request, bool all) struct run_request { enum keymap keymap; int key; - const char *argv[SIZEOF_ARG]; + const char **argv; }; static struct run_request *run_request; @@ -1683,22 +1718,19 @@ static size_t run_requests; DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8) static enum request -add_run_request(enum keymap keymap, int key, int argc, const char **argv) +add_run_request(enum keymap keymap, int key, const char **argv) { struct run_request *req; - if (argc >= ARRAY_SIZE(req->argv) - 1) - return REQ_NONE; - if (!realloc_run_requests(&run_request, run_requests, 1)) return REQ_NONE; req = &run_request[run_requests]; req->keymap = keymap; req->key = key; - req->argv[0] = NULL; + req->argv = NULL; - if (!argv_copy(req->argv, argv)) + if (!argv_copy(&req->argv, argv)) return REQ_NONE; return REQ_NONE + ++run_requests; @@ -1719,16 +1751,11 @@ add_builtin_run_requests(void) const char *checkout[] = { "git", "checkout", "%(branch)", NULL }; const char *commit[] = { "git", "commit", NULL }; const char *gc[] = { "git", "gc", NULL }; - struct { - enum keymap keymap; - int key; - int argc; - const char **argv; - } reqs[] = { - { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick }, - { KEYMAP_STATUS, 'C', ARRAY_SIZE(commit) - 1, commit }, - { KEYMAP_BRANCH, 'C', ARRAY_SIZE(checkout) - 1, checkout }, - { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc }, + struct run_request reqs[] = { + { KEYMAP_MAIN, 'C', cherry_pick }, + { KEYMAP_STATUS, 'C', commit }, + { KEYMAP_BRANCH, 'C', checkout }, + { KEYMAP_GENERIC, 'G', gc }, }; int i; @@ -1737,7 +1764,7 @@ add_builtin_run_requests(void) if (req != reqs[i].key) continue; - req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv); + req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argv); if (req != REQ_NONE) add_keybinding(reqs[i].keymap, req, reqs[i].key); } @@ -2010,7 +2037,7 @@ option_bind_command(int argc, const char *argv[]) } } if (request == REQ_UNKNOWN && *argv[2]++ == '!') - request = add_run_request(keymap, key, argc - 2, argv + 2); + request = add_run_request(keymap, key, argv + 2); if (request == REQ_UNKNOWN) { config_msg = "Unknown request name"; return ERR; @@ -2108,6 +2135,7 @@ load_options(void) const char *home = getenv("HOME"); const char *tigrc_user = getenv("TIGRC_USER"); const char *tigrc_system = getenv("TIGRC_SYSTEM"); + const char *tig_diff_opts = getenv("TIG_DIFF_OPTS"); char buf[SIZEOF_STR]; if (!tigrc_system) @@ -2125,6 +2153,17 @@ load_options(void) * that conflict with keybindings. */ add_builtin_run_requests(); + if (!opt_diff_args && tig_diff_opts && *tig_diff_opts) { + static const char *diff_opts[SIZEOF_ARG] = { NULL }; + int argc = 0; + + if (!string_format(buf, "%s", tig_diff_opts) || + !argv_from_string(diff_opts, &argc, buf)) + die("TIG_DIFF_OPTS contains too many arguments"); + else if (!argv_copy(&opt_diff_args, diff_opts)) + die("Failed to format TIG_DIFF_OPTS arguments"); + } + return OK; } @@ -2213,7 +2252,7 @@ struct view { bool has_scrolled; /* View was scrolled. */ /* Loading */ - const char *argv[SIZEOF_ARG]; /* Shell command arguments. */ + const char **argv; /* Shell command arguments. */ const char *dir; /* Directory from which to execute. */ struct io io; struct io *pipe; @@ -2370,7 +2409,15 @@ draw_space(struct view *view, enum line_type type, int max, int spaces) static bool draw_text(struct view *view, enum line_type type, const char *string, bool trim) { - view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim); + char text[SIZEOF_STR]; + + do { + size_t pos = string_expand(text, sizeof(text), string, opt_tab_size); + + view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, trim); + string += pos; + } while (*string && view->width + view->yoffset > view->col); + return view->width + view->yoffset <= view->col; } @@ -3142,17 +3189,33 @@ format_arg(const char *name) } static bool -format_argv(const char *dst_argv[], const char *src_argv[], bool replace) +format_argv(const char ***dst_argv, const char *src_argv[], bool replace) { char buf[SIZEOF_STR]; int argc; - argv_free(dst_argv); + argv_free(*dst_argv); for (argc = 0; src_argv[argc]; argc++) { const char *arg = src_argv[argc]; size_t bufpos = 0; + if (!strcmp(arg, "%(fileargs)")) { + if (!argv_append_array(dst_argv, opt_file_args)) + break; + continue; + + } else if (!strcmp(arg, "%(diffargs)")) { + if (!argv_append_array(dst_argv, opt_diff_args)) + break; + continue; + + } else if (!strcmp(arg, "%(revargs)")) { + if (!argv_append_array(dst_argv, opt_rev_args)) + break; + continue; + } + while (arg) { char *next = strstr(arg, "%("); int len = next - arg; @@ -3176,13 +3239,10 @@ format_argv(const char *dst_argv[], const char *src_argv[], bool replace) arg = next && replace ? strchr(next, ')') + 1 : NULL; } - dst_argv[argc] = strdup(buf); - if (!dst_argv[argc]) + if (!argv_append(dst_argv, buf)) break; } - dst_argv[argc] = NULL; - return src_argv[argc] == NULL; } @@ -3236,7 +3296,7 @@ static bool prepare_io(struct view *view, const char *dir, const char *argv[], bool replace) { view->dir = dir; - return format_argv(view->argv, argv, replace); + return format_argv(&view->argv, argv, replace); } static bool @@ -3286,7 +3346,8 @@ begin_update(struct view *view, bool refresh) string_copy_rev(view->ref, view->id); } - if (view->argv[0] && !io_run(&view->io, IO_RD, view->dir, view->argv)) + if (view->argv && view->argv[0] && + !io_run(&view->io, IO_RD, view->dir, view->argv)) return FALSE; setup_update(view, view->id); @@ -3565,16 +3626,18 @@ static void open_run_request(enum request request) { struct run_request *req = get_run_request(request); - const char *argv[ARRAY_SIZE(req->argv)] = { NULL }; + const char **argv = NULL; if (!req) { report("Unknown run request"); return; } - if (format_argv(argv, req->argv, TRUE)) + if (format_argv(&argv, req->argv, TRUE)) open_external_viewer(argv, NULL); - argv_free(argv); + if (argv) + argv_free(argv); + free(argv); } /* @@ -3935,73 +3998,6 @@ parse_author_line(char *ident, const char **author, struct time *time) } } -static bool -open_commit_parent_menu(char buf[SIZEOF_STR], int *parents) -{ - char rev[SIZEOF_REV]; - const char *revlist_argv[] = { - "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL - }; - struct menu_item *items; - char text[SIZEOF_STR]; - bool ok = TRUE; - int i; - - items = calloc(*parents + 1, sizeof(*items)); - if (!items) - return FALSE; - - for (i = 0; i < *parents; i++) { - string_copy_rev(rev, &buf[SIZEOF_REV * i]); - if (!io_run_buf(revlist_argv, text, sizeof(text)) || - !(items[i].text = strdup(text))) { - ok = FALSE; - break; - } - } - - if (ok) { - *parents = 0; - ok = prompt_menu("Select parent", items, parents); - } - for (i = 0; items[i].text; i++) - free((char *) items[i].text); - free(items); - return ok; -} - -static bool -select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) -{ - char buf[SIZEOF_STR * 4]; - const char *revlist_argv[] = { - "git", "log", "--no-color", "-1", - "--pretty=format:%P", id, "--", path, NULL - }; - int parents; - - if (!io_run_buf(revlist_argv, buf, sizeof(buf)) || - (parents = strlen(buf) / 40) < 0) { - report("Failed to get parent information"); - return FALSE; - - } else if (parents == 0) { - if (path) - report("Path '%s' does not exist in the parent", path); - else - report("The selected commit has no parents"); - return FALSE; - } - - if (parents == 1) - parents = 0; - else if (!open_commit_parent_menu(buf, &parents)) - return FALSE; - - string_copy_rev(rev, &buf[41 * parents]); - return TRUE; -} - /* * Pager backend */ @@ -4009,13 +4005,10 @@ select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path) static bool pager_draw(struct view *view, struct line *line, unsigned int lineno) { - char text[SIZEOF_STR]; - if (opt_line_number && draw_lineno(view, lineno)) return TRUE; - string_expand(text, sizeof(text), line->data, opt_tab_size); - draw_text(view, line->type, text, TRUE); + draw_text(view, line->type, line->data, TRUE); return TRUE; } @@ -4189,7 +4182,8 @@ static struct view_ops log_ops = { static const char *diff_argv[SIZEOF_ARG] = { "git", "show", "--pretty=fuller", "--no-color", "--root", - "--patch-with-stat", "--find-copies-harder", "-C", "%(commit)", NULL + "--patch-with-stat", "--find-copies-harder", "-C", + "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL }; static struct view_ops diff_ops = { @@ -4855,7 +4849,8 @@ struct blame_commit { const char *author; /* Author of the commit. */ struct time time; /* Date from the author ident. */ char filename[128]; /* Name of file. */ - bool has_previous; /* Was a "previous" line detected. */ + char parent_id[SIZEOF_REV]; /* Parent/previous SHA1 ID. */ + char parent_filename[128]; /* Parent/previous name of file. */ }; struct blame { @@ -4868,6 +4863,7 @@ static bool blame_open(struct view *view) { char path[SIZEOF_STR]; + size_t i; if (!view->prev && *opt_prefix) { string_copy(path, opt_file); @@ -4885,6 +4881,24 @@ blame_open(struct view *view) return FALSE; } + /* First pass: remove multiple references to the same commit. */ + for (i = 0; i < view->lines; i++) { + struct blame *blame = view->line[i].data; + + if (blame->commit && blame->commit->id[0]) + blame->commit->id[0] = 0; + else + blame->commit = NULL; + } + + /* Second pass: free existing references. */ + for (i = 0; i < view->lines; i++) { + struct blame *blame = view->line[i].data; + + if (blame->commit) + free(blame->commit); + } + setup_update(view, opt_file); string_format(view->ref, "%s ...", opt_file); @@ -5054,7 +5068,11 @@ blame_read(struct view *view, char *line) string_ncopy(commit->title, line, strlen(line)); } else if (match_blame_header("previous ", &line)) { - commit->has_previous = TRUE; + if (strlen(line) <= SIZEOF_REV) + return FALSE; + string_copy_rev(commit->parent_id, line); + line += SIZEOF_REV; + string_ncopy(commit->parent_filename, line, strlen(line)); } else if (match_blame_header("filename ", &line)) { string_ncopy(commit->filename, line, strlen(line)); @@ -5070,7 +5088,6 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno) struct blame *blame = line->data; struct time *time = NULL; const char *id = NULL, *author = NULL; - char text[SIZEOF_STR]; if (blame->commit && *blame->commit->filename) { id = blame->commit->id; @@ -5090,8 +5107,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno) if (draw_lineno(view, lineno)) return TRUE; - string_expand(text, sizeof(text), blame->text, opt_tab_size); - draw_text(view, LINE_DEFAULT, text, TRUE); + draw_text(view, LINE_DEFAULT, blame->text, TRUE); return TRUE; } @@ -5110,16 +5126,20 @@ check_blame_commit(struct blame *blame, bool check_null_id) static void setup_blame_parent_line(struct view *view, struct blame *blame) { + char from[SIZEOF_REF + SIZEOF_STR]; + char to[SIZEOF_REF + SIZEOF_STR]; const char *diff_tree_argv[] = { - "git", "diff-tree", "-U0", blame->commit->id, - "--", blame->commit->filename, NULL + "git", "diff", "--no-textconv", "--no-extdiff", "--no-color", + "-U0", from, to, "--", NULL }; struct io io; int parent_lineno = -1; int blamed_lineno = -1; char *line; - if (!io_run(&io, IO_RD, NULL, diff_tree_argv)) + if (!string_format(from, "%s:%s", opt_ref, opt_file) || + !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) || + !io_run(&io, IO_RD, NULL, diff_tree_argv)) return; while ((line = io_get(&io, '\n', TRUE))) { @@ -5161,10 +5181,13 @@ blame_request(struct view *view, enum request request, struct line *line) break; case REQ_PARENT: - if (check_blame_commit(blame, TRUE) && - select_commit_parent(blame->commit->id, opt_ref, - blame->commit->filename)) { - string_copy(opt_file, blame->commit->filename); + if (!check_blame_commit(blame, TRUE)) + break; + if (!*blame->commit->parent_id) { + report("The selected commit has no parents"); + } else { + string_copy_rev(opt_ref, blame->commit->parent_id); + string_copy(opt_file, blame->commit->parent_filename); setup_blame_parent_line(view, blame); open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH); } @@ -5185,7 +5208,7 @@ blame_request(struct view *view, enum request request, struct line *line) "-C", "-M", "HEAD", "--", view->vid, NULL }; - if (!blame->commit->has_previous) { + if (!*blame->commit->parent_id) { diff_index_argv[1] = "diff"; diff_index_argv[2] = "--no-color"; diff_index_argv[6] = "--"; @@ -5323,23 +5346,21 @@ branch_request(struct view *view, enum request request, struct line *line) return REQ_NONE; case REQ_ENTER: - if (branch->ref == &branch_all) { - const char *all_branches_argv[] = { - "git", "log", "--no-color", "--pretty=raw", "--parents", - "--topo-order", "--all", NULL - }; - struct view *main_view = VIEW(REQ_VIEW_MAIN); + { + const struct ref *ref = branch->ref; + const char *all_branches_argv[] = { + "git", "log", "--no-color", "--pretty=raw", "--parents", + "--topo-order", + ref == &branch_all ? "--all" : ref->name, NULL + }; + struct view *main_view = VIEW(REQ_VIEW_MAIN); - if (!prepare_update(main_view, all_branches_argv, NULL)) { - report("Failed to load view of all branches"); - return REQ_NONE; - } + if (!prepare_update(main_view, all_branches_argv, NULL)) + report("Failed to load view of all branches"); + else open_view(view, REQ_VIEW_MAIN, OPEN_PREPARED | OPEN_SPLIT); - } else { - open_view(view, REQ_VIEW_MAIN, OPEN_SPLIT); - } return REQ_NONE; - + } default: return request; } @@ -6686,7 +6707,8 @@ update_rev_graph(struct view *view, struct rev_graph *graph) static const char *main_argv[SIZEOF_ARG] = { "git", "log", "--no-color", "--pretty=raw", "--parents", - "--topo-order", "%(head)", NULL + "--topo-order", "%(diffargs)", "%(revargs)", + "--", "%(fileargs)", NULL }; static bool @@ -7044,12 +7066,13 @@ get_input(int prompt_position) { struct view *view; int i, key, cursor_y, cursor_x; - bool loading = FALSE; if (prompt_position) input_mode = TRUE; while (TRUE) { + bool loading = FALSE; + foreach_view (view, i) { update_view(view); if (view_is_displayed(view) && view->has_scrolled && @@ -7666,19 +7689,45 @@ warn(const char *msg, ...) va_end(args); } +static const char ***filter_args; + +static int +read_filter_args(char *name, size_t namelen, char *value, size_t valuelen) +{ + return argv_append(filter_args, name) ? OK : ERR; +} + +static void +filter_rev_parse(const char ***args, const char *arg1, const char *arg2, const char *argv[]) +{ + const char *rev_parse_argv[SIZEOF_ARG] = { "git", "rev-parse", arg1, arg2 }; + const char **all_argv = NULL; + + filter_args = args; + if (!argv_append_array(&all_argv, rev_parse_argv) || + !argv_append_array(&all_argv, argv) || + !io_run_load(all_argv, "\n", read_filter_args) == ERR) + die("Failed to split arguments"); + argv_free(all_argv); + free(all_argv); +} + +static void +filter_options(const char *argv[]) +{ + filter_rev_parse(&opt_file_args, "--no-revs", "--no-flags", argv); + filter_rev_parse(&opt_diff_args, "--no-revs", "--flags", argv); + filter_rev_parse(&opt_rev_args, "--symbolic", "--revs-only", argv); +} + static enum request parse_options(int argc, const char *argv[]) { enum request request = REQ_VIEW_MAIN; const char *subcommand; bool seen_dashdash = FALSE; - /* XXX: This is vulnerable to the user overriding options - * required for the main view parser. */ - const char *custom_argv[SIZEOF_ARG] = { - "git", "log", "--no-color", "--pretty=raw", "--parents", - "--topo-order", NULL - }; - int i, j = 6; + const char **filter_argv = NULL; + int i; if (!isatty(STDIN_FILENO)) { io_open(&VIEW(REQ_VIEW_PAGER)->io, ""); @@ -7686,7 +7735,7 @@ parse_options(int argc, const char *argv[]) } if (argc <= 1) - return REQ_NONE; + return REQ_VIEW_MAIN; subcommand = argv[1]; if (!strcmp(subcommand, "status")) { @@ -7714,16 +7763,16 @@ parse_options(int argc, const char *argv[]) subcommand = NULL; } - if (subcommand) { - custom_argv[1] = subcommand; - j = 2; - } - for (i = 1 + !!subcommand; i < argc; i++) { const char *opt = argv[i]; - if (seen_dashdash || !strcmp(opt, "--")) { + if (seen_dashdash) { + argv_append(&opt_file_args, opt); + continue; + + } else if (!strcmp(opt, "--")) { seen_dashdash = TRUE; + continue; } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) { printf("tig version %s\n", TIG_VERSION); @@ -7732,15 +7781,18 @@ parse_options(int argc, const char *argv[]) } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) { printf("%s\n", usage); quit(0); + + } else if (!strcmp(opt, "--all")) { + argv_append(&opt_rev_args, opt); + continue; } - custom_argv[j++] = opt; - if (j >= ARRAY_SIZE(custom_argv)) + if (!argv_append(&filter_argv, opt)) die("command too long"); } - if (!prepare_update(VIEW(request), custom_argv, NULL)) - die("Failed to format arguments"); + if (filter_argv) + filter_options(filter_argv); return request; } @@ -7788,17 +7840,18 @@ main(int argc, const char *argv[]) if (load_refs() == ERR) die("Failed to load refs."); - foreach_view (view, i) + foreach_view (view, i) { + if (getenv(view->cmd_env)) + warn("Use of the %s environment variable is deprecated," + " use options or TIG_DIFF_ARGS instead", + view->cmd_env); if (!argv_from_env(view->ops->argv, view->cmd_env)) die("Too many arguments in the `%s` environment variable", view->cmd_env); + } init_display(); - if (request != REQ_NONE) - open_view(NULL, request, OPEN_PREPARED); - request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE; - while (view_driver(display[current_view], request)) { int key = get_input(0);