diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 4b32322a67..8fdd8e1e42 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -14,6 +14,7 @@ SYNOPSIS [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] + [-O | --open-files-in-pager] [-z | --null] [-c | --count] [--all-match] [-q | --quiet] [--max-depth ] @@ -104,6 +105,13 @@ OPTIONS For better compatibility with 'git diff', `--name-only` is a synonym for `--files-with-matches`. +-O:: +--open-files-in-pager:: + Open the matching files in the pager (not the output of 'grep'). + If the pager happens to be "less" or "vi", and the user + specified only one pattern, the first file is positioned at + the first match automatically. + -z:: --null:: Output \0 instead of the character that normally follows a diff --git a/builtin/grep.c b/builtin/grep.c index a35ef71c27..d0af900ded 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -15,6 +15,7 @@ #include "grep.h" #include "quote.h" #include "dir.h" +#include "string-list.h" #ifndef NO_PTHREADS #include @@ -556,6 +557,31 @@ static int grep_file(struct grep_opt *opt, const char *filename) } } +static void append_path(struct grep_opt *opt, const void *data, size_t len) +{ + struct string_list *path_list = opt->output_priv; + + if (len == 1 && *(char *)data == '\0') + return; + string_list_append(xstrndup(data, len), path_list); +} + +static void run_pager(struct grep_opt *opt, const char *prefix) +{ + struct string_list *path_list = opt->output_priv; + char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1)); + int i; + + for (i = 0; i < path_list->nr; i++) + argv[i] = path_list->items[i].string; + argv[path_list->nr] = NULL; + + if (prefix) + chdir(prefix); + execvp(argv[0], argv); + error("Could not run pager %s: %s", argv[0], strerror(errno)); +} + static int grep_cache(struct grep_opt *opt, const char **paths, int cached) { int hit = 0; @@ -781,9 +807,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int cached = 0; int seen_dashdash = 0; int external_grep_allowed__ignored; + int show_in_pager = 0; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; + struct string_list path_list = { NULL, 0, 0, 0 }; int i; int dummy; int nongit = 0, use_index = 1; @@ -867,6 +895,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), + OPT_BOOLEAN('O', "open-files-in-pager", &show_in_pager, + "show matching files in the pager"), OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, "allow calling of grep(1) (ignored by this build)"), { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", @@ -942,6 +972,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc--; } + if (show_in_pager) { + const char *pager = getenv("GIT_PAGER"); + if (!pager) + pager = getenv("PAGER"); + if (!pager) + pager = "less"; + opt.name_only = 1; + opt.null_following_name = 1; + opt.output_priv = &path_list; + opt.output = append_path; + string_list_append(pager, &path_list); + use_threads = 0; + } + if (!opt.pattern_list) die("no pattern given."); if (!opt.fixed && opt.ignore_case) @@ -1010,6 +1054,29 @@ int cmd_grep(int argc, const char **argv, const char *prefix) return !hit; } + if (show_in_pager && (cached || list.nr)) + die ("--open-files-in-pager only works on the worktree"); + + if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) { + const char *pager = path_list.items[0].string; + int len = strlen(pager); + + if (len > 4 && is_dir_sep(pager[len - 5])) + pager += len - 4; + + if (!strcmp("less", pager) || !strcmp("vi", pager)) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "+/%s%s", + strcmp("less", pager) ? "" : "*", + opt.pattern_list->pattern); + string_list_append(buf.buf, &path_list); + strbuf_detach(&buf, NULL); + } + } + + if (!show_in_pager) + setup_pager(); + if (!list.nr) { if (!cached) setup_work_tree(); @@ -1032,6 +1099,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (use_threads) hit |= wait_all(); + + if (hit && show_in_pager) + run_pager(&opt, prefix); + free_grep_patterns(&opt); return !hit; } diff --git a/git.c b/git.c index 99f036302a..265fa09d8d 100644 --- a/git.c +++ b/git.c @@ -329,7 +329,7 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, USE_PAGER }, + { "grep", cmd_grep }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack },