]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'bw/grep-no-index-no-exclude'
authorJunio C Hamano <gitster@pobox.com>
Fri, 14 Oct 2011 02:03:18 +0000 (19:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 14 Oct 2011 02:03:18 +0000 (19:03 -0700)
* bw/grep-no-index-no-exclude:
  grep --no-index: don't use git standard exclusions
  grep: do not use --index in the short usage output

1  2 
builtin/grep.c
t/t7810-grep.sh

diff --combined builtin/grep.c
index a286692e467710d92346ab6900e98f1126cb967d,a10946db3c49797ef717734924416df0879b55a1..91c3a852f6eb88c9c56a75dec6239d9043683764
  #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"
  #include "dir.h"
 -
 -#ifndef NO_PTHREADS
  #include "thread-utils.h"
 -#include <pthread.h>
 -#endif
  
  static char const * const grep_usage[] = {
 -      "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
 +      "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
        NULL
  };
  
@@@ -40,7 -42,8 +40,7 @@@ enum work_type {WORK_SHA1, WORK_FILE}
   * threads. The producer adds struct work_items to 'todo' and the
   * consumers pick work items from the same array.
   */
 -struct work_item
 -{
 +struct work_item {
        enum work_type type;
        char *name;
  
@@@ -93,8 -96,6 +93,8 @@@ static pthread_cond_t cond_write
  /* Signalled when we are finished with everything. */
  static pthread_cond_t cond_result;
  
 +static int skip_first_line;
 +
  static void add_work(enum work_type type, char *name, void *id)
  {
        grep_lock();
@@@ -158,22 -159,7 +158,22 @@@ static void work_done(struct work_item 
        for(; todo[todo_done].done && todo_done != todo_start;
            todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
                w = &todo[todo_done];
 -              write_or_die(1, w->out.buf, w->out.len);
 +              if (w->out.len) {
 +                      const char *p = w->out.buf;
 +                      size_t len = w->out.len;
 +
 +                      /* Skip the leading hunk mark of the first file. */
 +                      if (skip_first_line) {
 +                              while (len) {
 +                                      len--;
 +                                      if (*p++ == '\n')
 +                                              break;
 +                              }
 +                              skip_first_line = 0;
 +                      }
 +
 +                      write_or_die(1, p, len);
 +              }
                free(w->name);
                free(w->identifier);
        }
@@@ -253,7 -239,7 +253,7 @@@ static void start_threads(struct grep_o
                err = pthread_create(&threads[i], NULL, run, o);
  
                if (err)
 -                      die("grep: failed to create thread: %s",
 +                      die(_("grep: failed to create thread: %s"),
                            strerror(err));
        }
  }
@@@ -303,7 -289,6 +303,7 @@@ static int wait_all(void
  static int grep_config(const char *var, const char *value, void *cb)
  {
        struct grep_opt *opt = cb;
 +      char *color = NULL;
  
        switch (userdiff_config(var, value)) {
        case 0: break;
        default: return 0;
        }
  
 -      if (!strcmp(var, "color.grep")) {
 -              opt->color = git_config_colorbool(var, value, -1);
 +      if (!strcmp(var, "grep.extendedregexp")) {
 +              if (git_config_bool(var, value))
 +                      opt->regflags |= REG_EXTENDED;
 +              else
 +                      opt->regflags &= ~REG_EXTENDED;
                return 0;
        }
 -      if (!strcmp(var, "color.grep.match")) {
 -              if (!value)
 -                      return config_error_nonbool(var);
 -              color_parse(value, var, opt->color_match);
 +
 +      if (!strcmp(var, "grep.linenumber")) {
 +              opt->linenum = git_config_bool(var, value);
                return 0;
        }
 -      return git_color_default_config(var, value, cb);
 -}
 -
 -/*
 - * Return non-zero if max_depth is negative or path has no more then max_depth
 - * slashes.
 - */
 -static int accept_subdir(const char *path, int max_depth)
 -{
 -      if (max_depth < 0)
 -              return 1;
  
 -      while ((path = strchr(path, '/')) != NULL) {
 -              max_depth--;
 -              if (max_depth < 0)
 -                      return 0;
 -              path++;
 +      if (!strcmp(var, "color.grep"))
 +              opt->color = git_config_colorbool(var, value);
 +      else if (!strcmp(var, "color.grep.context"))
 +              color = opt->color_context;
 +      else if (!strcmp(var, "color.grep.filename"))
 +              color = opt->color_filename;
 +      else if (!strcmp(var, "color.grep.function"))
 +              color = opt->color_function;
 +      else if (!strcmp(var, "color.grep.linenumber"))
 +              color = opt->color_lineno;
 +      else if (!strcmp(var, "color.grep.match"))
 +              color = opt->color_match;
 +      else if (!strcmp(var, "color.grep.selected"))
 +              color = opt->color_selected;
 +      else if (!strcmp(var, "color.grep.separator"))
 +              color = opt->color_sep;
 +      else
 +              return git_color_default_config(var, value, cb);
 +      if (color) {
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              color_parse(value, var, color);
        }
 -      return 1;
 -}
 -
 -/*
 - * Return non-zero if name is a subdirectory of match and is not too deep.
 - */
 -static int is_subdir(const char *name, int namelen,
 -              const char *match, int matchlen, int max_depth)
 -{
 -      if (matchlen > namelen || strncmp(name, match, matchlen))
 -              return 0;
 -
 -      if (name[matchlen] == '\0') /* exact match */
 -              return 1;
 -
 -      if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
 -              return accept_subdir(name + matchlen + 1, max_depth);
 -
        return 0;
  }
  
 -/*
 - * git grep pathspecs are somewhat different from diff-tree pathspecs;
 - * pathname wildcards are allowed.
 - */
 -static int pathspec_matches(const char **paths, const char *name, int max_depth)
 -{
 -      int namelen, i;
 -      if (!paths || !*paths)
 -              return accept_subdir(name, max_depth);
 -      namelen = strlen(name);
 -      for (i = 0; paths[i]; i++) {
 -              const char *match = paths[i];
 -              int matchlen = strlen(match);
 -              const char *cp, *meta;
 -
 -              if (is_subdir(name, namelen, match, matchlen, max_depth))
 -                      return 1;
 -              if (!fnmatch(match, name, 0))
 -                      return 1;
 -              if (name[namelen-1] != '/')
 -                      continue;
 -
 -              /* We are being asked if the directory ("name") is worth
 -               * descending into.
 -               *
 -               * Find the longest leading directory name that does
 -               * not have metacharacter in the pathspec; the name
 -               * we are looking at must overlap with that directory.
 -               */
 -              for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
 -                      char ch = *cp;
 -                      if (ch == '*' || ch == '[' || ch == '?') {
 -                              meta = cp;
 -                              break;
 -                      }
 -              }
 -              if (!meta)
 -                      meta = cp; /* fully literal */
 -
 -              if (namelen <= meta - match) {
 -                      /* Looking at "Documentation/" and
 -                       * the pattern says "Documentation/howto/", or
 -                       * "Documentation/diff*.txt".  The name we
 -                       * have should match prefix.
 -                       */
 -                      if (!memcmp(match, name, namelen))
 -                              return 1;
 -                      continue;
 -              }
 +static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 +{
 +      void *data;
  
 -              if (meta - match < namelen) {
 -                      /* Looking at "Documentation/howto/" and
 -                       * the pattern says "Documentation/h*";
 -                       * match up to "Do.../h"; this avoids descending
 -                       * into "Documentation/technical/".
 -                       */
 -                      if (!memcmp(match, name, meta - match))
 -                              return 1;
 -                      continue;
 -              }
 +      if (use_threads) {
 +              read_sha1_lock();
 +              data = read_sha1_file(sha1, type, size);
 +              read_sha1_unlock();
 +      } else {
 +              data = read_sha1_file(sha1, type, size);
        }
 -      return 0;
 +      return data;
  }
  
  static void *load_sha1(const unsigned char *sha1, unsigned long *size,
                       const char *name)
  {
        enum object_type type;
 -      char *data;
 -
 -      read_sha1_lock();
 -      data = read_sha1_file(sha1, &type, size);
 -      read_sha1_unlock();
 +      void *data = lock_and_read_sha1_file(sha1, &type, size);
  
        if (!data)
 -              error("'%s': unable to read %s", name, sha1_to_hex(sha1));
 +              error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
  
        return data;
  }
@@@ -422,21 -471,21 +422,21 @@@ static void *load_file(const char *file
        if (lstat(filename, &st) < 0) {
        err_ret:
                if (errno != ENOENT)
 -                      error("'%s': %s", filename, strerror(errno));
 -              return 0;
 +                      error(_("'%s': %s"), filename, strerror(errno));
 +              return NULL;
        }
        if (!S_ISREG(st.st_mode))
 -              return 0;
 +              return NULL;
        *sz = xsize_t(st.st_size);
        i = open(filename, O_RDONLY);
        if (i < 0)
                goto err_ret;
        data = xmalloc(*sz + 1);
        if (st.st_size != read_in_full(i, data, *sz)) {
 -              error("'%s': short read %s", filename, strerror(errno));
 +              error(_("'%s': short read %s"), filename, strerror(errno));
                close(i);
                free(data);
 -              return 0;
 +              return NULL;
        }
        close(i);
        data[*sz] = 0;
@@@ -475,34 -524,7 +475,34 @@@ static int grep_file(struct grep_opt *o
        }
  }
  
 -static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 +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 struct pathspec *pathspec, int cached)
  {
        int hit = 0;
        int nr;
                struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
 -              if (!pathspec_matches(paths, ce->name, opt->max_depth))
 +              if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
                        continue;
                /*
                 * If CE_VALID is on, we assume worktree file and its cache entry
                if (hit && opt->status_only)
                        break;
        }
 -      free_grep_patterns(opt);
        return hit;
  }
  
 -static int grep_tree(struct grep_opt *opt, const char **paths,
 -                   struct tree_desc *tree,
 -                   const char *tree_name, const char *base)
 +static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
 +                   struct tree_desc *tree, struct strbuf *base, int tn_len)
  {
 -      int len;
 -      int hit = 0;
 +      int hit = 0, match = 0;
        struct name_entry entry;
 -      char *down;
 -      int tn_len = strlen(tree_name);
 -      struct strbuf pathbuf;
 -
 -      strbuf_init(&pathbuf, PATH_MAX + tn_len);
 -
 -      if (tn_len) {
 -              strbuf_add(&pathbuf, tree_name, tn_len);
 -              strbuf_addch(&pathbuf, ':');
 -              tn_len = pathbuf.len;
 -      }
 -      strbuf_addstr(&pathbuf, base);
 -      len = pathbuf.len;
 +      int old_baselen = base->len;
  
        while (tree_entry(tree, &entry)) {
                int te_len = tree_entry_len(entry.path, entry.sha1);
 -              pathbuf.len = len;
 -              strbuf_add(&pathbuf, entry.path, te_len);
 -
 -              if (S_ISDIR(entry.mode))
 -                      /* Match "abc/" against pathspec to
 -                       * decide if we want to descend into "abc"
 -                       * directory.
 -                       */
 -                      strbuf_addch(&pathbuf, '/');
 -
 -              down = pathbuf.buf + tn_len;
 -              if (!pathspec_matches(paths, down, opt->max_depth))
 -                      ;
 -              else if (S_ISREG(entry.mode))
 -                      hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
 +
 +              if (match != 2) {
 +                      match = tree_entry_interesting(&entry, base, tn_len, pathspec);
 +                      if (match < 0)
 +                              break;
 +                      if (match == 0)
 +                              continue;
 +              }
 +
 +              strbuf_add(base, entry.path, te_len);
 +
 +              if (S_ISREG(entry.mode)) {
 +                      hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
 +              }
                else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        struct tree_desc sub;
                        void *data;
                        unsigned long size;
  
 -                      read_sha1_lock();
 -                      data = read_sha1_file(entry.sha1, &type, &size);
 -                      read_sha1_unlock();
 -
 +                      data = lock_and_read_sha1_file(entry.sha1, &type, &size);
                        if (!data)
 -                              die("unable to read tree (%s)",
 +                              die(_("unable to read tree (%s)"),
                                    sha1_to_hex(entry.sha1));
 +
 +                      strbuf_addch(base, '/');
                        init_tree_desc(&sub, data, size);
 -                      hit |= grep_tree(opt, paths, &sub, tree_name, down);
 +                      hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
                        free(data);
                }
 +              strbuf_setlen(base, old_baselen);
 +
                if (hit && opt->status_only)
                        break;
        }
 -      strbuf_release(&pathbuf);
        return hit;
  }
  
 -static int grep_object(struct grep_opt *opt, const char **paths,
 +static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                       struct object *obj, const char *name)
  {
        if (obj->type == OBJ_BLOB)
                struct tree_desc tree;
                void *data;
                unsigned long size;
 -              int hit;
 +              struct strbuf base;
 +              int hit, len;
 +
 +              read_sha1_lock();
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
 +              read_sha1_unlock();
 +
                if (!data)
 -                      die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
 +                      die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
 +
 +              len = name ? strlen(name) : 0;
 +              strbuf_init(&base, PATH_MAX + len + 1);
 +              if (len) {
 +                      strbuf_add(&base, name, len);
 +                      strbuf_addch(&base, ':');
 +              }
                init_tree_desc(&tree, data, size);
 -              hit = grep_tree(opt, paths, &tree, name, "");
 +              hit = grep_tree(opt, pathspec, &tree, &base, base.len);
 +              strbuf_release(&base);
                free(data);
                return hit;
        }
 -      die("unable to grep from object of type %s", typename(obj->type));
 +      die(_("unable to grep from object of type %s"), typename(obj->type));
  }
  
 -static int grep_directory(struct grep_opt *opt, const char **paths)
 +static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
 +                      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, pathspec, real_obj, list->objects[i].name)) {
 +                      hit = 1;
 +                      if (opt->status_only)
 +                              break;
 +              }
 +      }
 +      return hit;
 +}
 +
 +static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
  {
        struct dir_struct dir;
        int i, hit = 0;
  
        memset(&dir, 0, sizeof(dir));
-       setup_standard_excludes(&dir);
  
 -      fill_directory(&dir, paths);
 +      fill_directory(&dir, pathspec->raw);
        for (i = 0; i < dir.nr; i++) {
 +              const char *name = dir.entries[i]->name;
 +              int namelen = strlen(name);
 +              if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL))
 +                      continue;
                hit |= grep_file(opt, dir.entries[i]->name);
                if (hit && opt->status_only)
                        break;
        }
 -      free_grep_patterns(opt);
        return hit;
  }
  
@@@ -674,7 -676,7 +673,7 @@@ static int context_callback(const struc
        }
        value = strtol(arg, (char **)&endp, 10);
        if (*endp) {
 -              return error("switch `%c' expects a numerical value",
 +              return error(_("switch `%c' expects a numerical value"),
                             opt->short_name);
        }
        grep_opt->pre_context = grep_opt->post_context = value;
  static int file_callback(const struct option *opt, const char *arg, int unset)
  {
        struct grep_opt *grep_opt = opt->value;
 +      int from_stdin = !strcmp(arg, "-");
        FILE *patterns;
        int lno = 0;
        struct strbuf sb = STRBUF_INIT;
  
 -      patterns = fopen(arg, "r");
 +      patterns = from_stdin ? stdin : fopen(arg, "r");
        if (!patterns)
 -              die_errno("cannot open '%s'", arg);
 +              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);
 +      if (!from_stdin)
 +              fclose(patterns);
        strbuf_release(&sb);
        return 0;
  }
@@@ -756,29 -752,18 +755,30 @@@ 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 };
 +      struct object_array list = OBJECT_ARRAY_INIT;
        const char **paths = NULL;
 +      struct pathspec pathspec;
 +      struct string_list path_list = STRING_LIST_INIT_NODUP;
        int i;
        int dummy;
 -      int nongit = 0, use_index = 1;
 +      int use_index = 1;
 +      enum {
 +              pattern_type_unspecified = 0,
 +              pattern_type_bre,
 +              pattern_type_ere,
 +              pattern_type_fixed,
 +              pattern_type_pcre,
 +      };
 +      int pattern_type = pattern_type_unspecified;
 +
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
                        "search in index instead of in the work tree"),
-               OPT_BOOLEAN(0, "index", &use_index,
-                       "--no-index finds in contents not managed by git"),
+               { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
+                       "finds in contents not managed by git",
+                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
                        "descend at most <depth> levels", PARSE_OPT_NONEG,
                        NULL, 1 },
                OPT_GROUP(""),
 -              OPT_BIT('E', "extended-regexp", &opt.regflags,
 -                      "use extended POSIX regular expressions", REG_EXTENDED),
 -              OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
 -                      "use basic POSIX regular expressions (default)",
 -                      REG_EXTENDED),
 -              OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
 -                      "interpret patterns as fixed strings"),
 +              OPT_SET_INT('E', "extended-regexp", &pattern_type,
 +                          "use extended POSIX regular expressions",
 +                          pattern_type_ere),
 +              OPT_SET_INT('G', "basic-regexp", &pattern_type,
 +                          "use basic POSIX regular expressions (default)",
 +                          pattern_type_bre),
 +              OPT_SET_INT('F', "fixed-strings", &pattern_type,
 +                          "interpret patterns as fixed strings",
 +                          pattern_type_fixed),
 +              OPT_SET_INT('P', "perl-regexp", &pattern_type,
 +                          "use Perl-compatible regular expressions",
 +                          pattern_type_pcre),
                OPT_GROUP(""),
 -              OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
 +              OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"),
                OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
                OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
                OPT_NEGBIT(0, "full-name", &opt.relative,
                        "print NUL after filenames"),
                OPT_BOOLEAN('c', "count", &opt.count,
                        "show the number of matches instead of matching lines"),
 -              OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1),
 +              OPT__COLOR(&opt.color, "highlight matches"),
 +              OPT_BOOLEAN(0, "break", &opt.file_break,
 +                      "print empty line between matches from different files"),
 +              OPT_BOOLEAN(0, "heading", &opt.heading,
 +                      "show filename only once above matches from same file"),
                OPT_GROUP(""),
 -              OPT_CALLBACK('C', NULL, &opt, "n",
 +              OPT_CALLBACK('C', "context", &opt, "n",
                        "show <n> context lines before and after matches",
                        context_callback),
 -              OPT_INTEGER('B', NULL, &opt.pre_context,
 +              OPT_INTEGER('B', "before-context", &opt.pre_context,
                        "show <n> context lines before matches"),
 -              OPT_INTEGER('A', NULL, &opt.post_context,
 +              OPT_INTEGER('A', "after-context", &opt.post_context,
                        "show <n> context lines after matches"),
                OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
                        context_callback),
                OPT_BOOLEAN('p', "show-function", &opt.funcname,
                        "show a line with the function name before matches"),
 +              OPT_BOOLEAN('W', "function-context", &opt.funcbody,
 +                      "show the surrounding function"),
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, "file",
                        "read patterns from file", file_callback),
                { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
                  close_callback },
 -              OPT_BOOLEAN('q', "quiet", &opt.status_only,
 -                          "indicate hit with exit status without output"),
 +              OPT__QUIET(&opt.status_only,
 +                         "indicate hit with exit status without output"),
                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",
                OPT_END()
        };
  
 -      prefix = setup_git_directory_gently(&nongit);
 -
        /*
         * 'git grep -h', unlike 'git grep -h <pattern>', is a request
         * to show usage information and exit.
        opt.relative = 1;
        opt.pathname = 1;
        opt.pattern_tail = &opt.pattern_list;
 +      opt.header_tail = &opt.header_list;
        opt.regflags = REG_NEWLINE;
        opt.max_depth = -1;
  
 -      strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
 +      strcpy(opt.color_context, "");
 +      strcpy(opt.color_filename, "");
 +      strcpy(opt.color_function, "");
 +      strcpy(opt.color_lineno, "");
 +      strcpy(opt.color_match, GIT_COLOR_BOLD_RED);
 +      strcpy(opt.color_selected, "");
 +      strcpy(opt.color_sep, GIT_COLOR_CYAN);
        opt.color = -1;
        git_config(grep_config, &opt);
 -      if (opt.color == -1)
 -              opt.color = git_use_color_default;
  
        /*
         * If there is no -- then the paths must exist in the working
                             PARSE_OPT_KEEP_DASHDASH |
                             PARSE_OPT_STOP_AT_NON_OPTION |
                             PARSE_OPT_NO_INTERNAL_HELP);
 +      switch (pattern_type) {
 +      case pattern_type_fixed:
 +              opt.fixed = 1;
 +              opt.pcre = 0;
 +              break;
 +      case pattern_type_bre:
 +              opt.fixed = 0;
 +              opt.pcre = 0;
 +              opt.regflags &= ~REG_EXTENDED;
 +              break;
 +      case pattern_type_ere:
 +              opt.fixed = 0;
 +              opt.pcre = 0;
 +              opt.regflags |= REG_EXTENDED;
 +              break;
 +      case pattern_type_pcre:
 +              opt.fixed = 0;
 +              opt.pcre = 1;
 +              break;
 +      default:
 +              break; /* nothing */
 +      }
  
 -      if (use_index && nongit)
 +      if (use_index && !startup_info->have_repository)
                /* die the same way as if we did it at the beginning */
                setup_git_directory();
  
 +      /*
 +       * skip a -- separator; we know it cannot be
 +       * separating revisions from pathnames if
 +       * we haven't even had any patterns yet
 +       */
 +      if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) {
 +              argv++;
 +              argc--;
 +      }
 +
        /* First unrecognized non-option token */
        if (argc > 0 && !opt.pattern_list) {
                append_grep_pattern(&opt, argv[0], "command line", 0,
                argc--;
        }
  
 +      if (show_in_pager == default_pager)
 +              show_in_pager = git_pager(1);
 +      if (show_in_pager) {
 +              opt.color = 0;
 +              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.");
 +              die(_("no pattern given."));
        if (!opt.fixed && opt.ignore_case)
                opt.regflags |= REG_ICASE;
 -      if ((opt.regflags != REG_NEWLINE) && opt.fixed)
 -              die("cannot mix --fixed-strings and regexp");
  
  #ifndef NO_PTHREADS
        if (online_cpus() == 1 || !grep_threads_ok(&opt))
                use_threads = 0;
  
 -      if (use_threads)
 +      if (use_threads) {
 +              if (opt.pre_context || opt.post_context || opt.file_break ||
 +                  opt.funcbody)
 +                      skip_first_line = 1;
                start_threads(&opt);
 +      }
  #else
        use_threads = 0;
  #endif
                if (!get_sha1(arg, sha1)) {
                        struct object *object = parse_object(sha1);
                        if (!object)
 -                              die("bad object %s", arg);
 +                              die(_("bad object %s"), arg);
                        add_object_array(object, arg, &list);
                        continue;
                }
                        verify_filename(prefix, argv[j]);
        }
  
 -      if (i < argc)
 -              paths = get_pathspec(prefix, argv + i);
 -      else if (prefix) {
 -              paths = xcalloc(2, sizeof(const char *));
 -              paths[0] = prefix;
 -              paths[1] = NULL;
 +      paths = get_pathspec(prefix, argv + i);
 +      init_pathspec(&pathspec, paths);
 +      pathspec.max_depth = opt.max_depth;
 +      pathspec.recursive = 1;
 +
 +      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.");
 +                      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;
 +                      die(_("--no-index cannot be used with revs."));
 +              hit = grep_directory(&opt, &pathspec);
 +      } 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;
 -              }
 +              hit = grep_cache(&opt, &pathspec, cached);
 +      } else {
 +              if (cached)
 +                      die(_("both --cached and trees are given."));
 +              hit = grep_objects(&opt, &pathspec, &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/t7810-grep.sh
index 0d600163c8284a318fbd21f3a00dd7853b2f8956,918d33f7d1ab4d4d44dd6b13ca7e6029fe148ad5..4a05e79caee758ee7c2dbeb1914d41fc79346fd2
@@@ -26,17 -26,6 +26,17 @@@ test_expect_success setup 
                echo foo mmap bar_mmap
                echo foo_mmap bar mmap baz
        } >file &&
 +      {
 +              echo Hello world
 +              echo HeLLo world
 +              echo Hello_world
 +              echo HeLLo_world
 +      } >hello_world &&
 +      {
 +              echo "a+b*c"
 +              echo "a+bc"
 +              echo "abc"
 +      } >ab &&
        echo vvv >v &&
        echo ww w >w &&
        echo x x xx x >x &&
@@@ -70,35 -59,13 +70,35 @@@ d
                        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
 +              git -c grep.linenumber=false grep -n -w -e mmap $H >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      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 -c grep.linenumber=true grep -w -e mmap $H >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep -w $L" '
 +              {
 +                      echo ${HC}file:foo mmap bar
 +                      echo ${HC}file:foo_mmap bar mmap
 +                      echo ${HC}file:foo mmap bar_mmap
 +                      echo ${HC}file:foo_mmap bar mmap baz
 +              } >expected &&
 +              git -c grep.linenumber=true grep --no-line-number -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_must_fail git grep -n -w -e "^w" >actual &&
                test_cmp expected actual
        '
  
                        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)" '
                        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)" '
                        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)" '
                test_cmp expected actual
        '
  
 +      test_expect_success "grep --max-depth 0 -- . t $L" '
 +              {
 +                      echo ${HC}t/v:1:vvv
 +                      echo ${HC}v:1:vvv
 +              } >expected &&
 +              git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep --max-depth 0 -- t . $L" '
 +              {
 +                      echo ${HC}t/v:1:vvv
 +                      echo ${HC}v:1:vvv
 +              } >expected &&
 +              git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
 +              test_cmp expected actual
 +      '
 +      test_expect_success "grep $L with grep.extendedRegexp=false" '
 +              echo "ab:a+bc" >expected &&
 +              git -c grep.extendedRegexp=false grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep $L with grep.extendedRegexp=true" '
 +              echo "ab:abc" >expected &&
 +              git -c grep.extendedRegexp=true grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
  done
  
  cat >expected <<EOF
@@@ -346,11 -285,6 +346,11 @@@ test_expect_success 'grep -f, ignore em
        test_cmp expected actual
  '
  
 +test_expect_success 'grep -f, ignore empty lines, read patterns from stdin' '
 +      git grep -f - <patterns >actual &&
 +      test_cmp expected actual
 +'
 +
  cat >expected <<EOF
  y:y yy
  --
@@@ -390,13 -324,8 +390,13 @@@ test_expect_success 'log grep setup' 
  
        echo a >>file &&
        test_tick &&
 -      git commit -a -m "third"
 +      git commit -a -m "third" &&
  
 +      echo a >>file &&
 +      test_tick &&
 +      GIT_AUTHOR_NAME="Night Fall" \
 +      GIT_AUTHOR_EMAIL="nitfol@frobozz.com" \
 +      git commit -a -m "fourth"
  '
  
  test_expect_success 'log grep (1)' '
@@@ -424,7 -353,7 +424,7 @@@ test_expect_success 'log grep (4)' 
  '
  
  test_expect_success 'log grep (5)' '
 -      git log --author=Thor -F --grep=Thu --pretty=tformat:%s >actual &&
 +      git log --author=Thor -F --pretty=tformat:%s >actual &&
        ( echo third ; echo initial ) >expect &&
        test_cmp expect actual
  '
@@@ -435,36 -364,6 +435,36 @@@ test_expect_success 'log grep (6)' 
        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 'log with multiple --author uses union' '
 +      git log --author="Thor" --author="Aster" --format=%s >actual &&
 +      {
 +          echo third && echo second && echo initial
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'log with --grep and multiple --author uses all-match' '
 +      git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
 +      {
 +          echo third && echo initial
 +      } >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'log with --grep and multiple --author uses all-match' '
 +      git log --author="Thor" --author="Night" --grep=q --format=%s >actual &&
 +      >expect &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success 'grep with CE_VALID file' '
        git update-index --assume-unchanged t/t &&
        rm t/t &&
@@@ -509,20 -408,6 +509,20 @@@ test_expect_success 'grep -p -B5' 
        test_cmp expected actual
  '
  
 +cat >expected <<EOF
 +hello.c=int main(int argc, const char **argv)
 +hello.c-{
 +hello.c-      printf("Hello world.\n");
 +hello.c:      return 0;
 +hello.c-      /* char ?? */
 +hello.c-}
 +EOF
 +
 +test_expect_success 'grep -W' '
 +      git grep -W return >actual &&
 +      test_cmp expected actual
 +'
 +
  test_expect_success 'grep from a subdirectory to search wider area (1)' '
        mkdir -p s &&
        (
@@@ -554,12 -439,11 +554,11 @@@ test_expect_success 'outside of git rep
        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
 +      echo file2:world >non/expect.sub &&
        (
                GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
                export GIT_CEILING_DIRECTORIES &&
@@@ -581,11 -465,12 +580,12 @@@ test_expect_success 'inside git reposit
        echo world >is/git/sub/file2 &&
        echo ".*o*" >is/git/.gitignore &&
        {
+               echo ".gitignore:.*o*" &&
                echo file1:hello &&
                echo sub/file2:world
        } >is/expect.full &&
        : >is/expect.empty &&
 -      echo file2:world >is/expect.sub
 +      echo file2:world >is/expect.sub &&
        (
                cd is/git &&
                git init &&
        )
  '
  
 +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
 +'
 +
 +cat >expected <<EOF
 +hello.c:int main(int argc, const char **argv)
 +hello.c:      printf("Hello world.\n");
 +EOF
 +
 +test_expect_success LIBPCRE 'grep --perl-regexp pattern' '
 +      git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P pattern' '
 +      git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep pattern with grep.extendedRegexp=true' '
 +      >empty &&
 +      test_must_fail git -c grep.extendedregexp=true \
 +              grep "\p{Ps}.*?\p{Pe}" hello.c >actual &&
 +      test_cmp empty actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' '
 +      git -c grep.extendedregexp=true \
 +              grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P -v pattern' '
 +      {
 +              echo "ab:a+b*c"
 +              echo "ab:a+bc"
 +      } >expected &&
 +      git grep -P -v "abc" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P -i pattern' '
 +      cat >expected <<-EOF &&
 +      hello.c:        printf("Hello world.\n");
 +      EOF
 +      git grep -P -i "PRINTF\([^\d]+\)" hello.c >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P -w pattern' '
 +      {
 +              echo "hello_world:Hello world"
 +              echo "hello_world:HeLLo world"
 +      } >expected &&
 +      git grep -P -w "He((?i)ll)o" hello_world >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -G invalidpattern properly dies ' '
 +      test_must_fail git grep -G "a["
 +'
 +
 +test_expect_success 'grep -E invalidpattern properly dies ' '
 +      test_must_fail git grep -E "a["
 +'
 +
 +test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' '
 +      test_must_fail git grep -P "a["
 +'
 +
 +test_expect_success 'grep -G -E -F pattern' '
 +      echo "ab:a+b*c" >expected &&
 +      git grep -G -E -F "a+b*c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -E -F -G pattern' '
 +      echo "ab:a+bc" >expected &&
 +      git grep -E -F -G "a+b*c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -F -G -E pattern' '
 +      echo "ab:abc" >expected &&
 +      git grep -F -G -E "a+b*c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -G -F -P -E pattern' '
 +      >empty &&
 +      test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual &&
 +      test_cmp empty actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
 +      echo "ab:a+b*c" >expected &&
 +      git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_config() {
 +      git config "$1" "$2" &&
 +      test_when_finished "git config --unset $1"
 +}
 +
 +cat >expected <<EOF
 +hello.c<RED>:<RESET>int main(int argc, const char **argv)
 +hello.c<RED>-<RESET>{
 +<RED>--<RESET>
 +hello.c<RED>:<RESET>  /* char ?? */
 +hello.c<RED>-<RESET>}
 +<RED>--<RESET>
 +hello_world<RED>:<RESET>Hello_world
 +hello_world<RED>-<RESET>HeLLo_world
 +EOF
 +
 +test_expect_success 'grep --color, separator' '
 +      test_config color.grep.context          normal &&
 +      test_config color.grep.filename         normal &&
 +      test_config color.grep.function         normal &&
 +      test_config color.grep.linenumber       normal &&
 +      test_config color.grep.match            normal &&
 +      test_config color.grep.selected         normal &&
 +      test_config color.grep.separator        red &&
 +
 +      git grep --color=always -A1 -e char -e lo_w hello.c hello_world |
 +      test_decode_color >actual &&
 +      test_cmp expected actual
 +'
 +
 +cat >expected <<EOF
 +hello.c:int main(int argc, const char **argv)
 +hello.c:      /* char ?? */
 +
 +hello_world:Hello_world
 +EOF
 +
 +test_expect_success 'grep --break' '
 +      git grep --break -e char -e lo_w hello.c hello_world >actual &&
 +      test_cmp expected actual
 +'
 +
 +cat >expected <<EOF
 +hello.c:int main(int argc, const char **argv)
 +hello.c-{
 +--
 +hello.c:      /* char ?? */
 +hello.c-}
 +
 +hello_world:Hello_world
 +hello_world-HeLLo_world
 +EOF
 +
 +test_expect_success 'grep --break with context' '
 +      git grep --break -A1 -e char -e lo_w hello.c hello_world >actual &&
 +      test_cmp expected actual
 +'
 +
 +cat >expected <<EOF
 +hello.c
 +int main(int argc, const char **argv)
 +      /* char ?? */
 +hello_world
 +Hello_world
 +EOF
 +
 +test_expect_success 'grep --heading' '
 +      git grep --heading -e char -e lo_w hello.c hello_world >actual &&
 +      test_cmp expected actual
 +'
 +
 +cat >expected <<EOF
 +<BOLD;GREEN>hello.c<RESET>
 +2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
 +6:    /* <BLACK;BYELLOW>char<RESET> ?? */
 +
 +<BOLD;GREEN>hello_world<RESET>
 +3:Hel<BLACK;BYELLOW>lo_w<RESET>orld
 +EOF
 +
 +test_expect_success 'mimic ack-grep --group' '
 +      test_config color.grep.context          normal &&
 +      test_config color.grep.filename         "bold green" &&
 +      test_config color.grep.function         normal &&
 +      test_config color.grep.linenumber       normal &&
 +      test_config color.grep.match            "black yellow" &&
 +      test_config color.grep.selected         normal &&
 +      test_config color.grep.separator        normal &&
 +
 +      git grep --break --heading -n --color \
 +              -e char -e lo_w hello.c hello_world |
 +      test_decode_color >actual &&
 +      test_cmp expected actual
 +'
 +
  test_done