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

1  2 
Documentation/git-grep.txt
builtin/grep.c
t/t7006-pager.sh
t/t7810-grep.sh

@@@ -14,6 -14,7 +14,7 @@@ SYNOPSI
           [-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 +105,13 @@@ OPTION
        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
  Examples
  --------
  
 -git grep 'time_t' -- '*.[ch]'::
 +git grep 'time_t' \-- '*.[ch]'::
        Looks for `time_t` in all tracked .c and .h files in the working
        directory and its subdirectories.
  
diff --combined builtin/grep.c
@@@ -11,6 -11,8 +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 +558,33 @@@ static int grep_file(struct grep_opt *o
        }
  }
  
+ 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;
                if (hit && opt->status_only)
                        break;
        }
-       free_grep_patterns(opt);
        return hit;
  }
  
@@@ -675,6 -703,25 +703,25 @@@ static int grep_object(struct grep_opt 
        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;
                if (hit && opt->status_only)
                        break;
        }
-       free_grep_patterns(opt);
        return hit;
  }
  
@@@ -724,15 -770,11 +770,15 @@@ static int file_callback(const struct o
        if (!patterns)
                die_errno("cannot open '%s'", arg);
        while (strbuf_getline(&sb, patterns, '\n') == 0) {
 +              char *s;
 +              size_t len;
 +
                /* ignore empty line like grep does */
                if (sb.len == 0)
                        continue;
 -              append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg,
 -                                  ++lno, GREP_PATTERN);
 +
 +              s = strbuf_detach(&sb, &len);
 +              append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
        }
        fclose(patterns);
        strbuf_release(&sb);
@@@ -786,9 -828,11 +832,11 @@@ int cmd_grep(int argc, const char **arg
        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;
                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",
                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)
                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 --combined t/t7006-pager.sh
@@@ -3,6 -3,7 +3,7 @@@
  test_description='Test automatic use of a pager.'
  
  . ./test-lib.sh
+ . "$TEST_DIRECTORY"/lib-pager.sh
  
  cleanup_fail() {
        echo >&2 cleanup failed
@@@ -40,7 -41,7 +41,7 @@@ els
  fi
  
  test_expect_success 'setup' '
 -      unset GIT_PAGER GIT_PAGER_IN_USE &&
 +      unset GIT_PAGER GIT_PAGER_IN_USE;
        test_might_fail git config --unset core.pager &&
  
        PAGER="cat >paginated.out" &&
@@@ -109,7 -110,7 +110,7 @@@ test_expect_success TTY 'no pager with 
  # for the first color; the text "commit" comes later.
  colorful() {
        read firstline <$1
 -      ! expr "$firstline" : "^[a-zA-Z]" >/dev/null
 +      ! expr "$firstline" : "[a-zA-Z]" >/dev/null
  }
  
  test_expect_success 'tests can detect color' '
@@@ -158,22 -159,13 +159,13 @@@ test_expect_success 'color when writin
        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 &&
 +      unset PAGER GIT_PAGER;
        test_might_fail git config --unset core.pager &&
        rm -f default_pager_used ||
        cleanup_fail &&
  '
  
  test_expect_success TTY 'PAGER overrides default pager' '
 -      unset GIT_PAGER &&
 +      unset GIT_PAGER;
        test_might_fail git config --unset core.pager &&
        rm -f PAGER_used ||
        cleanup_fail &&
  '
  
  test_expect_success TTY 'core.pager overrides PAGER' '
 -      unset GIT_PAGER &&
 +      unset GIT_PAGER;
        rm -f core.pager_used ||
        cleanup_fail &&
  
diff --combined t/t7810-grep.sh
@@@ -60,7 -60,7 +60,7 @@@ d
                        echo ${HC}file:5:foo_mmap bar mmap baz
                } >expected &&
                git grep -n -w -e mmap $H >actual &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep -w $L (w)" '
@@@ -74,7 -74,7 +74,7 @@@
                        echo ${HC}x:1:x x xx x
                } >expected &&
                git grep -n -w -e "x xx* x" $H >actual &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep -w $L (y-1)" '
@@@ -82,7 -82,7 +82,7 @@@
                        echo ${HC}y:1:y yy
                } >expected &&
                git grep -n -w -e "^y" $H >actual &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep -w $L (y-2)" '
@@@ -93,7 -93,7 +93,7 @@@
                        cat actual
                        false
                else
 -                      diff expected actual
 +                      test_cmp expected actual
                fi
        '
  
                        cat actual
                        false
                else
 -                      diff expected actual
 +                      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 &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep $L (t-2)" '
                        cd t &&
                        git grep -n -e test $H
                ) >actual &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep $L (t-3)" '
                        cd t &&
                        git grep --full-name -n -e test $H
                ) >actual &&
 -              diff expected actual
 +              test_cmp expected actual
        '
  
        test_expect_success "grep -c $L (no /dev/null)" '