Code

grep: Add the option '--open-files-in-pager'
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Sat, 12 Jun 2010 16:36:51 +0000 (11:36 -0500)
committerJunio C Hamano <gitster@pobox.com>
Sun, 13 Jun 2010 16:16:38 +0000 (09:16 -0700)
This adds an option to open the matching files in the pager, and if the
pager happens to be "less" (or "vi") and there is only one grep pattern,
it also jumps to the first match right away.

The short option was chose as '-O' to avoid clashes with GNU grep's
options (as suggested by Junio).

So, 'git grep -O abc' is a short form for 'less +/abc $(grep -l abc)'
except that it works also with spaces in file names, and it does not
start the pager if there was no matching file.

[jn: rebased and added tests; with error handling fix from Junio
squashed in]

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-grep.txt
builtin/grep.c
git.c
t/lib-pager.sh [new file with mode: 0644]
t/t7002-grep.sh [deleted file]
t/t7006-pager.sh
t/t7810-grep.sh [new file with mode: 0755]
t/t7811-grep-open.sh [new file with mode: 0755]

index 4b32322a67d9f3de8b555d3d9d03e7ddab14e0a7..8fdd8e1e42789a5afd01a23d6c1739f96272cebe 100644 (file)
@@ -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 <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
index 211121289049d9e3d6422b4a0dfc43146850b0b2..1e8b9465ed509122f298aa1b9aa21f3d70e3297d 100644 (file)
@@ -11,6 +11,8 @@
 #include "tree-walk.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "string-list.h"
+#include "run-command.h"
 #include "userdiff.h"
 #include "grep.h"
 #include "quote.h"
@@ -556,6 +558,33 @@ 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 && *(const 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;
+       const char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1));
+       int i, status;
+
+       for (i = 0; i < path_list->nr; i++)
+               argv[i] = path_list->items[i].string;
+       argv[path_list->nr] = NULL;
+
+       if (prefix && chdir(prefix))
+               die("Failed to chdir: %s", prefix);
+       status = run_command_v_opt(argv, RUN_USING_SHELL);
+       if (status)
+               exit(status);
+       free(argv);
+}
+
 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 {
        int hit = 0;
@@ -799,9 +828,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;
@@ -885,6 +916,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",
@@ -960,6 +993,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                argc--;
        }
 
+       if (show_in_pager) {
+               const char *pager = git_pager(1);
+               if (!pager) {
+                       show_in_pager = 0;
+               } else {
+                       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)
@@ -1016,6 +1063,30 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                paths[1] = NULL;
        }
 
+       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 (!use_index) {
                if (cached)
                        die("--cached cannot be used with --no-index.");
@@ -1035,6 +1106,8 @@ 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 99f036302a7e6d884369d1d3f4ce428e437cbccd..265fa09d8d361ec0dab3626c846906f100cd6949 100644 (file)
--- 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 },
diff --git a/t/lib-pager.sh b/t/lib-pager.sh
new file mode 100644 (file)
index 0000000..f8c6025
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+test_expect_success 'determine default pager' '
+       test_might_fail git config --unset core.pager &&
+       less=$(
+               unset PAGER GIT_PAGER;
+               git var GIT_PAGER
+       ) &&
+       test -n "$less"
+'
+
+if expr "$less" : '^[a-z][a-z]*$' >/dev/null
+then
+       test_set_prereq SIMPLEPAGER
+fi
diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh
deleted file mode 100755 (executable)
index e249c3e..0000000
+++ /dev/null
@@ -1,530 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Junio C Hamano
-#
-
-test_description='git grep various.
-'
-
-. ./test-lib.sh
-
-cat >hello.c <<EOF
-#include <stdio.h>
-int main(int argc, const char **argv)
-{
-       printf("Hello world.\n");
-       return 0;
-       /* char ?? */
-}
-EOF
-
-test_expect_success setup '
-       {
-               echo foo mmap bar
-               echo foo_mmap bar
-               echo foo_mmap bar mmap
-               echo foo mmap bar_mmap
-               echo foo_mmap bar mmap baz
-       } >file &&
-       echo vvv >v &&
-       echo ww w >w &&
-       echo x x xx x >x &&
-       echo y yy >y &&
-       echo zzz > z &&
-       mkdir t &&
-       echo test >t/t &&
-       echo vvv >t/v &&
-       mkdir t/a &&
-       echo vvv >t/a/v &&
-       git add . &&
-       test_tick &&
-       git commit -m initial
-'
-
-test_expect_success 'grep should not segfault with a bad input' '
-       test_must_fail git grep "("
-'
-
-for H in HEAD ''
-do
-       case "$H" in
-       HEAD)   HC='HEAD:' L='HEAD' ;;
-       '')     HC= L='in working tree' ;;
-       esac
-
-       test_expect_success "grep -w $L" '
-               {
-                       echo ${HC}file:1:foo mmap bar
-                       echo ${HC}file:3:foo_mmap bar mmap
-                       echo ${HC}file:4:foo mmap bar_mmap
-                       echo ${HC}file:5:foo_mmap bar mmap baz
-               } >expected &&
-               git grep -n -w -e mmap $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (w)" '
-               : >expected &&
-               ! git grep -n -w -e "^w" >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep -w $L (x)" '
-               {
-                       echo ${HC}x:1:x x xx x
-               } >expected &&
-               git grep -n -w -e "x xx* x" $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (y-1)" '
-               {
-                       echo ${HC}y:1:y yy
-               } >expected &&
-               git grep -n -w -e "^y" $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -w $L (y-2)" '
-               : >expected &&
-               if git grep -n -w -e "^y y" $H >actual
-               then
-                       echo should not have matched
-                       cat actual
-                       false
-               else
-                       diff expected actual
-               fi
-       '
-
-       test_expect_success "grep -w $L (z)" '
-               : >expected &&
-               if git grep -n -w -e "^z" $H >actual
-               then
-                       echo should not have matched
-                       cat actual
-                       false
-               else
-                       diff expected actual
-               fi
-       '
-
-       test_expect_success "grep $L (t-1)" '
-               echo "${HC}t/t:1:test" >expected &&
-               git grep -n -e test $H >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep $L (t-2)" '
-               echo "${HC}t:1:test" >expected &&
-               (
-                       cd t &&
-                       git grep -n -e test $H
-               ) >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep $L (t-3)" '
-               echo "${HC}t/t:1:test" >expected &&
-               (
-                       cd t &&
-                       git grep --full-name -n -e test $H
-               ) >actual &&
-               diff expected actual
-       '
-
-       test_expect_success "grep -c $L (no /dev/null)" '
-               ! git grep -c test $H | grep /dev/null
-        '
-
-       test_expect_success "grep --max-depth -1 $L" '
-               {
-                       echo ${HC}t/a/v:1:vvv
-                       echo ${HC}t/v:1:vvv
-                       echo ${HC}v:1:vvv
-               } >expected &&
-               git grep --max-depth -1 -n -e vvv $H >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep --max-depth 0 $L" '
-               {
-                       echo ${HC}v:1:vvv
-               } >expected &&
-               git grep --max-depth 0 -n -e vvv $H >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep --max-depth 0 -- '*' $L" '
-               {
-                       echo ${HC}t/a/v:1:vvv
-                       echo ${HC}t/v:1:vvv
-                       echo ${HC}v:1:vvv
-               } >expected &&
-               git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep --max-depth 1 $L" '
-               {
-                       echo ${HC}t/v:1:vvv
-                       echo ${HC}v:1:vvv
-               } >expected &&
-               git grep --max-depth 1 -n -e vvv $H >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep --max-depth 0 -- t $L" '
-               {
-                       echo ${HC}t/v:1:vvv
-               } >expected &&
-               git grep --max-depth 0 -n -e vvv $H -- t >actual &&
-               test_cmp expected actual
-       '
-
-done
-
-cat >expected <<EOF
-file:foo mmap bar_mmap
-EOF
-
-test_expect_success 'grep -e A --and -e B' '
-       git grep -e "foo mmap" --and -e bar_mmap >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-file:foo_mmap bar mmap
-file:foo_mmap bar mmap baz
-EOF
-
-
-test_expect_success 'grep ( -e A --or -e B ) --and -e B' '
-       git grep \( -e foo_ --or -e baz \) \
-               --and -e " mmap" >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-file:foo mmap bar
-EOF
-
-test_expect_success 'grep -e A --and --not -e B' '
-       git grep -e "foo mmap" --and --not -e bar_mmap >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'grep should ignore GREP_OPTIONS' '
-       GREP_OPTIONS=-v git grep " mmap bar\$" >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'grep -f, non-existent file' '
-       test_must_fail git grep -f patterns
-'
-
-cat >expected <<EOF
-file:foo mmap bar
-file:foo_mmap bar
-file:foo_mmap bar mmap
-file:foo mmap bar_mmap
-file:foo_mmap bar mmap baz
-EOF
-
-cat >pattern <<EOF
-mmap
-EOF
-
-test_expect_success 'grep -f, one pattern' '
-       git grep -f pattern >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-file:foo mmap bar
-file:foo_mmap bar
-file:foo_mmap bar mmap
-file:foo mmap bar_mmap
-file:foo_mmap bar mmap baz
-t/a/v:vvv
-t/v:vvv
-v:vvv
-EOF
-
-cat >patterns <<EOF
-mmap
-vvv
-EOF
-
-test_expect_success 'grep -f, multiple patterns' '
-       git grep -f patterns >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-file:foo mmap bar
-file:foo_mmap bar
-file:foo_mmap bar mmap
-file:foo mmap bar_mmap
-file:foo_mmap bar mmap baz
-t/a/v:vvv
-t/v:vvv
-v:vvv
-EOF
-
-cat >patterns <<EOF
-
-mmap
-
-vvv
-
-EOF
-
-test_expect_success 'grep -f, ignore empty lines' '
-       git grep -f patterns >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-y:y yy
---
-z:zzz
-EOF
-
-test_expect_success 'grep -q, silently report matches' '
-       >empty &&
-       git grep -q mmap >actual &&
-       test_cmp empty actual &&
-       test_must_fail git grep -q qfwfq >actual &&
-       test_cmp empty actual
-'
-
-# Create 1024 file names that sort between "y" and "z" to make sure
-# the two files are handled by different calls to an external grep.
-# This depends on MAXARGS in builtin-grep.c being 1024 or less.
-c32="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v"
-test_expect_success 'grep -C1, hunk mark between files' '
-       for a in $c32; do for b in $c32; do : >y-$a$b; done; done &&
-       git add y-?? &&
-       git grep -C1 "^[yz]" >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'grep -C1 hunk mark between files' '
-       git grep -C1 "^[yz]" >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'log grep setup' '
-       echo a >>file &&
-       test_tick &&
-       GIT_AUTHOR_NAME="With * Asterisk" \
-       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
-       git commit -a -m "second" &&
-
-       echo a >>file &&
-       test_tick &&
-       git commit -a -m "third"
-
-'
-
-test_expect_success 'log grep (1)' '
-       git log --author=author --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (2)' '
-       git log --author=" * " -F --pretty=tformat:%s >actual &&
-       ( echo second ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (3)' '
-       git log --author="^A U" --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (4)' '
-       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
-       ( echo second ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (5)' '
-       git log --author=Thor -F --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log grep (6)' '
-       git log --author=-0700  --pretty=tformat:%s >actual &&
-       >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'log --grep --author implicitly uses all-match' '
-       # grep matches initial and second but not third
-       # author matches only initial and third
-       git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
-       echo initial >expect &&
-       test_cmp expect actual
-'
-
-test_expect_success 'grep with CE_VALID file' '
-       git update-index --assume-unchanged t/t &&
-       rm t/t &&
-       test "$(git grep test)" = "t/t:test" &&
-       git update-index --no-assume-unchanged t/t &&
-       git checkout t/t
-'
-
-cat >expected <<EOF
-hello.c=#include <stdio.h>
-hello.c:       return 0;
-EOF
-
-test_expect_success 'grep -p with userdiff' '
-       git config diff.custom.funcname "^#" &&
-       echo "hello.c diff=custom" >.gitattributes &&
-       git grep -p return >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-hello.c=int main(int argc, const char **argv)
-hello.c:       return 0;
-EOF
-
-test_expect_success 'grep -p' '
-       rm -f .gitattributes &&
-       git grep -p return >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-hello.c-#include <stdio.h>
-hello.c=int main(int argc, const char **argv)
-hello.c-{
-hello.c-       printf("Hello world.\n");
-hello.c:       return 0;
-EOF
-
-test_expect_success 'grep -p -B5' '
-       git grep -p -B5 return >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'grep from a subdirectory to search wider area (1)' '
-       mkdir -p s &&
-       (
-               cd s && git grep "x x x" ..
-       )
-'
-
-test_expect_success 'grep from a subdirectory to search wider area (2)' '
-       mkdir -p s &&
-       (
-               cd s || exit 1
-               ( git grep xxyyzz .. >out ; echo $? >status )
-               ! test -s out &&
-               test 1 = $(cat status)
-       )
-'
-
-cat >expected <<EOF
-hello.c:int main(int argc, const char **argv)
-EOF
-
-test_expect_success 'grep -Fi' '
-       git grep -Fi "CHAR *" >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success 'outside of git repository' '
-       rm -fr non &&
-       mkdir -p non/git/sub &&
-       echo hello >non/git/file1 &&
-       echo world >non/git/sub/file2 &&
-       echo ".*o*" >non/git/.gitignore &&
-       {
-               echo file1:hello &&
-               echo sub/file2:world
-       } >non/expect.full &&
-       echo file2:world >non/expect.sub
-       (
-               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
-               export GIT_CEILING_DIRECTORIES &&
-               cd non/git &&
-               test_must_fail git grep o &&
-               git grep --no-index o >../actual.full &&
-               test_cmp ../expect.full ../actual.full
-               cd sub &&
-               test_must_fail git grep o &&
-               git grep --no-index o >../../actual.sub &&
-               test_cmp ../../expect.sub ../../actual.sub
-       )
-'
-
-test_expect_success 'inside git repository but with --no-index' '
-       rm -fr is &&
-       mkdir -p is/git/sub &&
-       echo hello >is/git/file1 &&
-       echo world >is/git/sub/file2 &&
-       echo ".*o*" >is/git/.gitignore &&
-       {
-               echo file1:hello &&
-               echo sub/file2:world
-       } >is/expect.full &&
-       : >is/expect.empty &&
-       echo file2:world >is/expect.sub
-       (
-               cd is/git &&
-               git init &&
-               test_must_fail git grep o >../actual.full &&
-               test_cmp ../expect.empty ../actual.full &&
-               git grep --no-index o >../actual.full &&
-               test_cmp ../expect.full ../actual.full &&
-               cd sub &&
-               test_must_fail git grep o >../../actual.sub &&
-               test_cmp ../../expect.empty ../../actual.sub &&
-               git grep --no-index o >../../actual.sub &&
-               test_cmp ../../expect.sub ../../actual.sub
-       )
-'
-
-test_expect_success 'setup double-dash tests' '
-cat >double-dash <<EOF &&
---
-->
-other
-EOF
-git add double-dash
-'
-
-cat >expected <<EOF
-double-dash:->
-EOF
-test_expect_success 'grep -- pattern' '
-       git grep -- "->" >actual &&
-       test_cmp expected actual
-'
-test_expect_success 'grep -- pattern -- pathspec' '
-       git grep -- "->" -- double-dash >actual &&
-       test_cmp expected actual
-'
-test_expect_success 'grep -e pattern -- path' '
-       git grep -e "->" -- double-dash >actual &&
-       test_cmp expected actual
-'
-
-cat >expected <<EOF
-double-dash:--
-EOF
-test_expect_success 'grep -e -- -- path' '
-       git grep -e -- -- double-dash >actual &&
-       test_cmp expected actual
-'
-
-test_done
index 3bc7a2a796bdb97702eaaf92e26b189cc4204c90..fc993fc8c48167f7a7deebf15e1969d6e4320e78 100755 (executable)
@@ -3,6 +3,7 @@
 test_description='Test automatic use of a pager.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pager.sh
 
 cleanup_fail() {
        echo >&2 cleanup failed
@@ -158,21 +159,12 @@ test_expect_success 'color when writing to a file intended for a pager' '
        colorful colorful.log
 '
 
-test_expect_success 'determine default pager' '
-       unset PAGER GIT_PAGER &&
-       test_might_fail git config --unset core.pager ||
-       cleanup_fail &&
-
-       less=$(git var GIT_PAGER) &&
-       test -n "$less"
-'
-
-if expr "$less" : '^[a-z][a-z]*$' >/dev/null && test_have_prereq TTY
+if test_have_prereq SIMPLEPAGER && test_have_prereq TTY
 then
-       test_set_prereq SIMPLEPAGER
+       test_set_prereq SIMPLEPAGERTTY
 fi
 
-test_expect_success SIMPLEPAGER 'default pager is used by default' '
+test_expect_success SIMPLEPAGERTTY 'default pager is used by default' '
        unset PAGER GIT_PAGER &&
        test_might_fail git config --unset core.pager &&
        rm -f default_pager_used ||
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
new file mode 100755 (executable)
index 0000000..e249c3e
--- /dev/null
@@ -0,0 +1,530 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git grep various.
+'
+
+. ./test-lib.sh
+
+cat >hello.c <<EOF
+#include <stdio.h>
+int main(int argc, const char **argv)
+{
+       printf("Hello world.\n");
+       return 0;
+       /* char ?? */
+}
+EOF
+
+test_expect_success setup '
+       {
+               echo foo mmap bar
+               echo foo_mmap bar
+               echo foo_mmap bar mmap
+               echo foo mmap bar_mmap
+               echo foo_mmap bar mmap baz
+       } >file &&
+       echo vvv >v &&
+       echo ww w >w &&
+       echo x x xx x >x &&
+       echo y yy >y &&
+       echo zzz > z &&
+       mkdir t &&
+       echo test >t/t &&
+       echo vvv >t/v &&
+       mkdir t/a &&
+       echo vvv >t/a/v &&
+       git add . &&
+       test_tick &&
+       git commit -m initial
+'
+
+test_expect_success 'grep should not segfault with a bad input' '
+       test_must_fail git grep "("
+'
+
+for H in HEAD ''
+do
+       case "$H" in
+       HEAD)   HC='HEAD:' L='HEAD' ;;
+       '')     HC= L='in working tree' ;;
+       esac
+
+       test_expect_success "grep -w $L" '
+               {
+                       echo ${HC}file:1:foo mmap bar
+                       echo ${HC}file:3:foo_mmap bar mmap
+                       echo ${HC}file:4:foo mmap bar_mmap
+                       echo ${HC}file:5:foo_mmap bar mmap baz
+               } >expected &&
+               git grep -n -w -e mmap $H >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep -w $L (w)" '
+               : >expected &&
+               ! git grep -n -w -e "^w" >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep -w $L (x)" '
+               {
+                       echo ${HC}x:1:x x xx x
+               } >expected &&
+               git grep -n -w -e "x xx* x" $H >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep -w $L (y-1)" '
+               {
+                       echo ${HC}y:1:y yy
+               } >expected &&
+               git grep -n -w -e "^y" $H >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep -w $L (y-2)" '
+               : >expected &&
+               if git grep -n -w -e "^y y" $H >actual
+               then
+                       echo should not have matched
+                       cat actual
+                       false
+               else
+                       diff expected actual
+               fi
+       '
+
+       test_expect_success "grep -w $L (z)" '
+               : >expected &&
+               if git grep -n -w -e "^z" $H >actual
+               then
+                       echo should not have matched
+                       cat actual
+                       false
+               else
+                       diff expected actual
+               fi
+       '
+
+       test_expect_success "grep $L (t-1)" '
+               echo "${HC}t/t:1:test" >expected &&
+               git grep -n -e test $H >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep $L (t-2)" '
+               echo "${HC}t:1:test" >expected &&
+               (
+                       cd t &&
+                       git grep -n -e test $H
+               ) >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep $L (t-3)" '
+               echo "${HC}t/t:1:test" >expected &&
+               (
+                       cd t &&
+                       git grep --full-name -n -e test $H
+               ) >actual &&
+               diff expected actual
+       '
+
+       test_expect_success "grep -c $L (no /dev/null)" '
+               ! git grep -c test $H | grep /dev/null
+        '
+
+       test_expect_success "grep --max-depth -1 $L" '
+               {
+                       echo ${HC}t/a/v:1:vvv
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth -1 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 $L" '
+               {
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 -- '*' $L" '
+               {
+                       echo ${HC}t/a/v:1:vvv
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- "*" >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 1 $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+                       echo ${HC}v:1:vvv
+               } >expected &&
+               git grep --max-depth 1 -n -e vvv $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep --max-depth 0 -- t $L" '
+               {
+                       echo ${HC}t/v:1:vvv
+               } >expected &&
+               git grep --max-depth 0 -n -e vvv $H -- t >actual &&
+               test_cmp expected actual
+       '
+
+done
+
+cat >expected <<EOF
+file:foo mmap bar_mmap
+EOF
+
+test_expect_success 'grep -e A --and -e B' '
+       git grep -e "foo mmap" --and -e bar_mmap >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo_mmap bar mmap
+file:foo_mmap bar mmap baz
+EOF
+
+
+test_expect_success 'grep ( -e A --or -e B ) --and -e B' '
+       git grep \( -e foo_ --or -e baz \) \
+               --and -e " mmap" >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+EOF
+
+test_expect_success 'grep -e A --and --not -e B' '
+       git grep -e "foo mmap" --and --not -e bar_mmap >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep should ignore GREP_OPTIONS' '
+       GREP_OPTIONS=-v git grep " mmap bar\$" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -f, non-existent file' '
+       test_must_fail git grep -f patterns
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+EOF
+
+cat >pattern <<EOF
+mmap
+EOF
+
+test_expect_success 'grep -f, one pattern' '
+       git grep -f pattern >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+t/a/v:vvv
+t/v:vvv
+v:vvv
+EOF
+
+cat >patterns <<EOF
+mmap
+vvv
+EOF
+
+test_expect_success 'grep -f, multiple patterns' '
+       git grep -f patterns >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file:foo mmap bar
+file:foo_mmap bar
+file:foo_mmap bar mmap
+file:foo mmap bar_mmap
+file:foo_mmap bar mmap baz
+t/a/v:vvv
+t/v:vvv
+v:vvv
+EOF
+
+cat >patterns <<EOF
+
+mmap
+
+vvv
+
+EOF
+
+test_expect_success 'grep -f, ignore empty lines' '
+       git grep -f patterns >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+y:y yy
+--
+z:zzz
+EOF
+
+test_expect_success 'grep -q, silently report matches' '
+       >empty &&
+       git grep -q mmap >actual &&
+       test_cmp empty actual &&
+       test_must_fail git grep -q qfwfq >actual &&
+       test_cmp empty actual
+'
+
+# Create 1024 file names that sort between "y" and "z" to make sure
+# the two files are handled by different calls to an external grep.
+# This depends on MAXARGS in builtin-grep.c being 1024 or less.
+c32="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v"
+test_expect_success 'grep -C1, hunk mark between files' '
+       for a in $c32; do for b in $c32; do : >y-$a$b; done; done &&
+       git add y-?? &&
+       git grep -C1 "^[yz]" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep -C1 hunk mark between files' '
+       git grep -C1 "^[yz]" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'log grep setup' '
+       echo a >>file &&
+       test_tick &&
+       GIT_AUTHOR_NAME="With * Asterisk" \
+       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
+       git commit -a -m "second" &&
+
+       echo a >>file &&
+       test_tick &&
+       git commit -a -m "third"
+
+'
+
+test_expect_success 'log grep (1)' '
+       git log --author=author --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (2)' '
+       git log --author=" * " -F --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (3)' '
+       git log --author="^A U" --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (4)' '
+       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (5)' '
+       git log --author=Thor -F --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (6)' '
+       git log --author=-0700  --pretty=tformat:%s >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log --grep --author implicitly uses all-match' '
+       # grep matches initial and second but not third
+       # author matches only initial and third
+       git log --author="A U Thor" --grep=s --grep=l --format=%s >actual &&
+       echo initial >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'grep with CE_VALID file' '
+       git update-index --assume-unchanged t/t &&
+       rm t/t &&
+       test "$(git grep test)" = "t/t:test" &&
+       git update-index --no-assume-unchanged t/t &&
+       git checkout t/t
+'
+
+cat >expected <<EOF
+hello.c=#include <stdio.h>
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p with userdiff' '
+       git config diff.custom.funcname "^#" &&
+       echo "hello.c diff=custom" >.gitattributes &&
+       git grep -p return >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c=int main(int argc, const char **argv)
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p' '
+       rm -f .gitattributes &&
+       git grep -p return >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c-#include <stdio.h>
+hello.c=int main(int argc, const char **argv)
+hello.c-{
+hello.c-       printf("Hello world.\n");
+hello.c:       return 0;
+EOF
+
+test_expect_success 'grep -p -B5' '
+       git grep -p -B5 return >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'grep from a subdirectory to search wider area (1)' '
+       mkdir -p s &&
+       (
+               cd s && git grep "x x x" ..
+       )
+'
+
+test_expect_success 'grep from a subdirectory to search wider area (2)' '
+       mkdir -p s &&
+       (
+               cd s || exit 1
+               ( git grep xxyyzz .. >out ; echo $? >status )
+               ! test -s out &&
+               test 1 = $(cat status)
+       )
+'
+
+cat >expected <<EOF
+hello.c:int main(int argc, const char **argv)
+EOF
+
+test_expect_success 'grep -Fi' '
+       git grep -Fi "CHAR *" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'outside of git repository' '
+       rm -fr non &&
+       mkdir -p non/git/sub &&
+       echo hello >non/git/file1 &&
+       echo world >non/git/sub/file2 &&
+       echo ".*o*" >non/git/.gitignore &&
+       {
+               echo file1:hello &&
+               echo sub/file2:world
+       } >non/expect.full &&
+       echo file2:world >non/expect.sub
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               test_must_fail git grep o &&
+               git grep --no-index o >../actual.full &&
+               test_cmp ../expect.full ../actual.full
+               cd sub &&
+               test_must_fail git grep o &&
+               git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub
+       )
+'
+
+test_expect_success 'inside git repository but with --no-index' '
+       rm -fr is &&
+       mkdir -p is/git/sub &&
+       echo hello >is/git/file1 &&
+       echo world >is/git/sub/file2 &&
+       echo ".*o*" >is/git/.gitignore &&
+       {
+               echo file1:hello &&
+               echo sub/file2:world
+       } >is/expect.full &&
+       : >is/expect.empty &&
+       echo file2:world >is/expect.sub
+       (
+               cd is/git &&
+               git init &&
+               test_must_fail git grep o >../actual.full &&
+               test_cmp ../expect.empty ../actual.full &&
+               git grep --no-index o >../actual.full &&
+               test_cmp ../expect.full ../actual.full &&
+               cd sub &&
+               test_must_fail git grep o >../../actual.sub &&
+               test_cmp ../../expect.empty ../../actual.sub &&
+               git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub
+       )
+'
+
+test_expect_success 'setup double-dash tests' '
+cat >double-dash <<EOF &&
+--
+->
+other
+EOF
+git add double-dash
+'
+
+cat >expected <<EOF
+double-dash:->
+EOF
+test_expect_success 'grep -- pattern' '
+       git grep -- "->" >actual &&
+       test_cmp expected actual
+'
+test_expect_success 'grep -- pattern -- pathspec' '
+       git grep -- "->" -- double-dash >actual &&
+       test_cmp expected actual
+'
+test_expect_success 'grep -e pattern -- path' '
+       git grep -e "->" -- double-dash >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+double-dash:--
+EOF
+test_expect_success 'grep -e -- -- path' '
+       git grep -e -- -- double-dash >actual &&
+       test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh
new file mode 100755 (executable)
index 0000000..fcfc56e
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+test_description='git grep --open-files-in-pager
+'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pager.sh
+unset PAGER GIT_PAGER
+
+test_expect_success 'setup' '
+       test_commit initial grep.h "
+enum grep_pat_token {
+       GREP_PATTERN,
+       GREP_PATTERN_HEAD,
+       GREP_PATTERN_BODY,
+       GREP_AND,
+       GREP_OPEN_PAREN,
+       GREP_CLOSE_PAREN,
+       GREP_NOT,
+       GREP_OR,
+};" &&
+
+       test_commit add-user revision.c "
+       }
+       if (seen_dashdash)
+               read_pathspec_from_stdin(revs, &sb, prune);
+       strbuf_release(&sb);
+}
+
+static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
+{
+       append_grep_pattern(&revs->grep_filter, ptn, \"command line\", 0, what);
+" &&
+
+       mkdir subdir &&
+       test_commit subdir subdir/grep.c "enum grep_pat_token" &&
+
+       test_commit uninteresting unrelated "hello, world" &&
+
+       echo GREP_PATTERN >untracked
+'
+
+test_expect_success SIMPLEPAGER 'git grep -O' '
+       cat >$less <<-\EOF &&
+       #!/bin/sh
+       printf "%s\n" "$@" >pager-args
+       EOF
+       chmod +x $less &&
+       cat >expect.less <<-\EOF &&
+       +/*GREP_PATTERN
+       grep.h
+       EOF
+       echo grep.h >expect.notless &&
+       >empty &&
+
+       PATH=.:$PATH git grep -O GREP_PATTERN >out &&
+       {
+               test_cmp expect.less pager-args ||
+               test_cmp expect.notless pager-args
+       } &&
+       test_cmp empty out
+'
+
+test_expect_success 'git grep -O --cached' '
+       test_must_fail git grep --cached -O GREP_PATTERN >out 2>msg &&
+       grep open-files-in-pager msg
+'
+
+test_expect_success 'git grep -O --no-index' '
+       rm -f expect.less pager-args out &&
+       cat >expect <<-\EOF &&
+       grep.h
+       untracked
+       EOF
+       >empty &&
+
+       (
+               GIT_PAGER='\''printf "%s\n" >pager-args'\'' &&
+               export GIT_PAGER &&
+               git grep --no-index -O GREP_PATTERN >out
+       ) &&
+       test_cmp expect pager-args &&
+       test_cmp empty out
+'
+
+test_expect_success 'setup: fake "less"' '
+       cat >less <<-\EOF
+       #!/bin/sh
+       printf "%s\n" "$@" >actual
+       EOF
+'
+
+test_expect_success 'git grep -O jumps to line in less' '
+       cat >expect <<-\EOF &&
+       +/*GREP_PATTERN
+       grep.h
+       EOF
+       >empty &&
+
+       GIT_PAGER=./less git grep -O GREP_PATTERN >out &&
+       test_cmp expect actual &&
+       test_cmp empty out
+'
+
+test_expect_success 'modified file' '
+       rm -f actual &&
+       cat >less <<-\EOF &&
+       #!/bin/sh
+       printf "%s\n" "$@" >actual
+       EOF
+       chmod +x $less &&
+       cat >expect <<-\EOF &&
+       +/*enum grep_pat_token
+       grep.h
+       revision.c
+       subdir/grep.c
+       unrelated
+       EOF
+       >empty &&
+
+       echo "enum grep_pat_token" >unrelated &&
+       test_when_finished "git checkout HEAD unrelated" &&
+       GIT_PAGER=./less git grep -F -O "enum grep_pat_token" >out &&
+       test_cmp expect actual &&
+       test_cmp empty out
+'
+
+test_expect_success 'run from subdir' '
+       rm -f actual &&
+       echo grep.c >expect &&
+       >empty &&
+
+       (
+               cd subdir &&
+               export GIT_PAGER &&
+               GIT_PAGER='\''printf "%s\n" >../args'\'' &&
+               git grep -O "enum grep_pat_token" >../out &&
+               GIT_PAGER="pwd >../dir; :" &&
+               git grep -O "enum grep_pat_token" >../out2
+       ) &&
+       case $(cat dir) in
+       *subdir)
+               : good
+               ;;
+       *)
+               false
+               ;;
+       esac &&
+       test_cmp expect args &&
+       test_cmp empty out &&
+       test_cmp empty out2
+'
+
+test_done