void ref_update_env(struct argv_env *env, const struct ref *ref, bool recurse) { bool clear = recurse ? !ref->next : true; if (recurse && ref->next) ref_update_env(env, ref->next, true); if (clear) env->tag[0] = env->remote[0] = env->branch[0] = 0; string_copy_rev(env->commit, ref->id); if (ref_is_tag(ref)) { string_ncopy(env->tag, ref->name, strlen(ref->name)); } else if (ref_is_remote(ref)) { const char *sep = strchr(ref->name, '/'); if (!sep) return; string_ncopy(env->remote, ref->name, sep - ref->name); string_ncopy(env->branch, sep + 1, strlen(sep + 1)); } else if (ref->type == REFERENCE_BRANCH || ref->type == REFERENCE_HEAD) { string_ncopy(env->branch, ref->name, strlen(ref->name)); } }
static void blame_go_forward(struct view *view, struct blame *blame, bool parent) { struct blame_state *state = view->private; struct blame_history_state *history_state = &state->history_state; struct blame_commit *commit = blame->commit; const char *id = parent ? commit->parent_id : commit->id; const char *filename = parent ? commit->parent_filename : commit->filename; if (!*id && parent) { report("The selected commit has no parents"); return; } if (!strcmp(history_state->id, id) && !strcmp(history_state->filename, filename)) { report("The selected commit is already displayed"); return; } if (!push_view_history_state(&blame_view_history, &view->pos, history_state)) { report("Failed to save current view state"); return; } string_ncopy(view->env->ref, id, sizeof(commit->id)); string_ncopy(view->env->file, filename, strlen(filename)); if (parent) setup_blame_parent_line(view, blame); view->env->lineno = blame->lineno; reload_view(view); }
enum status_code add_keybinding(struct keymap *table, enum request request, struct key_input *input) { char buf[SIZEOF_STR]; bool conflict = FALSE; size_t i; for (i = 0; i < table->size; i++) { if (keybinding_equals(&table->data[i].input, input, &conflict)) { enum request old_request = table->data[i].request; const char *old_name; table->data[i].request = request; if (!conflict) return SUCCESS; old_name = get_request_name(old_request); string_ncopy(buf, old_name, strlen(old_name)); return error("Key binding for %s and %s conflict; " "keys using Ctrl are case insensitive", buf, get_request_name(request)); } } table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data)); if (!table->data) die("Failed to allocate keybinding"); table->data[table->size].input = *input; table->data[table->size++].request = request; return SUCCESS; }
void open_editor(const char *file, unsigned int lineno) { const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL }; char editor_cmd[SIZEOF_STR]; char lineno_cmd[SIZEOF_STR]; const char *editor; int argc = 0; editor = getenv("GIT_EDITOR"); if (!editor && *opt_editor) editor = opt_editor; if (!editor) editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); if (!editor) editor = "vi"; string_ncopy(editor_cmd, editor, strlen(editor)); if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) { report("Failed to read editor command"); return; } if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno)) editor_argv[argc++] = lineno_cmd; editor_argv[argc] = file; if (!open_external_viewer(editor_argv, repo.cdup, FALSE, TRUE, EDITOR_LINENO_MSG)) opt_editor_line_number = FALSE; }
static enum status_code read_repo_info(char *name, size_t namelen, char *value, size_t valuelen, void *data) { struct repo_info_state *state = data; const char *arg = *state->argv ? *state->argv++ : ""; if (!strcmp(arg, REPO_INFO_GIT_DIR)) { string_ncopy(repo.git_dir, name, namelen); } else if (!strcmp(arg, REPO_INFO_WORK_TREE)) { /* This can be 3 different values depending on the * version of git being used. If git-rev-parse does not * understand --is-inside-work-tree it will simply echo * the option else either "true" or "false" is printed. * Default to true for the unknown case. */ repo.is_inside_work_tree = strcmp(name, "false") ? true : false; } else if (!strcmp(arg, REPO_INFO_SHOW_CDUP)) { string_ncopy(repo.cdup, name, namelen); } else if (!strcmp(arg, REPO_INFO_SHOW_PREFIX)) { /* Some versions of Git does not emit anything for --show-prefix * when the user is in the repository root directory. Try to detect * this special case by looking at the emitted value. If it looks * like a commit ID and there's no cdup path assume that no value * was emitted. */ if (!*repo.cdup && namelen == 40 && iscommit(name)) return read_repo_info(name, namelen, value, valuelen, data); string_ncopy(repo.prefix, name, namelen); } else if (!strcmp(arg, REPO_INFO_RESOLVED_HEAD)) { string_ncopy(repo.head_id, name, namelen); } else if (!strcmp(arg, REPO_INFO_SYMBOLIC_HEAD)) { if (!prefixcmp(name, "refs/heads/")) { const char *head = name + STRING_SIZE("refs/heads/"); string_ncopy(repo.head, head, strlen(head) + 1); add_ref(repo.head_id, name, repo.remote, repo.head); } state->argv++; } return SUCCESS; }
const char * diff_context_arg() { static char opt_diff_context_arg[9] = ""; if (!string_format(opt_diff_context_arg, "-U%u", opt_diff_context)) string_ncopy(opt_diff_context_arg, "-U3", 3); return opt_diff_context_arg; }
static void blame_select(struct view *view, struct line *line) { struct blame *blame = line->data; struct blame_commit *commit = blame->commit; if (!commit) return; if (string_rev_is_null(commit->id)) string_ncopy(view->env->commit, "HEAD", 4); else string_copy_rev(view->env->commit, commit->id); }
static void blame_go_back(struct view *view) { struct blame_history_state history_state; if (!pop_view_history_state(&blame_view_history, &view->pos, &history_state)) { report("Already at start of history"); return; } string_copy(view->env->ref, history_state.id); string_ncopy(view->env->file, history_state.filename, strlen(history_state.filename)); view->env->lineno = view->pos.lineno; reload_view(view); }
static void set_remote_branch(const char *name, const char *value, size_t valuelen) { if (!strcmp(name, ".remote")) { string_ncopy(repo.remote, value, valuelen); } else if (*repo.remote && !strcmp(name, ".merge")) { size_t from = strlen(repo.remote); if (!prefixcmp(value, "refs/heads/")) value += STRING_SIZE("refs/heads/"); if (!string_format_from(repo.remote, &from, "/%s", value)) repo.remote[0] = 0; } }
void update_options_from_argv(const char *argv[]) { int next, flags_pos; for (next = flags_pos = 0; argv[next]; next++) { const char *flag = argv[next]; int value = -1; if (map_enum(&value, commit_order_arg_map, flag)) { opt_commit_order = value; mark_option_seen(&opt_commit_order); continue; } if (map_enum(&value, ignore_space_arg_map, flag)) { opt_ignore_space = value; mark_option_seen(&opt_ignore_space); continue; } if (!strcmp(flag, "--no-notes")) { opt_show_notes = FALSE; mark_option_seen(&opt_show_notes); continue; } if (!prefixcmp(flag, "--show-notes") || !prefixcmp(flag, "--notes")) { opt_show_notes = TRUE; string_ncopy(opt_notes_arg, flag, strlen(flag)); mark_option_seen(&opt_show_notes); continue; } if (!prefixcmp(flag, "-U") && parse_int(&value, flag + 2, 0, 999999) == SUCCESS) { opt_diff_context = value; mark_option_seen(&opt_diff_context); continue; } argv[flags_pos++] = flag; } argv[flags_pos] = NULL; }
bool parse_blame_header(struct blame_header *header, const char *text, size_t max_lineno) { const char *pos = text + SIZEOF_REV - 2; if (strlen(text) <= SIZEOF_REV || pos[1] != ' ') return FALSE; string_ncopy(header->id, text, SIZEOF_REV); if (!parse_number(&pos, &header->orig_lineno, 1, 9999999) || !parse_number(&pos, &header->lineno, 1, max_lineno) || !parse_number(&pos, &header->group, 1, max_lineno - header->lineno + 1)) return FALSE; return TRUE; }
bool parse_blame_info(struct blame_commit *commit, char author[SIZEOF_STR], char *line) { if (match_blame_header("author ", &line)) { string_ncopy_do(author, SIZEOF_STR, line, strlen(line)); } else if (match_blame_header("author-mail ", &line)) { char *end = strchr(line, '>'); if (end) *end = 0; if (*line == '<') line++; commit->author = get_author(author, line); author[0] = 0; } else if (match_blame_header("author-time ", &line)) { parse_timesec(&commit->time, line); } else if (match_blame_header("author-tz ", &line)) { parse_timezone(&commit->time, line); } else if (match_blame_header("summary ", &line)) { string_ncopy(commit->title, line, strlen(line)); } else if (match_blame_header("previous ", &line)) { if (strlen(line) <= SIZEOF_REV) return FALSE; string_copy_rev(commit->parent_id, line); line += SIZEOF_REV; commit->parent_filename = get_path(line); if (!commit->parent_filename) return TRUE; } else if (match_blame_header("filename ", &line)) { commit->filename = get_path(line); return TRUE; } return FALSE; }
static int read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen, void *data) { if (!strcmp(name, "i18n.commitencoding")) set_encoding(&default_encoding, value, FALSE); else if (!strcmp(name, "gui.encoding")) set_encoding(&default_encoding, value, TRUE); else if (!strcmp(name, "core.editor")) string_ncopy(opt_editor, value, valuelen); else if (!strcmp(name, "core.worktree")) set_work_tree(value); else if (!strcmp(name, "core.abbrev")) parse_int(&opt_id_width, value, 0, SIZEOF_REV - 1); else if (!prefixcmp(name, "tig.color.")) set_repo_config_option(name + 10, value, option_color_command); else if (!prefixcmp(name, "tig.bind.")) set_repo_config_option(name + 9, value, option_bind_command); else if (!prefixcmp(name, "tig.")) set_repo_config_option(name + 4, value, option_set_command); else if (!prefixcmp(name, "color.")) set_git_color_option(name + STRING_SIZE("color."), value); else if (*repo.head && !prefixcmp(name, "branch.") && !strncmp(name + 7, repo.head, strlen(repo.head))) set_remote_branch(name + 7 + strlen(repo.head), value, valuelen); else if (!strcmp(name, "diff.context")) { if (!find_option_info_by_value(&opt_diff_context)->seen) opt_diff_context = -atoi(value); } return OK; }
static void __set_input_filename (const char *fileName) { const char* head = fileName; const char* tail = file_get_basename (head); if (g_file.name != NULL) string_delete(g_file.name); g_file.name = string_new_init(fileName); if (g_file.path != NULL) string_delete (g_file.path); if (tail == head){ g_file.path = NULL; } else{ const size_t length = tail - head - 1; g_file.path = string_new(); string_ncopy(g_file.path, fileName, length); } }
static struct blame_commit * get_blame_commit(struct view *view, const char *id) { size_t i; for (i = 0; i < view->lines; i++) { struct blame *blame = view->line[i].data; if (!blame->commit) continue; if (!strncmp(blame->commit->id, id, SIZEOF_REV - 1)) return blame->commit; } { struct blame_commit *commit = calloc(1, sizeof(*commit)); if (commit) string_ncopy(commit->id, id, SIZEOF_REV); return commit; } }
void ref_update_env(struct argv_env *env, const struct ref *ref, bool clear) { if (clear) env->tag[0] = env->remote[0] = env->branch[0] = 0; string_copy_rev(env->commit, ref->id); if (ref_is_tag(ref)) { string_copy_rev(env->tag, ref->name); } else if (ref_is_remote(ref)) { const char *sep = strchr(ref->name, '/'); if (!sep) return; string_ncopy(env->remote, ref->name, sep - ref->name); string_copy_rev(env->branch, sep + 1); } else if (ref->type == REFERENCE_BRANCH) { string_copy_rev(env->branch, ref->name); } }
enum status_code add_keybinding(struct keymap *table, enum request request, const struct key key[], size_t keys) { struct keybinding *keybinding; char buf[SIZEOF_STR]; bool conflict = false; size_t i; for (i = 0; i < table->size; i++) { if (keybinding_equals(table->data[i], key, keys, &conflict)) { enum request old_request = table->data[i]->request; const char *old_name; table->data[i]->request = request; if (!conflict) return SUCCESS; old_name = get_request_name(old_request); string_ncopy(buf, old_name, strlen(old_name)); return error("Key binding for %s and %s conflict; " "keys using Ctrl are case insensitive", buf, get_request_name(request)); } } table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data)); keybinding = calloc(1, sizeof(*keybinding) + (sizeof(*key) * (keys - 1))); if (!table->data || !keybinding) die("Failed to allocate keybinding"); memcpy(keybinding->key, key, sizeof(*key) * keys); keybinding->keys = keys; keybinding->request = request; table->data[table->size++] = keybinding; return SUCCESS; }
enum request run_prompt_command(struct view *view, const char *argv[]) { enum request request; const char *cmd = argv[0]; size_t cmdlen = cmd ? strlen(cmd) : 0; if (!cmd) return REQ_NONE; if (string_isnumber(cmd)) { int lineno = view->pos.lineno + 1; if (parse_int(&lineno, cmd, 1, view->lines + 1) == SUCCESS) { select_view_line(view, lineno - 1); report_clear(); } else { report("Unable to parse '%s' as a line number", cmd); } } else if (iscommit(cmd)) { string_ncopy(view->env->search, cmd, cmdlen); return REQ_JUMP_COMMIT; } else if (cmdlen > 1 && (cmd[0] == '/' || cmd[0] == '?')) { char search[SIZEOF_STR]; if (!argv_to_string(argv, search, sizeof(search), " ")) { report("Failed to copy search string"); return REQ_NONE; } if (!strcmp(search + 1, view->env->search)) return cmd[0] == '/' ? REQ_FIND_NEXT : REQ_FIND_PREV; string_ncopy(view->env->search, search + 1, strlen(search + 1)); return cmd[0] == '/' ? REQ_SEARCH : REQ_SEARCH_BACK; } else if (cmdlen > 1 && cmd[0] == '!') { struct view *next = &pager_view; bool copied; /* Trim the leading '!'. */ argv[0] = cmd + 1; copied = argv_format(view->env, &next->argv, argv, FALSE, TRUE); argv[0] = cmd; if (!copied) { report("Argument formatting failed"); } else { /* When running random commands, initially show the * command in the title. However, it maybe later be * overwritten if a commit line is selected. */ argv_to_string(next->argv, next->ref, sizeof(next->ref), " "); next->dir = NULL; open_pager_view(view, OPEN_PREPARED | OPEN_WITH_STDERR); } } else if (!strcmp(cmd, "toggle")) { char action[SIZEOF_STR] = ""; enum view_flag flags = prompt_toggle(view, argv, action); int i; if (flags & VIEW_RESET_DISPLAY) { resize_display(); redraw_display(TRUE); } foreach_displayed_view(view, i) { if (view_has_flags(view, flags) && !view->unrefreshable) reload_view(view); else redraw_view(view); } if (*action) report("%s", action); } else {
static bool blame_open(struct view *view, enum open_flags flags) { struct blame_state *state = view->private; const char *file_argv[] = { repo.cdup, view->env->file , NULL }; char path[SIZEOF_STR]; size_t i; if (is_initial_view(view)) { /* Finish validating and setting up blame options */ if (!opt_file_argv || opt_file_argv[1] || (opt_rev_argv && opt_rev_argv[1])) usage("Invalid number of options to blame"); if (opt_rev_argv) { string_ncopy(view->env->ref, opt_rev_argv[0], strlen(opt_rev_argv[0])); } string_ncopy(view->env->file, opt_file_argv[0], strlen(opt_file_argv[0])); opt_blame_options = opt_cmdline_argv; opt_cmdline_argv = NULL; } if (!view->env->file[0]) { report("No file chosen, press %s to open tree view", get_view_key(view, REQ_VIEW_TREE)); return FALSE; } if (!view->prev && *repo.prefix && !(flags & (OPEN_RELOAD | OPEN_REFRESH))) { string_copy(path, view->env->file); if (!string_format(view->env->file, "%s%s", repo.prefix, path)) { report("Failed to setup the blame view"); return FALSE; } } if (*view->env->ref || !begin_update(view, repo.cdup, file_argv, flags)) { const char *blame_cat_file_argv[] = { "git", "cat-file", "blob", "%(ref):%(file)", NULL }; if (!begin_update(view, repo.cdup, blame_cat_file_argv, flags)) 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); } if (!(flags & OPEN_RELOAD)) reset_view_history(&blame_view_history); string_copy_rev(state->history_state.id, view->env->ref); state->history_state.filename = get_path(view->env->file); if (!state->history_state.filename) return FALSE; string_format(view->vid, "%s", view->env->file); string_format(view->ref, "%s ...", view->env->file); return TRUE; }
void update_diff_context_arg(int diff_context) { if (!string_format(opt_diff_context_arg, "-U%u", diff_context)) string_ncopy(opt_diff_context_arg, "-U3", 3); }
static enum request run_prompt_command(struct view *view, char *cmd) { enum request request; if (cmd && string_isnumber(cmd)) { int lineno = view->pos.lineno + 1; if (parse_int(&lineno, cmd, 1, view->lines + 1) == SUCCESS) { select_view_line(view, lineno - 1); report_clear(); } else { report("Unable to parse '%s' as a line number", cmd); } } else if (cmd && iscommit(cmd)) { string_ncopy(view->env->search, cmd, strlen(cmd)); request = view_request(view, REQ_JUMP_COMMIT); if (request == REQ_JUMP_COMMIT) { report("Jumping to commits is not supported by the '%s' view", view->name); } } else if (cmd && strlen(cmd) == 1) { struct key_input input = { { cmd[0] } }; return get_keybinding(&view->ops->keymap, &input); } else if (cmd && cmd[0] == '!') { struct view *next = VIEW(REQ_VIEW_PAGER); const char *argv[SIZEOF_ARG]; int argc = 0; cmd++; /* When running random commands, initially show the * command in the title. However, it maybe later be * overwritten if a commit line is selected. */ string_ncopy(next->ref, cmd, strlen(cmd)); if (!argv_from_string(argv, &argc, cmd)) { report("Too many arguments"); } else if (!argv_format(view->env, &next->argv, argv, FALSE, TRUE)) { report("Argument formatting failed"); } else { next->dir = NULL; open_view(view, REQ_VIEW_PAGER, OPEN_PREPARED); } } else if (cmd) { request = get_request(cmd); if (request != REQ_UNKNOWN) return request; char *args = strchr(cmd, ' '); if (args) { *args++ = 0; if (set_option(cmd, args) == SUCCESS) { request = !view->unrefreshable ? REQ_REFRESH : REQ_SCREEN_REDRAW; if (!strcmp(cmd, "color")) init_colors(); } } return request; } return REQ_NONE; }
enum request run_prompt_command(struct view *view, const char *argv[]) { enum request request; const char *cmd = argv[0]; size_t cmdlen = cmd ? strlen(cmd) : 0; if (!cmd) return REQ_NONE; if (string_isnumber(cmd)) { int lineno = view->pos.lineno + 1; if (parse_int(&lineno, cmd, 0, view->lines + 1) == SUCCESS) { if (!lineno) lineno = 1; select_view_line(view, lineno - 1); report_clear(); } else { report("Unable to parse '%s' as a line number", cmd); } } else if (iscommit(cmd)) { int lineno; if (!(view->ops->column_bits & view_column_bit(ID))) { report("Jumping to commits is not supported by the %s view", view->name); return REQ_NONE; } for (lineno = 0; lineno < view->lines; lineno++) { struct view_column_data column_data = {}; struct line *line = &view->line[lineno]; if (view->ops->get_column_data(view, line, &column_data) && column_data.id && !strncasecmp(column_data.id, cmd, cmdlen)) { string_ncopy(view->env->search, cmd, cmdlen); select_view_line(view, lineno); report_clear(); return REQ_NONE; } } report("Unable to find commit '%s'", view->env->search); return REQ_NONE; } else if (cmdlen > 1 && (cmd[0] == '/' || cmd[0] == '?')) { char search[SIZEOF_STR]; if (!argv_to_string(argv, search, sizeof(search), " ")) { report("Failed to copy search string"); return REQ_NONE; } if (!strcmp(search + 1, view->env->search)) return cmd[0] == '/' ? REQ_FIND_NEXT : REQ_FIND_PREV; string_ncopy(view->env->search, search + 1, strlen(search + 1)); return cmd[0] == '/' ? REQ_SEARCH : REQ_SEARCH_BACK; } else if (cmdlen > 1 && cmd[0] == '!') { struct view *next = &pager_view; bool copied; /* Trim the leading '!'. */ argv[0] = cmd + 1; copied = argv_format(view->env, &next->argv, argv, FALSE, TRUE); argv[0] = cmd; if (!copied) { report("Argument formatting failed"); } else { /* When running random commands, initially show the * command in the title. However, it maybe later be * overwritten if a commit line is selected. */ argv_to_string(next->argv, next->ref, sizeof(next->ref), " "); next->dir = NULL; open_pager_view(view, OPEN_PREPARED | OPEN_WITH_STDERR); } } else if (!strcmp(cmd, "save-display")) { const char *path = argv[1] ? argv[1] : "tig-display.txt"; if (!save_display(path)) report("Failed to save screen to %s", path); else report("Saved screen to %s", path); } else if (!strcmp(cmd, "exec")) { struct run_request req = { view->keymap, {}, argv + 1 }; enum status_code code = parse_run_request_flags(&req.flags, argv + 1); if (code != SUCCESS) { report("Failed to execute command: %s", get_status_message(code)); } else { return exec_run_request(view, &req); } } else if (!strcmp(cmd, "toggle")) { enum view_flag flags = VIEW_NO_FLAGS; enum status_code code = prompt_toggle(view, argv, &flags); const char *action = get_status_message(code); if (code != SUCCESS) { report("%s", action); return REQ_NONE; } prompt_update_display(flags); if (*action) report("%s", action); } else if (!strcmp(cmd, "script")) { if (is_script_executing()) { report("Scripts cannot be run from scripts"); } else if (!open_script(argv[1])) { report("Failed to open %s", argv[1]); } } else { struct key key = {}; enum status_code code; enum view_flag flags = VIEW_NO_FLAGS; /* Try :<key> */ key.modifiers.multibytes = 1; string_ncopy(key.data.bytes, cmd, cmdlen); request = get_keybinding(view->keymap, &key, 1); if (request != REQ_NONE) return request; /* Try :<command> */ request = get_request(cmd); if (request != REQ_UNKNOWN) return request; code = set_option(argv[0], argv_size(argv + 1), &argv[1]); if (code != SUCCESS) { report("%s", get_status_message(code)); return REQ_NONE; } if (!strcmp(cmd, "set")) { struct prompt_toggle *toggle; toggle = find_prompt_toggle(option_toggles, ARRAY_SIZE(option_toggles), "", argv[1], strlen(argv[1])); if (toggle) flags = toggle->flags; } if (flags) { prompt_update_display(flags); } else { request = view_can_refresh(view) ? REQ_REFRESH : REQ_SCREEN_REDRAW; if (!strcmp(cmd, "color")) init_colors(); resize_display(); redraw_display(TRUE); } } return REQ_NONE; }