Code

Merge branch 'jn/grep-open'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 Jun 2010 18:55:38 +0000 (11:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 Jun 2010 18:55:38 +0000 (11:55 -0700)
* jn/grep-open:
  t/t7811-grep-open.sh: remove broken/redundant creation of fake "less" script
  t/t7811-grep-open.sh: ensure fake "less" is made executable
  t/lib-pager.sh: remove unnecessary '^' from 'expr' regular expression
  grep -O: allow optional argument specifying the pager (or editor)
  grep: Add the option '--open-files-in-pager'
  Unify code paths of threaded greps
  grep: refactor grep_objects loop into its own function

Conflicts:
t/t7006-pager.sh

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 912bddd7b67bb486a703814f116a73fa047e667c..5474dd7f94c4b3217230808e656662d0edcef207 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) [<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 [<pager>]::
+--open-files-in-pager [<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 d0a73da07a0d7d8d4b01adcfcda88a98130910a0..232cd1ce07bf3f695c722e6217ce653ff704d64b 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(path_list, xstrndup(data, len));
+}
+
+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;
@@ -590,7 +619,6 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
                if (hit && opt->status_only)
                        break;
        }
-       free_grep_patterns(opt);
        return hit;
 }
 
@@ -675,6 +703,25 @@ static int grep_object(struct grep_opt *opt, const char **paths,
        die("unable to grep from object of type %s", typename(obj->type));
 }
 
+static int grep_objects(struct grep_opt *opt, const char **paths,
+                       const struct object_array *list)
+{
+       unsigned int i;
+       int hit = 0;
+       const unsigned int nr = list->nr;
+
+       for (i = 0; i < nr; i++) {
+               struct object *real_obj;
+               real_obj = deref_tag(list->objects[i].item, NULL, 0);
+               if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+                       hit = 1;
+                       if (opt->status_only)
+                               break;
+               }
+       }
+       return hit;
+}
+
 static int grep_directory(struct grep_opt *opt, const char **paths)
 {
        struct dir_struct dir;
@@ -689,7 +736,6 @@ static int grep_directory(struct grep_opt *opt, const char **paths)
                if (hit && opt->status_only)
                        break;
        }
-       free_grep_patterns(opt);
        return hit;
 }
 
@@ -786,9 +832,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        int cached = 0;
        int seen_dashdash = 0;
        int external_grep_allowed__ignored;
+       const char *show_in_pager = NULL, *default_pager = "dummy";
        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;
@@ -872,6 +920,9 @@ 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(""),
+               { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
+                       "pager", "show matching files in the pager",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t)default_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",
@@ -947,6 +998,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                argc--;
        }
 
+       if (show_in_pager == default_pager)
+               show_in_pager = git_pager(1);
+       if (show_in_pager) {
+               opt.name_only = 1;
+               opt.null_following_name = 1;
+               opt.output_priv = &path_list;
+               opt.output = append_path;
+               string_list_append(&path_list, show_in_pager);
+               use_threads = 0;
+       }
+
        if (!opt.pattern_list)
                die("no pattern given.");
        if (!opt.fixed && opt.ignore_case)
@@ -1003,44 +1065,51 @@ 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(&path_list, buf.buf);
+                       strbuf_detach(&buf, NULL);
+               }
+       }
+
+       if (!show_in_pager)
+               setup_pager();
+
+
        if (!use_index) {
-               int hit;
                if (cached)
                        die("--cached cannot be used with --no-index.");
                if (list.nr)
                        die("--no-index cannot be used with revs.");
                hit = grep_directory(&opt, paths);
-               if (use_threads)
-                       hit |= wait_all();
-               return !hit;
-       }
-
-       if (!list.nr) {
-               int hit;
+       } else if (!list.nr) {
                if (!cached)
                        setup_work_tree();
 
                hit = grep_cache(&opt, paths, cached);
-               if (use_threads)
-                       hit |= wait_all();
-               return !hit;
-       }
-
-       if (cached)
-               die("both --cached and trees are given.");
-
-       for (i = 0; i < list.nr; i++) {
-               struct object *real_obj;
-               real_obj = deref_tag(list.objects[i].item, NULL, 0);
-               if (grep_object(&opt, paths, real_obj, list.objects[i].name)) {
-                       hit = 1;
-                       if (opt.status_only)
-                               break;
-               }
+       } else {
+               if (cached)
+                       die("both --cached and trees are given.");
+               hit = grep_objects(&opt, paths, &list);
        }
 
        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..ba03eab
--- /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 8a63227..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 &&
-               test_cmp 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 &&
-               test_cmp 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 &&
-               test_cmp 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
-                       test_cmp 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
-                       test_cmp expected actual
-               fi
-       '
-
-       test_expect_success "grep $L (t-1)" '
-               echo "${HC}t/t:1:test" >expected &&
-               git grep -n -e test $H >actual &&
-               test_cmp expected actual
-       '
-
-       test_expect_success "grep $L (t-2)" '
-               echo "${HC}t:1:test" >expected &&
-               (
-                       cd t &&
-                       git grep -n -e test $H
-               ) >actual &&
-               test_cmp 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 &&
-               test_cmp 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 9a83241c942f63c7c7b2f5c739164c34c80ab68d..c2a3c8e2e7351ba5c49fa363a1856ec82904d444 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..8a63227
--- /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 &&
+               test_cmp 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 &&
+               test_cmp 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 &&
+               test_cmp 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
+                       test_cmp 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
+                       test_cmp expected actual
+               fi
+       '
+
+       test_expect_success "grep $L (t-1)" '
+               echo "${HC}t/t:1:test" >expected &&
+               git grep -n -e test $H >actual &&
+               test_cmp expected actual
+       '
+
+       test_expect_success "grep $L (t-2)" '
+               echo "${HC}t:1:test" >expected &&
+               (
+                       cd t &&
+                       git grep -n -e test $H
+               ) >actual &&
+               test_cmp 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 &&
+               test_cmp 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..c110441
--- /dev/null
@@ -0,0 +1,153 @@
+#!/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
+       chmod +x less
+'
+
+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 &&
+
+       git grep -O./less GREP_PATTERN >out2 &&
+       test_cmp expect actual &&
+       test_cmp empty out2
+'
+
+test_expect_success 'modified file' '
+       rm -f actual &&
+       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 grep -O"pwd >../dir; :" "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