From 97771e1b166d65c4e60dd99a73db24f307e40a0d Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Thu, 4 Aug 2016 20:00:36 -0400 Subject: [PATCH] Unify jumping to line IDs using a `git-rev-parse`-able expression This allows to create keybindings to go to the 2nd parent and jumping to a commit by branch name. --- NEWS.adoc | 2 + doc/manual.adoc | 4 ++ include/tig/io.h | 4 +- include/tig/view.h | 1 + src/blob.c | 2 +- src/io.c | 12 ++--- src/main.c | 39 +------------- src/prompt.c | 31 ++++------- src/status.c | 2 +- src/view.c | 55 ++++++++++++++++++++ test/main/{move-to-parent-test => goto-test} | 9 ++-- 11 files changed, 87 insertions(+), 74 deletions(-) rename test/main/{move-to-parent-test => goto-test} (99%) diff --git a/NEWS.adoc b/NEWS.adoc index 6ed2bb9fd..f8e3f0665 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -34,6 +34,8 @@ Improvements: - Compact relative date display mode. (GH #331) - Add date column option controlling whether to show local date. - Move to parent commit in the main view. (GH #388) + - Add `:goto ` prompt command to go to a `git-rev-parse`d revision, e.g. + `:goto some/branch` or `:goto %(commit)^2`. - Respect the XDG standard for configuration files. (GH #513) - Support for custom `strftime(3)` date formats, e.g.: diff --git a/doc/manual.adoc b/doc/manual.adoc index a0faae5a2..fb116a925 100644 --- a/doc/manual.adoc +++ b/doc/manual.adoc @@ -468,6 +468,10 @@ Prompt |: |Execute the corresponding key binding, e.g. `:q`. |:! |Execute a system command in a pager, e.g. `:!git log -p`. |: |Execute a Tig command, e.g. `:edit`. +|:goto |Jump to a specific revision, e.g. `:goto %(commit)^2` + to goto the current commit's 2nd parent or + `:goto some/branch` to goto the commit denoting the + branch `some/branch`. |:save-display |Save current display to ``. |:save-options |Save current options to ``. |:script |Execute commands from ``. diff --git a/include/tig/io.h b/include/tig/io.h index 091518ce9..680d6837a 100644 --- a/include/tig/io.h +++ b/include/tig/io.h @@ -87,8 +87,8 @@ ssize_t io_read(struct io *io, void *buf, size_t bufsize); bool io_get(struct io *io, struct buffer *buf, int c, bool can_read); bool io_write(struct io *io, const void *buf, size_t bufsize); bool io_printf(struct io *io, const char *fmt, ...) PRINTF_LIKE(2, 3); -bool io_read_buf(struct io *io, char buf[], size_t bufsize); -bool io_run_buf(const char **argv, char buf[], size_t bufsize); +bool io_read_buf(struct io *io, char buf[], size_t bufsize, bool allow_empty); +bool io_run_buf(const char **argv, char buf[], size_t bufsize, bool allow_empty); enum status_code io_load(struct io *io, const char *separators, io_read_fn read_property, void *data); enum status_code io_load_span(struct io *io, const char *separators, diff --git a/include/tig/view.h b/include/tig/view.h index 2ef4eaa5b..d359b7c3c 100644 --- a/include/tig/view.h +++ b/include/tig/view.h @@ -258,6 +258,7 @@ void select_view_line(struct view *view, unsigned long lineno); void do_scroll_view(struct view *view, int lines); void scroll_view(struct view *view, enum request request); void move_view(struct view *view, enum request request); +void goto_id(struct view *view, const char *expression, bool from_start, bool save_search); /* * View history diff --git a/src/blob.c b/src/blob.c index b23344831..799cbc15c 100644 --- a/src/blob.c +++ b/src/blob.c @@ -71,7 +71,7 @@ blob_open(struct view *view, enum open_flags flags) }; if (!string_format(blob_spec, "%s:%s", commit, view->env->file) || - !io_run_buf(rev_parse_argv, view->env->blob, sizeof(view->env->blob))) { + !io_run_buf(rev_parse_argv, view->env->blob, sizeof(view->env->blob), false)) { report("Failed to resolve blob from file name"); return false; } diff --git a/src/io.c b/src/io.c index 374db330d..e2eb162ca 100644 --- a/src/io.c +++ b/src/io.c @@ -109,7 +109,7 @@ get_path_encoding(const char *path, struct encoding *default_encoding) /* : encoding: */ - if (!*path || !io_run_buf(check_attr_argv, buf, sizeof(buf)) + if (!*path || !io_run_buf(check_attr_argv, buf, sizeof(buf), false) || !(encoding = strstr(buf, ENCODING_SEP))) return default_encoding; @@ -121,7 +121,7 @@ get_path_encoding(const char *path, struct encoding *default_encoding) "file", "-I", "--", path, NULL }; - if (!*path || !io_run_buf(file_argv, buf, sizeof(buf)) + if (!*path || !io_run_buf(file_argv, buf, sizeof(buf), false) || !(encoding = strstr(buf, CHARSET_SEP))) return default_encoding; @@ -532,7 +532,7 @@ io_printf(struct io *io, const char *fmt, ...) } bool -io_read_buf(struct io *io, char buf[], size_t bufsize) +io_read_buf(struct io *io, char buf[], size_t bufsize, bool allow_empty) { struct buffer result = {0}; @@ -541,15 +541,15 @@ io_read_buf(struct io *io, char buf[], size_t bufsize) string_ncopy_do(buf, bufsize, result.data, strlen(result.data)); } - return io_done(io) && result.data; + return io_done(io) && (result.data || allow_empty); } bool -io_run_buf(const char **argv, char buf[], size_t bufsize) +io_run_buf(const char **argv, char buf[], size_t bufsize, bool allow_empty) { struct io io; - return io_run(&io, IO_RD, NULL, NULL, argv) && io_read_buf(&io, buf, bufsize); + return io_run(&io, IO_RD, NULL, NULL, argv) && io_read_buf(&io, buf, bufsize, allow_empty); } bool diff --git a/src/main.c b/src/main.c index 34d051d46..b3d0c0a5a 100644 --- a/src/main.c +++ b/src/main.c @@ -500,43 +500,6 @@ main_read(struct view *view, struct buffer *buf, bool force_stop) return true; } -static void -main_move_to_parent(struct view *view) -{ - struct line *line = &view->line[view->pos.lineno]; - struct commit *commit = line->data; - - /* Handle staged and unstaged commit lines. */ - if (string_rev_is_null(commit->id)) { - line++; - - } else { - const char *rev_list_parents_argv[] = { - "git", "log", "--no-color", "-n1", "--pretty=format:%P", - commit->id, NULL - }; - char parents[SIZEOF_STR] = ""; - - if (!io_run_buf(rev_list_parents_argv, parents, sizeof(parents))) { - report("Failed to read commit parent(s)"); - return; - } - - /* Find a commit line matching the first parent commit ID. */ - for (line++; view_has_line(view, line); line++) { - commit = line->data; - - if (!strncasecmp(commit->id, parents, strlen(commit->id))) - break; - } - } - - if (view_has_line(view, line)) { - select_view_line(view, line - view->line); - report_clear(); - } -} - enum request main_request(struct view *view, enum request request, struct line *line) { @@ -570,7 +533,7 @@ main_request(struct view *view, enum request request, struct line *line) break; case REQ_PARENT: - main_move_to_parent(view); + goto_id(view, "%(commit)^", true, false); break; default: diff --git a/src/prompt.c b/src/prompt.c index 6e76ca55f..c9435ceaf 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -234,6 +234,7 @@ readline_action_generator(const char *text, int state) "bind", "set", "toggle", + "goto", "save-display", "save-options", "exec", @@ -753,28 +754,7 @@ run_prompt_command(struct view *view, const char *argv[]) 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 = {0}; - 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); + goto_id(view, cmd, true, true); return REQ_NONE; } else if (cmdlen > 1 && (cmd[0] == '/' || cmd[0] == '?')) { @@ -812,6 +792,13 @@ run_prompt_command(struct view *view, const char *argv[]) open_pager_view(view, OPEN_PREPARED | OPEN_WITH_STDERR); } + } else if (!strcmp(cmd, "goto")) { + if (!argv[1] || !strlen(argv[1])) + report("goto requires an argument"); + else + goto_id(view, argv[1], true, true); + return REQ_NONE; + } else if (!strcmp(cmd, "save-display")) { const char *path = argv[1] ? argv[1] : "tig-display.txt"; diff --git a/src/status.c b/src/status.c index 0112af485..fb027aa92 100644 --- a/src/status.c +++ b/src/status.c @@ -299,7 +299,7 @@ status_update_onbranch(void) struct io io; if (io_open(&io, "%s/%s", repo.git_dir, paths[i][1]) && - io_read_buf(&io, buf, sizeof(buf))) { + io_read_buf(&io, buf, sizeof(buf), false)) { head = buf; if (!prefixcmp(head, "refs/heads/")) head += STRING_SIZE("refs/heads/"); diff --git a/src/view.c b/src/view.c index 1cc9e6a70..6766cfb12 100644 --- a/src/view.c +++ b/src/view.c @@ -288,6 +288,61 @@ select_view_line(struct view *view, unsigned long lineno) } } +void +goto_id(struct view *view, const char *expr, bool from_start, bool save_search) +{ + struct view_column_data column_data = {0}; + char id[SIZEOF_STR] = ""; + size_t idlen; + struct line *line = &view->line[view->pos.lineno]; + + if (!(view->ops->column_bits & view_column_bit(ID))) { + report("Jumping to ID is not supported by the %s view", view->name); + return; + } else { + char *rev = argv_format_arg(view->env, expr); + const char *rev_parse_argv[] = { + "git", "rev-parse", "--revs-only", rev, NULL + }; + bool ok = rev && io_run_buf(rev_parse_argv, id, sizeof(id), true); + + free(rev); + if (!ok) { + report("Failed to parse expression '%s'", expr); + return; + } + } + + if (!id[0]) { + if (view->ops->get_column_data(view, line, &column_data) + && column_data.id && string_rev_is_null(column_data.id)) { + select_view_line(view, view->pos.lineno + 1); + report_clear(); + } else { + report("Expression '%s' is not a meaningful revision", expr); + } + return; + } + + line = from_start ? view->line : &view->line[view->pos.lineno]; + + for (idlen = strlen(id); view_has_line(view, line); line++) { + struct view_column_data column_data = {0}; + + if (view->ops->get_column_data(view, line, &column_data) && + column_data.id && + !strncasecmp(column_data.id, id, idlen)) { + if (save_search) + string_ncopy(view->env->search, id, idlen); + select_view_line(view, line - view->line); + report_clear(); + return; + } + } + + report("Unable to find commit '%s'", view->env->search); +} + /* * View history */ diff --git a/test/main/move-to-parent-test b/test/main/goto-test similarity index 99% rename from test/main/move-to-parent-test rename to test/main/goto-test index f7b9e2e72..d446be50a 100755 --- a/test/main/move-to-parent-test +++ b/test/main/goto-test @@ -7,6 +7,11 @@ export LINES=10 steps ' + :3 + :save-display unstaged-changes.screen + :parent + :save-display commit-3.screen + :goto conflict-branch :save-display commit-1.screen :parent :save-display commit-2.screen @@ -14,10 +19,6 @@ steps ' :save-display commit-5.screen :parent :save-display commit-5-still.screen - :3 - :save-display unstaged-changes.screen - :parent - :save-display commit-3.screen ' tigrc <